Compare commits
7 Commits
Author | SHA1 | Date |
---|---|---|
Unboring SL | 61b54a2a10 | 5 years ago |
Unboring SL | 23f961b19c | 5 years ago |
Unboring SL | c96d57ccb2 | 5 years ago |
Unboring SL | 103e404dd9 | 5 years ago |
Unboring SL | f781760a45 | 5 years ago |
Unboring SL | 1f6f070c18 | 5 years ago |
Unboring SL | 07c397dc56 | 5 years ago |
@ -0,0 +1,2 @@ |
||||
node_modules |
||||
package-lock.json |
@ -0,0 +1,2 @@ |
||||
{ |
||||
} |
@ -1,21 +0,0 @@ |
||||
MIT License |
||||
|
||||
Copyright (c) 2020 Fabien Benetou |
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy |
||||
of this software and associated documentation files (the "Software"), to deal |
||||
in the Software without restriction, including without limitation the rights |
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
||||
copies of the Software, and to permit persons to whom the Software is |
||||
furnished to do so, subject to the following conditions: |
||||
|
||||
The above copyright notice and this permission notice shall be included in all |
||||
copies or substantial portions of the Software. |
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
||||
SOFTWARE. |
@ -0,0 +1,2 @@ |
||||
CreativeCommons Attribution-NonCommercial-ShareAlike 4.0 International. |
||||
http://creativecommons.org/licenses/by-nc-sa/4.0/ |
@ -1,35 +1,16 @@ |
||||
# relax-plus-think-space |
||||
an infinite space for your big ideas |
||||
|
||||
## Principle |
||||
Natural interaction (6DoF controllers) |
||||
in an immersive environment (relaxing setup to induce state of flow) |
||||
with work related content (e.g. Github issues, photos of work posters and post-it notes) |
||||
to be freely organised in visual categories (e.g. kanban). |
||||
An infinite space for your big ideas |
||||
|
||||
## UX flow for demos and tests |
||||
### new user on phone without own photos : |
||||
1. visit https://learnwebvr.xyz and get redirected to https://learnwebvr.xyz/setup/ |
||||
1. upload photos |
||||
1. generate a personalised link |
||||
1. share that link to target device (e.g. Firefox Send Tab to Devices on Oculus Quest) |
||||
1. experience on device |
||||
1. remove device and visit 2D links e.g. https://learnwebvr.xyz/flat.html?email=fabien@benetou.fr |
||||
## Development |
||||
|
||||
### returning user on 6DoF device |
||||
1. visit https://learnwebvr.xyz |
||||
1 ... |
||||
Have Node (< v12, recommended v11) and npm installed. |
||||
|
||||
### new user on 6DoF device |
||||
1. visit https://learnwebvr.xyz |
||||
1 ... |
||||
Change 192.168.0.12 to your local IP on package.json scripts/start |
||||
|
||||
## Code |
||||
* frontend : `index.html` and `setup/index.html` |
||||
* backend : `setup/server/upload.php` |
||||
``` |
||||
npm install |
||||
npm run start |
||||
``` |
||||
|
||||
## License |
||||
MIT. |
||||
|
||||
## Property and rights |
||||
Iterative Explorations SCS based in Belgium |
||||
Then head to `https://[YOUR LOCAL IP]:3000` in your browser. |
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 68 KiB |
After Width: | Height: | Size: 92 KiB |
After Width: | Height: | Size: 92 KiB |
@ -0,0 +1,32 @@ |
||||
<!DOCTYPE html> |
||||
<html lang="en"> |
||||
<head> |
||||
<title>relax-plus-think-space</title> |
||||
<meta charset="utf-8"> |
||||
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0"> |
||||
<meta name="theme-color" content="#353449"> |
||||
<style> |
||||
body { |
||||
margin: 0; |
||||
} |
||||
canvas { |
||||
display: block; |
||||
-webkit-touch-callout: none; |
||||
-webkit-user-select: none; |
||||
-khtml-user-select: none; |
||||
-moz-user-select: none; |
||||
-ms-user-select: none; |
||||
user-select: none; |
||||
outline: none; |
||||
-webkit-tap-highlight-color: rgba(255, 255, 255, 0); /* mobile webkit */ |
||||
} |
||||
</style> |
||||
<link rel="shortcut icon" href="assets/img/favicon.png" type="image/x-icon"> |
||||
<script src="aframe-master.js"></script> |
||||
<script src="bundle.js"></script> |
||||
</head> |
||||
<body> |
||||
<div id="app"></div> |
||||
<a id="vrButton" href="#" title="Enter VR / Fullscreen"></a> |
||||
</body> |
||||
</html> |
@ -1,111 +0,0 @@ |
||||
<!DOCTYPE html> |
||||
<html lang="en"> |
||||
<head> |
||||
<!-- Required meta tags --> |
||||
<meta charset="utf-8"> |
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> |
||||
|
||||
<!-- Bootstrap CSS --> |
||||
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta/css/bootstrap.min.css" integrity="sha384-/Y6pD6FV/Vv2HJnA6t+vslU6fwYXjCFtcEpHbNJ0lyAFsXTsjBbfaDjzALeQsN6M" crossorigin="anonymous"> |
||||
<link href="https://getbootstrap.com/docs/4.0/examples/signin/signin.css" rel="stylesheet"> |
||||
|
||||
<script src="setup/upload/server/php/files/usersdb.js"></script> |
||||
<script src="https://aframe.io/releases/1.0.0/aframe.min.js"></script> |
||||
</head> |
||||
<style> |
||||
body { background-color: transparent; } |
||||
|
||||
#email, #sms { |
||||
font-size: xx-large; |
||||
} |
||||
</style> |
||||
|
||||
<body> |
||||
|
||||
<img src="setup/productlogo.png"> |
||||
|
||||
<h3>flat viewer by category sorted by position</h3> |
||||
|
||||
<div id="spacesholder">Your spaces: |
||||
<ul id="spaces"></ul> |
||||
</div> |
||||
|
||||
<div>Already have an account? <span onclick="login()" style="text-decoration: underline;">Log-in</span> |
||||
<form> |
||||
<div id="login" style="display:none"><input id="useremail"/> |
||||
<button style="margin-top:2px;" type="button" onclick="loginViaEmail()" id="loginemail" class="btn btn-lg btn-primary btn-block">Login</button> |
||||
</div> |
||||
</form> |
||||
<div style="display:none" id="nouser">User not found. Double check your email address then contact fabien@iterative-explorations.com</a></div> |
||||
</div> |
||||
|
||||
<script> |
||||
const urlParams = new URLSearchParams(window.location.search); |
||||
const email = urlParams.get('email'); |
||||
if (email) loginViaEmail() |
||||
|
||||
var uploadedURL = "setup/upload/server/php/files/"; |
||||
var images = [] |
||||
var url = "/" |
||||
var urlParameters = "?customimages=" |
||||
var userspace |
||||
|
||||
function login(){ |
||||
document.querySelector("#login").style.display = "block" |
||||
} |
||||
|
||||
function findCategory(image){ |
||||
if (!userspace) return |
||||
console.log(image.filename) |
||||
var imagePos = new THREE.Vector3(); |
||||
imagePos.copy ( AFRAME.utils.coordinates.parse(image.position) ) |
||||
|
||||
var closest |
||||
var smallestDistance = 1000 |
||||
for (var category of userspace.categories){ |
||||
var categoryPos = new THREE.Vector3(); |
||||
categoryPos.copy ( AFRAME.utils.coordinates.parse(category.position) ) |
||||
var distance = categoryPos.distanceTo( imagePos ) |
||||
if (distance < smallestDistance){ |
||||
smallestDistance = distance |
||||
closest = category.label |
||||
} |
||||
console.log(distance, category.label) |
||||
} |
||||
|
||||
return closest |
||||
} |
||||
|
||||
function loginViaEmail(){ |
||||
var path = "setup/" |
||||
if (!email) |
||||
email = document.querySelector("#useremail").value |
||||
userspace = database[email] |
||||
if (!userspace){ |
||||
document.querySelector("#nouser").style.display = "block" |
||||
return |
||||
} |
||||
|
||||
document.querySelector("#spacesholder").style.display = "block" |
||||
var spaces = document.querySelector("#spaces") |
||||
var space = document.createElement("li") |
||||
var spacelink = document.createElement("a") |
||||
var images = userspace.files |
||||
spacelink.href = url + urlParameters |
||||
spacelink.target = "_blank" |
||||
spacelink.innerHTML = userspace.last_login |
||||
space.appendChild(spacelink) |
||||
for (var image of images){ |
||||
urlParameters += image.filename + "," |
||||
space.innerHTML += path + image.filename + " " + findCategory( image ) |
||||
} |
||||
console.log(space) |
||||
spaces.appendChild(space) |
||||
console.log("userspace", userspace) |
||||
} |
||||
</script> |
||||
|
||||
<h3 style="position:absolute; bottom:0px; right:0px;">A product by <a href="https://iterative-explorations.com"><img width="200px" src="https://iterative-explorations.com/logo.svg"></a>.</h3> |
||||
|
||||
</body> |
||||
</html> |
@ -1,150 +0,0 @@ |
||||
<head> |
||||
<meta charset="UTF-8"> |
||||
<title>Think + Relax space by Iterative Explorations</title> |
||||
<script src="https://aframe.io/releases/1.0.0/aframe.min.js"></script> |
||||
<script src="https://rawgit.com/feiss/aframe-environment-component/master/dist/aframe-environment-component.min.js"></script> |
||||
<script src="https://cdn.rawgit.com/donmccurdy/aframe-extras/v4.1.2/dist/aframe-extras.min.js"></script> |
||||
<script src="https://unpkg.com/super-hands@3.0.0/dist/super-hands.min.js"></script> |
||||
<script src="pimvrhelpers.js"></script> |
||||
</head> |
||||
<script> |
||||
// could add an in VR edit mode for the categories, to position them, name them, etc |
||||
// should be mostly for adjustments |
||||
var timer = AFRAME.utils.getUrlParameter('timer') |
||||
if (!timer) timer = 300 //5min |
||||
|
||||
function saveNewItemsPosition(){ |
||||
var selector = ".notes"; |
||||
var pos = "" |
||||
document.querySelectorAll(selector).forEach( |
||||
e => pos += "\n" + AFRAME.utils.coordinates.stringify( e.getAttribute("position") ) |
||||
) |
||||
pimvrSaveRemote("WeWorkTest", pos) // from pimvrhelpers.js |
||||
// should be saved by username by space |
||||
// could be pointed at from the database file (thus having multiple storages) |
||||
} |
||||
|
||||
AFRAME.registerComponent("watch", { |
||||
init: function() { |
||||
document.querySelector("#timer").setAttribute("text","value:"+timer) |
||||
this.tick = AFRAME.utils.throttleTick(this.tick, 1000, this); |
||||
// details https://aframe.io/docs/1.0.0/core/utils.html#aframe-utils-throttle-function-minimuminterval-optionalcontext |
||||
}, |
||||
|
||||
tick: function (t, dt) { |
||||
var time = Number( document.querySelector("#timer").getAttribute("text").value ) |
||||
time-- |
||||
document.querySelector("#timer").setAttribute("text","value:"+(time)) |
||||
}, |
||||
}) |
||||
|
||||
AFRAME.registerComponent("relaxing-introduction", { |
||||
init: function() { |
||||
// get in flow state |
||||
// for now controlled just with AFrame animation |
||||
// cf https://aframe.io/docs/1.0.0/components/animation.html |
||||
|
||||
setTimeout(function(){ |
||||
for (var note of document.querySelectorAll(".notes")){ note.emit("fadein") } |
||||
}, 10 * 1000) // arbitrary 10s, should be instead after the introduction is finished |
||||
} |
||||
}) |
||||
|
||||
AFRAME.registerComponent("decompression-conclusion", { |
||||
init: function() { |
||||
// cf PoC at 2017 W3C web authoring workshop with Roland Dubois |
||||
setTimeout(function(){ |
||||
var intructions = document.querySelector("#instructions") |
||||
instructions.setAttribute("position", "-1 1.5 -1") // assumes the user is roughtly centered... could force on camera instead |
||||
instructions.setAttribute("text", "value", "Your session will end soon\n\nVisit https://learnWebVR.xyz/flat.html\n\nto work outside of VR.") |
||||
instructions.setAttribute("opacity", "1") // could be animated instead |
||||
|
||||
console.log("X min voice over and notes fade-out with contextual information (time, weather, etc)") |
||||
|
||||
document.querySelector("[environment]").setAttribute("environment", "preset:checkerboard") |
||||
}, timer * 1000); |
||||
|
||||
} |
||||
}) |
||||
|
||||
AFRAME.registerComponent("environment-by-url", { |
||||
schema: {}, |
||||
init: function() { |
||||
var environment = AFRAME.utils.getUrlParameter('environment') |
||||
if (environment) document.querySelector("[environment]").setAttribute("environment", "preset:" + environment) |
||||
} |
||||
}) |
||||
|
||||
AFRAME.registerComponent("photo-importer", { |
||||
schema: {}, |
||||
init: function() { |
||||
var baseURL = "setup/" |
||||
var images = AFRAME.utils.getUrlParameter('customimages').split(','); |
||||
if (images.length == 1) window.location = baseURL; // no images? must be configured first |
||||
// could also display a tutorial instead |
||||
var i=1 |
||||
for (var image of images){ |
||||
if (image){ |
||||
var id = "placeholder"+i |
||||
var placeholder = document.querySelector("#"+id) |
||||
if (!placeholder){ |
||||
// TODO somehow not interactable anymore?! |
||||
// had a similar silly issue before but can't recall how I fixed it :( |
||||
// something basic about entities or hierarchy... |
||||
placeholder = document.createElement("a-box") |
||||
placeholder.id = id |
||||
placeholder.setAttribute("material", "shader:flat") |
||||
placeholder.setAttribute("hoverable", "") |
||||
placeholder.setAttribute("grabbable", "") |
||||
placeholder.setAttribute("draggable", "") |
||||
placeholder.setAttribute("dropppable", "") |
||||
// somehow does appear visually correct but are NOT interactable! |
||||
placeholder.setAttribute("position", "" + i/1.5-1.5 + " 1.5 -0.7") |
||||
placeholder.setAttribute("scale", "0.5 0.3 0.01") |
||||
// assume 1 * 1.5 photo ratio (landscape) |
||||
AFRAME.scenes[0].appendChild(placeholder) |
||||
} |
||||
placeholder.className += "notes" |
||||
placeholder.setAttribute("opacity", "0") |
||||
placeholder.setAttribute("animation", "property: components.material.material.opacity; to: 1; dur: 1500; easing: linear; startEvents: fadein;") |
||||
placeholder.setAttribute("src", baseURL+image) |
||||
i++ |
||||
} |
||||
} |
||||
} |
||||
}) |
||||
|
||||
|
||||
</script> |
||||
<body> |
||||
<a-scene photo-importer environment-by-url relaxing-introduction decompression-conclusion> |
||||
<a-entity environment="preset: forest;"></a-entity> |
||||
|
||||
<a-entity sphere-collider="objects: a-box" super-hands hand-controls="left"></a-entity> |
||||
<a-entity sphere-collider="objects: a-box" super-hands hand-controls="right"></a-entity> |
||||
|
||||
<!-- could be wrist watch as I've done via Twitter years ago --> |
||||
<a-text watch id="timer" rotation="180 0 180" position="0 1 2" value="time"></a-text> |
||||
|
||||
<a-text id="instructions" position="-1 1.5 -0.65" value="welcome to your\n\nthink+relax space" opacity="0" |
||||
animation__fadein="property: components.text.material.uniforms.opacity.value; to: 0.99; dur: 1500; easing: linear" |
||||
animation__fadeout="property: components.text.material.uniforms.opacity.value; to: 0.01; dur: 1500; easing: linear; startEvents: fadeout;" |
||||
animation__leave="property: object3D.position.z; to: -100; dur: 15000; easing: linear; delay: 2500;" |
||||
></a-text> |
||||
|
||||
|
||||
<a-text rotation="-20 20 0" position="-2 0.2 -2" scale="1 1 1" value="waiting"></a-text> |
||||
<a-box material="wireframe:true" color="orange" position="-2 1 -2" scale="2 2 2"></a-box> |
||||
<a-text rotation="-20 0 0" position=" 0 0.2 -2" scale="1 1 1" value="in progress"></a-text> |
||||
<a-box material="wireframe:true" color="red" position="0 1 -2" scale="2 2 2"></a-box> |
||||
<a-text rotation="-20 -20 0" position=" 2 0.2 -2" scale="1 1 1" value="completed"></a-text> |
||||
<a-box material="wireframe:true" color="green" position="2 1 -2" scale="2 2 2"></a-box> |
||||
|
||||
<a-box material="shader:flat" id="placeholder1" hoverable grabbable stretchable draggable dropppable position="0.5 1.5 -0.5" scale="0.5 0.3 0.01"></a-box> |
||||
<a-box material="shader:flat" id="placeholder2" hoverable grabbable stretchable draggable dropppable position="0 1.9 -0.7" scale="0.5 0.3 0.01"></a-box> |
||||
<a-box material="shader:flat" id="placeholder3" hoverable grabbable stretchable draggable dropppable position="-0.5 1.5 -0.7" scale="0.5 0.3 0.01"></a-box> |
||||
<!-- somehow creating dynamically fails... even though it did (!) work at some point (but wasn't saved via git)--> |
||||
<!-- until then will rely on a pool of objects --> |
||||
|
||||
</a-scene> |
||||
</body> |
@ -0,0 +1,42 @@ |
||||
{ |
||||
"name": "relax-plus-think-space", |
||||
"version": "0.0.1", |
||||
"license": "CC-BY-NC-SA-4.0", |
||||
"description": "An infinite space for your big ideas", |
||||
"repository": { |
||||
"type": "git", |
||||
"url": "" |
||||
}, |
||||
"main": "index.js", |
||||
"dependencies": {}, |
||||
"scripts": { |
||||
"start": "webpack-dev-server --mode development --host 0.0.0.0", |
||||
"build": "webpack --mode production --config webpack.config.js" |
||||
}, |
||||
"keywords": [], |
||||
"author": "Arturo Paracuellos", |
||||
"devDependencies": { |
||||
"aframe-environment-component": "^2.0.0", |
||||
"aframe-event-set-component": "^5.0.0", |
||||
"aframe-super-hot-loader": "^1.7.0", |
||||
"aframe-super-hot-html-loader": "^2.1.0", |
||||
"babel-core": "^6.26.3", |
||||
"babel-loader": "^7.1.4", |
||||
"babel-preset-env": "^1.7.0", |
||||
"css-loader": "^3.4.1", |
||||
"html-require-loader": "^1.0.1", |
||||
"json-loader": "^0.5.7", |
||||
"style-loader": "^0.23.1", |
||||
"url-loader": "^1.1.2", |
||||
"webpack": "^4.39.3", |
||||
"webpack-cli": "^3.3.7", |
||||
"webpack-dev-server": "^3.8.0", |
||||
"webpack-glsl-loader": "^1.0.1" |
||||
}, |
||||
"standard": { |
||||
"globals": [ |
||||
"AFRAME", |
||||
"THREE" |
||||
] |
||||
} |
||||
} |
@ -1,173 +0,0 @@ |
||||
//---------PIM helper functions-----------------------------------------------------------
|
||||
/* |
||||
currently PmWiki backend |
||||
could use http://fabien.benetou.fr/Site/AllRecentChanges?action=source to check for update
|
||||
heavy but nearly no processing required |
||||
enough if done once per minute or so |
||||
if update, request serverrender on modified page |
||||
IFF it's being displayed |
||||
planned Evernote backend |
||||
https://github.com/evernote/evernote-sdk-js
|
||||
https://github.com/wanasit/everest-js
|
||||
https://stackoverflow.com/questions/24580588/how-to-list-all-the-notes-from-an-evernote-notebook-javascript-node-js
|
||||
https://dev.evernote.com/doc/articles/polling_notification.php
|
||||
or other popular PIMs |
||||
https://developers.trello.com/
|
||||
http://www.xmind.net/developer/
|
||||
ideally with webhooks on a backend abstraction with coherent API
|
||||
|
||||
*/ |
||||
|
||||
function pimvrSaveItemsStates(callback) { |
||||
function updateItemsStates(globalStates){
|
||||
let pageStates = {}; |
||||
let [group, page] = getPageGroup(); |
||||
let elements = document.body.querySelectorAll('.pimvr-item'); |
||||
for (let item of elements) { |
||||
let id = item.getAttribute("id"); |
||||
let position = item.getComputedAttribute("position"); |
||||
pageStates[id] = {"position": position}; |
||||
} |
||||
globalStates[group+"_"+page] = pageStates; |
||||
pimvrSaveRemote("ItemsStates", JSON.stringify(globalStates)); |
||||
return "Items states saved"; |
||||
|
||||
} |
||||
pimvrLoadRemote("ItemsStates", updateItemsStates); |
||||
}
|
||||
|
||||
function pimvrSaveConfiguration(callback) { |
||||
let configuration = {}; |
||||
let elements = document.body.querySelectorAll('.pimvr-configuration'); |
||||
for (let item of elements) { |
||||
let id = item.getAttribute("id"); |
||||
let position = item.getComputedAttribute("position"); |
||||
configuration[id] = {"position": position}; |
||||
} |
||||
|
||||
pimvrSaveRemote("Configuration", JSON.stringify(configuration)); |
||||
return "Configuration saved"; |
||||
} |
||||
|
||||
function pimvrLoadIoTData(callback) { |
||||
// should give min/max ranges, here seems to be 0-1010
|
||||
// to use (once normalized) as an attribute value
|
||||
// used on http://jsbin.com/nucanat/edit?html,output
|
||||
// warning HTTPS on tick is really hammering
|
||||
const readURL = "https://fabien.benetou.fr/PIMVRdata/IoTData?action=source"; |
||||
|
||||
var myRequest = new XMLHttpRequest(); |
||||
myRequest.open('GET', readURL); |
||||
myRequest.onreadystatechange = function () { |
||||
if (myRequest.readyState === 4) { |
||||
callback(myRequest.responseText); |
||||
} |
||||
}; |
||||
myRequest.send(); |
||||
}
|
||||
|
||||
function pimvrServerRender(group, page, callback) { |
||||
const readURL = "https://fabien.benetou.fr/"+group+"/"+page+"?action=serverrender"; |
||||
|
||||
var myRequest = new XMLHttpRequest(); |
||||
myRequest.open('GET', readURL); |
||||
myRequest.onreadystatechange = function () { |
||||
if (myRequest.readyState === 4) { |
||||
callback(JSON.parse(myRequest.responseText).res); |
||||
} |
||||
}; |
||||
myRequest.send(); |
||||
} |
||||
|
||||
function pimvrLoadRemoteSmarthWatchConfiguration(callback) { |
||||
|
||||
const readURL = "https://fabien.benetou.fr/PIMVRdata/SmartWatchConfiguration?action=source"; |
||||
|
||||
var myRequest = new XMLHttpRequest(); |
||||
myRequest.open('GET', readURL); |
||||
myRequest.onreadystatechange = function () { |
||||
if (myRequest.readyState === 4) { |
||||
callback(JSON.parse(myRequest.responseText)); |
||||
} |
||||
}; |
||||
myRequest.send(); |
||||
} |
||||
//pimvrLoadRemoteSmarthWatchConfiguration(console.log);
|
||||
// usage unclear, can be used as
|
||||
// haptic feedback on interactible items e.g. vibrate on gaze
|
||||
// controller backup e.g. gaze+click
|
||||
// controller locator e.g. making 2 bright columns
|
||||
// heart rate monitor (sadly not with PebbleTime) to reshape experience
|
||||
|
||||
function pimvrLoadRemoteMetadata(group, page, callback, query) { |
||||
|
||||
const readURL = "https://fabien.benetou.fr/"+group+"/"+page+"?action=metajson"; |
||||
|
||||
var myRequest = new XMLHttpRequest(); |
||||
myRequest.open('GET', readURL+"&query="+query, true); |
||||
myRequest.onreadystatechange = function () { |
||||
if (myRequest.readyState === 4) { |
||||
callback(JSON.parse(myRequest.responseText)); |
||||
} |
||||
}; |
||||
myRequest.send(); |
||||
} |
||||
|
||||
function pimvrLoadRemote(page, callback) { |
||||
|
||||
const readURL = "https://fabien.benetou.fr/PIMVRdata/"+page+"?action=source"; |
||||
// assumes JSON
|
||||
|
||||
var myRequest = new XMLHttpRequest(); |
||||
myRequest.open('GET', readURL); |
||||
myRequest.onreadystatechange = function () { |
||||
if (myRequest.readyState === 4) { |
||||
callback(JSON.parse(myRequest.responseText)); |
||||
} |
||||
}; |
||||
myRequest.send(); |
||||
} |
||||
|
||||
function pimvrSaveRemote(page, data) { |
||||
const writeURL = "https://fabien.benetou.fr/PIMVRdata/"+page+"?action=edit"; |
||||
|
||||
var myWriteRequest = new XMLHttpRequest(); |
||||
myWriteRequest.open('POST', writeURL, true); |
||||
myWriteRequest.setRequestHeader("Content-Type", |
||||
"application/x-www-form-urlencoded"); |
||||
myWriteRequest.onreadystatechange = function () { |
||||
if (myWriteRequest.readyState === 4) { |
||||
//console.log(myWriteRequest.responseText);
|
||||
console.log("Save on "+page+" sucessful"); |
||||
} |
||||
}; |
||||
console.log("trying to open "+writeURL+"post=1&author=PIMVR&authpw=edit_password&text="+data) |
||||
myWriteRequest.send("post=1&author=PIMVR&authpw=edit_password&text="+data); |
||||
// cf http://www.pmwiki.org/wiki/PmWiki/EditingAPI
|
||||
} |
||||
|
||||
function loadRemoteGraph(callback, params){ |
||||
const myDataURL = "https://vatelier.net/MyDemo/newtooling/wiki_graph.json"; |
||||
// not that as agressive as it gets cached
|
||||
|
||||
var myRequest = new XMLHttpRequest(); |
||||
myRequest.open('GET', myDataURL); |
||||
myRequest.onreadystatechange = function () { |
||||
if (myRequest.readyState === 4) { |
||||
//window.PIMgraph = JSON.parse(myRequest.responseText).Nodes;
|
||||
callback(JSON.parse(myRequest.responseText).Nodes, params); |
||||
} |
||||
}; |
||||
myRequest.send(); |
||||
} |
||||
|
||||
function getMyNeighbours(nodes, page){ |
||||
console.log(nodes[page].Targets); |
||||
} |
||||
|
||||
function getPageGroup(){
|
||||
let group = QueryString.group || "Main"; |
||||
let page = QueryString.page || "HomePage"; |
||||
return [group, page]; |
||||
} |
||||
|
@ -1,187 +0,0 @@ |
||||
<!DOCTYPE html> |
||||
<html lang="en"> |
||||
<head> |
||||
<!-- Required meta tags --> |
||||
<meta charset="utf-8"> |
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> |
||||
|
||||
<!-- Bootstrap CSS --> |
||||
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta/css/bootstrap.min.css" integrity="sha384-/Y6pD6FV/Vv2HJnA6t+vslU6fwYXjCFtcEpHbNJ0lyAFsXTsjBbfaDjzALeQsN6M" crossorigin="anonymous"> |
||||
<link href="https://getbootstrap.com/docs/4.0/examples/signin/signin.css" rel="stylesheet"> |
||||
|
||||
<script src="clipboard.min.js"></script> |
||||
<script src="upload/server/php/files/usersdb.js"></script> |
||||
</head> |
||||
<style> |
||||
body { background-color: transparent; } |
||||
|
||||
#email, #sms { |
||||
font-size: xx-large; |
||||
} |
||||
</style> |
||||
|
||||
<body> |
||||
|
||||
<img src="productlogo.png"> |
||||
|
||||
<script> |
||||
var tickMark = "✔"; |
||||
var uploadedURL = "upload/server/php/files/"; |
||||
var images = [] |
||||
var url = "/" |
||||
var urlParameters = "?customimages=" |
||||
|
||||
function generateURL(){ |
||||
//var url = "https://learnwebvr.xyz/importer.html" |
||||
for (var image of images){ |
||||
urlParameters += image + "," |
||||
} |
||||
console.log ("URL", urlParameters ); |
||||
|
||||
var encodedURL = url + urlParameters |
||||
|
||||
encodedURL += "&environment=" + document.querySelector("#environment").value |
||||
document.querySelector("#link").href = encodedURL |
||||
document.querySelector("#copyBtn").setAttribute("data-clipboard-text", encodedURL) |
||||
document.querySelector("#generateURL").style.display = "block" |
||||
|
||||
document.querySelector("#email").href = "mailto:?&subject=session&body=" + encodeURIComponent( encodedURL ) |
||||
// tested on iPhone 6S and Pixel2 and desktop |
||||
|
||||
if( /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent) ) { |
||||
} |
||||
else { |
||||
document.querySelector("#copydiv").className = "cold-md-6" |
||||
document.querySelector("#emaildiv").className = "cold-md-6" |
||||
} |
||||
} |
||||
</script> |
||||
<div class="container"> |
||||
|
||||
<form class="form-signin"> |
||||
|
||||
Environment: |
||||
<select id="environment"> |
||||
<option name="contact">contact</option> |
||||
<option name="egypt">egypt</option> |
||||
<option name="checkerboard">checkerboard</option> |
||||
<option name="forest" selected="selected">forest</option> |
||||
<option name="goaland">goaland</option> |
||||
<option name="yavapai">yavapai</option> |
||||
<option name="goldmine">goldmine</option> |
||||
<option name="threetowers">threetowers</option> |
||||
<option name="poison">poison</option> |
||||
<option name="arches">arches</option> |
||||
<option name="tron">tron</option> |
||||
<option name="japan">japan</option> |
||||
<option name="dream">dream</option> |
||||
<option name="volcano">volcano</option> |
||||
<option name="starry">starry</option> |
||||
<option name="osiris">osiris</option> |
||||
</select> |
||||
|
||||
<button style="margin-top:2px;" type="button" onclick="unhide('#fileupload2');" class="btn btn-primary btn-block">Upload your office photos<br/>(post-its, white board, etc)</button> |
||||
|
||||
No content available? Try <a target="_blank" href="https://learnwebvr.xyz/?customimages=upload/server/php/files/photo6035074301452988871.jpg,upload/server/php/files/photo6035074301452988870.jpg,upload/server/php/files/IMG_6496 (1).jpg"> this example instead</a>! |
||||
|
||||
<input style="display:none;" id="fileupload2" type="file" name="files[]" data-url="upload/server/php/" multiple> |
||||
<div style="display:none;" id="fileuploaded2">Files uploaded: </div> |
||||
<div style="opacity:0.7;" id="uploadrate"></div> |
||||
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script> |
||||
<script src="upload/js/vendor/jquery.ui.widget.js"></script> |
||||
<script src="upload/js/jquery.iframe-transport.js"></script> |
||||
<script src="upload/js/jquery.fileupload.js"></script> |
||||
<script> |
||||
|
||||
function unhide( element ){ |
||||
document.querySelector(element).style.display = "block"; |
||||
} |
||||
|
||||
$(function () { |
||||
|
||||
$('#fileupload2').fileupload({ |
||||
dataType: 'json', |
||||
done: function (e, data) { |
||||
$.each(data.result.files, function (index, file) { |
||||
var imageURL = uploadedURL + (file.name) |
||||
images.push(imageURL) |
||||
var uploaded = document.querySelector("#fileuploaded2"); |
||||
uploaded.style.display = "block"; |
||||
uploaded.innerHTML += tickMark; |
||||
}); |
||||
} |
||||
}); |
||||
|
||||
}); |
||||
</script> |
||||
|
||||
<button style="margin-top:2px;" type="button" onclick="generateURL()" id="generate-simulation" class="btn btn-lg btn-primary btn-block">Generate your<br/>relax+think space</button> |
||||
</form> |
||||
</div> |
||||
|
||||
<div id="generateURL" style="display:none; text-align:center;" class="container"> |
||||
<div class="container"> |
||||
<a id="link" target="_blank" href=""><button type="button" class="btn btn-lg btn-primary btn-block">Launch relax+think space</button></a> |
||||
</div> |
||||
<br/> |
||||
<div class="container"> |
||||
<div class="row"> |
||||
<div class="col-md-4" id="copydiv"> |
||||
<button class="btn" data-clipboard-text="" id="copyBtn">Copy to clipboard</button> |
||||
<script> |
||||
new Clipboard('.btn'); |
||||
</script> |
||||
</div> |
||||
<div class="col-md-4" id="emaildiv"> |
||||
<a id="email" href="" target="_blank">email link</a> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
|
||||
<script> |
||||
function login(){ |
||||
document.querySelector("#login").style.display = "block" |
||||
} |
||||
function loginViaEmail(){ |
||||
var email = document.querySelector("#useremail").value |
||||
var userspace = database[email] |
||||
if (!userspace){ |
||||
document.querySelector("#nouser").style.display = "block" |
||||
return |
||||
} |
||||
|
||||
document.querySelector("#spacesholder").style.display = "block" |
||||
var spaces = document.querySelector("#spaces") |
||||
var space = document.createElement("li") |
||||
var spacelink = document.createElement("a") |
||||
var images = userspace.files |
||||
for (var image of images){ |
||||
urlParameters += image.filename + "," |
||||
} |
||||
spacelink.href = url + urlParameters |
||||
spacelink.target = "_blank" |
||||
spacelink.innerHTML = userspace.last_login |
||||
space.appendChild(spacelink) |
||||
console.log(space) |
||||
spaces.appendChild(space) |
||||
console.log("userspace", userspace) |
||||
} |
||||
</script> |
||||
|
||||
<div>Already have an account? <span onclick="login()" style="text-decoration: underline;">Log-in</span> |
||||
<form> |
||||
<div id="login" style="display:none"><input id="useremail"/> |
||||
<button style="margin-top:2px;" type="button" onclick="loginViaEmail()" id="loginemail" class="btn btn-lg btn-primary btn-block">Login</button> |
||||
</div> |
||||
</form> |
||||
<div style="display:none" id="nouser">User not found. Double check your email address then contact fabien@iterative-explorations.com</a></div> |
||||
<div style="display:none" id="spacesholder">Your spaces: |
||||
<ul id="spaces"></ul> |
||||
</div> |
||||
</div> |
||||
|
||||
<h3 style="position:absolute; bottom:0px; right:0px;">A product by <a href="https://iterative-explorations.com"><img width="200px" src="https://iterative-explorations.com/logo.svg"></a>.</h3> |
||||
|
||||
</body> |
||||
</html> |
File diff suppressed because it is too large
Load Diff
@ -1,18 +0,0 @@ |
||||
var database = { |
||||
"fabien@benetou.fr" : { |
||||
"username": "fabien", |
||||
"password": "test", |
||||
"last_login": 1576749381516, |
||||
"environment": "forest", |
||||
"files" : [ |
||||
{ "position": "-2 1 -2", "filename" : "upload/server/php/files/C1C4E2F7-865A-4DB4-9274-1EA69B6C4A7E.jpeg" }, |
||||
{ "position": "0 1 -2", "filename" : "upload/server/php/files/4A21A76C-F375-4884-9040-BC51A24057A6.jpeg" }, |
||||
{ "position": "2 1 -2", "filename" : "upload/server/php/files/2CD9932F-891C-4BA5-BDDA-A3B39B39CF09.jpeg" } |
||||
], |
||||
"categories" : [ |
||||
{ "position": "-2 1 -2", "color" : "orange", "label" : "waiting" }, |
||||
{ "position": "0 1 -2", "color" : "red", "label" : "in progress" }, |
||||
{ "position": "2 1 -2", "color" : "green", "label" : "completed" } |
||||
] |
||||
} |
||||
} |
@ -1,17 +0,0 @@ |
||||
<?php |
||||
/* |
||||
* jQuery File Upload Plugin PHP Example |
||||
* https://github.com/blueimp/jQuery-File-Upload |
||||
* |
||||
* Copyright 2010, Sebastian Tschan |
||||
* https://blueimp.net |
||||
* |
||||
* Licensed under the MIT license: |
||||
* https://opensource.org/licenses/MIT |
||||
*/ |
||||
|
||||
error_reporting(E_ALL | E_STRICT); |
||||
require('UploadHandler.php'); |
||||
$upload_handler = new UploadHandler(array( |
||||
'accept_file_types' => '/\.(png|jpe?g|mp4)$/i' |
||||
)); |
@ -0,0 +1,105 @@ |
||||
/* global AFRAME, THREE */ |
||||
|
||||
/** |
||||
* Implement AABB collision detection for entities with a mesh. |
||||
* (https://en.wikipedia.org/wiki/Minimum_bounding_box#Axis-aligned_minimum_bounding_box)
|
||||
* It sets the specified state on the intersected entities. |
||||
* |
||||
* @property {string} objects - Selector of the entities to test for collision. |
||||
* @property {string} state - State to set on collided entities. |
||||
* |
||||
*/ |
||||
AFRAME.registerComponent('aabb-collider', { |
||||
schema: { |
||||
objects: {default: ''}, |
||||
state: {default: 'collided'} |
||||
}, |
||||
|
||||
init: function () { |
||||
this.els = []; |
||||
this.collisions = []; |
||||
this.elMax = new THREE.Vector3(); |
||||
this.elMin = new THREE.Vector3(); |
||||
}, |
||||
|
||||
/** |
||||
* Update list of entities to test for collision. |
||||
*/ |
||||
update: function () { |
||||
var data = this.data; |
||||
var objectEls; |
||||
|
||||
// Push entities into list of els to intersect.
|
||||
if (data.objects) { |
||||
objectEls = this.el.sceneEl.querySelectorAll(data.objects); |
||||
} else { |
||||
// If objects not defined, intersect with everything.
|
||||
objectEls = this.el.sceneEl.children; |
||||
} |
||||
// Convert from NodeList to Array
|
||||
this.els = Array.prototype.slice.call(objectEls); |
||||
}, |
||||
|
||||
tick: (function () { |
||||
var boundingBox = new THREE.Box3(); |
||||
return function () { |
||||
var collisions = []; |
||||
var el = this.el; |
||||
var mesh = el.getObject3D('mesh'); |
||||
var self = this; |
||||
// No mesh, no collisions
|
||||
if (!mesh) { return; } |
||||
// Update the bounding box to account for rotations and
|
||||
// position changes.
|
||||
updateBoundingBox(); |
||||
// Update collisions.
|
||||
this.els.forEach(intersect); |
||||
// Emit events.
|
||||
collisions.forEach(handleHit); |
||||
// No collisions.
|
||||
if (collisions.length === 0) { self.el.emit('hit', {el: null}); } |
||||
// Updated the state of the elements that are not intersected anymore.
|
||||
this.collisions.filter(function (el) { |
||||
return collisions.indexOf(el) === -1; |
||||
}).forEach(function removeState (el) { |
||||
el.removeState(self.data.state); |
||||
el.emit('hitend'); |
||||
}); |
||||
// Store new collisions
|
||||
this.collisions = collisions; |
||||
|
||||
// AABB collision detection
|
||||
function intersect (el) { |
||||
var intersected; |
||||
var mesh = el.getObject3D('mesh'); |
||||
var elMin; |
||||
var elMax; |
||||
if (!mesh) { return; } |
||||
boundingBox.setFromObject(mesh); |
||||
elMin = boundingBox.min; |
||||
elMax = boundingBox.max; |
||||
// Bounding boxes are always aligned with the world coordinate system.
|
||||
// The collision test checks for the conditions where cubes intersect.
|
||||
// It's an extension to 3 dimensions of this approach (with the condition negated)
|
||||
// https://www.youtube.com/watch?v=ghqD3e37R7E
|
||||
intersected = (self.elMin.x <= elMax.x && self.elMax.x >= elMin.x) && |
||||
(self.elMin.y <= elMax.y && self.elMax.y >= elMin.y) && |
||||
(self.elMin.z <= elMax.z && self.elMax.z >= elMin.z); |
||||
if (!intersected) { return; } |
||||
collisions.push(el); |
||||
} |
||||
|
||||
function handleHit (hitEl) { |
||||
hitEl.emit('hit'); |
||||
hitEl.addState(self.data.state); |
||||
self.el.emit('hit', {el: hitEl}); |
||||
} |
||||
|
||||
function updateBoundingBox () { |
||||
boundingBox.setFromObject(mesh); |
||||
self.elMin.copy(boundingBox.min); |
||||
self.elMax.copy(boundingBox.max); |
||||
} |
||||
}; |
||||
})() |
||||
}); |
@ -0,0 +1,89 @@ |
||||
/* global AFRAME, THREE */ |
||||
|
||||
/** |
||||
* Handles events coming from the hand-controls. |
||||
* Determines if the entity is grabbed or released. |
||||
* Updates its position to move along the controller. |
||||
*/ |
||||
AFRAME.registerComponent('grab', { |
||||
init: function () { |
||||
this.GRABBED_STATE = 'grabbed'; |
||||
// Bind event handlers
|
||||
this.onHit = this.onHit.bind(this); |
||||
this.onGripOpen = this.onGripOpen.bind(this); |
||||
this.onGripClose = this.onGripClose.bind(this); |
||||
this.currentPosition = new THREE.Vector3(); |
||||
}, |
||||
|
||||
play: function () { |
||||
var el = this.el; |
||||
el.addEventListener('hit', this.onHit); |
||||
el.addEventListener('buttondown', this.onGripClose); |
||||
el.addEventListener('buttonup', this.onGripOpen); |
||||
}, |
||||
|
||||
pause: function () { |
||||
var el = this.el; |
||||
el.removeEventListener('hit', this.onHit); |
||||
el.addEventListener('buttondown', this.onGripClose); |
||||
el.addEventListener('buttonup', this.onGripOpen); |
||||
}, |
||||
|
||||
onGripClose: function (evt) { |
||||
if (this.grabbing) { return; } |
||||
this.grabbing = true; |
||||
this.pressedButtonId = evt.detail.id; |
||||
delete this.previousPosition; |
||||
}, |
||||
|
||||
onGripOpen: function (evt) { |
||||
var hitEl = this.hitEl; |
||||
if (this.pressedButtonId !== evt.detail.id) { return; } |
||||
this.grabbing = false; |
||||
if (!hitEl) { return; } |
||||
hitEl.removeState(this.GRABBED_STATE); |
||||
hitEl.emit('grabend'); |
||||
this.hitEl = undefined; |
||||
}, |
||||
|
||||
onHit: function (evt) { |
||||
var hitEl = evt.detail.el; |
||||
// If the element is already grabbed (it could be grabbed by another controller).
|
||||
// If the hand is not grabbing the element does not stick.
|
||||
// If we're already grabbing something you can't grab again.
|
||||
if (!hitEl || hitEl.is(this.GRABBED_STATE) || !this.grabbing || this.hitEl) { return; } |
||||
hitEl.addState(this.GRABBED_STATE); |
||||
this.hitEl = hitEl; |
||||
}, |
||||
|
||||
tick: function () { |
||||
var hitEl = this.hitEl; |
||||
var position; |
||||
if (!hitEl) { return; } |
||||
this.updateDelta(); |
||||
position = hitEl.getAttribute('position'); |
||||
hitEl.setAttribute('position', { |
||||
x: position.x + this.deltaPosition.x, |
||||
y: position.y + this.deltaPosition.y, |
||||
z: position.z + this.deltaPosition.z |
||||
}); |
||||
}, |
||||
|
||||
updateDelta: function () { |
||||
var currentPosition = this.currentPosition; |
||||
this.el.object3D.updateMatrixWorld(); |
||||
currentPosition.setFromMatrixPosition(this.el.object3D.matrixWorld); |
||||
if (!this.previousPosition) { |
||||
this.previousPosition = new THREE.Vector3(); |
||||
this.previousPosition.copy(currentPosition); |
||||
} |
||||
var previousPosition = this.previousPosition; |
||||
var deltaPosition = { |
||||
x: currentPosition.x - previousPosition.x, |
||||
y: currentPosition.y - previousPosition.y, |
||||
z: currentPosition.z - previousPosition.z |
||||
}; |
||||
this.previousPosition.copy(currentPosition); |
||||
this.deltaPosition = deltaPosition; |
||||
} |
||||
}); |
@ -0,0 +1,32 @@ |
||||
AFRAME.registerComponent("watch", { |
||||
init: function() { |
||||
this.timer = AFRAME.utils.getUrlParameter('timer') |
||||
if (!this.timer) this.timer = 300 //5min
|
||||
document.querySelector("#watch").setAttribute("text","value:" + this.formatSeconds(this.timer)) |
||||
this.tick = AFRAME.utils.throttleTick(this.tick, 1000, this); |
||||
// details https://aframe.io/docs/1.0.0/core/utils.html#aframe-utils-throttle-function-minimuminterval-optionalcontext
|
||||
}, |
||||
|
||||
formatSeconds: function(secs) { |
||||
function pad(n) { |
||||
return (n < 10 ? "0" + n : n) |
||||
} |
||||
|
||||
var m = Math.floor(secs / 60); |
||||
var s = Math.floor(secs - m * 60); |
||||
|
||||
return pad(m) + ":" + pad(s); |
||||
}, |
||||
|
||||
tick: function (t, dt) { |
||||
var time = Number( this.timer ) |
||||
console.log(this.timer); |
||||
if(this.timer > 0){ |
||||
this.timer-- |
||||
} else { |
||||
this.timer = 0 |
||||
document.querySelector("#endMessage").setAttribute("visible","true") |
||||
} |
||||
document.querySelector("#watch").setAttribute("text","value:"+ this.formatSeconds(time)) |
||||
}, |
||||
}); |
@ -0,0 +1,72 @@ |
||||
<a-scene background="color: #FAFAFA" vr-mode-ui="enterVRButton: #vrButton"> |
||||
<a-assets> |
||||
<a-mixin id="sticker" |
||||
event-set__grab="material.color: #FFEF4F" |
||||
event-set__grabend="material.color: #F2E646" |
||||
event-set__hit="material.color: #F2E646" |
||||
event-set__hitend="material.color: #FEFEFE" |
||||
geometry="primitive: box; height: 0.2; width: 0.2; depth: 0.01" |
||||
material="color: #FEFEFE;"> |
||||
</a-mixin> |
||||
</a-assets> |
||||
<!-- Extra lights --> |
||||
<a-light type="directional" color="#ffffff" intensity="0.3" position="0.5 2.2 2.866"></a-light> |
||||
<!-- Board --> |
||||
<a-entity position="0 0 -0.5"> |
||||
<a-entity |
||||
position="-1 2 0" |
||||
text="color: white; align: center; width: 2; font: exo2semibold; |
||||
value: To Do" |
||||
></a-entity> |
||||
<a-plane position="-0.5 1.25 0" width="0.01" height="1.6" material="shader: flat; color: #ffffff"></a-plane> |
||||
<a-entity |
||||
position="0 2 0" |
||||
text="color: white; align: center; width: 2; font: exo2semibold; |
||||
value: In Progress" |
||||
></a-entity> |
||||
<a-plane position="0.5 1.25 0" width="0.01" height="1.6" material="shader: flat; color: #ffffff"></a-plane> |
||||
<a-entity |
||||
position="1 2 0" |
||||
text="color: white; align: center; width: 2; font: exo2semibold; |
||||
value: Done" |
||||
></a-entity> |
||||
</a-entity> |
||||
<!-- End message--> |
||||
<a-entity id="endMessage" |
||||
position="0 2.2 -1" |
||||
text="color: #FFCC00; align: center; width: 4; font: exo2semibold; |
||||
value: SESSION FINISHED" visible="false" |
||||
></a-entity> |
||||
<!-- Stickers --> |
||||
|
||||
<a-entity position="0 1.5 -0.2"> |
||||
<a-entity class="sticker" mixin="sticker" position="-0.30 0 0"> |
||||
<a-entity |
||||
position="0 0 0.005" |
||||
text="color: black; align: center; width: 1; font: exo2semibold; |
||||
value: Task #1" |
||||
></a-entity> |
||||
</a-entity> |
||||
<a-entity class="sticker" mixin="sticker" position="0 0 0"> |
||||
<a-entity |
||||
position="0 0 0.005" |
||||
text="color: black; align: center; width: 1; font: exo2semibold; |
||||
value: Task #2" |
||||
></a-entity> |
||||
</a-entity> |
||||
<a-entity class="sticker" mixin="sticker" position="0.30 0 0"> |
||||
<a-entity |
||||
position="0 0 0.005" |
||||
text="color: black; align: center; width: 1; font: exo2semibold; |
||||
value: Task #3" |
||||
></a-entity> |
||||
</a-entity> |
||||
</a-entity> |
||||
|
||||
<a-entity environment="preset: forest;"></a-entity> |
||||
<a-entity hand-controls="left" aabb-collider="objects: .sticker;" grab> |
||||
<a-entity watch id="watch" position="-0.02 -0.005 0.16" rotation="0 -90 180" text="color: white; align: center; width: .5; font: exo2semibold; |
||||
value: 00:59"></a-entity> |
||||
</a-entity> |
||||
<a-entity hand-controls="right" aabb-collider="objects: .sticker;" grab></a-entity> |
||||
</a-scene> |
@ -0,0 +1,58 @@ |
||||
html { |
||||
background: #000; |
||||
} |
||||
|
||||
#vrButton { |
||||
position: absolute; |
||||
background: url('../dist/assets/img/enter-vr-button-background.png') no-repeat; |
||||
background-size: cover; |
||||
border: 0; |
||||
cursor: pointer; |
||||
right: 20px; |
||||
bottom: 20px; |
||||
text-decoration: none; |
||||
z-index: 9999999; |
||||
width: 64px; |
||||
height: 64px; |
||||
} |
||||
|
||||
#vrButton.a-hidden { |
||||
visibility: hidden; |
||||
} |
||||
|
||||
#vrButton:active { |
||||
border: 0; |
||||
} |
||||
|
||||
#vrButton:hover { |
||||
background-position: 0 -64.5px; |
||||
} |
||||
|
||||
#vrButton p { |
||||
bottom: 45px; |
||||
color: #FFF; |
||||
font-size: 12px; |
||||
font-family: monospace; |
||||
font-weight: bold; |
||||
text-align: center; |
||||
text-transform: uppercase; |
||||
position: relative; |
||||
} |
||||
|
||||
.a-loader-title { |
||||
animation: loaderTitle 1s infinite alternate; |
||||
color: rgba(0,0,0,0); |
||||
background: none; |
||||
background-image: url('../dist/assets/img/loadingLogo.png'); |
||||
height: 36.2vh; |
||||
background-repeat: no-repeat; |
||||
background-position: center; |
||||
margin-top: 0; |
||||
background-size: contain; |
||||
} |
||||
|
||||
@keyframes loaderTitle { |
||||
0% { opacity: 1; } |
||||
50% { opacity: 0.8; } |
||||
100% { opacity: 1; } |
||||
} |
@ -0,0 +1,17 @@ |
||||
function requireAll (req) { req.keys().forEach(req); } |
||||
|
||||
console.time = () => {}; |
||||
console.timeEnd = () => {}; |
||||
|
||||
require('aframe-environment-component'); |
||||
require('aframe-event-set-component'); |
||||
|
||||
require('./components/aabb-collider'); |
||||
require('./components/grab'); |
||||
require('./components/watch'); |
||||
|
||||
require('./index.css') |
||||
|
||||
require('./home.html') |
||||
|
||||
if (module.hot) { module.hot.accept(); } |
@ -0,0 +1,66 @@ |
||||
const path = require('path') |
||||
|
||||
module.exports = { |
||||
entry: { |
||||
index: './src/index.js' |
||||
}, |
||||
output: { |
||||
filename: 'bundle.js', |
||||
path: path.resolve(__dirname, 'dist') |
||||
}, |
||||
devServer: { |
||||
https: true, |
||||
port: 3000, |
||||
contentBase: './dist' |
||||
}, |
||||
devtool: 'source-map', |
||||
plugins: [ |
||||
], |
||||
optimization: { |
||||
}, |
||||
module: { |
||||
rules: [ |
||||
{ |
||||
test: /\.js/, |
||||
exclude: /(node_modules)/, |
||||
use: ['babel-loader', 'aframe-super-hot-loader'] |
||||
}, |
||||
{ |
||||
test: /\.json/, |
||||
exclude: /(node_modules)/, |
||||
type: 'javascript/auto', |
||||
loader: ['json-loader'] |
||||
}, |
||||
{ |
||||
test: /\.html/, |
||||
exclude: /(node_modules)/, |
||||
use: [ |
||||
'aframe-super-hot-html-loader', |
||||
{ |
||||
loader: 'html-require-loader', |
||||
options: { |
||||
root: path.resolve(__dirname, 'src') |
||||
} |
||||
} |
||||
] |
||||
}, |
||||
{ |
||||
test: /\.glsl/, |
||||
exclude: /(node_modules)/, |
||||
loader: 'webpack-glsl-loader' |
||||
}, |
||||
{ |
||||
test: /\.css$/, |
||||
exclude: /(node_modules)/, |
||||
use: ['style-loader', 'css-loader'] |
||||
}, |
||||
{ |
||||
test: /\.(png|jpg)/, |
||||
loader: 'url-loader' |
||||
} |
||||
] |
||||
}, |
||||
resolve: { |
||||
modules: [path.join(__dirname, 'node_modules')] |
||||
} |
||||
} |
Loading…
Reference in new issue