├── .gitignore ├── LICENSE.md ├── README.md ├── app ├── assets │ ├── grid.png │ ├── index.html │ └── sky.png ├── css │ └── app.css └── js │ └── initialize.js ├── assets └── img │ ├── aframe-logo-bw.png │ ├── aframe-logo.png │ └── aframe-logo.sketch ├── commands ├── build.js ├── deploy.js ├── index.js ├── new.js ├── serve.js └── submit.js ├── index.js ├── lerna.json ├── lib ├── brunch-config.js ├── build.js ├── clean-url.js ├── deploy.js ├── init-template.js ├── manifest.js ├── print-banner.js ├── serve.js ├── submit.js └── utils.js ├── package-lock.json ├── package.json ├── templates ├── aframe-360-tour-template │ ├── README.md │ ├── app │ │ ├── assets │ │ │ ├── images │ │ │ │ ├── aquabus.jpg │ │ │ │ ├── dock.jpg │ │ │ │ ├── seawall.jpg │ │ │ │ └── yaletown.jpg │ │ │ └── index.html │ │ ├── css │ │ │ └── app.css │ │ └── js │ │ │ ├── components │ │ │ ├── helper.js │ │ │ ├── hotspot.js │ │ │ ├── panorama.js │ │ │ └── tour.js │ │ │ └── initialize.js │ ├── images │ │ └── hotspot-helper.png │ ├── package-lock.json │ └── package.json ├── aframe-default-template │ ├── app │ │ ├── assets │ │ │ ├── grid.png │ │ │ ├── index.html │ │ │ └── sky.png │ │ ├── css │ │ │ └── app.css │ │ └── js │ │ │ └── initialize.js │ ├── package-lock.json │ └── package.json ├── aframe-model-template │ ├── app │ │ ├── assets │ │ │ ├── images │ │ │ │ ├── arrow.png │ │ │ │ ├── grid.png │ │ │ │ └── sky.png │ │ │ ├── index.html │ │ │ ├── models │ │ │ │ ├── rocky.bin │ │ │ │ ├── rocky.gltf │ │ │ │ └── texture.png │ │ │ └── sounds │ │ │ │ └── switch.wav │ │ ├── css │ │ │ └── app.css │ │ └── js │ │ │ ├── components │ │ │ └── teleport.js │ │ │ └── initialize.js │ ├── package-lock.json │ └── package.json ├── aframe-primitives-template │ ├── app │ │ ├── assets │ │ │ ├── images │ │ │ │ ├── grid.png │ │ │ │ └── sky.png │ │ │ └── index.html │ │ ├── css │ │ │ └── app.css │ │ └── js │ │ │ └── initialize.js │ ├── package-lock.json │ └── package.json ├── aframe-room-template │ ├── app │ │ ├── assets │ │ │ ├── images │ │ │ │ ├── grid.png │ │ │ │ └── sky.png │ │ │ └── index.html │ │ ├── css │ │ │ └── app.css │ │ └── js │ │ │ └── initialize.js │ ├── package-lock.json │ └── package.json └── index.json └── tools └── publish-all.js /.gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | .DS_Store 3 | .component 4 | .sw[ponm] 5 | examples/build.js 6 | examples/node_modules/ 7 | gh-pages 8 | my-*/ 9 | my-aframe-project 10 | node_modules/ 11 | tmp/ 12 | 13 | .public/ 14 | public/ 15 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright © A-Frame CLI authors. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # aframe-cli 2 | 3 | A command-line tool for building, managing, and publishing A-Frame scenes. 4 | 5 | > **⚠ NOTE:️ This is not meant to be used just yet. This is a WIP!** 6 | 7 | > _TODO: Upstream changes to [`angle`](https://github.com/aframevr/angle)._ 8 | 9 | 10 | ### Usage 11 | 12 | ```sh 13 | npm install -g aframe-cli 14 | aframe 15 | ``` 16 | 17 | 18 | ### Commands 19 | 20 | To get a list of all commands and options: 21 | 22 | ```sh 23 | aframe --help 24 | ``` 25 | 26 | #### `aframe new [template]` 27 | 28 | To create a new A-Frame scene in the working directory: 29 | 30 | ```sh 31 | aframe new 32 | ``` 33 | 34 | To create a new A-Frame scene in a different directory: 35 | 36 | ```sh 37 | aframe new my-project-directory 38 | ``` 39 | 40 | To bootstrap a new A-Frame scene from a boilerplate template: 41 | 42 | ```sh 43 | aframe new my-project-directory --template default 44 | ``` 45 | 46 | From a GitHub repository (such as [`aframevr/aframe-default-template`](https://github.com/aframevr/aframe-default-template)): 47 | 48 | ```sh 49 | aframe new my-project-directory --template aframevr/aframe-default-template 50 | ``` 51 | 52 | #### `aframe build [options]` 53 | 54 | To build the static files (i.e., HTML/CSS/JS) for your A-Frame scene in the working directory: 55 | 56 | ```sh 57 | aframe build 58 | ``` 59 | 60 | The files will be written to the `.public` directory, by default, in your A-Frame project's working directory (you can override the `paths.public` value in your own custom Brunch config file). [This default Brunch config file](lib/brunch-config.js) will be used if a `brunch-config.js` file does not exist and the `--config ` flag is not passed when calling `aframe build`). 61 | 62 | For other options, refer to the usage information returned from `aframe serve --help`: 63 | 64 | ``` 65 | Command: aframe build 66 | 67 | Usage: build|b [options] [path] 68 | 69 | Build an A-Frame project in path (default: current directory). 70 | 71 | Options: 72 | 73 | -h, --help output usage information 74 | -e, --env [setting] specify a set of override settings to apply 75 | -p, --production same as `--env production` 76 | -d, --debug [pattern] print verbose debug output to stdout 77 | -j, --jobs [num] parallelize the build 78 | -c, --config [path] specify a path to Brunch config file 79 | ``` 80 | 81 | #### `aframe serve [options]` 82 | 83 | To start a local development server for your A-Frame scene from your project's directory: 84 | 85 | ```sh 86 | aframe serve 87 | ``` 88 | 89 | The server (which defaults to listening on port `3333`) you can now load here: **[http://localhost:3333/](http://localhost:3333/)** 90 | 91 | To create an A-Frame scene in a different directory: 92 | 93 | ```sh 94 | aframe serve my-project-directory 95 | ``` 96 | 97 | To run in the production mode (how your site would look when published and deployed online): 98 | 99 | ```sh 100 | aframe serve my-project-directory --production 101 | ``` 102 | 103 | To change the server port, for example, to `8080`: 104 | 105 | ```sh 106 | aframe serve -P 8080 107 | ``` 108 | 109 | For other options, refer to the usage information returned from `aframe serve --help`: 110 | 111 | ``` 112 | Command: aframe serve 113 | 114 | Usage: serve|s [options] [path] 115 | 116 | Serve an A-Frame project in path (default: current directory). 117 | 118 | Options: 119 | 120 | -h, --help output usage information 121 | -e, --env [setting] specify a set of override settings to apply 122 | -p, --production same as `--env production` 123 | -s, --server run a simple HTTP server for the public directory on localhost 124 | -n, --network if `server` was given, allow access from the network 125 | -P, --port [port] if `server` was given, listen on this port 126 | -d, --debug [pattern] print verbose debug output to stdout 127 | -j, --jobs [num] parallelize the build 128 | -c, --config [path] specify a path to Brunch config file 129 | --stdin listen to stdin and exit when stdin closes 130 | ``` 131 | 132 | #### `aframe install [scene-filename.html]` 133 | 134 | Install a component from the [A-Frame Registry](https://aframe.io/registry) to an HTML file. This will detect the A-Frame version from your HTML file and install the appropriate version of the component as a ` 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /app/assets/sky.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aframevr-userland/aframe-cli/e99a855d4c4e8109b41461907090e2c61b0b5a71/app/assets/sky.png -------------------------------------------------------------------------------- /app/css/app.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aframevr-userland/aframe-cli/e99a855d4c4e8109b41461907090e2c61b0b5a71/app/css/app.css -------------------------------------------------------------------------------- /app/js/initialize.js: -------------------------------------------------------------------------------- 1 | document.addEventListener('DOMContentLoaded', () => { 2 | console.log('Initialized app'); 3 | }); 4 | -------------------------------------------------------------------------------- /assets/img/aframe-logo-bw.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aframevr-userland/aframe-cli/e99a855d4c4e8109b41461907090e2c61b0b5a71/assets/img/aframe-logo-bw.png -------------------------------------------------------------------------------- /assets/img/aframe-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aframevr-userland/aframe-cli/e99a855d4c4e8109b41461907090e2c61b0b5a71/assets/img/aframe-logo.png -------------------------------------------------------------------------------- /assets/img/aframe-logo.sketch: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aframevr-userland/aframe-cli/e99a855d4c4e8109b41461907090e2c61b0b5a71/assets/img/aframe-logo.sketch -------------------------------------------------------------------------------- /commands/build.js: -------------------------------------------------------------------------------- 1 | module.exports = require('../lib/build.js').build; 2 | -------------------------------------------------------------------------------- /commands/deploy.js: -------------------------------------------------------------------------------- 1 | module.exports = require('../lib/deploy.js').deploy; 2 | -------------------------------------------------------------------------------- /commands/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 'build': require('./build.js'), 3 | 'deploy': require('./deploy.js'), 4 | 'new': require('./new.js'), 5 | 'serve': require('./serve.js'), 6 | 'submit': require('./submit.js') 7 | }; 8 | -------------------------------------------------------------------------------- /commands/new.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | const logger = require('loggy'); 4 | 5 | const checkLegacyNewSyntax = options => { 6 | const rawArgs = options.parent.rawArgs; 7 | const newArgs = rawArgs.slice(rawArgs.indexOf('new') + 1); 8 | const oldSyntax = !options.template && newArgs.length === 2; 9 | if (!oldSyntax) { return; } 10 | }; 11 | 12 | module.exports = (rootPath, options) => { 13 | options = options || { 14 | parent: { 15 | args: [] 16 | } 17 | }; 18 | 19 | const initTemplate = require('../lib/init-template.js').init; 20 | const template = options.template || 'aframe-default-template'; 21 | 22 | rootPath = rootPath || options.parent.args[0]; 23 | 24 | return initTemplate(template, { 25 | logger, 26 | rootPath, 27 | commandName: 'aframe new' 28 | }); 29 | }; 30 | -------------------------------------------------------------------------------- /commands/serve.js: -------------------------------------------------------------------------------- 1 | module.exports = require('../lib/serve.js').serve; 2 | -------------------------------------------------------------------------------- /commands/submit.js: -------------------------------------------------------------------------------- 1 | module.exports = require('../lib/submit.js').submit; 2 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | process.on('unhandledRejection', err => { 3 | throw err; 4 | }); 5 | process.on('SIGINT', () => { 6 | process.exit(); 7 | }); 8 | process.on('SIGTERM', () => { 9 | process.exit(); 10 | }); 11 | 12 | const path = require('path'); 13 | 14 | const chalk = require('chalk'); 15 | const fs = require('fs-extra'); 16 | const program = require('commander'); 17 | 18 | const commands = require('./commands/index.js'); 19 | const pkg = require('./package.json'); 20 | 21 | const displayHelp = () => program.outputHelp(colorizeHelp); 22 | const displayLogo = () => { 23 | const pictureTube = require('picture-tube'); 24 | return fs.createReadStream(path.join(__dirname, 'assets', 'img', 'aframe-logo.png')) 25 | .pipe(pictureTube({cols: 46})) 26 | .pipe(process.stdout); 27 | }; 28 | 29 | program 30 | .version(pkg.version, '-v, --version', 'output the version number') 31 | .usage('[command] [options]'); 32 | 33 | if (process.argv[2] === 'publish' || process.argv[2] === 'push') { 34 | process.argv[2] = 'deploy'; 35 | } 36 | 37 | // For backwards compatibility, convert `aframe new` commands from the old to new CLI format. 38 | // 39 | // aframe new 360-tour => aframe new --template 360-tour 40 | // aframe new 360-tour my-scene => aframe new --template 360-tour my-scene 41 | // 42 | // NOTE: The new CLI usage is defined here: https://aframe.io/cli/ 43 | // The new CLI rearchitecture is being properly addressed in PR #68: 44 | // https://github.com/aframevr-userland/aframe-cli/pull/68 45 | if (process.argv[2] === 'new') { 46 | if (!process.argv.includes('--template')) { 47 | process.argv.splice(3, 0, '--template'); 48 | } 49 | } 50 | 51 | program 52 | .command('deploy [path]') 53 | .alias('d') 54 | .description('Deploy (to a static CDN) an A-Frame project in path (default: current directory).') 55 | .option('-p, --production', 'same as `--env production`') 56 | .option('-c, --config [path]', 'specify a path to Brunch config file') 57 | .option('-t, --timeout [timeout]', 'timeout (in milliseconds) for connecting to CDN (default: `5000`)', parseFloat, 5000) 58 | .option('-d, --disconnect-timeout [timeout]', 'timeout (in milliseconds) for disconnecting to CDN (default: `10000`)', parseFloat, 10000) 59 | .option('--no-open', 'do not automatically open browser window') 60 | .option('--no-clipboard', 'do not automatically add deployed URL to clipboard') 61 | .option('--no-submit', 'do not submit site to the A-Frame Index') 62 | .action((filePath, options) => { 63 | displayLogo(); 64 | setTimeout(() => { 65 | process.stdout.write('\n'); 66 | commands.deploy(filePath, options); 67 | }, 150); 68 | }); 69 | 70 | program 71 | .command('submit [url]') 72 | .alias('i') 73 | .description('Submit site URL (of an A-Frame project) to the A-Frame Index.') 74 | .option('-t, --timeout [timeout]', 'timeout (in milliseconds) for submitting to A-Frame Index API (default: `5000`)', parseFloat, 5000) 75 | .option('--no-open', 'do not automatically open browser window') 76 | .option('--no-clipboard', 'do not automatically add deployed URL to clipboard') 77 | .option('--no-submit', 'do not submit site to the A-Frame Index') 78 | .action((siteUrl, options) => { 79 | displayLogo(); 80 | setTimeout(() => { 81 | process.stdout.write('\n'); 82 | commands.submit(siteUrl, options); 83 | }, 150); 84 | }); 85 | 86 | program 87 | .command('serve [path] [options]') 88 | .alias('s') 89 | .description('Serve an A-Frame project in path (default: current directory).') 90 | .option('-e, --env [setting]', 'specify a set of override settings to apply') 91 | .option('-p, --production', 'same as `--env production`') 92 | .option('-s, --server', 'run a simple HTTP server for the public directory on localhost') 93 | .option('-n, --network', 'if `server` was given, allow access from the network') 94 | .option('-P, --port [port]', 'if `server` was given, listen on this port') 95 | .option('-d, --debug [pattern]', 'print verbose debug output to stdout') 96 | .option('-j, --jobs [num]', 'parallelize the build') 97 | .option('-c, --config [path]', 'specify a path to Brunch config file') 98 | .option('--stdin', 'listen to stdin and exit when stdin closes') 99 | .option('--no-open', 'do not automatically open browser window') 100 | .option('--no-clipboard', 'do not automatically add served URL to clipboard') 101 | .action((watchPath, options) => { 102 | displayLogo(); 103 | setTimeout(() => { 104 | process.stdout.write('\n'); 105 | commands.serve(watchPath, options); 106 | }, 150); 107 | }); 108 | 109 | program 110 | .command('build [path]') 111 | .alias('b') 112 | .description('Build an A-Frame project in path (default: current directory).') 113 | .option('-e, --env [setting]', 'specify a set of override settings to apply') 114 | .option('-p, --production', 'same as `--env production`') 115 | .option('-d, --debug [pattern]', 'print verbose debug output to stdout') 116 | .option('-j, --jobs [num]', 'parallelize the build') 117 | .option('-c, --config [path]', 'specify a path to Brunch config file') 118 | .action((filePath, options) => commands.build(filePath, options)); 119 | 120 | program 121 | .command('new [path]') 122 | .alias('n') 123 | .description('Create a new A-Frame project in path.') 124 | .option('-t, --template [name]', 'template name or URL from https://aframe.io/templates') 125 | .on('--help', () => { 126 | require('./lib/init-template.js').printBanner('aframe new --template'); 127 | }) 128 | .action(commands.new); 129 | 130 | program 131 | .command('help', null, {isDefault: true}) 132 | .alias('h') 133 | .description('Output usage information.') 134 | .action(displayHelp); 135 | 136 | let args = process.argv.slice(); 137 | const programName = args[1] = 'aframe'; 138 | const helpFlag = args.includes('--help') || args.includes('-h'); 139 | const helpCommand = args.includes('help') || args.includes('h'); 140 | const help = helpFlag || helpCommand; 141 | const versionFlag = args.includes('--version') || args.includes('-v'); 142 | const versionCommand = args.includes('version') || args.includes('v'); 143 | const version = versionFlag || versionCommand; 144 | let showInvalidMessage = args.length > 2 && !help && !version; 145 | 146 | const command = args[2]; 147 | 148 | const validCommand = program.commands.some(cmd => { 149 | return cmd.name() === command || cmd.alias() === command; 150 | }); 151 | 152 | showInvalidMessage = !validCommand && !help && !version; 153 | 154 | const colorizeHelp = txt => { 155 | // TODO: Figure out how to get `commander` to colorize command help 156 | // (i.e., the `Usage:` section). 157 | return txt 158 | .replace('Usage: ', `${programName} `) 159 | .replace(new RegExp(`${programName} `, 'g'), `${chalk.bold.cyan(programName)} `) 160 | .replace(/\[command\] /g, `${chalk.magenta('[command]')} `); 161 | }; 162 | 163 | function init () { 164 | // User ran command `aframe`. 165 | if (args.length < 3) { 166 | displayLogo(); 167 | process.on('exit', () => { 168 | process.stdout.write('\n'); 169 | if (args.length < 3) { 170 | displayHelp(); 171 | } 172 | }); 173 | 174 | setTimeout(() => { 175 | if (args.length < 3) { 176 | process.exit(); 177 | } 178 | }, 150); 179 | return; 180 | } 181 | 182 | // User ran command `aframe --help`. 183 | if (args.length < 4 && helpFlag) { 184 | displayHelp(); 185 | process.exit(0); 186 | } 187 | 188 | if (help && validCommand) { 189 | console.log(); 190 | console.error(` ${chalk.black.bgGreen('Command:')} ${chalk.bold.cyan(programName)} ${chalk.magenta(command)}`); 191 | } 192 | 193 | program.parse(args); 194 | 195 | if (!validCommand) { 196 | if (showInvalidMessage) { 197 | console.log(); 198 | console.error(` ${chalk.black.bgRed('Invalid command:')} ${chalk.cyan(programName)} ${chalk.magenta(command)}`); 199 | } 200 | displayHelp(); 201 | process.exit(1); 202 | } 203 | } 204 | 205 | init(); 206 | 207 | module.exports.init = init; 208 | module.exports.displayHelp = displayHelp; 209 | module.exports.displayLogo = displayLogo; 210 | module.exports.program = program; 211 | module.exports.programName = programName; 212 | -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "lerna": "2.0.0-rc.5", 3 | "version": "0.5.18", 4 | "packages": [ 5 | "templates/*" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /lib/brunch-config.js: -------------------------------------------------------------------------------- 1 | // See docs: http://brunch.io/docs/config 2 | let config = { 3 | files: { 4 | javascripts: { 5 | joinTo: { 6 | 'js/vendor.js': /^(?!app)/, // Match files not in the `app` directory. 7 | 'js/bundle.js': /^app/ 8 | } 9 | }, 10 | stylesheets: { 11 | joinTo: 'css/bundle.css' 12 | }, 13 | templates: { 14 | joinTo: { 15 | 'js/bundle.js': /^app\/templates/ 16 | } 17 | } 18 | }, 19 | npm: { 20 | globals: { 21 | AFRAME: 'aframe' 22 | } 23 | }, 24 | paths: { 25 | public: '.public' 26 | }, 27 | modules: { 28 | autoRequire: { 29 | 'js/bundle.js': [ 30 | 'js/initialize.js' 31 | ] 32 | } 33 | }, 34 | plugins: { 35 | babel: { 36 | presets: [ 37 | 'latest', 38 | 'stage-0' 39 | ], 40 | ignore: [ 41 | /^(node_modules)/ 42 | ] 43 | }, 44 | swPrecache: { 45 | autorequire: [ 46 | '.public/index.html' 47 | ], 48 | options: { 49 | staticFileGlobs: [ 50 | '.public/**/!(*map*)' 51 | ], 52 | stripPrefix: '.public/' 53 | } 54 | } 55 | } 56 | }; 57 | 58 | module.exports = config; 59 | -------------------------------------------------------------------------------- /lib/build.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | const brunch = require('brunch'); 4 | const logger = require('loggy'); 5 | 6 | const getBrunchConfigPath = require('./utils.js').getBrunchConfigPath; 7 | 8 | const build = (filePath, options) => { 9 | return new Promise((resolve, reject) => { 10 | filePath = filePath || process.cwd(); 11 | options = options || {}; 12 | options.config = options.config || getBrunchConfigPath(filePath, options); 13 | logger.log(`Building project "${filePath}" …`); 14 | 15 | try { 16 | brunch.build(options, resolve); 17 | } catch (err) { 18 | logger.error(`Could not build project "${filePath}" …`); 19 | throw err; 20 | } 21 | }).then(() => { 22 | let builtPath = null; 23 | try { 24 | builtPath = path.resolve(filePath, require(options.config).paths.public); 25 | } catch (err) { 26 | } 27 | if (builtPath) { 28 | logger.log(`Built project "${filePath}" to "${builtPath}"`); 29 | return Promise.resolve(builtPath); 30 | } else { 31 | logger.log(`Built project "${filePath}"`); 32 | } 33 | }); 34 | }; 35 | 36 | exports.build = build; 37 | exports.printBanner = commandName => { 38 | // return printBanner(commandName).catch(err => { 39 | // console.log(err.message); 40 | // }); 41 | }; 42 | -------------------------------------------------------------------------------- /lib/clean-url.js: -------------------------------------------------------------------------------- 1 | const hostedGitInfo = require('hosted-git-info'); 2 | const loggy = require('loggy'); 3 | const normalizeGitUrl = require('normalize-git-url'); 4 | 5 | const cleanURL = address => { 6 | let git = address.replace(/^gh:/, 'github:'); 7 | const hosted = hostedGitInfo.fromUrl(git); 8 | if (hosted) { 9 | git = hosted.git(); 10 | } else { 11 | loggy.warn(`Couldn't interpret "${git}" as a hosted git URL`); 12 | } 13 | 14 | return normalizeGitUrl(git).url; 15 | }; 16 | 17 | module.exports = cleanURL; 18 | -------------------------------------------------------------------------------- /lib/deploy.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | const brunch = require('brunch'); 4 | const clipboardy = require('clipboardy'); 5 | const fs = require('fs-extra'); 6 | const glob = require('glob'); 7 | const logger = require('loggy'); 8 | const opn = require('opn'); 9 | 10 | const getBrunchConfigPath = require('./utils.js').getBrunchConfigPath; 11 | const submitToIndex = require('./submit.js').submit; 12 | 13 | const deploy = (deployPath, options) => { 14 | deployPath = deployPath || process.cwd(); 15 | options = options || {}; 16 | options.config = options.config || getBrunchConfigPath(deployPath, options); 17 | logger.log(`Deploying project "${deployPath}" …`); 18 | 19 | const build = require('./build.js').build; 20 | return build(deployPath, options).then(builtPath => { 21 | if (builtPath) { 22 | deployPath = builtPath; 23 | buildDone(builtPath); 24 | } 25 | }).catch(() => { 26 | buildDone(); 27 | }); 28 | 29 | function buildDone (builtPath) { 30 | return fs.readJson(path.join(builtPath ? builtPath : deployPath, 'package.json')).then(pkgObj => { 31 | return pkgObj; 32 | }, err => { 33 | return fs.readJson(path.join(deployPath, 'package.json')).then(pkgObj => { 34 | return pkgObj; 35 | }, () => { 36 | return {}; 37 | }); 38 | }).then(pkgObj => { 39 | pkgObj = pkgObj || {}; 40 | let rootDir = path.basename(deployPath); 41 | if (deployPath === builtPath) { 42 | rootDir = path.basename(path.resolve(builtPath, '..')); 43 | } 44 | return deployToIPFS(options, deployPath, rootDir, pkgObj).then(deployedUrl => { 45 | if (!deployedUrl) { 46 | throw new Error(`Unknown error occurred upon deploying project "${deployPath}"`); 47 | } 48 | return deployedUrl; 49 | }, err => { 50 | throw err; 51 | }); 52 | }).catch(err => { 53 | if (err) { 54 | logger.error(`Could not deploy project "${deployPath}":`, err); 55 | } else { 56 | logger.error(`Could not deploy project "${deployPath}"`); 57 | } 58 | throw err; 59 | process.exit(1); 60 | }); 61 | } 62 | }; 63 | 64 | const deployToIPFS = (options, deployPath, rootDir, pkgObj) => { 65 | const IPFS = require('ipfs-daemon'); 66 | 67 | const ipfsAddFiles = ipfsNode => { 68 | // The IPFS daemon is running, and the IPFS node is now ready to use. 69 | return new Promise((resolve, reject) => { 70 | glob('**/*', { 71 | cwd: deployPath, 72 | dot: true, 73 | nodir: true, 74 | ignore: [ 75 | '.git/**', 76 | 'node_modules/**', 77 | 'ipfs/**' 78 | ] 79 | }, (err, files) => { 80 | if (err || !files) { 81 | reject(new Error('Could not find public files to deploy')); 82 | return; 83 | } 84 | resolve(files); 85 | }); 86 | }).then(files => { 87 | const crypto = require('crypto'); 88 | let hash = crypto.createHash('sha256'); 89 | let strToHash = ''; 90 | 91 | let filesToAddPromises = files.map(filename => { 92 | return new Promise((resolve, reject) => { 93 | let file = { 94 | _realPath: path.join(deployPath, filename), 95 | path: `${rootDir}/${filename}`, 96 | content: fs.createReadStream(path.join(deployPath, filename)) 97 | }; 98 | 99 | file.content.on('data', data => { 100 | strToHash += file.path + ';' + data.toString(); 101 | }); 102 | file.content.on('end', () => { 103 | resolve(file); 104 | }); 105 | file.content.on('error', reject); 106 | }); 107 | }); 108 | 109 | return Promise.all(filesToAddPromises).then(filesToAdd => { 110 | hash.update(strToHash); 111 | 112 | const rootDirHash = hash.digest('hex'); 113 | 114 | filesToAdd.map(file => { 115 | file.path = `${rootDirHash}/${file.path}`; 116 | file.content = fs.createReadStream(file._realPath); 117 | delete file._realPath; 118 | return file; 119 | }); 120 | 121 | return ipfsNode.files.add(filesToAdd); 122 | }); 123 | }).then(filesAdded => { 124 | const dirMultihash = filesAdded[filesAdded.length - 1].hash; 125 | const numFiles = filesAdded.length - 1; 126 | logger.info(`Successfully published directory "${deployPath}" (file${numFiles === 1 ? '' : 's'}) to IPFS: ${dirMultihash}`); 127 | const deployedUrl = `https://ipfs.io/ipfs/${dirMultihash}/${rootDir}/`; 128 | return deployedUrl; 129 | }, err => { 130 | logger.error('Error adding to IPFS:', err); 131 | }); 132 | }; 133 | 134 | let ipfsNode = null; 135 | return new Promise((resolve, reject) => { 136 | let ipfsNodeReady = false; 137 | logger.info('Starting IPFS node'); 138 | ipfsNode = new IPFS(); 139 | ipfsNode.on('ready', () => { 140 | logger.info('IPFS node is ready'); 141 | ipfsNodeReady = true; 142 | resolve(ipfsAddFiles(ipfsNode)); 143 | }); 144 | ipfsNode.on('error', err => { 145 | ipfsNodeReady = true; 146 | reject(new Error('Error occurred with IPFS node: ' + err)); 147 | }); 148 | setTimeout(() => { 149 | if (!ipfsNodeReady) { 150 | reject(new Error(`Could not connect to IPFS node (timed out after ${options.timeout / 1000} seconds)`)); 151 | } 152 | }, options.timeout); 153 | }).then(deployedUrl => new Promise((resolve, reject) => { 154 | if (deployedUrl) { 155 | logger.log(`Deployed project to ${deployedUrl}`); 156 | // Copy to the user's clipboard the URL of the deployed project. 157 | if (!options.noClipboard) { 158 | clipboardy.writeSync(deployedUrl); 159 | } 160 | if (!options.noOpen) { 161 | // opn(deployedUrl, {wait: false}); 162 | } 163 | if (!options.noSubmit) { 164 | submitToIndex(deployedUrl); 165 | } 166 | } 167 | 168 | logger.info(`Keeping IPFS node open (for ${options.disconnectTimeout / 1000} seconds)`); 169 | 170 | let ipfsNodeStopped = false; 171 | 172 | setTimeout(() => { 173 | logger.info('Stopping IPFS node'); 174 | 175 | ipfsNode.stop(); 176 | ipfsNode = null; 177 | 178 | logger.info('IPFS node stopped'); 179 | 180 | resolve(deployedUrl); 181 | 182 | setTimeout(() => { 183 | if (ipfsNode) { 184 | reject(new Error(`Could not stop IPFS node (timed out after ${options.timeout / 1000} seconds)`)); 185 | } 186 | }, options.timeout); 187 | 188 | if (!ipfsNode) { 189 | process.exit(); 190 | } 191 | }, options.disconnectTimeout); 192 | })); 193 | }; 194 | 195 | exports.deploy = deploy; 196 | exports.deployToIPFS = deployToIPFS; 197 | exports.printBanner = commandName => { 198 | // return printBanner(commandName).catch(err => { 199 | // console.log(err.message); 200 | // }); 201 | }; 202 | -------------------------------------------------------------------------------- /lib/init-template.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | const fs = require('fs-extra'); 4 | const install = require('deps-install'); 5 | const logger = require('loggy'); 6 | 7 | const cleanURL = require('./clean-url.js'); 8 | const printBanner = require('./print-banner.js'); 9 | 10 | const defaultCommandName = 'aframe new'; 11 | 12 | const initTemplate = (templateAlias, options) => { 13 | options = options || {}; 14 | 15 | const cwd = process.cwd(); 16 | 17 | const rootPath = path.resolve(options.rootPath || cwd).trim(); 18 | const commandName = options.commandName || defaultCommandName; 19 | 20 | if (!templateAlias || typeof templateAlias !== 'string' || 21 | templateAlias.charAt(0) === '.' && rootPath === cwd) { 22 | printBanner(defaultCommandName); 23 | process.exit(1); 24 | } 25 | 26 | const copyTemplateDir = (template, dir) => { 27 | logger.log(`Using template "${template}" …`); 28 | 29 | const filter = filepath => !/^\.(git|hg|svn)$/.test(path.basename(filepath)); 30 | logger.log(`Copying local template to "${rootPath}" …`); 31 | 32 | // TODO: Perhaps have `rootPath` default to `my-aframe-project`. 33 | return fs.copy(dir, rootPath, {errorOnExist: true, filter}).then(() => { 34 | const pkgType = ['package', 'bower']; 35 | return install({ 36 | rootPath, 37 | pkgType, 38 | logger 39 | }); 40 | }); 41 | }; 42 | 43 | let templateDir = path.join(__dirname, '..', 'templates', templateAlias); 44 | 45 | if (fs.existsSync(templateDir)) { 46 | return copyTemplateDir(templateAlias, templateDir); 47 | } 48 | 49 | const localTemplateAlias = `aframe-${templateAlias}-template`; 50 | const localTemplateDir = path.join(__dirname, '..', 'templates', localTemplateAlias); 51 | 52 | if (fs.existsSync(localTemplateDir)) { 53 | return copyTemplateDir(localTemplateAlias, localTemplateDir); 54 | } 55 | 56 | if (templateAlias.indexOf('/') === -1 && templateAlias.startsWith('aframe-')) { 57 | templateAlias = 'aframevr-userland/' + templateAlias; 58 | } 59 | 60 | const initSkeleton = require('init-skeleton').init; 61 | 62 | logger.log(`Using template "${templateAlias}" …`); 63 | 64 | return initSkeleton(templateAlias, { 65 | logger, 66 | rootPath, 67 | commandName 68 | }); 69 | }; 70 | 71 | exports.init = initTemplate; 72 | 73 | exports.cleanURL = cleanURL; 74 | -------------------------------------------------------------------------------- /lib/manifest.js: -------------------------------------------------------------------------------- 1 | const deepAssign = require('deep-assign'); 2 | const fs = require('fs-extra'); 3 | 4 | module.exports.merge = (filename, data) => { 5 | return fs.readJson(filename).then(manifest => { 6 | return deepAssign({}, manifest, data); 7 | }) 8 | .catch((data) => { 9 | return data || {}; 10 | }) 11 | .then((data) => { 12 | console.log('updated: ', filename); 13 | return fs.writeJson(filename, data); 14 | }) 15 | }; 16 | -------------------------------------------------------------------------------- /lib/print-banner.js: -------------------------------------------------------------------------------- 1 | const chalk = require('chalk'); 2 | 3 | let templates = {}; 4 | templates.all = require('../templates/index.json').templates; 5 | templates.withAliases = templates.all.filter(template => template && 'alias' in template); 6 | templates.urlFor = alias => { 7 | for (const template of withAliases) { 8 | if (template.alias === alias) { 9 | return template.url; 10 | } 11 | } 12 | }; 13 | const suggestedCount = 8; 14 | const othersCount = templates.all.length - suggestedCount; 15 | 16 | const printBanner = commandName => { 17 | if (!commandName) { 18 | commandName = 'aframe new'; 19 | } 20 | 21 | const commandNameChunks = commandName.trim().split(' '); 22 | 23 | let commandStr = chalk.cyan(commandNameChunks[0]); 24 | if (commandNameChunks[1]) { 25 | commandStr += ` ${chalk.magenta(commandNameChunks[1])}`; 26 | } 27 | 28 | const suggestions = templates.withAliases 29 | .slice(0, suggestedCount) 30 | .map(template => { 31 | let line = ` - ${commandStr} ${chalk.green.bold('')} --template ${chalk.underline(template.alias || template.url)}`; 32 | 33 | template.technologies = template.technologies || ''; 34 | if (Array.isArray(template.technologies)) { 35 | template.technologies = template.technologies.replace(/\s+/g, ' ').join(', '); 36 | } 37 | // Strip trailing punctuation (i.e., a period). 38 | template.technologies = template.technologies.replace(/\.\s*/, ''); 39 | 40 | if (template.description) { 41 | line += chalk.bold.white(`\n ${template.description}`); 42 | } 43 | if (template.technologies) { 44 | line += chalk.dim(` (Supports ${template.technologies}.)`); 45 | } 46 | return line + '\n'; 47 | }) 48 | .join('\n').trim(); 49 | 50 | const error = new Error(` 51 | Please specify the project directory: 52 | 53 | ${commandStr} ${chalk.green.bold('')} 54 | 55 | For example: 56 | 57 | ${commandStr} ${chalk.green('my-project-directory')} 58 | 59 | You should specify the template (boilerplate) from which your new 60 | A-Frame scene will be initialized. Pass a template name or URL like so: 61 | 62 | ${commandStr} ${chalk.green('my-project-directory')} --template ${chalk.underline('default')} 63 | ${commandStr} ${chalk.green('my-project-directory')} --template ${chalk.underline('aframe-default-template')} 64 | ${commandStr} ${chalk.green('my-project-directory')} --template ${chalk.underline('https://github.com/aframevr-userland/aframe-default-template')} 65 | ${commandStr} ${chalk.green('my-project-directory')} --template ${chalk.underline('aframevr-userland/aframe-default-template')} 66 | 67 | Here are a few popular A-Frame scene templates: 68 | 69 | ${suggestions} 70 | 71 | View other${othersCount > 0 ? ` ${othersCount}` : ''} templates here: 72 | 73 | ${chalk.bold.underline('https://aframe.io/templates')} 74 | 75 | Run ${chalk.cyan('aframe')} ${chalk.magenta('--help')} to see all options. 76 | `); 77 | 78 | error.code = 'TEMPLATE_MISSING'; 79 | 80 | console.log(error.message.replace(/\n/g, '\n ')); 81 | 82 | return Promise.reject(error); 83 | }; 84 | 85 | module.exports = printBanner; 86 | -------------------------------------------------------------------------------- /lib/serve.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const url = require('url'); 3 | 4 | const brunch = require('brunch'); 5 | const clipboardy = require('clipboardy'); 6 | const express = require('express'); 7 | const formidable = require('formidable'); 8 | const logger = require('loggy'); 9 | const opn = require('opn'); 10 | const fs = require('fs-extra'); 11 | const manifest = require('./manifest.js'); 12 | 13 | const getBrunchConfigPath = require('./utils.js').getBrunchConfigPath; 14 | 15 | const serve = (watchPath, options) => new Promise((resolve, reject) => { 16 | watchPath = watchPath || process.cwd(); 17 | options = options || {}; 18 | options.config = options.config || getBrunchConfigPath(watchPath, options); 19 | options.server = true; 20 | options.network = true; 21 | 22 | const optionsFile = require(options.config); 23 | 24 | logger.log(`Watching "${watchPath}" …`); 25 | 26 | // Copy the server URL to the user's clipboard. 27 | const port = (optionsFile.server && optionsFile.server.port) || 3333; 28 | const https = options.https || options.ssl || options.secure || false; 29 | const serverUrl = `http${https ? 's' : ''}://localhost:${port}/`; 30 | try { 31 | const watcher = brunch.watch(options, () => { 32 | // saves preview videos from recorder component. 33 | watcher.server.on('request', function (req, res) { 34 | const method = req.method.toLowerCase(); 35 | const pathname = url.parse(req.url).pathname; 36 | 37 | if (method === 'post' && pathname === '/upload') { 38 | let form = new formidable.IncomingForm(); 39 | let files = []; 40 | 41 | form.encoding = 'binary'; 42 | form.keepExtensions = true; 43 | form.multiple = true; 44 | 45 | const uploadDir = path.join(watchPath, 'app/assets/video/'); 46 | 47 | fs.ensureDir(uploadDir); 48 | 49 | form.on('fileBegin', function(name, file) { 50 | file.path = path.join(uploadDir, file.name); 51 | }) 52 | 53 | form.on('file', (field, file) => { 54 | files.push([field, file]); 55 | var data = { 56 | 'aframe': { 57 | 'videos': [{ 58 | 'src': 'assets/video/' + file.name, 59 | 'type': file.type 60 | }] 61 | } 62 | }; 63 | manifest.merge(watchPath + '/package.json', data); 64 | }); 65 | 66 | form.on('error', formErr => { 67 | console.error(formErr); 68 | }); 69 | 70 | form.on('end', () => { 71 | res.end(); 72 | }); 73 | 74 | form.parse(req); 75 | } 76 | }); 77 | 78 | logger.log(`Local server running: ${serverUrl}`); 79 | if (!options.noClipboard) { 80 | clipboardy.writeSync(serverUrl); 81 | } 82 | if (!options.noOpen) { 83 | // opn(serverUrl, {wait: false}); 84 | } 85 | resolve(serverUrl); 86 | }); 87 | } catch (err) { 88 | logger.error(`Could not watch "${watchPath}" …`); 89 | reject(err); 90 | } 91 | }); 92 | 93 | exports.serve = serve; 94 | exports.printBanner = commandName => { 95 | // return printBanner(commandName).catch(err => { 96 | // console.log(err.message); 97 | // }); 98 | }; 99 | -------------------------------------------------------------------------------- /lib/submit.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | const clipboardy = require('clipboardy'); 4 | const request = require('request-promise-native'); 5 | const logger = require('loggy'); 6 | const opn = require('opn'); 7 | 8 | const submit = (siteUrl, options) => new Promise((resolve, reject) => { 9 | options = options || {}; 10 | logger.log(`Submitting site "${siteUrl}" to the A-Frame Index …`); 11 | 12 | let submitted = false; 13 | 14 | setTimeout(() => { 15 | if (!submitted) { 16 | reject(new Error(`Could not reach the A-Frame Index API at https://index-api.aframe.io/ (timed out after ${options.timeout / 1000} seconds)`)); 17 | } 18 | }, options.timeout); 19 | 20 | return request({ 21 | method: 'POST', 22 | uri: 'https://index-api.aframe.io/api/manifests', 23 | form: { 24 | url: siteUrl 25 | }, 26 | json: true 27 | }).then(json => { 28 | submitted = true; 29 | const worksUrl = `https://index-api.aframe.io/api/works/${json._work_idx}`; 30 | logger.log(`Submitted site "${siteUrl}": ${worksUrl}`); 31 | if (!options.noClipboard) { 32 | clipboardy.writeSync(worksUrl); 33 | } 34 | if (!options.noOpen) { 35 | // opn(worksUrl, {wait: false}); 36 | } 37 | return worksUrl; 38 | }).then(() => { 39 | process.exit(); 40 | }).catch(err => { 41 | logger.log(`Could not submit site "${siteUrl}" to the A-Frame Index: ` + (err ? err : 'Unknown Error')); 42 | throw err; 43 | process.exit(1); 44 | }); 45 | }); 46 | 47 | exports.submit = submit; 48 | -------------------------------------------------------------------------------- /lib/utils.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | const fs = require('fs-extra'); 4 | 5 | module.exports.getBrunchConfigPath = (filePath, options) => { 6 | filePath = filePath || process.cwd(); 7 | options = options || {}; 8 | 9 | if (options.config) { 10 | return options.config; 11 | } 12 | 13 | let brunchConfigPath = path.join(filePath, 'brunch-config.js'); 14 | if (fs.existsSync(brunchConfigPath)) { 15 | return brunchConfigPath; 16 | } 17 | 18 | return path.join(__dirname, 'brunch-config.js'); 19 | }; 20 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "aframe-cli", 3 | "description": "A command-line tool for building, managing, and publishing A-Frame scenes.", 4 | "author": "A-Frame authors", 5 | "version": "0.5.16", 6 | "main": "index.js", 7 | "bin": { 8 | "aframe": "index.js" 9 | }, 10 | "license": "MIT", 11 | "repository": "cvan/aframe-cli", 12 | "scripts": { 13 | "start": "npm run cli", 14 | "cli": "node index.js", 15 | "templates-bootstrap": "lerna bootstrap", 16 | "tb": "npm run templates-bootstrap", 17 | "release": "lerna publish" 18 | }, 19 | "dependencies": { 20 | "auto-reload-brunch": "^2.7.1", 21 | "babel-brunch": "^6.1.1", 22 | "babel-preset-stage-0": "^6.24.1", 23 | "brunch": "^2.10.9", 24 | "chalk": "^1.1.3", 25 | "clean-css-brunch": "^2.10.0", 26 | "clipboardy": "^1.1.2", 27 | "commander": "^2.10.0", 28 | "deep-assign": "^2.0.0", 29 | "formidable": "^1.1.1", 30 | "fs-extra": "^3.0.1", 31 | "glob": "^7.1.2", 32 | "ipfs": "^0.24.1", 33 | "ipfs-daemon": "^0.3.1", 34 | "loggy": "^1.0.2", 35 | "opn": "^5.1.0", 36 | "picture-tube": "^1.0.0", 37 | "request": "^2.81.0", 38 | "request-promise-native": "^1.0.4", 39 | "uglify-js-brunch": "^2.10.0" 40 | }, 41 | "devDependencies": { 42 | "lerna": "^2.0.0-rc.5" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /templates/aframe-360-tour-template/README.md: -------------------------------------------------------------------------------- 1 | # A-Frame 360 tour template 2 | 3 | String together a tour with hotspots using 360° panorama images. 4 | 5 | ## Usage 6 | 7 | ### Basic setup 8 | 9 | 1. Install A-Frame CLI. [See Installation instructions](https://github.com/aframevr-userland/aframe-cli/blob/master/README.md) 10 | 11 | 2. Create a new A-Frame project with the 360 tour template: 12 | ```bash 13 | aframe new my-360-tour --template 360-tour 14 | ``` 15 | 16 | 3. Run local development server from your project's directory. 17 | ```bash 18 | cd my-360-tour 19 | aframe serve 20 | ``` 21 | 22 | ### Template Usage 23 | 24 | 1. setup a tour and define panorama images. 25 | ```html 26 | 27 | 28 | 29 | 30 | ``` 31 | 32 | 2. Define the hotspots for each panorama 33 | 34 | ```html 35 | 36 | 37 | 38 | 39 | 40 | 41 | ``` 42 | 43 | 3. Style hotspots 44 | 45 | We use a _mixin_ to set the hotspot style: 46 | 47 | ```html 48 | 49 | 50 | 51 | ``` 52 | 53 | ### Hotspot-helper 54 | 55 | ![Hotspot Helper](https://raw.githubusercontent.com/aframevr-userland/aframe-cli/master/templates/aframe-360-tour-template/images/hotspot-helper.png) 56 | 57 | Included in the tour template is a `hotspot-helper` component that helps make it 58 | easier to place hotspots in your scene. To use the helper, add it to `` 59 | and set it to target the hotspot you want to place. 60 | 61 | ```html 62 | 63 | 64 | 65 | 66 | ``` 67 | 68 | Once you have found a good placement for your hotspot, copy the `position` and `rotation` 69 | values into the hotspot markup. -------------------------------------------------------------------------------- /templates/aframe-360-tour-template/app/assets/images/aquabus.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aframevr-userland/aframe-cli/e99a855d4c4e8109b41461907090e2c61b0b5a71/templates/aframe-360-tour-template/app/assets/images/aquabus.jpg -------------------------------------------------------------------------------- /templates/aframe-360-tour-template/app/assets/images/dock.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aframevr-userland/aframe-cli/e99a855d4c4e8109b41461907090e2c61b0b5a71/templates/aframe-360-tour-template/app/assets/images/dock.jpg -------------------------------------------------------------------------------- /templates/aframe-360-tour-template/app/assets/images/seawall.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aframevr-userland/aframe-cli/e99a855d4c4e8109b41461907090e2c61b0b5a71/templates/aframe-360-tour-template/app/assets/images/seawall.jpg -------------------------------------------------------------------------------- /templates/aframe-360-tour-template/app/assets/images/yaletown.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aframevr-userland/aframe-cli/e99a855d4c4e8109b41461907090e2c61b0b5a71/templates/aframe-360-tour-template/app/assets/images/yaletown.jpg -------------------------------------------------------------------------------- /templates/aframe-360-tour-template/app/assets/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 360° Tour 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 53 | 54 | 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /templates/aframe-360-tour-template/app/css/app.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aframevr-userland/aframe-cli/e99a855d4c4e8109b41461907090e2c61b0b5a71/templates/aframe-360-tour-template/app/css/app.css -------------------------------------------------------------------------------- /templates/aframe-360-tour-template/app/js/components/helper.js: -------------------------------------------------------------------------------- 1 | /* global AFRAME, THREE */ 2 | 3 | AFRAME.registerComponent('hotspot-helper', { 4 | schema: { 5 | target: {type: 'selector'}, 6 | distance: {type: 'number', default: 5}, 7 | distanceIncrement: {type: 'number', default: 0.25}, 8 | }, 9 | 10 | init: function () { 11 | if (!this.data.target) { 12 | console.error('Hotspot-helper: You must specify a target element!'); 13 | return; 14 | } 15 | 16 | var self = this; 17 | 18 | this.camera = document.querySelector('[camera]'); 19 | this.targetRotationOrigin = this.data.target.getAttribute('rotation'); 20 | this.targetPositionOrigin = this.data.target.getAttribute('position'); 21 | 22 | // Helper UI. 23 | var uiContainer = this.makeUi(); 24 | document.body.appendChild(uiContainer); 25 | 26 | // Enabled. 27 | this.enabled = uiContainer.querySelector('#hh-enabled'); 28 | this.enabled.addEventListener('click', function () { 29 | uiContainer.dataset.enabled = !!self.enabled.checked; 30 | }); 31 | 32 | // Set distance. 33 | var distanceInput = this.distanceInput = uiContainer.querySelector('#hh-distance'); 34 | distanceInput.addEventListener('input', function () { 35 | self.updateDistance(this.value); 36 | }); 37 | distanceInput.value = this.data.distance; 38 | 39 | // Copy position to clipboard. 40 | var copyPosition = uiContainer.querySelector('#hh-copy-position'); 41 | copyPosition.addEventListener('click', function () { 42 | self.copyToClipboard(self.position); 43 | }); 44 | 45 | // Mouse-wheel distance. 46 | window.addEventListener('wheel', this.handleWheel.bind(this)); 47 | 48 | // Rotation. 49 | this.rotation = uiContainer.querySelector('#hh-rotation'); 50 | 51 | // Copy rotation to clipboard. 52 | var copyRotation = uiContainer.querySelector('#hh-copy-rotation'); 53 | copyRotation.addEventListener('click', function () { 54 | self.copyToClipboard(self.rotation); 55 | }); 56 | 57 | // Look at. 58 | this.lookToggle = uiContainer.querySelector('#hh-lookat'); 59 | 60 | // Position. 61 | this.position = uiContainer.querySelector('#hh-position'); 62 | 63 | // Empty object3D for position. 64 | var targetObject = this.targetObject = new THREE.Object3D(); 65 | this.dolly = new THREE.Object3D(); 66 | this.dolly.add(targetObject); 67 | this.el.object3D.add(this.dolly); 68 | this.updateDistance(this.data.distance); 69 | 70 | // Set positioning on target so that clicks are not triggered when placing hotspot. 71 | this.data.target.setAttribute('hotspot', {positioning: true}); 72 | }, 73 | 74 | makeUi: function () { 75 | var uiContainer = document.createElement('div'); 76 | uiContainer.id = 'hh'; 77 | var markup = ` 78 | 140 | 141 |

hotspot-helper

142 | 143 | 144 | 147 | 148 | 149 |
150 | 154 |
155 | 156 |
157 | 162 |
163 | 164 |
165 |

166 | 167 | 168 | 173 |
174 | `; 175 | uiContainer.innerHTML = markup; 176 | return uiContainer; 177 | }, 178 | 179 | updateDistance: function (distance) { 180 | this.targetObject.position.z = -distance; 181 | }, 182 | 183 | copyToClipboard: function (input) { 184 | input.select(); 185 | document.execCommand('copy'); 186 | if (window.getSelection) { 187 | window.getSelection().removeAllRanges(); 188 | } 189 | }, 190 | 191 | handleWheel: function (e) { 192 | var input = this.distanceInput; 193 | var data = this.data; 194 | var increment = e.deltaY < 0 ? data.distanceIncrement : -data.distanceIncrement; 195 | var value = parseFloat(input.value) + increment; 196 | if (value < 0) { 197 | value = 0; 198 | } 199 | input.value = value; 200 | this.updateDistance(value); 201 | }, 202 | 203 | updateRotation: function () { 204 | var target = this.data.target; 205 | if (this.lookToggle.checked) { 206 | if (!target.hasAttribute('look-at')) { 207 | target.setAttribute('look-at', '[camera]'); 208 | } 209 | var worldRotation = this.data.target.object3D.getWorldRotation(); 210 | this.rotation.value = this.toDeg(worldRotation.x).toFixed(2) + ' ' + this.toDeg(worldRotation.y).toFixed(2) + ' ' + this.toDeg(worldRotation.z).toFixed(2); 211 | } else { 212 | if (target.hasAttribute('look-at')) { 213 | target.removeAttribute('look-at'); 214 | } 215 | this.rotation.value = `${this.targetRotationOrigin.x} ${this.targetRotationOrigin.y} ${this.targetRotationOrigin.z}`; 216 | target.setAttribute('rotation', this.targetRotationOrigin); 217 | } 218 | }, 219 | 220 | toDeg: function (rad) { 221 | return rad * 180 / Math.PI; 222 | }, 223 | 224 | tick: function () { 225 | var target = this.data.target; 226 | if (!target) return; 227 | if (this.enabled.checked) { 228 | var rotation = this.camera.object3D.getWorldRotation(); 229 | this.dolly.rotation.copy(rotation); 230 | var position = this.targetObject.getWorldPosition(); 231 | var cords = position.x.toFixed(2) + ' ' + position.y.toFixed(2) + ' ' + position.z.toFixed(2); 232 | target.setAttribute('position', { 233 | x: position.x, 234 | y: position.y, 235 | z: position.z 236 | }); 237 | this.position.value = cords; 238 | this.updateRotation(); 239 | } else { 240 | target.setAttribute('position', this.targetPositionOrigin); 241 | target.setAttribute('rotation', this.targetRotationOrigin); 242 | } 243 | } 244 | }); 245 | -------------------------------------------------------------------------------- /templates/aframe-360-tour-template/app/js/components/hotspot.js: -------------------------------------------------------------------------------- 1 | AFRAME.registerPrimitive('a-hotspot', { 2 | defaultComponents: { 3 | hotspot: {} 4 | }, 5 | mappings: { 6 | for: 'hotspot.for', 7 | to: 'hotspot.to' 8 | } 9 | }); 10 | 11 | AFRAME.registerComponent('hotspot', { 12 | schema: { 13 | for: { type: 'string' }, 14 | to: { type: 'string' }, 15 | positioning: { type: 'boolean', default: false } 16 | }, 17 | 18 | init: function () { 19 | this.tour = document.querySelector('a-tour'); 20 | this.el.addEventListener('click', this.handleClick.bind(this)); 21 | }, 22 | 23 | handleClick: function () { 24 | if (this.data.positioning) return; 25 | var tour = this.tour.components['tour']; 26 | tour.loadSceneId(this.data.to); 27 | } 28 | }); -------------------------------------------------------------------------------- /templates/aframe-360-tour-template/app/js/components/panorama.js: -------------------------------------------------------------------------------- 1 | AFRAME.registerPrimitive('a-panorama', { 2 | defaultComponents: { 3 | panorama: {} 4 | } 5 | }); 6 | 7 | AFRAME.registerComponent('panorama', { 8 | schema: { 9 | rotation: { type: 'vec3' }, 10 | src: { type: 'string' } 11 | } 12 | }); -------------------------------------------------------------------------------- /templates/aframe-360-tour-template/app/js/components/tour.js: -------------------------------------------------------------------------------- 1 | AFRAME.registerPrimitive('a-tour', { 2 | defaultComponents: { 3 | tour: {} 4 | } 5 | }); 6 | 7 | AFRAME.registerComponent('tour', { 8 | init: function () { 9 | this.sky = document.createElement('a-sky'); 10 | this.el.appendChild(this.sky); 11 | var images = Array.prototype.slice.call(this.el.querySelectorAll('a-panorama')); 12 | if (images.length === 0) { 13 | console.error('You need to specify at least 1 image!'); 14 | return; 15 | } 16 | var start = images[0]; 17 | this.loadSceneId(start.getAttribute('id')); 18 | }, 19 | 20 | loadSceneId: function(id) { 21 | this.loadImage(this.el.querySelector('a-panorama#' + id)); 22 | this.setHotspots(id); 23 | }, 24 | 25 | loadImage: function (image) { 26 | var sky = this.sky; 27 | sky.setAttribute('src', image.getAttribute('src')); 28 | var camera = this.el.sceneEl.camera.el; 29 | camera.setAttribute('rotation', image.getAttribute('rotation')); 30 | }, 31 | 32 | setHotspots: function(id) { 33 | var hotspots = Array.prototype.slice.call(this.el.querySelectorAll('a-hotspot')); 34 | hotspots.forEach(function (spot) { 35 | var visible = spot.getAttribute('for') == id ? true : false; 36 | spot.setAttribute('visible', visible); 37 | }) 38 | } 39 | }); 40 | -------------------------------------------------------------------------------- /templates/aframe-360-tour-template/app/js/initialize.js: -------------------------------------------------------------------------------- 1 | require('aframe-look-at-component'); 2 | require('./components/tour'); 3 | require('./components/panorama'); 4 | require('./components/hotspot'); 5 | require('./components/helper'); 6 | 7 | document.addEventListener('DOMContentLoaded', () => { 8 | console.log('Initialized app'); 9 | }); 10 | -------------------------------------------------------------------------------- /templates/aframe-360-tour-template/images/hotspot-helper.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aframevr-userland/aframe-cli/e99a855d4c4e8109b41461907090e2c61b0b5a71/templates/aframe-360-tour-template/images/hotspot-helper.png -------------------------------------------------------------------------------- /templates/aframe-360-tour-template/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "aframe-360-tour-template", 3 | "description": "A WebVR scene. Made with A-Frame, a WebVR framework.", 4 | "author": "A-Frame authors", 5 | "version": "0.5.17", 6 | "license": "CC0-1.0", 7 | "repository": "aframevr-userland/aframe-360-tour-template", 8 | "scripts": { 9 | "start": "npm run serve", 10 | "serve": "aframe serve", 11 | "build": "aframe build" 12 | }, 13 | "dependencies": { 14 | "aframe": "^0.6.0", 15 | "aframe-look-at-component": "^0.5.1" 16 | }, 17 | "devDependencies": { 18 | "aframe-cli": "0.5.16" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /templates/aframe-default-template/app/assets/grid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aframevr-userland/aframe-cli/e99a855d4c4e8109b41461907090e2c61b0b5a71/templates/aframe-default-template/app/assets/grid.png -------------------------------------------------------------------------------- /templates/aframe-default-template/app/assets/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Default scene • Made with A-Frame 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /templates/aframe-default-template/app/assets/sky.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aframevr-userland/aframe-cli/e99a855d4c4e8109b41461907090e2c61b0b5a71/templates/aframe-default-template/app/assets/sky.png -------------------------------------------------------------------------------- /templates/aframe-default-template/app/css/app.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aframevr-userland/aframe-cli/e99a855d4c4e8109b41461907090e2c61b0b5a71/templates/aframe-default-template/app/css/app.css -------------------------------------------------------------------------------- /templates/aframe-default-template/app/js/initialize.js: -------------------------------------------------------------------------------- 1 | document.addEventListener('DOMContentLoaded', () => { 2 | console.log('Initialized app'); 3 | }); 4 | -------------------------------------------------------------------------------- /templates/aframe-default-template/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "aframe-default-template", 3 | "description": "A WebVR scene. Made with A-Frame, a WebVR framework.", 4 | "author": "A-Frame authors", 5 | "version": "0.5.17", 6 | "license": "CC0-1.0", 7 | "repository": "aframevr-userland/aframe-default-template", 8 | "scripts": { 9 | "start": "npm run serve", 10 | "serve": "aframe serve", 11 | "build": "aframe build" 12 | }, 13 | "dependencies": { 14 | "aframe": "^0.6.0" 15 | }, 16 | "devDependencies": { 17 | "aframe-cli": "0.5.16" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /templates/aframe-model-template/app/assets/images/arrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aframevr-userland/aframe-cli/e99a855d4c4e8109b41461907090e2c61b0b5a71/templates/aframe-model-template/app/assets/images/arrow.png -------------------------------------------------------------------------------- /templates/aframe-model-template/app/assets/images/grid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aframevr-userland/aframe-cli/e99a855d4c4e8109b41461907090e2c61b0b5a71/templates/aframe-model-template/app/assets/images/grid.png -------------------------------------------------------------------------------- /templates/aframe-model-template/app/assets/images/sky.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aframevr-userland/aframe-cli/e99a855d4c4e8109b41461907090e2c61b0b5a71/templates/aframe-model-template/app/assets/images/sky.png -------------------------------------------------------------------------------- /templates/aframe-model-template/app/assets/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Model viewer • Made with A-Frame 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /templates/aframe-model-template/app/assets/models/rocky.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aframevr-userland/aframe-cli/e99a855d4c4e8109b41461907090e2c61b0b5a71/templates/aframe-model-template/app/assets/models/rocky.bin -------------------------------------------------------------------------------- /templates/aframe-model-template/app/assets/models/rocky.gltf: -------------------------------------------------------------------------------- 1 | { 2 | "accessors": { 3 | "accessor_102": { 4 | "bufferView": "bufferView_191", 5 | "byteOffset": 0, 6 | "byteStride": 0, 7 | "componentType": 5123, 8 | "count": 1182, 9 | "type": "SCALAR" 10 | }, 11 | "accessor_104": { 12 | "bufferView": "bufferView_192", 13 | "byteOffset": 0, 14 | "byteStride": 12, 15 | "componentType": 5126, 16 | "count": 1182, 17 | "max": [ 18 | 0.327038, 19 | 0.107074, 20 | 0.562413 21 | ], 22 | "min": [ 23 | -0.327038, 24 | -0.193471, 25 | -0.5 26 | ], 27 | "type": "VEC3" 28 | }, 29 | "accessor_106": { 30 | "bufferView": "bufferView_192", 31 | "byteOffset": 14184, 32 | "byteStride": 12, 33 | "componentType": 5126, 34 | "count": 1182, 35 | "max": [ 36 | 0.99548, 37 | 0.99471, 38 | 0.986767 39 | ], 40 | "min": [ 41 | -0.99548, 42 | -0.999671, 43 | -0.970338 44 | ], 45 | "type": "VEC3" 46 | }, 47 | "accessor_108": { 48 | "bufferView": "bufferView_192", 49 | "byteOffset": 28368, 50 | "byteStride": 8, 51 | "componentType": 5126, 52 | "count": 1182, 53 | "max": [ 54 | 0.667627, 55 | 0.977207 56 | ], 57 | "min": [ 58 | 0.433623, 59 | 0.0137818 60 | ], 61 | "type": "VEC2" 62 | }, 63 | "accessor_129": { 64 | "bufferView": "bufferView_191", 65 | "byteOffset": 4470, 66 | "byteStride": 0, 67 | "componentType": 5123, 68 | "count": 96, 69 | "type": "SCALAR" 70 | }, 71 | "accessor_131": { 72 | "bufferView": "bufferView_192", 73 | "byteOffset": 71520, 74 | "byteStride": 12, 75 | "componentType": 5126, 76 | "count": 96, 77 | "max": [ 78 | 0.0462228, 79 | 0.00504655, 80 | 0.0636294 81 | ], 82 | "min": [ 83 | -0.0473277, 84 | -0.0474461, 85 | 0.00438797 86 | ], 87 | "type": "VEC3" 88 | }, 89 | "accessor_133": { 90 | "bufferView": "bufferView_192", 91 | "byteOffset": 72672, 92 | "byteStride": 12, 93 | "componentType": 5126, 94 | "count": 96, 95 | "max": [ 96 | 0.991944, 97 | 0.995052, 98 | 0.991116 99 | ], 100 | "min": [ 101 | -0.988815, 102 | -0.974675, 103 | -0.915355 104 | ], 105 | "type": "VEC3" 106 | }, 107 | "accessor_135": { 108 | "bufferView": "bufferView_192", 109 | "byteOffset": 73824, 110 | "byteStride": 8, 111 | "componentType": 5126, 112 | "count": 96, 113 | "max": [ 114 | 0.368633, 115 | 0.938224 116 | ], 117 | "min": [ 118 | 0.293841, 119 | 0.892859 120 | ], 121 | "type": "VEC2" 122 | }, 123 | "accessor_156": { 124 | "bufferView": "bufferView_191", 125 | "byteOffset": 4806, 126 | "byteStride": 0, 127 | "componentType": 5123, 128 | "count": 210, 129 | "type": "SCALAR" 130 | }, 131 | "accessor_158": { 132 | "bufferView": "bufferView_192", 133 | "byteOffset": 76896, 134 | "byteStride": 12, 135 | "componentType": 5126, 136 | "count": 210, 137 | "max": [ 138 | 0.633279, 139 | 0.145202, 140 | 0.542065 141 | ], 142 | "min": [ 143 | -0.183321, 144 | -0.493952, 145 | -0.016356 146 | ], 147 | "type": "VEC3" 148 | }, 149 | "accessor_160": { 150 | "bufferView": "bufferView_192", 151 | "byteOffset": 79416, 152 | "byteStride": 12, 153 | "componentType": 5126, 154 | "count": 210, 155 | "max": [ 156 | 0.989987, 157 | 0.998314, 158 | 0.0821588 159 | ], 160 | "min": [ 161 | -0.988717, 162 | -0.994564, 163 | -0.504083 164 | ], 165 | "type": "VEC3" 166 | }, 167 | "accessor_162": { 168 | "bufferView": "bufferView_192", 169 | "byteOffset": 81936, 170 | "byteStride": 8, 171 | "componentType": 5126, 172 | "count": 210, 173 | "max": [ 174 | 0.918144, 175 | 0.910046 176 | ], 177 | "min": [ 178 | 0.732512, 179 | 0.105189 180 | ], 181 | "type": "VEC2" 182 | }, 183 | "accessor_183": { 184 | "bufferView": "bufferView_191", 185 | "byteOffset": 4662, 186 | "byteStride": 0, 187 | "componentType": 5123, 188 | "count": 72, 189 | "type": "SCALAR" 190 | }, 191 | "accessor_185": { 192 | "bufferView": "bufferView_192", 193 | "byteOffset": 74592, 194 | "byteStride": 12, 195 | "componentType": 5126, 196 | "count": 72, 197 | "max": [ 198 | 1.1664, 199 | 0.933567, 200 | 0.347018 201 | ], 202 | "min": [ 203 | -1.4433, 204 | -1.42228, 205 | 0 206 | ], 207 | "type": "VEC3" 208 | }, 209 | "accessor_187": { 210 | "bufferView": "bufferView_192", 211 | "byteOffset": 75456, 212 | "byteStride": 12, 213 | "componentType": 5126, 214 | "count": 72, 215 | "max": [ 216 | 0.79047, 217 | 0.861284, 218 | 1 219 | ], 220 | "min": [ 221 | -0.845154, 222 | -0.842295, 223 | 0.501419 224 | ], 225 | "type": "VEC3" 226 | }, 227 | "accessor_189": { 228 | "bufferView": "bufferView_192", 229 | "byteOffset": 76320, 230 | "byteStride": 8, 231 | "componentType": 5126, 232 | "count": 72, 233 | "max": [ 234 | 0.969397, 235 | 0.598303 236 | ], 237 | "min": [ 238 | 0.725916, 239 | 0.354822 240 | ], 241 | "type": "VEC2" 242 | }, 243 | "accessor_21": { 244 | "bufferView": "bufferView_191", 245 | "byteOffset": 5514, 246 | "byteStride": 0, 247 | "componentType": 5123, 248 | "count": 144, 249 | "type": "SCALAR" 250 | }, 251 | "accessor_23": { 252 | "bufferView": "bufferView_192", 253 | "byteOffset": 88224, 254 | "byteStride": 12, 255 | "componentType": 5126, 256 | "count": 144, 257 | "max": [ 258 | 0.0263289, 259 | 0.0179624, 260 | 0.0361985 261 | ], 262 | "min": [ 263 | -0.0263289, 264 | -0.0544348, 265 | -0.0361985 266 | ], 267 | "type": "VEC3" 268 | }, 269 | "accessor_25": { 270 | "bufferView": "bufferView_192", 271 | "byteOffset": 89952, 272 | "byteStride": 12, 273 | "componentType": 5126, 274 | "count": 144, 275 | "max": [ 276 | 0.919976, 277 | 0.817619, 278 | 0.902692 279 | ], 280 | "min": [ 281 | -0.919976, 282 | -0.817619, 283 | -0.902692 284 | ], 285 | "type": "VEC3" 286 | }, 287 | "accessor_27": { 288 | "bufferView": "bufferView_192", 289 | "byteOffset": 91680, 290 | "byteStride": 8, 291 | "componentType": 5126, 292 | "count": 144, 293 | "max": [ 294 | 0.41187, 295 | 0.846955 296 | ], 297 | "min": [ 298 | 0.105559, 299 | 0.0307453 300 | ], 301 | "type": "VEC2" 302 | }, 303 | "accessor_48": { 304 | "bufferView": "bufferView_191", 305 | "byteOffset": 5226, 306 | "byteStride": 0, 307 | "componentType": 5123, 308 | "count": 144, 309 | "type": "SCALAR" 310 | }, 311 | "accessor_50": { 312 | "bufferView": "bufferView_192", 313 | "byteOffset": 83616, 314 | "byteStride": 12, 315 | "componentType": 5126, 316 | "count": 144, 317 | "max": [ 318 | 0.0263289, 319 | 0.0179624, 320 | 0.0361985 321 | ], 322 | "min": [ 323 | -0.0263289, 324 | -0.0544348, 325 | -0.0361985 326 | ], 327 | "type": "VEC3" 328 | }, 329 | "accessor_52": { 330 | "bufferView": "bufferView_192", 331 | "byteOffset": 85344, 332 | "byteStride": 12, 333 | "componentType": 5126, 334 | "count": 144, 335 | "max": [ 336 | 0.919976, 337 | 0.817619, 338 | 0.902692 339 | ], 340 | "min": [ 341 | -0.919976, 342 | -0.817619, 343 | -0.902692 344 | ], 345 | "type": "VEC3" 346 | }, 347 | "accessor_54": { 348 | "bufferView": "bufferView_192", 349 | "byteOffset": 87072, 350 | "byteStride": 8, 351 | "componentType": 5126, 352 | "count": 144, 353 | "max": [ 354 | 0.41187, 355 | 0.846955 356 | ], 357 | "min": [ 358 | 0.105559, 359 | 0.0307453 360 | ], 361 | "type": "VEC2" 362 | }, 363 | "accessor_75": { 364 | "bufferView": "bufferView_191", 365 | "byteOffset": 2364, 366 | "byteStride": 0, 367 | "componentType": 5123, 368 | "count": 1053, 369 | "type": "SCALAR" 370 | }, 371 | "accessor_77": { 372 | "bufferView": "bufferView_192", 373 | "byteOffset": 37824, 374 | "byteStride": 12, 375 | "componentType": 5126, 376 | "count": 1053, 377 | "max": [ 378 | 0.803023, 379 | 0.610228, 380 | 0.543327 381 | ], 382 | "min": [ 383 | -0.92947, 384 | -0.980396, 385 | -0.499457 386 | ], 387 | "type": "VEC3" 388 | }, 389 | "accessor_79": { 390 | "bufferView": "bufferView_192", 391 | "byteOffset": 50460, 392 | "byteStride": 12, 393 | "componentType": 5126, 394 | "count": 1053, 395 | "max": [ 396 | 0.997625, 397 | 0.999991, 398 | 1 399 | ], 400 | "min": [ 401 | -0.998167, 402 | -0.993659, 403 | -1 404 | ], 405 | "type": "VEC3" 406 | }, 407 | "accessor_81": { 408 | "bufferView": "bufferView_192", 409 | "byteOffset": 63096, 410 | "byteStride": 8, 411 | "componentType": 5126, 412 | "count": 1053, 413 | "max": [ 414 | 0.36248, 415 | 0.954708 416 | ], 417 | "min": [ 418 | 0.00213736, 419 | 0.0736805 420 | ], 421 | "type": "VEC2" 422 | } 423 | }, 424 | "animations": {}, 425 | "asset": { 426 | "generator": "collada2gltf@11bf28f97f1b5380fbefacf862ce7c597873d368", 427 | "premultipliedAlpha": true, 428 | "profile": { 429 | "api": "WebGL", 430 | "version": "1.0.2" 431 | }, 432 | "version": 1 433 | }, 434 | "bufferViews": { 435 | "bufferView_191": { 436 | "buffer": "rocky", 437 | "byteLength": 5804, 438 | "byteOffset": 0, 439 | "target": 34963 440 | }, 441 | "bufferView_192": { 442 | "buffer": "rocky", 443 | "byteLength": 92832, 444 | "byteOffset": 5804, 445 | "target": 34962 446 | } 447 | }, 448 | "buffers": { 449 | "rocky": { 450 | "byteLength": 98636, 451 | "type": "arraybuffer", 452 | "uri": "rocky.bin" 453 | } 454 | }, 455 | "extensionsUsed": [ 456 | "KHR_materials_common" 457 | ], 458 | "images": { 459 | "texture_png": { 460 | "name": "texture_png", 461 | "uri": "texture.png" 462 | } 463 | }, 464 | "materials": { 465 | "Scene-effect": { 466 | "extensions": { 467 | "KHR_materials_common": { 468 | "technique": "CONSTANT", 469 | "values": { 470 | "emission": "texture_texture_png" 471 | } 472 | } 473 | }, 474 | "name": "Scene" 475 | } 476 | }, 477 | "meshes": { 478 | "Cube_002-mesh": { 479 | "name": "Cube.002", 480 | "primitives": [ 481 | { 482 | "attributes": { 483 | "NORMAL": "accessor_106", 484 | "POSITION": "accessor_104", 485 | "TEXCOORD_0": "accessor_108" 486 | }, 487 | "indices": "accessor_102", 488 | "material": "Scene-effect", 489 | "mode": 4 490 | } 491 | ] 492 | }, 493 | "Cube_004-mesh": { 494 | "name": "Cube.004", 495 | "primitives": [ 496 | { 497 | "attributes": { 498 | "NORMAL": "accessor_79", 499 | "POSITION": "accessor_77", 500 | "TEXCOORD_0": "accessor_81" 501 | }, 502 | "indices": "accessor_75", 503 | "material": "Scene-effect", 504 | "mode": 4 505 | } 506 | ] 507 | }, 508 | "Cube_005-mesh": { 509 | "name": "Cube.005", 510 | "primitives": [ 511 | { 512 | "attributes": { 513 | "NORMAL": "accessor_133", 514 | "POSITION": "accessor_131", 515 | "TEXCOORD_0": "accessor_135" 516 | }, 517 | "indices": "accessor_129", 518 | "material": "Scene-effect", 519 | "mode": 4 520 | } 521 | ] 522 | }, 523 | "Plane-mesh": { 524 | "name": "Plane", 525 | "primitives": [ 526 | { 527 | "attributes": { 528 | "NORMAL": "accessor_187", 529 | "POSITION": "accessor_185", 530 | "TEXCOORD_0": "accessor_189" 531 | }, 532 | "indices": "accessor_183", 533 | "material": "Scene-effect", 534 | "mode": 4 535 | } 536 | ] 537 | }, 538 | "Plane_005-mesh": { 539 | "name": "Plane.005", 540 | "primitives": [ 541 | { 542 | "attributes": { 543 | "NORMAL": "accessor_160", 544 | "POSITION": "accessor_158", 545 | "TEXCOORD_0": "accessor_162" 546 | }, 547 | "indices": "accessor_156", 548 | "material": "Scene-effect", 549 | "mode": 4 550 | } 551 | ] 552 | }, 553 | "Sphere_002-mesh": { 554 | "name": "Sphere.002", 555 | "primitives": [ 556 | { 557 | "attributes": { 558 | "NORMAL": "accessor_52", 559 | "POSITION": "accessor_50", 560 | "TEXCOORD_0": "accessor_54" 561 | }, 562 | "indices": "accessor_48", 563 | "material": "Scene-effect", 564 | "mode": 4 565 | } 566 | ] 567 | }, 568 | "Sphere_003-mesh": { 569 | "name": "Sphere.003", 570 | "primitives": [ 571 | { 572 | "attributes": { 573 | "NORMAL": "accessor_25", 574 | "POSITION": "accessor_23", 575 | "TEXCOORD_0": "accessor_27" 576 | }, 577 | "indices": "accessor_21", 578 | "material": "Scene-effect", 579 | "mode": 4 580 | } 581 | ] 582 | } 583 | }, 584 | "nodes": { 585 | "Rocks": { 586 | "children": [], 587 | "matrix": [ 588 | 1, 589 | 0, 590 | 0, 591 | 0, 592 | 0, 593 | 1, 594 | 0, 595 | 0, 596 | 0, 597 | 0, 598 | 1, 599 | 0, 600 | 0.291692, 601 | 0.310846, 602 | 0.604556, 603 | 1 604 | ], 605 | "meshes": [ 606 | "Cube_004-mesh" 607 | ], 608 | "name": "Rocks" 609 | }, 610 | "character": { 611 | "children": [], 612 | "matrix": [ 613 | 1, 614 | 0, 615 | 0, 616 | 0, 617 | 0, 618 | 1, 619 | 0, 620 | 0, 621 | 0, 622 | 0, 623 | 1, 624 | 0, 625 | 0.291692, 626 | 0.310846, 627 | 1.51966, 628 | 1 629 | ], 630 | "meshes": [ 631 | "Cube_002-mesh" 632 | ], 633 | "name": "character" 634 | }, 635 | "eyes": { 636 | "children": [], 637 | "matrix": [ 638 | 1, 639 | 0, 640 | 0, 641 | 0, 642 | 0, 643 | 1, 644 | 0, 645 | 0, 646 | 0, 647 | 0, 648 | 1, 649 | 0, 650 | 0.343529, 651 | 0.150857, 652 | 1.77806, 653 | 1 654 | ], 655 | "meshes": [ 656 | "Sphere_002-mesh" 657 | ], 658 | "name": "eyes" 659 | }, 660 | "eyes_001": { 661 | "children": [], 662 | "matrix": [ 663 | 1, 664 | 0, 665 | 0, 666 | 0, 667 | 0, 668 | 1, 669 | 0, 670 | 0, 671 | 0, 672 | 0, 673 | 1, 674 | 0, 675 | 0.26311, 676 | 0.150857, 677 | 1.77806, 678 | 1 679 | ], 680 | "meshes": [ 681 | "Sphere_003-mesh" 682 | ], 683 | "name": "eyes_001" 684 | }, 685 | "grass-1": { 686 | "children": [], 687 | "matrix": [ 688 | 1, 689 | 0, 690 | 0, 691 | 0, 692 | 0, 693 | 1, 694 | 0, 695 | 0, 696 | 0, 697 | 0, 698 | 1, 699 | 0, 700 | -0.492588, 701 | -0.00222963, 702 | 0.133358, 703 | 1 704 | ], 705 | "meshes": [ 706 | "Plane_005-mesh" 707 | ], 708 | "name": "grass-1" 709 | }, 710 | "ground": { 711 | "children": [], 712 | "matrix": [ 713 | 1, 714 | 0, 715 | 0, 716 | 0, 717 | 0, 718 | 1, 719 | 0, 720 | 0, 721 | 0, 722 | 0, 723 | 1, 724 | 0, 725 | 0.399157, 726 | 0.425368, 727 | 0, 728 | 1 729 | ], 730 | "meshes": [ 731 | "Plane-mesh" 732 | ], 733 | "name": "ground" 734 | }, 735 | "mouth": { 736 | "children": [], 737 | "matrix": [ 738 | 1, 739 | 0, 740 | 0, 741 | 0, 742 | 0, 743 | 1, 744 | 0, 745 | 0, 746 | 0, 747 | 0, 748 | 1, 749 | 0, 750 | 0.300458, 751 | 0.142244, 752 | 1.62517, 753 | 1 754 | ], 755 | "meshes": [ 756 | "Cube_005-mesh" 757 | ], 758 | "name": "mouth" 759 | }, 760 | "node_7": { 761 | "children": [ 762 | "eyes_001", 763 | "eyes", 764 | "Rocks", 765 | "character", 766 | "mouth", 767 | "grass-1", 768 | "ground" 769 | ], 770 | "matrix": [ 771 | 1, 772 | 0, 773 | 0, 774 | 0, 775 | 0, 776 | 0, 777 | -1, 778 | 0, 779 | 0, 780 | 1, 781 | 0, 782 | 0, 783 | 0, 784 | 0, 785 | 0, 786 | 1 787 | ], 788 | "name": "Y_UP_Transform" 789 | } 790 | }, 791 | "samplers": { 792 | "sampler_0": { 793 | "magFilter": 9729, 794 | "minFilter": 9729, 795 | "wrapS": 10497, 796 | "wrapT": 10497 797 | } 798 | }, 799 | "scene": "defaultScene", 800 | "scenes": { 801 | "defaultScene": { 802 | "nodes": [ 803 | "node_7" 804 | ] 805 | } 806 | }, 807 | "skins": {}, 808 | "textures": { 809 | "texture_texture_png": { 810 | "format": 6408, 811 | "internalFormat": 6408, 812 | "sampler": "sampler_0", 813 | "source": "texture_png", 814 | "target": 3553, 815 | "type": 5121 816 | } 817 | } 818 | } -------------------------------------------------------------------------------- /templates/aframe-model-template/app/assets/models/texture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aframevr-userland/aframe-cli/e99a855d4c4e8109b41461907090e2c61b0b5a71/templates/aframe-model-template/app/assets/models/texture.png -------------------------------------------------------------------------------- /templates/aframe-model-template/app/assets/sounds/switch.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aframevr-userland/aframe-cli/e99a855d4c4e8109b41461907090e2c61b0b5a71/templates/aframe-model-template/app/assets/sounds/switch.wav -------------------------------------------------------------------------------- /templates/aframe-model-template/app/css/app.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aframevr-userland/aframe-cli/e99a855d4c4e8109b41461907090e2c61b0b5a71/templates/aframe-model-template/app/css/app.css -------------------------------------------------------------------------------- /templates/aframe-model-template/app/js/components/teleport.js: -------------------------------------------------------------------------------- 1 | AFRAME.registerComponent('teleport', { 2 | play: function () { 3 | this.el.addEventListener('click', this.handleClick.bind(this)); 4 | }, 5 | 6 | pause: function () { 7 | this.el.removeEventListener('click', this.handleClick.bind(this)); 8 | }, 9 | 10 | handleClick: function (e) { 11 | var camera = this.el.sceneEl.camera.el; 12 | var hotspotPosition = e.target.getAttribute('position'); 13 | var hotspotRotation = e.target.getAttribute('rotation'); 14 | var cameraPosition = camera.getAttribute('position'); 15 | camera.setAttribute('position', { 16 | x: hotspotPosition.x, 17 | y: cameraPosition.y, 18 | z: hotspotPosition.z 19 | }); 20 | camera.setAttribute('rotation', { 21 | y: hotspotRotation.y 22 | }); 23 | } 24 | }); -------------------------------------------------------------------------------- /templates/aframe-model-template/app/js/initialize.js: -------------------------------------------------------------------------------- 1 | require('./components/teleport'); 2 | 3 | document.addEventListener('DOMContentLoaded', () => { 4 | console.log('Initialized app'); 5 | }); 6 | -------------------------------------------------------------------------------- /templates/aframe-model-template/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "aframe-model-template", 3 | "description": "Model viewer. Made with A-Frame, a WebVR framework.", 4 | "author": "A-Frame authors", 5 | "version": "0.5.17", 6 | "license": "CC0-1.0", 7 | "repository": "aframevr-userland/aframe-model-template", 8 | "scripts": { 9 | "start": "npm run serve", 10 | "serve": "aframe serve", 11 | "build": "aframe build" 12 | }, 13 | "dependencies": { 14 | "aframe": "^0.6.0", 15 | "webrtcsupport": "^2.2.0" 16 | }, 17 | "devDependencies": { 18 | "aframe-cli": "0.5.16" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /templates/aframe-primitives-template/app/assets/images/grid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aframevr-userland/aframe-cli/e99a855d4c4e8109b41461907090e2c61b0b5a71/templates/aframe-primitives-template/app/assets/images/grid.png -------------------------------------------------------------------------------- /templates/aframe-primitives-template/app/assets/images/sky.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aframevr-userland/aframe-cli/e99a855d4c4e8109b41461907090e2c61b0b5a71/templates/aframe-primitives-template/app/assets/images/sky.png -------------------------------------------------------------------------------- /templates/aframe-primitives-template/app/assets/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Primitives • Made with A-Frame 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /templates/aframe-primitives-template/app/css/app.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aframevr-userland/aframe-cli/e99a855d4c4e8109b41461907090e2c61b0b5a71/templates/aframe-primitives-template/app/css/app.css -------------------------------------------------------------------------------- /templates/aframe-primitives-template/app/js/initialize.js: -------------------------------------------------------------------------------- 1 | document.addEventListener('DOMContentLoaded', () => { 2 | console.log('Initialized app'); 3 | }); 4 | -------------------------------------------------------------------------------- /templates/aframe-primitives-template/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "aframe-primitives-template", 3 | "description": "A-Frame scene built from primitives.", 4 | "author": "A-Frame authors", 5 | "version": "0.5.17", 6 | "license": "CC0-1.0", 7 | "repository": "aframevr-userland/aframe-primitives-template", 8 | "scripts": { 9 | "start": "npm run serve", 10 | "serve": "aframe serve", 11 | "build": "aframe build" 12 | }, 13 | "dependencies": { 14 | "aframe": "^0.6.0" 15 | }, 16 | "devDependencies": { 17 | "aframe-cli": "0.5.7" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /templates/aframe-room-template/app/assets/images/grid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aframevr-userland/aframe-cli/e99a855d4c4e8109b41461907090e2c61b0b5a71/templates/aframe-room-template/app/assets/images/grid.png -------------------------------------------------------------------------------- /templates/aframe-room-template/app/assets/images/sky.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aframevr-userland/aframe-cli/e99a855d4c4e8109b41461907090e2c61b0b5a71/templates/aframe-room-template/app/assets/images/sky.png -------------------------------------------------------------------------------- /templates/aframe-room-template/app/assets/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Room • Made with A-Frame 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 54 | 55 | 56 | 57 | 58 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | -------------------------------------------------------------------------------- /templates/aframe-room-template/app/css/app.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aframevr-userland/aframe-cli/e99a855d4c4e8109b41461907090e2c61b0b5a71/templates/aframe-room-template/app/css/app.css -------------------------------------------------------------------------------- /templates/aframe-room-template/app/js/initialize.js: -------------------------------------------------------------------------------- 1 | document.addEventListener('DOMContentLoaded', () => { 2 | console.log('Initialized app'); 3 | }); 4 | -------------------------------------------------------------------------------- /templates/aframe-room-template/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "aframe-room-template", 3 | "description": "Room built using A-Frame.", 4 | "author": "A-Frame authors", 5 | "version": "0.5.18", 6 | "license": "CC0-1.0", 7 | "repository": "aframevr-userland/aframe-room-template", 8 | "scripts": { 9 | "start": "npm run serve", 10 | "serve": "aframe serve", 11 | "build": "aframe build" 12 | }, 13 | "dependencies": { 14 | "aframe": "^0.6.0" 15 | }, 16 | "devDependencies": { 17 | "aframe-cli": "0.5.16" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /templates/index.json: -------------------------------------------------------------------------------- 1 | { 2 | "templates": [ 3 | { 4 | "alias": "default", 5 | "url": "aframevr-userland/aframe-default-template", 6 | "title": "Default A-Frame scene template", 7 | "description": "Default template for a default A-Frame scene.", 8 | "technologies": "ES6" 9 | }, 10 | { 11 | "alias": "primitives", 12 | "url": "aframevr-userland/aframe-primitives-template", 13 | "title": "Primitives template", 14 | "description": "A-Frame scene built from primitives.", 15 | "components": {}, 16 | "technologies": "ES6" 17 | }, 18 | { 19 | "alias": "room", 20 | "url": "aframevr-userland/aframe-room-template", 21 | "title": "Room template", 22 | "description": "Room built using A-Frame.", 23 | "components": {}, 24 | "technologies": "ES6" 25 | }, 26 | { 27 | "alias": "model", 28 | "url": "aframevr-userland/aframe-model-template", 29 | "title": "Model Viewer", 30 | "description": "Model viewer built using A-Frame.", 31 | "components": {}, 32 | "technologies": "ES6" 33 | }, 34 | { 35 | "alias": "360-tour", 36 | "url": "aframevr-userland/aframe-360-tour-template", 37 | "title": "360 tour template", 38 | "description": "360 tour built using A-Frame.", 39 | "components": {}, 40 | "technologies": "ES6" 41 | } 42 | ] 43 | } 44 | -------------------------------------------------------------------------------- /tools/publish-all.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const logger = require('loggy'); 4 | 5 | const publishTemplates = require('../lib/publish-all'); 6 | 7 | if (module.parent) { 8 | logger.error('`npm run publish-all` should be called from the command line'); 9 | process.exit(1); 10 | } 11 | 12 | const argv = process.argv.slice(2); 13 | let version = argv[0]; 14 | 15 | publishTemplates(version); 16 | --------------------------------------------------------------------------------