This commit is contained in:
Коммит
e78974074f
|
@ -0,0 +1,12 @@
|
|||
# top-most EditorConfig file
|
||||
root = true
|
||||
|
||||
# Unix-style newlines with a newline ending every file
|
||||
[*]
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
charset = utf-8
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
#### joe made this: http://goel.io/joe
|
||||
#### node ####
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
|
||||
# nyc test coverage
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (http://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
|
||||
# Dependency directories
|
||||
node_modules
|
||||
jspm_packages
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
dist
|
||||
.idea/
|
||||
.DS_Store
|
||||
|
||||
# Editor
|
||||
.vscode
|
|
@ -0,0 +1,57 @@
|
|||
# Logs
|
||||
logs
|
||||
*.log
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
|
||||
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
|
||||
# Compiled binary addons (http://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
|
||||
# Dependency directory
|
||||
# Commenting this out is preferred by some people, see
|
||||
# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git-
|
||||
node_modules
|
||||
|
||||
# Users Environment Variables
|
||||
.lock-wscript
|
||||
.tsdrc
|
||||
|
||||
#IntelliJ configuration files
|
||||
.idea
|
||||
|
||||
dist
|
||||
dev
|
||||
docs
|
||||
lib
|
||||
test
|
||||
|
||||
Thumbs.db
|
||||
.DS_Store
|
||||
*.yml
|
||||
!*.d.ts
|
||||
/src
|
||||
*.spec.*
|
||||
*.e2e.*
|
||||
CONTRIBUTING.md
|
||||
karma-shim.js
|
||||
karma.conf.js
|
||||
protractor.conf.js
|
||||
tsconfig.json
|
||||
tslint.json
|
||||
typedoc.json
|
||||
webpack.config.js
|
||||
.travis.yml
|
||||
.jshintrc
|
||||
.editorconfig
|
|
@ -0,0 +1,50 @@
|
|||
Much of the code and algorithms in this project are from the project at
|
||||
https://git.io/vPxQQ, which is provided under the following license:
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016 Microsoft. All rights reserved.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
|
||||
The original code in this project is available under the following license:
|
||||
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2016 Beam Interactive, Inc.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
|
@ -0,0 +1,70 @@
|
|||
import { ArcModule, InputService } from '../../../../src';
|
||||
import { Component, NgModule } from '@angular/core';
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
|
||||
@Component({
|
||||
selector: 'demo-app',
|
||||
styles: [`
|
||||
:host {
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
.area {
|
||||
max-width: 960px;
|
||||
border: 1px solid #000;
|
||||
margin: 15px auto;
|
||||
|
||||
.arc--selected {
|
||||
border-color: #f00;
|
||||
}
|
||||
}
|
||||
|
||||
.box {
|
||||
width: 100px;
|
||||
float: left;
|
||||
margin: 15px;
|
||||
background: #000;
|
||||
color: #fff;
|
||||
|
||||
.arc--selected {
|
||||
background: #f00;
|
||||
}
|
||||
}
|
||||
`],
|
||||
template: `
|
||||
<div class="area">
|
||||
<div class="box" *ngFor="let box of boxes">
|
||||
{{ box }}
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
})
|
||||
export class DemoComponent {
|
||||
public boxes: string[] = [];
|
||||
|
||||
constructor(input: InputService) {
|
||||
for (let i = 0; i < 100; i++) {
|
||||
this.boxes.push(String(`Box ${i}`));
|
||||
}
|
||||
|
||||
input.bootstrap();
|
||||
}
|
||||
}
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
BrowserModule,
|
||||
ArcModule,
|
||||
],
|
||||
providers: [
|
||||
InputService,
|
||||
],
|
||||
declarations: [
|
||||
DemoComponent,
|
||||
],
|
||||
bootstrap: [
|
||||
DemoComponent,
|
||||
],
|
||||
})
|
||||
export class AppModule {
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
|
||||
import { AppModule } from './demo/demo.module';
|
||||
|
||||
platformBrowserDynamic().bootstrapModule(AppModule);
|
|
@ -0,0 +1,4 @@
|
|||
import 'ts-helpers';
|
||||
|
||||
import 'core-js/es7/reflect';
|
||||
import 'zone.js/dist/zone';
|
|
@ -0,0 +1,13 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Arcade Machine</title>
|
||||
<link rel="stylesheet" href="normalize.css">
|
||||
<base href="/">
|
||||
</head>
|
||||
<body>
|
||||
<demo-app></demo-app>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,73 @@
|
|||
const path = require('path');
|
||||
const webpack = require('webpack');
|
||||
const atl = require('awesome-typescript-loader');
|
||||
const CleanWebpackPlugin = require('clean-webpack-plugin');
|
||||
const HtmlWebpackPlugin = require('html-webpack-plugin');
|
||||
|
||||
const chunkOrder = ['inline', 'polyfill', 'main'];
|
||||
const paths = {};
|
||||
paths.root = paths.root || path.resolve(__dirname);
|
||||
paths.dist = path.resolve(__dirname, 'dist');
|
||||
paths.indexTemplate = path.resolve(__dirname, 'src/index.ejs');
|
||||
paths.tsconfig = path.resolve(__dirname, '../tsconfig.json');
|
||||
|
||||
module.exports = {
|
||||
devtool: 'source-map',
|
||||
context: paths.root,
|
||||
entry: {
|
||||
main: path.resolve(__dirname, 'src/bundles/main.ts'),
|
||||
polyfills: path.resolve(__dirname, 'src/bundles/polyfills.ts')
|
||||
},
|
||||
output: {
|
||||
path: paths.dist,
|
||||
filename: 'bundles/[name].bundle.js',
|
||||
},
|
||||
resolve: {
|
||||
extensions: ['.js', '.ts'],
|
||||
},
|
||||
devServer: {
|
||||
port: 8080,
|
||||
historyApiFallback: true,
|
||||
contentBase: 'demo/dist'
|
||||
},
|
||||
module: {
|
||||
loaders: [
|
||||
{
|
||||
test: /\.ts$/,
|
||||
loaders: [
|
||||
{
|
||||
loader: 'awesome-typescript-loader',
|
||||
query: {
|
||||
useForkChecker: true,
|
||||
tsconfig: paths.tsconfig
|
||||
}
|
||||
},
|
||||
],
|
||||
}
|
||||
]
|
||||
},
|
||||
plugins: [
|
||||
new HtmlWebpackPlugin({
|
||||
template: paths.indexTemplate,
|
||||
hash: true,
|
||||
chunksSortMode: (a, b) => chunkOrder.indexOf(a.names[0]) > chunkOrder.indexOf(b.names[0]),
|
||||
}),
|
||||
|
||||
new atl.ForkCheckerPlugin(),
|
||||
|
||||
// Fix for critical dependency warning due to System.import in angular.
|
||||
// See https://github.com/angular/angular/issues/11580
|
||||
new webpack.ContextReplacementPlugin(
|
||||
/angular(\\|\/)core(\\|\/)(esm(\\|\/)src|src)(\\|\/)linker/,
|
||||
paths.app
|
||||
),
|
||||
|
||||
],
|
||||
node: {
|
||||
fs: 'empty',
|
||||
crypto: 'empty',
|
||||
module: false,
|
||||
clearImmediate: false,
|
||||
setImmediate: false
|
||||
}
|
||||
};
|
|
@ -0,0 +1,109 @@
|
|||
const gulp = require('gulp');
|
||||
const gulpMerge = require('merge2');
|
||||
const gulpRunSequence = require('run-sequence');
|
||||
const gulpConcat = require('gulp-concat');
|
||||
const gulpAutoprefixer = require('gulp-autoprefixer');
|
||||
|
||||
const tsc = require('gulp-typescript');
|
||||
const karma = require('karma');
|
||||
const autoprefixer = require('autoprefixer');
|
||||
const gulpClean = require('gulp-clean');
|
||||
const path = require('path');
|
||||
const webpack = require('webpack');
|
||||
|
||||
let isTestRun = false;
|
||||
|
||||
/**
|
||||
* Inline the templates and styles, and the compile to javascript.
|
||||
*/
|
||||
gulp.task(':build:app', () => {
|
||||
const tsProject = tsc.createProject('tsconfig.json', {
|
||||
module: isTestRun ? 'commonjs' : 'es2015',
|
||||
});
|
||||
|
||||
gulp.src(['./src/**/*.ts'])
|
||||
.pipe(tsProject())
|
||||
.pipe(gulp.dest('./dist'));
|
||||
});
|
||||
|
||||
/**
|
||||
* Cleans the build folder
|
||||
*/
|
||||
gulp.task('clean', () => gulp.src('dist', { read: false }).pipe(gulpClean(null)));
|
||||
|
||||
/**
|
||||
* Builds the main framework to the build folder
|
||||
*/
|
||||
gulp.task('build', (cb) => gulpRunSequence(
|
||||
'clean',
|
||||
[
|
||||
':build:app',
|
||||
],
|
||||
cb
|
||||
));
|
||||
|
||||
/**
|
||||
* Bundles vendor files for test access
|
||||
*/
|
||||
gulp.task(':test:vendor', function () {
|
||||
const npmVendorFiles = [
|
||||
'@angular', 'core-js/client', 'systemjs/dist', 'zone.js/dist'
|
||||
];
|
||||
|
||||
return gulpMerge(
|
||||
npmVendorFiles.map(function (root) {
|
||||
const glob = path.join(root, '**/*.+(js|js.map)');
|
||||
|
||||
return gulp.src(path.join('node_modules', glob))
|
||||
.pipe(gulp.dest(path.join('dist/vendor', root)));
|
||||
}));
|
||||
});
|
||||
|
||||
/**
|
||||
* Bundles systemjs files
|
||||
*/
|
||||
gulp.task(':test:system', () => {
|
||||
gulp.src('test/bin/**.*')
|
||||
.pipe(tsc())
|
||||
.pipe(gulp.dest('dist/bin'));
|
||||
});
|
||||
|
||||
/**
|
||||
* Pre-test setup task
|
||||
*/
|
||||
gulp.task(':test:deps', (cb) => {
|
||||
isTestRun = true;
|
||||
gulpRunSequence(
|
||||
'clean',
|
||||
[
|
||||
':test:system',
|
||||
':test:vendor',
|
||||
':build:app',
|
||||
],
|
||||
cb
|
||||
);
|
||||
});
|
||||
|
||||
/**
|
||||
* Karma unit-testing
|
||||
*/
|
||||
gulp.task('test', [':test:deps'], (done) => {
|
||||
new karma.Server({
|
||||
configFile: path.join(process.cwd(), 'test/karma.confloader.js')
|
||||
}, done).start();
|
||||
});
|
||||
|
||||
gulp.task(':demo:clean', () => gulp.src('demo/dist', { read: false }).pipe(gulpClean(null)));
|
||||
|
||||
gulp.task(':demo:build:ts', (cb) => {
|
||||
webpack(require('./demo/webpack.config.js'), cb);
|
||||
});
|
||||
|
||||
gulp.task('demo', (cb) => gulpRunSequence(
|
||||
':demo:clean',
|
||||
[
|
||||
':demo:build:ts',
|
||||
],
|
||||
cb
|
||||
));
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
{
|
||||
"name": "arcade-machine",
|
||||
"version": "0.1.0",
|
||||
"scripts": {
|
||||
"gulp": "gulp",
|
||||
"build": "gulp build",
|
||||
"prepublish": "npm run build",
|
||||
"clean": "rimraf node_modules doc dist && npm cache clean",
|
||||
"test:lint": "tslint 'src/**/*.ts' --project tsconfig.json",
|
||||
"test:unit": "gulp test",
|
||||
"test": "npm run lint && npm run test:unit",
|
||||
"start": "npm run serve",
|
||||
"serve": "webpack-dev-server --config demo/webpack.config.js"
|
||||
},
|
||||
"main": "dist/index.js",
|
||||
"typings": "dist/index.d.ts",
|
||||
"license": "Proprietary",
|
||||
"dependencies": {
|
||||
"@angular/common": "^2.0.1",
|
||||
"@angular/compiler": "^2.0.1",
|
||||
"@angular/core": "^2.0.1",
|
||||
"@angular/platform-browser": "^2.0.1",
|
||||
"@angular/platform-browser-dynamic": "^2.0.1",
|
||||
"@types/core-js": "^0.9.34",
|
||||
"@types/node": "^6.0.40",
|
||||
"core-js": "^2.4.1",
|
||||
"systemjs": "0.19.38",
|
||||
"zone.js": "^0.6.23"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jasmine": "^2.2.34",
|
||||
"@types/karma": "^0.13.33",
|
||||
"@types/lodash": "^4.14.36",
|
||||
"@types/node": "^6.0.41",
|
||||
"autoprefixer": "^6.5.0",
|
||||
"awesome-typescript-loader": "^2.2.4",
|
||||
"clean-webpack-plugin": "^0.1.10",
|
||||
"codelyzer": "0.0.28",
|
||||
"copy-webpack-plugin": "^3.0.0",
|
||||
"extract-text-webpack-plugin": "^1.0.1",
|
||||
"gulp": "^3.9.1",
|
||||
"gulp-autoprefixer": "^3.1.1",
|
||||
"gulp-clean": "^0.3.2",
|
||||
"gulp-concat": "^2.6.0",
|
||||
"gulp-typescript": "^3.0.1",
|
||||
"html-webpack-plugin": "^2.22.0",
|
||||
"istanbul-instrumenter-loader": "^0.2.0",
|
||||
"jasmine-core": "^2.3.4",
|
||||
"jasmine-spec-reporter": "^2.4.0",
|
||||
"karma": "^1.1.1",
|
||||
"karma-browserstack-launcher": "^1.0.1",
|
||||
"karma-chrome-launcher": "^1.0.1",
|
||||
"karma-coverage": "^1.1.1",
|
||||
"karma-firefox-launcher": "^1.0.0",
|
||||
"karma-jasmine": "^1.0.2",
|
||||
"karma-mocha-reporter": "^2.2.0",
|
||||
"karma-sauce-launcher": "^1.0.0",
|
||||
"karma-sourcemap-loader": "^0.3.7",
|
||||
"merge2": "^1.0.2",
|
||||
"phantomjs-prebuilt": "^2.1.4",
|
||||
"reflect-metadata": "^0.1.8",
|
||||
"rimraf": "^2.5.1",
|
||||
"run-sequence": "^1.2.2",
|
||||
"sass-loader": "^4.0.2",
|
||||
"ts-helpers": "^1.1.1",
|
||||
"tslint": "^3.15.1",
|
||||
"tslint-loader": "^2.1.0",
|
||||
"tslint-microsoft-contrib": "^2.0.12",
|
||||
"typedoc": "^0.3.12",
|
||||
"typescript": "2.0.3",
|
||||
"webpack": "^2.1.0-beta.25",
|
||||
"webpack-dev-server": "^2.1.0-beta.7"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
export enum Direction {
|
||||
LEFT,
|
||||
RIGHT,
|
||||
UP,
|
||||
DOWN,
|
||||
SUBMIT,
|
||||
BACK,
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
import { NgModule } from '@angular/core';
|
||||
|
||||
export { InputService } from './input.service';
|
||||
|
||||
@NgModule({
|
||||
imports: [],
|
||||
exports: [],
|
||||
})
|
||||
export class ArcModule {
|
||||
}
|
|
@ -0,0 +1,268 @@
|
|||
import { Direction } from './focus.service';
|
||||
import { Injectable } from '@angular/core';
|
||||
|
||||
interface GamepadWrapper {
|
||||
// Directional returns from the gamepad. They debounce themselves and
|
||||
// trigger again after debounce times.
|
||||
left(now: number): boolean;
|
||||
right(now: number): boolean;
|
||||
up(now: number): boolean;
|
||||
down(now: number): boolean;
|
||||
|
||||
/**
|
||||
* Returns if the user is pressing the "back" button.
|
||||
*/
|
||||
back(now: number): boolean;
|
||||
|
||||
/**
|
||||
* Returns if the user is pressing the "submit" button.
|
||||
*/
|
||||
submit(now: number): boolean;
|
||||
|
||||
/**
|
||||
* Returns whether the gamepad is still connected;
|
||||
*/
|
||||
isConnected(): boolean;
|
||||
}
|
||||
|
||||
enum DebouncerStage {
|
||||
IDLE,
|
||||
HELD,
|
||||
FAST,
|
||||
}
|
||||
|
||||
class DirectionalDebouncer {
|
||||
|
||||
/**
|
||||
* fn is a bound function that can be called to check if the key is held.
|
||||
*/
|
||||
public fn: (time: number) => boolean;
|
||||
|
||||
/**
|
||||
* Initial debounce after a joystick is pressed before beginning shorter
|
||||
* press debouncded.
|
||||
*/
|
||||
public static JoystickInitialDebounce = 500;
|
||||
|
||||
/**
|
||||
* Fast debounce time for joysticks when they're being held in a direction.
|
||||
*/
|
||||
public static JoystickFastDebounce = 200;
|
||||
|
||||
private heldAt = 0;
|
||||
private stage = DebouncerStage.IDLE;
|
||||
|
||||
constructor(private predicate: () => boolean) {}
|
||||
|
||||
/**
|
||||
* Returns whether the key should be registered as pressed.
|
||||
*/
|
||||
public attempt(now: number): boolean {
|
||||
const result = this.predicate();
|
||||
if (!result) {
|
||||
this.stage = DebouncerStage.IDLE;
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (this.stage) {
|
||||
case DebouncerStage.IDLE:
|
||||
this.stage = DebouncerStage.HELD;
|
||||
return true;
|
||||
|
||||
case DebouncerStage.HELD:
|
||||
if (now - this.heldAt < DirectionalDebouncer.JoystickInitialDebounce) {
|
||||
this.heldAt = now;
|
||||
return false;
|
||||
}
|
||||
this.stage = DebouncerStage.FAST;
|
||||
return true;
|
||||
|
||||
case DebouncerStage.FAST:
|
||||
if (now - this.heldAt < DirectionalDebouncer.JoystickFastDebounce) {
|
||||
this.heldAt = now;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
|
||||
default:
|
||||
throw new Error(`Unknown debouncer stage ${this.stage}!`);
|
||||
}
|
||||
}
|
||||
}
|
||||
class XboxGamepadWrapper implements GamepadWrapper {
|
||||
|
||||
public left: (now: number) => boolean;
|
||||
public right: (now: number) => boolean;
|
||||
public up: (now: number) => boolean;
|
||||
public down: (now: number) => boolean;
|
||||
public back: (now: number) => boolean;
|
||||
public submit: (now: number) => boolean;
|
||||
|
||||
constructor(private pad: Gamepad) {
|
||||
const left = new DirectionalDebouncer(() => pad.axes[0] < -InputService.JoystickThreshold);
|
||||
const right = new DirectionalDebouncer(() => pad.axes[0] > InputService.JoystickThreshold);
|
||||
const up = new DirectionalDebouncer(() => pad.axes[1] < -InputService.JoystickThreshold);
|
||||
const down = new DirectionalDebouncer(() => pad.axes[0] > InputService.JoystickThreshold);
|
||||
const back = new DirectionalDebouncer(() => pad.buttons[1].pressed);
|
||||
const submit = new DirectionalDebouncer(() => pad.buttons[0].pressed);
|
||||
|
||||
this.left = now => left.attempt(now);
|
||||
this.right = now => right.attempt(now);
|
||||
this.up = now => up.attempt(now);
|
||||
this.down = now => down.attempt(now);
|
||||
this.back = now => back.attempt(now);
|
||||
this.submit = now => submit.attempt(now);
|
||||
}
|
||||
|
||||
public isConnected() {
|
||||
return this.pad.connected;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* InputService handles passing input from the external device (gamepad API
|
||||
* or keyboard) to the arc internals.
|
||||
*/
|
||||
@Injectable()
|
||||
export class InputService {
|
||||
|
||||
/**
|
||||
* Mangitude that joysticks have to go in one direction to be translated
|
||||
* into a direction key press.
|
||||
*/
|
||||
public static JoystickThreshold = 0.5;
|
||||
|
||||
/**
|
||||
* DirectionCodes is a map of directions to key code names.
|
||||
*/
|
||||
public static DirectionCodes = new Map<Direction, number[]>([
|
||||
[Direction.DOWN, []]
|
||||
]);
|
||||
|
||||
private gamepads: GamepadWrapper[];
|
||||
private pollRaf: number;
|
||||
|
||||
/**
|
||||
* Bootstrap attaches event listeners from the service to the DOM.
|
||||
*/
|
||||
public bootstrap() {
|
||||
// The gamepadInputEmulation is a string property that exists in
|
||||
// JavaScript UWAs and in WebViews in UWAs. It won't exist in
|
||||
// Win8.1 style apps or browsers.
|
||||
if (typeof (<any> navigator).gamepadInputEmulation === "string") {
|
||||
// We want the gamepad to provide gamepad VK keyboard events rather than moving a
|
||||
// mouse like cursor. Set to "keyboard", the gamepad will provide such keyboard events
|
||||
// and provide input to the DOM navigator.getGamepads API.
|
||||
(<any> navigator).gamepadInputEmulation = "keyboard";
|
||||
} else if (typeof navigator.getGamepads === 'function') {
|
||||
// Otherwise poll for connected gamepads and use that for input.
|
||||
this.watchForGamepad();
|
||||
}
|
||||
|
||||
this.addKeyboardListeners();
|
||||
}
|
||||
/**
|
||||
* Detects any connected gamepads and watches for new ones to start
|
||||
* polling them. This is the entry point for gamepad input handling.
|
||||
*/
|
||||
private watchForGamepad() {
|
||||
const addGamepads = () => {
|
||||
// it's not an array, originally, and contains undefined elements.
|
||||
this.gamepads = Array.from(navigator.getGamepads())
|
||||
.filter(pad => !!pad)
|
||||
.map(pad => {
|
||||
if (/xbox/i.test(pad.id)) {
|
||||
return new XboxGamepadWrapper(pad);
|
||||
}
|
||||
|
||||
// We can try, at least ¯\_(ツ)_/¯ and this should
|
||||
// usually be OK due to remapping.
|
||||
return new XboxGamepadWrapper(pad);
|
||||
});
|
||||
};
|
||||
|
||||
addGamepads();
|
||||
if (this.gamepads.length > 0) {
|
||||
this.scheduleGamepadPoll();
|
||||
}
|
||||
|
||||
addEventListener('gamepadconnected', () => {
|
||||
addGamepads();
|
||||
cancelAnimationFrame(this.pollRaf);
|
||||
this.scheduleGamepadPoll();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedules a new gamepad poll at the next animation frame.
|
||||
*/
|
||||
private scheduleGamepadPoll() {
|
||||
this.pollRaf = requestAnimationFrame(now => this.pollGamepad(now));
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks for input provided by the gamepad and fires off events as
|
||||
* necessary. It schedules itself again provided that there's still
|
||||
* a connected gamepad somewhere.
|
||||
*/
|
||||
private pollGamepad(now: number) {
|
||||
for (let i = 0; i < this.gamepads.length; i++) {
|
||||
const gamepad = this.gamepads[i];
|
||||
if (!gamepad.isConnected()) {
|
||||
this.gamepads.splice(i, 1);
|
||||
i -= 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (gamepad.left(now)) {
|
||||
this.handleDirection(Direction.LEFT);
|
||||
} else if (gamepad.right(now)) {
|
||||
this.handleDirection(Direction.RIGHT);
|
||||
} else if (gamepad.down(now)) {
|
||||
this.handleDirection(Direction.DOWN);
|
||||
} else if (gamepad.up(now)) {
|
||||
this.handleDirection(Direction.UP);
|
||||
} else if (gamepad.submit(now)) {
|
||||
this.handleDirection(Direction.SUBMIT);
|
||||
} else if (gamepad.back(now)) {
|
||||
this.handleDirection(Direction.BACK);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.gamepads.length > 0) {
|
||||
this.scheduleGamepadPoll();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private handleDirection(direction: Direction): boolean {
|
||||
console.log('dir', direction);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles a key down event, returns whether the event has resulted
|
||||
* in a navigation and should be cancelled.
|
||||
*/
|
||||
private handleKeyDown(keyCode: number): boolean {
|
||||
let result: boolean;
|
||||
InputService.DirectionCodes.forEach((codes, direction) => {
|
||||
if (result === undefined && codes.indexOf(keyCode) !== -1) {
|
||||
result = this.handleDirection(direction);
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds listeners for keyboard events.
|
||||
*/
|
||||
private addKeyboardListeners() {
|
||||
addEventListener('keydown', ev => {
|
||||
if (!ev.defaultPrevented && this.handleKeyDown(ev.keyCode)) {
|
||||
ev.preventDefault();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
declare var System: any;
|
||||
|
||||
System.config({
|
||||
map: {
|
||||
'rxjs': 'vendor/rxjs',
|
||||
|
||||
// Angular specific mappings.
|
||||
'@angular/core': 'vendor/@angular/core/bundles/core.umd.js',
|
||||
'@angular/core/testing': 'vendor/@angular/core/bundles/core-testing.umd.js',
|
||||
'@angular/common': 'vendor/@angular/common/bundles/common.umd.js',
|
||||
'@angular/common/testing': 'vendor/@angular/common/bundles/common-testing.umd.js',
|
||||
'@angular/compiler': 'vendor/@angular/compiler/bundles/compiler.umd.js',
|
||||
'@angular/compiler/testing': 'vendor/@angular/compiler/bundles/compiler-testing.umd.js',
|
||||
'@angular/http': 'vendor/@angular/http/bundles/http.umd.js',
|
||||
'@angular/http/testing': 'vendor/@angular/http/bundles/http-testing.umd.js',
|
||||
'@angular/forms': 'vendor/@angular/forms/bundles/forms.umd.js',
|
||||
'@angular/forms/testing': 'vendor/@angular/forms/bundles/forms-testing.umd.js',
|
||||
'@angular/router': 'vendor/@angular/router/bundles/router.umd.js',
|
||||
'@angular/router/testing': 'vendor/@angular/router/bundles/router-testing.umd.js',
|
||||
'@angular/platform-browser': 'vendor/@angular/platform-browser/bundles/platform-browser.umd.js',
|
||||
'@angular/platform-browser/testing':
|
||||
'vendor/@angular/platform-browser/bundles/platform-browser-testing.umd.js',
|
||||
'@angular/platform-browser-dynamic':
|
||||
'vendor/@angular/platform-browser-dynamic/bundles/platform-browser-dynamic.umd.js',
|
||||
'@angular/platform-browser-dynamic/testing':
|
||||
'vendor/@angular/platform-browser-dynamic/bundles/platform-browser-dynamic-testing.umd.js',
|
||||
},
|
||||
packages: {
|
||||
// Thirdparty barrels.
|
||||
'rxjs': { main: 'index' },
|
||||
// Set the default extension for the root package, because otherwise the demo-app can't
|
||||
// be built within the production mode. Due to missing file extensions.
|
||||
'.': {
|
||||
defaultExtension: 'js'
|
||||
}
|
||||
}
|
||||
});
|
|
@ -0,0 +1,56 @@
|
|||
/*global jasmine, __karma__, window*/
|
||||
Error.stackTraceLimit = Infinity;
|
||||
jasmine.DEFAULT_TIMEOUT_INTERVAL = 3000;
|
||||
|
||||
__karma__.loaded = function () {
|
||||
};
|
||||
|
||||
var distPath = '/base/dist/';
|
||||
|
||||
function isJsFile(path) {
|
||||
return path.slice(-3) == '.js';
|
||||
}
|
||||
|
||||
function isSpecFile(path) {
|
||||
return path.slice(-8) == '.spec.js';
|
||||
}
|
||||
|
||||
function isAppFile(path) {
|
||||
return isJsFile(path) && path.indexOf('vendor') == -1;
|
||||
}
|
||||
|
||||
var allSpecFiles = Object.keys(window.__karma__.files)
|
||||
.filter(isSpecFile)
|
||||
.filter(isAppFile);
|
||||
|
||||
// Load our SystemJS configuration.
|
||||
System.config({
|
||||
baseURL: distPath,
|
||||
});
|
||||
|
||||
// Load and configure the TestComponentBuilder.
|
||||
System.import(distPath + 'bin/system-config-spec.js').then(function() {
|
||||
// Load and configure the TestComponentBuilder.
|
||||
return Promise.all([
|
||||
System.import('@angular/core/testing'),
|
||||
System.import('@angular/platform-browser-dynamic/testing')
|
||||
]).then(function (providers) {
|
||||
var testing = providers[0];
|
||||
var testingBrowser = providers[1];
|
||||
|
||||
jasmine.DEFAULT_TIMEOUT_INTERVAL = 10000;
|
||||
|
||||
testing.TestBed.initTestEnvironment(
|
||||
testingBrowser.BrowserDynamicTestingModule,
|
||||
testingBrowser.platformBrowserDynamicTesting());
|
||||
});
|
||||
}).then(function() {
|
||||
// Finally, load all spec files.
|
||||
// This will run the tests directly.
|
||||
return Promise.all(
|
||||
allSpecFiles.map(function (moduleName) {
|
||||
return System.import(moduleName).then(function(module) {
|
||||
return module;
|
||||
});
|
||||
}));
|
||||
}).then(__karma__.start, __karma__.error);
|
|
@ -0,0 +1,98 @@
|
|||
import path = require('path');
|
||||
|
||||
export function config(config: any) {
|
||||
|
||||
config.set({
|
||||
|
||||
// base path that will be used to resolve all patterns (eg. files, exclude)
|
||||
basePath: path.join(__dirname, '..'),
|
||||
|
||||
failOnEmptyTestSuite: false,
|
||||
|
||||
// frameworks to use
|
||||
// available frameworks: https://npmjs.org/browse/keyword/karma-adapter
|
||||
frameworks: ['jasmine'],
|
||||
|
||||
plugins: [
|
||||
require('karma-jasmine'),
|
||||
require('karma-mocha-reporter'),
|
||||
require('karma-coverage'),
|
||||
require('karma-chrome-launcher'),
|
||||
require('karma-firefox-launcher'),
|
||||
],
|
||||
|
||||
// list of files / patterns to load in the browser
|
||||
files: [
|
||||
{ pattern: 'dist/vendor/core-js/client/core.js', included: true, watched: false },
|
||||
{ pattern: 'dist/vendor/systemjs/dist/system-polyfills.js', included: true, watched: false },
|
||||
{ pattern: 'dist/vendor/systemjs/dist/system.src.js', included: true, watched: false },
|
||||
|
||||
{ pattern: 'dist/vendor/zone.js/dist/zone.js', included: true, watched: false },
|
||||
{ pattern: 'dist/vendor/zone.js/dist/sync-test.js', included: true, watched: false },
|
||||
{ pattern: 'dist/vendor/zone.js/dist/async-test.js', included: true, watched: false },
|
||||
{ pattern: 'dist/vendor/zone.js/dist/proxy.js', include: true, watched: false },
|
||||
{ pattern: 'dist/vendor/zone.js/dist/fake-async-test.js', included: true, watched: false },
|
||||
{ pattern: 'dist/vendor/zone.js/dist/long-stack-trace-zone.js', include: true, watched: false },
|
||||
{ pattern: 'dist/vendor/zone.js/dist/jasmine-patch.js', included: true, watched: false },
|
||||
|
||||
{ pattern: 'dist/vendor/hammerjs/hammer.min.js', included: true, watched: false },
|
||||
|
||||
{ pattern: 'test/karma-shim.js', included: true, watched: false },
|
||||
|
||||
{ pattern: 'dist/bin/system-config-spec.js', included: true, watched: false },
|
||||
|
||||
// paths loaded via module imports
|
||||
{ pattern: 'dist/**/*.js', included: false, watched: true },
|
||||
],
|
||||
|
||||
proxies: {
|
||||
// required for component assets fetched by Angular's compiler
|
||||
'/components/': '/base/dist/components/',
|
||||
'/core/': '/base/dist/core/',
|
||||
},
|
||||
|
||||
// list of files to exclude
|
||||
exclude: [],
|
||||
|
||||
coverageReporter: {
|
||||
dir: 'coverage/',
|
||||
reporters: [
|
||||
{type: 'text-summary'},
|
||||
{type: 'html'}
|
||||
]
|
||||
},
|
||||
|
||||
// preprocess matching files before serving them to the browser
|
||||
// available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
|
||||
preprocessors: {},
|
||||
|
||||
// test results reporter to use
|
||||
// possible values: 'dots', 'progress', 'mocha'
|
||||
// available reporters: https://npmjs.org/browse/keyword/karma-reporter
|
||||
reporters: ['mocha', 'coverage'],
|
||||
|
||||
port: 9876,
|
||||
colors: true,
|
||||
|
||||
// level of logging
|
||||
// possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
|
||||
logLevel: config.LOG_INFO,
|
||||
|
||||
|
||||
// enable / disable watching file and executing tests whenever any file changes
|
||||
autoWatch: false,
|
||||
|
||||
// start these browsers
|
||||
// available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
|
||||
browsers: ['Chrome'],
|
||||
|
||||
browserDisconnectTimeout: 2000000,
|
||||
browserNoActivityTimeout: 2400000,
|
||||
captureTimeout: 12000000,
|
||||
|
||||
// Continuous Integration mode
|
||||
// if true, Karma captures browsers, runs the tests and exits
|
||||
singleRun: true
|
||||
});
|
||||
|
||||
};
|
|
@ -0,0 +1,28 @@
|
|||
const fs = require('fs');
|
||||
const ts = require('typescript');
|
||||
|
||||
const old = require.extensions['.ts'];
|
||||
|
||||
require.extensions['.ts'] = function(m, filename) {
|
||||
// If we're in node module, either call the old hook or simply compile the
|
||||
// file without transpilation. We do not touch node_modules/**.
|
||||
if (filename.match(/node_modules/)) {
|
||||
if (old) {
|
||||
return old(m, filename);
|
||||
}
|
||||
return m._compile(fs.readFileSync(filename), filename);
|
||||
}
|
||||
|
||||
// Node requires all require hooks to be sync.
|
||||
const source = fs.readFileSync(filename).toString();
|
||||
const result = ts.transpile(source, {
|
||||
target: ts.ScriptTarget.ES5,
|
||||
module: ts.ModuleKind.CommonJs,
|
||||
});
|
||||
|
||||
// Send it to node to execute.
|
||||
return m._compile(result, filename);
|
||||
};
|
||||
|
||||
// Import the TS once we know it's safe to require.
|
||||
module.exports = require('./karma.conf.ts').config;
|
|
@ -0,0 +1,30 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"experimentalDecorators": true,
|
||||
"emitDecoratorMetadata": true,
|
||||
"moduleResolution": "node",
|
||||
"noImplicitAny": true,
|
||||
"strictNullChecks": false,
|
||||
"module": "commonjs",
|
||||
"sourceMap": true,
|
||||
"target": "es5",
|
||||
"pretty": true,
|
||||
"declaration": true,
|
||||
"lib": [
|
||||
"dom",
|
||||
"es2015"
|
||||
],
|
||||
"typeRoots": [
|
||||
"node_modules/@types"
|
||||
],
|
||||
"types": [
|
||||
"jasmine",
|
||||
"node"
|
||||
]
|
||||
},
|
||||
"exclude": [
|
||||
"node_modules"
|
||||
]
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
{
|
||||
"extends": "tslint-microsoft-contrib",
|
||||
"rulesDirectory": [
|
||||
"node_modules/codelyzer",
|
||||
"node_modules/tslint-microsoft-contrib"
|
||||
],
|
||||
"rules": {
|
||||
"no-increment-decrement": false,
|
||||
"no-multiline-string": false,
|
||||
"no-for-in-array": false,
|
||||
"restrict-plus-operands": false,
|
||||
"no-constructor-vars": false,
|
||||
"no-relative-imports": false,
|
||||
"mocha-no-side-effect-code": false,
|
||||
"missing-jsdoc": false,
|
||||
"export-name": false,
|
||||
"no-stateless-class": false,
|
||||
"no-any": false,
|
||||
"trailing-comma": [true, {"multiline": "always", "singleline": "never"}],
|
||||
|
||||
// Angular
|
||||
"directive-selector-name": [
|
||||
true,
|
||||
"camelCase"
|
||||
],
|
||||
"component-selector-name": [
|
||||
true,
|
||||
"kebab-case"
|
||||
],
|
||||
"directive-selector-type": [
|
||||
true,
|
||||
"attribute"
|
||||
],
|
||||
"component-selector-type": [
|
||||
true,
|
||||
"element"
|
||||
],
|
||||
"directive-selector-prefix": [
|
||||
true,
|
||||
"bui"
|
||||
],
|
||||
"component-selector-prefix": [
|
||||
true,
|
||||
"bui"
|
||||
],
|
||||
"use-input-property-decorator": true,
|
||||
"use-output-property-decorator": true,
|
||||
"use-host-property-decorator": true,
|
||||
"no-attribute-parameter-decorator": true,
|
||||
"no-input-rename": false,
|
||||
"no-output-rename": false,
|
||||
"no-forward-ref": false,
|
||||
"use-life-cycle-interface": true,
|
||||
"use-pipe-transform-interface": true,
|
||||
"pipe-naming": [
|
||||
true,
|
||||
"camelCase",
|
||||
"bui"
|
||||
],
|
||||
"component-class-suffix": true,
|
||||
"directive-class-suffix": true,
|
||||
"import-destructuring-spacing": true
|
||||
}
|
||||
}
|
Загрузка…
Ссылка в новой задаче