diff --git a/assets/img/keyboard/sk-basic.png b/assets/img/keyboard/sk-basic.png new file mode 100644 index 0000000..9b9e55f Binary files /dev/null and b/assets/img/keyboard/sk-basic.png differ diff --git a/package-lock.json b/package-lock.json index 24e39a6..876a41d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -151,6 +151,36 @@ "aframe": "github:aframevr/aframe#2afb6ac191f1a44edbbfe6f0e733380356a9e0c8" } }, + "aframe-super-keyboard": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/aframe-super-keyboard/-/aframe-super-keyboard-2.0.0.tgz", + "integrity": "sha512-GYRMEm0mu8QO1+x0BawM1dMedlzlGidivtsvOxo3MVUnwJTrnE4TokzOP7PxO+IO5Zxl7VDTcloJ/vEskm/ZUQ==", + "requires": { + "uglify-js": "^3.3.23", + "uglifyjs": "^2.4.11" + }, + "dependencies": { + "commander": { + "version": "2.16.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.16.0.tgz", + "integrity": "sha512-sVXqklSaotK9at437sFlFpyOcJonxe0yST/AG9DkQKUdIE6IqGIMv4SfAQSKaJbSdVEJYItASCrBiVQHq1HQew==" + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + }, + "uglify-js": { + "version": "3.4.5", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.4.5.tgz", + "integrity": "sha512-Fm52gLqJqFBnT+Sn411NPDnsgaWiYeRLw42x7Va/mS8TKgaepwoGY7JLXHSEef3d3PmdFXSz1Zx7KMLL89E2QA==", + "requires": { + "commander": "~2.16.0", + "source-map": "~0.6.1" + } + } + } + }, "agentkeepalive": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-2.2.0.tgz", @@ -10230,6 +10260,11 @@ "integrity": "sha1-bgkk1r2mta/jSeOabWMoUKD4grc=", "optional": true }, + "uglifyjs": { + "version": "2.4.11", + "resolved": "https://registry.npmjs.org/uglifyjs/-/uglifyjs-2.4.11.tgz", + "integrity": "sha1-NEDWTgRXWViVJEGOtkHGi7kNET4=" + }, "uglifyjs-webpack-plugin": { "version": "0.4.6", "resolved": "https://registry.npmjs.org/uglifyjs-webpack-plugin/-/uglifyjs-webpack-plugin-0.4.6.tgz", diff --git a/package.json b/package.json index 3381d05..c420aa5 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "aframe-proxy-event-component": "^1.1.1", "aframe-slice9-component": "^1.0.0", "aframe-state-component": "^4.1.0", + "aframe-super-keyboard": "^2.0.0", "algoliasearch": "^3.29.0", "ansi-html": "0.0.7", "autoprefixer": "^7.2.3", diff --git a/src/components/controller.js b/src/components/controller.js new file mode 100644 index 0000000..e7cb217 --- /dev/null +++ b/src/components/controller.js @@ -0,0 +1,109 @@ +var handData = { + right: {hand: 'right', model: false}, + left: {hand: 'left', model: false}, +}; + +/** + * Controller visuals. + */ +AFRAME.registerComponent('controller', { + schema: { + hand: {type: 'string'}, + menuActive: {default: true} + }, + + init: function() { + var data = this.data; + var el = this.el; + + this.controllerType = ''; + + el.addEventListener('object3dset', evt => { + var mesh; + if (evt.detail.type !== 'mesh') { return; } + if (this.controllerType) { + this.setControllerRotation(); + } + }); + + // Set controllers. + // TODO: Add controller model. + el.setAttribute('geometry', {primitive: 'box', width: 0.05, depth: 0.05, height: 0.05}); + el.setAttribute('oculus-touch-controls', handData[data.hand]); + el.setAttribute('vive-controls', handData[data.hand]); + el.setAttribute('windows-motion-controls', handData[data.hand]); + + el.addEventListener('controllerconnected', evt => { + this.controllerConnected = true; + this.controllerType = evt.detail.name; + if (this.el.getObject3D('mesh')) { this.setControllerRotation(); } + if (data.hand === 'left') { return; } + const controllerConfig = this.config[this.controllerType]; + el.setAttribute('raycaster', controllerConfig.raycaster || {}); + el.setAttribute('cursor', controllerConfig.cursor || {}); + el.setAttribute('line', {opacity: 0.75, color: 'pink'}); + }); + }, + + /** + * TODO: Adjust rotation depending on controller type. + */ + setControllerRotation: function() { + var el = this.el; + mesh = el.getObject3D('mesh'); + if (this.controllerType === 'vive-controls') { + mesh.rotation.x = THREE.Math.degToRad(90); + mesh.rotation.y = THREE.Math.degToRad(this.data.hand === 'left' ? -90 : 90); + mesh.rotation.z = THREE.Math.degToRad(180); + } else { + mesh.rotation.y = THREE.Math.degToRad(180); + } + }, + + update: function() { + var data = this.data; + var el = this.el; + if (data.hand === 'left') { return; } + if (this.controllerConnected) { + el.setAttribute('raycaster', 'enabled', data.menuActive); + el.setAttribute('raycaster', 'showLine', data.menuActive); + } + }, + + config: { + 'oculus-touch-controls': { + cursor: { + downEvents: [ + 'triggerdown', + 'gripdown', + 'abuttondown', + 'bbuttondown', + 'xbuttondown', + 'ybuttondown', + ], + upEvents: [ + 'triggerup', + 'gripup', + 'abuttonup', + 'bbuttonup', + 'xbuttonup', + 'ybuttonup', + ], + }, + }, + + 'vive-controls': { + cursor: { + downEvents: ['trackpaddown', 'triggerdown', 'gripdown'], + upEvents: ['trackpadup', 'triggerup', 'gripup'], + }, + }, + + 'windows-motion-controls': { + cursor: { + downEvents: ['trackpaddown', 'triggerdown', 'gripdown'], + upEvents: ['trackpadup', 'triggerup', 'gripup'], + }, + } + } +}); diff --git a/src/components/keyboard-raycastable.js b/src/components/keyboard-raycastable.js index 50543fd..6e385ec 100644 --- a/src/components/keyboard-raycastable.js +++ b/src/components/keyboard-raycastable.js @@ -1,10 +1,8 @@ AFRAME.registerComponent('keyboard-raycastable', { - play: function() { - var els; - var i; - els = this.el.querySelectorAll('*'); - for (i = 0; i < els.length; i++) { - els[i].setAttribute('bind-toggle__raycastable', 'isSearchScreen'); - } + dependencies: ['super-keyboard'], + + play: function () { + // TODO: bind-toggle__raycastable for when search is activated. + this.el.components['super-keyboard'].kbImg.setAttribute('raycastable', ''); }, }); diff --git a/src/components/search.js b/src/components/search.js index 47566ea..ecea022 100644 --- a/src/components/search.js +++ b/src/components/search.js @@ -7,6 +7,7 @@ var index = client.initIndex('supersaber'); /** * Search (including the initial list of popular searches). + * Attached to super-keyboard. */ AFRAME.registerComponent('search', { init: function() { @@ -17,6 +18,10 @@ AFRAME.registerComponent('search', { this.search(''); }, + superkeyboardchange: bindEvent(function (evt) { + this.search(evt.detail.value); + }), + search: function (query) { this.queryObject.query = query; index.search(this.queryObject, (err, content) => { @@ -35,6 +40,7 @@ AFRAME.registerComponent('search-result', { this.audio = new Audio(); this.audio.currentTime = el.getAttribute('data-preview-start-time'); this.audio.src = utils.getS3FileUrl(el.getAttribute('data-id'), 'song.ogg'); + this.audio.volume = 0.5; this.eventDetail = {}; }, diff --git a/src/index.html b/src/index.html index 42becd0..e559fbb 100644 --- a/src/index.html +++ b/src/index.html @@ -15,7 +15,7 @@ console-shortcuts search> - + @@ -37,8 +37,8 @@ - - + + diff --git a/src/index.js b/src/index.js index 7e44dfe..2709819 100644 --- a/src/index.js +++ b/src/index.js @@ -12,6 +12,7 @@ require('aframe-particle-system-component'); require('aframe-proxy-event-component'); require('aframe-state-component'); require('aframe-slice9-component'); +require('aframe-super-keyboard'); requireAll(require.context('./components/', true, /\.js$/)); requireAll(require.context('./state/', true, /\.js$/)); diff --git a/src/state/index.js b/src/state/index.js index 569da69..6123ced 100644 --- a/src/state/index.js +++ b/src/state/index.js @@ -4,117 +4,66 @@ AFRAME.registerState({ initialState: { challenge: { id: AFRAME.utils.getUrlParameter('challenge'), - isLoading: false, - loadingText: '' + isLoading: false }, discoLightsOn: true, discotube: {speedX: -0.05, speedY: -0.1}, - featuredPanelShowing: !hasInitialChallenge, inVR: false, - isChallengeScreen: hasInitialChallenge, - isFeaturedScreen: !hasInitialChallenge, - isSearchScreen: false, maxStreak: 0, menuActive: true, - playButtonShowing: hasInitialChallenge, playButtonText: 'Play', score: 0, scoreText: '', - screen: hasInitialChallenge ? 'challenge' : 'featured', + // screen: keep track of layers or depth. Like breadcrumbs. + screen: hasInitialChallenge ? 'challenge' : 'home', screenHistory: [], searchResults: [], streak: 0 }, handlers: { - /** - * Update search results. Will automatically render using `bind-for` (menu.html). - */ - searchresults: function (state, payload) { - var i; - state.searchResults.length = 0; - for (i = 0; i < 6; i++) { - state.searchResults.push(payload.results[i]); - } - }, - - setscreen: function (state, payload) { - console.log('setscreen: ' + payload.screen); - if (state.screen === payload.screen) { - return; - } - switch (payload.screen) { - case 'challenge': - case 'featured': - case 'search': - state.screenHistory.push(state.screen); - state.screen = payload.screen; - break; - default: - console.log('Unknown screen set: ' + payload.screen); - } - }, - - popscreen: function (state, payload) { - var prevScreen = state.screenHistory.pop(); - if (!prevScreen) { - return; - } - state.screen = prevScreen; - console.log('popscreen: ' + state.screen); - }, - - challengeloaded: function (state, payload) { + beatloaderfinish: function (state, payload) { state.challenge.isLoading = false; - state.isLoadingFrames = false; - state.isLoadingPunches = false; - state.score = 0; }, - challengeloadstart: function (state, payload) { + beatloaderstart: function (state, payload) { state.challenge.isLoading = true; }, - challengeloadingframes: function (state, payload) { - state.challenge.isLoadingPunches = false; - state.challenge.isLoadingFrames = true; - }, - - challengeloadingpunches: function (state, payload) { - state.challenge.isLoadingPunches = true; - state.challenge.isLoadingFrames = false; - }, - challengeset: function (state, payload) { state.challenge.id = payload.challengeId; state.score = 0; state.streak = 0; state.maxStreak = 0; state.menuActive = false; - - this.setscreen(state, {screen: 'challenge'}); + setScreen(state, 'challenge'); }, playbuttonclick: function (state) { state.menuActive = false; }, - searchblur: function (state) { - this.popscreen(state); - }, - - searchfocus: function (state) { - this.setscreen(state, {screen: 'search'}); - }, - - togglemenu: function (state) { - state.menuActive = !state.menuActive; + /** + * Update search results. Will automatically render using `bind-for` (menu.html). + */ + searchresults: function (state, payload) { + var i; + state.searchResults.length = 0; + for (i = 0; i < 6; i++) { + if (!payload.results[i]) { continue; } + state.searchResults.push(payload.results[i]); + } + state.searchResults.__dirty = true; }, togglediscolights: function (state, payload) { state.discoLightsOn = !state.discoLightsOn; }, + togglemenu: function (state) { + state.menuActive = !state.menuActive; + }, + 'enter-vr': function (state, payload) { state.inVR = true; }, @@ -126,16 +75,28 @@ AFRAME.registerState({ computeState: function (state) { state.scoreText = `Streak: ${state.streak} / Max Streak: ${state.maxStreak} / Score: ${state.score}`; - state.isFeaturedScreen = state.screen === 'featured'; - state.isSearchScreen = state.screen === 'search'; - state.isChallengeScreen = state.screen === 'challenge'; - - state.featuredPanelShowing = state.isFeaturedScreen || (state.isChallengeScreen && state.menuActive); - - if (state.challenge.isLoading) { - state.loadingText = 'Loading challenge...'; - } else { - state.loadingText = ''; - } } }); + +/** + * Push screen onto history and set to current. + */ +function setScreen (state, screen) { + if (state.screen === screen) { return; } + state.screenHistory.push(screen); + state.screen = screen; +} + +/** + * Pop screen off history. + * Set new current screen if any. + */ +function popScreen (state) { + var prevScreen; + prevScreen = state.screenHistory.pop(); + if (state.screenHistory.length === 0) { + state.screen = ''; + return; + } + state.screen = state.screenHistory[state.screenHistory.length - 1]; +} diff --git a/src/templates/menu.html b/src/templates/menu.html index 83470fc..c96852c 100644 --- a/src/templates/menu.html +++ b/src/templates/menu.html @@ -34,25 +34,9 @@ - + + + - - - {{ searchResults() }} diff --git a/src/templates/ui.html b/src/templates/ui.html deleted file mode 100644 index 2a890c5..0000000 --- a/src/templates/ui.html +++ /dev/null @@ -1,25 +0,0 @@ -{% macro searchresult (featured) %} - - - - - - - - - - - -{% endmacro %} diff --git a/webpack.config.js b/webpack.config.js index 6d7c577..a83901a 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -10,11 +10,12 @@ var webpack = require('webpack'); var nunjucks = Nunjucks.configure(path.resolve(__dirname, 'src'), { noCache: true, }); +nunjucks.addGlobal('DEBUG_KEYBOARD', !!process.env.DEBUG_KEYBOARD); nunjucks.addGlobal('HOST', ip.address()); +nunjucks.addGlobal('IS_PRODUCTION', process.env.NODE_ENV === 'production'); // Initial Nunjucks render. -var context = {IS_PRODUCTION: process.env.NODE_ENV === 'production'}; -fs.writeFileSync('index.html', nunjucks.render('index.html', context)); +fs.writeFileSync('index.html', nunjucks.render('index.html')); // For development, watch HTML for changes to compile Nunjucks. // The production Express server will handle Nunjucks by itself. @@ -24,7 +25,7 @@ if (process.env.NODE_ENV !== 'production') { return; } try { - fs.writeFileSync('index.html', nunjucks.render('index.html', context)); + fs.writeFileSync('index.html', nunjucks.render('index.html')); } catch (e) { console.error(e); }