瀏覽代碼

Boilerplate for Flux

Initial commit with code inspired by the Flux architecture, code written
in Typescript and a build system based on Gulp.
crobi 9 年之前
父節點
當前提交
8ee2d4b98f

+ 3
- 0
client/.gitignore 查看文件

@@ -0,0 +1,3 @@
1
+/node_modules
2
+/js/app.js
3
+/js/app.js.map

+ 6
- 0
client/README.md 查看文件

@@ -0,0 +1,6 @@
1
+Building
2
+========
3
+
4
+npm install
5
+npm install -g gulp
6
+gulp

+ 52
- 0
client/css/rpg-cards.css 查看文件

@@ -0,0 +1,52 @@
1
+* {
2
+    box-sizing: border-box;
3
+}
4
+
5
+body {
6
+    margin: 0;
7
+    font-family: 'Roboto', 'Helvetica Neue', Helvetica, Arial;
8
+}
9
+
10
+/* ---------------------------------------------------------------------------*/
11
+/* Header */
12
+/* ---------------------------------------------------------------------------*/
13
+.navbar {
14
+    display: flex;
15
+    flex-direction: row;
16
+    height: 3rem;
17
+    background-color: rgb(126, 120, 3);
18
+    color: rgb(228, 228, 238);
19
+    font-size: 1.8rem;
20
+    line-height: 3rem;
21
+}
22
+.navbar-item, .plain-navbar-item {
23
+    padding: 0 .5rem;
24
+}
25
+.navbar-item:hover {
26
+    cursor: pointer;
27
+    background-color: #999;
28
+}
29
+.flex-navbar-item {
30
+    flex: 1;
31
+}
32
+.navbar-item.account-id {
33
+    display: flex;
34
+    flex-direction: column;
35
+    margin-right: 1rem;
36
+}
37
+.navbar-item.account-id .heading {
38
+    font-size: 0.8rem;
39
+    line-height: 0.9rem;
40
+    color: rgb(217, 216, 219);
41
+    text-align: center;
42
+    text-transform: uppercase;
43
+    margin-top: 0.4rem;
44
+}
45
+.navbar-item.account-id .value {
46
+    font-size: 1.5rem;
47
+    line-height: 1.5rem;
48
+}
49
+.navbar-item.account-id:hover {
50
+    cursor: initial;
51
+    background-color: inherit;
52
+}

+ 46
- 0
client/gulpfile.js 查看文件

@@ -0,0 +1,46 @@
1
+var gulp       = require('gulp')
2
+  , ts         = require('gulp-typescript')
3
+  , sourcemaps = require('gulp-sourcemaps')
4
+  , babel      = require('gulp-babel')
5
+  , rev        = require('gulp-rev')
6
+  ;
7
+
8
+var tsProject = ts.createProject({
9
+      declarationFiles: false,
10
+      noExternalResolve: true,
11
+      removeComments: true,
12
+      target: 'ES6',
13
+      typescript: require('typescript'),
14
+      out: 'app.js'
15
+  });
16
+
17
+  var tsProject2 = ts.createProject('tsconfig.json');
18
+
19
+gulp.task('default', function() {
20
+    return tsProject2.src()
21
+        .pipe(sourcemaps.init())
22
+        .pipe(ts(tsProject)).js
23
+        .pipe(babel())
24
+        .pipe(sourcemaps.write('.', {sourceRoot: '../src'}))
25
+        .pipe(gulp.dest('./js/'))
26
+        ;
27
+});
28
+
29
+gulp.task('rev', function() {
30
+    return gulp.src(['src/**/*.ts'], { base: './' })
31
+        .pipe(sourcemaps.init())
32
+        .pipe(ts(tsProject)).js
33
+        .pipe(babel())
34
+        .pipe(sourcemaps.write('.', {sourceRoot: '../src'}))
35
+        .pipe(gulp.dest('./js/'))
36
+        .pipe(rev())
37
+        .pipe(sourcemaps.write('.', {sourceRoot: '../src'}))
38
+        .pipe(gulp.dest('./js/'))
39
+        .pipe(rev.manifest())
40
+        .pipe(gulp.dest('./js/'))
41
+        ;
42
+});
43
+
44
+gulp.task('watch', ['default'], function() {
45
+    gulp.watch('src/**/*.ts', ['default']);
46
+});

+ 18
- 0
client/index.html 查看文件

@@ -0,0 +1,18 @@
1
+<!DOCTYPE html>
2
+<html lang="en">
3
+  <head>
4
+    <meta charset="utf-8">
5
+    <title>RPG Cards</title>
6
+    <link rel="stylesheet" href="./css/rpg-cards.css">
7
+  </head>
8
+  <body>
9
+      <div id="cardeditor"></div>
10
+      <!-- libraries -->
11
+      <script src="js/react.js"></script>
12
+
13
+      <!-- teh app -->
14
+      <script src="js/app.js"></script>
15
+
16
+      <script>rpgcards.bootstrap();</script>
17
+  </body>
18
+</html>

+ 21642
- 0
client/js/react-with-addons.js
文件差異過大導致無法顯示
查看文件


+ 18
- 0
client/js/react-with-addons.min.js
文件差異過大導致無法顯示
查看文件


+ 19602
- 0
client/js/react.js
文件差異過大導致無法顯示
查看文件


+ 16
- 0
client/js/react.min.js
文件差異過大導致無法顯示
查看文件


+ 25
- 0
client/package.json 查看文件

@@ -0,0 +1,25 @@
1
+{
2
+  "name": "rpg-cards",
3
+  "version": "0.0.0",
4
+  "description": "Card creation tool",
5
+  "repository": "https://github.com/crobi/rpg-cards",
6
+  "main": "js/app.js",
7
+  "scripts": {
8
+    "test": "echo \"Error: no test specified\" && exit 1"
9
+  },
10
+  "author": "crobi",
11
+  "dependencies": {
12
+    "flux": "^2.0.1",
13
+    "react": "^0.12.0"
14
+  },
15
+  "devDependencies": {
16
+    "babel": "*",
17
+    "typescript": "1.5.x",
18
+    "gulp": "*",
19
+    "gulp-typescript": "*",
20
+    "gulp-sourcemaps": "*",
21
+    "gulp-babel": "*",
22
+    "gulp-rev": "*"
23
+  },
24
+  "license": "BSD-2-Clause"
25
+}

+ 61
- 0
client/src/actions/actions.ts 查看文件

@@ -0,0 +1,61 @@
1
+/// <reference path="../dispatcher/dispatcher.ts"/>
2
+
3
+module rpgcards {
4
+
5
+    /* Deck actions */
6
+    export class ActionNewDeck implements Action {
7
+        constructor() {}
8
+    }
9
+    export class ActionDeleteDeck implements Action {
10
+        constructor(private _id: string) {}
11
+        get id(): string {return this._id}
12
+    }
13
+    export class ActionSelectDeck implements Action {
14
+        constructor(private _id: string) {}
15
+        get id(): string {return this._id}
16
+    }
17
+
18
+    /* Card actions */
19
+    export class ActionNewCard implements Action {
20
+        constructor(private _deck_id: string) {}
21
+        get deck_id(): string {return this._deck_id}
22
+    }
23
+    export class ActionDeleteCard implements Action {
24
+        constructor(private _id: string) {}
25
+        get id(): string {return this._id}
26
+    }
27
+    export class ActionSelectCard implements Action {
28
+        constructor(private _id: string) {}
29
+        get id(): string {return this._id}
30
+    }
31
+    export class ActionModifyCard implements Action {
32
+        constructor(private _id: string, private _prop: string, private _val: string) {}
33
+        get id(): string {return this._id}
34
+        get prop(): string {return this._prop}
35
+        get val(): string {return this._val}
36
+    }
37
+
38
+    export class Actions  {
39
+        constructor(private _dispatcher: Dispatcher) {
40
+        }
41
+        public newDeck(): void {
42
+            this._dispatcher.dispatch(new ActionNewDeck());
43
+        }
44
+        public deleteDeck(id: string): void {
45
+            this._dispatcher.dispatch(new ActionDeleteDeck(id));
46
+        }
47
+        public selectDeck(id: string): void {
48
+            this._dispatcher.dispatch(new ActionSelectDeck(id));
49
+        }
50
+        public newCard(deck_id: string): void {
51
+            this._dispatcher.dispatch(new ActionNewCard(deck_id));
52
+        }
53
+        public deleteCard(id: string): void {
54
+            this._dispatcher.dispatch(new ActionDeleteCard(id));
55
+        }
56
+        public selectCard(id: string): void {
57
+            this._dispatcher.dispatch(new ActionSelectCard(id));
58
+        }
59
+    }
60
+
61
+}

+ 33
- 0
client/src/app.ts 查看文件

@@ -0,0 +1,33 @@
1
+/// <reference path="./dispatcher/dispatcher.ts"/>
2
+/// <reference path="./stores/store.ts"/>
3
+/// <reference path="./actions/actions.ts"/>
4
+/// <reference path="./views/header.ts"/>
5
+
6
+module rpgcards {
7
+
8
+    export var appDispatcher: Dispatcher = null;
9
+    export var appActions: Actions = null;
10
+    export var appStore: Store = null;
11
+
12
+    export function bootstrap() {
13
+        appDispatcher = new Dispatcher();
14
+        appActions = new Actions(appDispatcher);
15
+        appStore = new Store(appDispatcher);
16
+        appStore.addChangeListener(refresh);
17
+
18
+        refresh();
19
+    }
20
+
21
+    function renderApp(store: Store) {
22
+        return React.DOM.div(
23
+            {},
24
+            renderHeader(store),
25
+            renderDecks(store)
26
+            );
27
+    }
28
+
29
+    export function refresh() {
30
+        React.render(renderApp(appStore), document.body);
31
+    }
32
+
33
+}

+ 260
- 0
client/src/dispatcher/dispatcher.ts 查看文件

@@ -0,0 +1,260 @@
1
+/*
2
+* Copyright (c) 2014, Facebook, Inc.
3
+* All rights reserved.
4
+*
5
+* This source code is licensed under the BSD-style license found in the
6
+* LICENSE file in the root directory of this source tree. An additional grant
7
+* of patent rights can be found in the PATENTS file in the same directory.
8
+*
9
+* @providesModule Dispatcher
10
+* @typechecks
11
+*/
12
+
13
+// 2014-10-16 Dan Roberts: Minor adjustments for use as TypeScript with support for the option "Allow implicit 'any' types" to be disabled. The copyright message is maintained from the original file at https://github.com/facebook/flux/blob/master/src/Dispatcher.js
14
+
15
+/// <reference path="./invariant.ts"/>
16
+
17
+module rpgcards {
18
+
19
+    var _lastID = 1;
20
+    var _prefix = 'ID_';
21
+
22
+    export interface Action {};
23
+    export type ActionCallback = (a: Action) => void;
24
+
25
+    /**
26
+     * Dispatcher is used to broadcast payloads to registered callbacks. This is
27
+     * different from generic pub-sub systems in two ways:
28
+     *
29
+     *   1) Callbacks are not subscribed to particular events. Every payload is
30
+     *      dispatched to every registered callback.
31
+     *   2) Callbacks can be deferred in whole or part until other callbacks have
32
+     *      been executed.
33
+     *
34
+     * For example, consider this hypothetical flight destination form, which
35
+     * selects a default city when a country is selected:
36
+     *
37
+     *   var flightDispatcher = new Dispatcher();
38
+     *
39
+     *   // Keeps track of which country is selected
40
+     *   var CountryStore = {country: null};
41
+     *
42
+     *   // Keeps track of which city is selected
43
+     *   var CityStore = {city: null};
44
+     *
45
+     *   // Keeps track of the base flight price of the selected city
46
+     *   var FlightPriceStore = {price: null}
47
+     *
48
+     * When a user changes the selected city, we dispatch the payload:
49
+     *
50
+     *   flightDispatcher.dispatch({
51
+     *     actionType: 'city-update',
52
+     *     selectedCity: 'paris'
53
+     *   });
54
+     *
55
+     * This payload is digested by `CityStore`:
56
+     *
57
+     *   flightDispatcher.register(function(payload) {
58
+     *     if (payload.actionType === 'city-update') {
59
+     *       CityStore.city = payload.selectedCity;
60
+     *     }
61
+     *   });
62
+     *
63
+     * When the user selects a country, we dispatch the payload:
64
+     *
65
+     *   flightDispatcher.dispatch({
66
+     *     actionType: 'country-update',
67
+     *     selectedCountry: 'australia'
68
+     *   });
69
+     *
70
+     * This payload is digested by both stores:
71
+     *
72
+     *    CountryStore.dispatchToken = flightDispatcher.register(function(payload) {
73
+     *     if (payload.actionType === 'country-update') {
74
+     *       CountryStore.country = payload.selectedCountry;
75
+     *     }
76
+     *   });
77
+     *
78
+     * When the callback to update `CountryStore` is registered, we save a reference
79
+     * to the returned token. Using this token with `waitFor()`, we can guarantee
80
+     * that `CountryStore` is updated before the callback that updates `CityStore`
81
+     * needs to query its data.
82
+     *
83
+     *   CityStore.dispatchToken = flightDispatcher.register(function(payload) {
84
+     *     if (payload.actionType === 'country-update') {
85
+     *       // `CountryStore.country` may not be updated.
86
+     *       flightDispatcher.waitFor([CountryStore.dispatchToken]);
87
+     *       // `CountryStore.country` is now guaranteed to be updated.
88
+     *
89
+     *       // Select the default city for the new country
90
+     *       CityStore.city = getDefaultCityForCountry(CountryStore.country);
91
+     *     }
92
+     *   });
93
+     *
94
+     * The usage of `waitFor()` can be chained, for example:
95
+     *
96
+     *   FlightPriceStore.dispatchToken =
97
+     *     flightDispatcher.register(function(payload) {
98
+     *       switch (payload.actionType) {
99
+     *         case 'country-update':
100
+     *           flightDispatcher.waitFor([CityStore.dispatchToken]);
101
+     *           FlightPriceStore.price =
102
+     *             getFlightPriceStore(CountryStore.country, CityStore.city);
103
+     *           break;
104
+     *
105
+     *         case 'city-update':
106
+     *           FlightPriceStore.price =
107
+     *             FlightPriceStore(CountryStore.country, CityStore.city);
108
+     *           break;
109
+     *     }
110
+     *   });
111
+     *
112
+     * The `country-update` payload will be guaranteed to invoke the stores'
113
+     * registered callbacks in order: `CountryStore`, `CityStore`, then
114
+     * `FlightPriceStore`.
115
+     */
116
+    export class Dispatcher {
117
+        private _callbacks: {[id: string]: ActionCallback};
118
+        private _isPending: {[id: string]: boolean};
119
+        private _isHandled: {[id: string]: boolean};
120
+        private _isDispatching: boolean;
121
+        private _pendingPayload: Action;
122
+        constructor() {
123
+            this._callbacks = {};
124
+            this._isPending = {};
125
+            this._isHandled = {};
126
+            this._isDispatching = false;
127
+            this._pendingPayload = null;
128
+        }
129
+
130
+        /**
131
+         * Registers a callback to be invoked with every dispatched payload. Returns
132
+         * a token that can be used with `waitFor()`.
133
+         *
134
+         * @param {function} callback
135
+         * @return {string}
136
+         */
137
+        public register(callback: ActionCallback): string {
138
+            var id = _prefix + _lastID++;
139
+            this._callbacks[id] = callback;
140
+            return id;
141
+        }
142
+
143
+        /**
144
+         * Removes a callback based on its token.
145
+         *
146
+         * @param {string} id
147
+         */
148
+        public unregister(id: string) {
149
+            invariant(
150
+                this._callbacks[id] !== undefined,
151
+                'Dispatcher.unregister(...): `%s` does not map to a registered callback.',
152
+                id
153
+                );
154
+            delete this._callbacks[id];
155
+        }
156
+
157
+        /**
158
+         * Waits for the callbacks specified to be invoked before continuing execution
159
+         * of the current callback. This method should only be used by a callback in
160
+         * response to a dispatched payload.
161
+         *
162
+         * @param {array<string>} ids
163
+         */
164
+        public waitFor(ids: string[]): void {
165
+            invariant(
166
+                this._isDispatching,
167
+                'Dispatcher.waitFor(...): Must be invoked while dispatching.'
168
+                );
169
+            for (var ii = 0; ii < ids.length; ii++) {
170
+                var id = ids[ii];
171
+                if (this._isPending[id]) {
172
+                    invariant(
173
+                        this._isHandled[id],
174
+                        'Dispatcher.waitFor(...): Circular dependency detected while ' +
175
+                        'waiting for `%s`.',
176
+                        id
177
+                        );
178
+                    continue;
179
+                }
180
+                invariant(
181
+                    this._callbacks[id] !== undefined,
182
+                    'Dispatcher.waitFor(...): `%s` does not map to a registered callback.',
183
+                    id
184
+                    );
185
+                this._invokeCallback(id);
186
+            }
187
+        }
188
+
189
+        /**
190
+         * Dispatches a payload to all registered callbacks.
191
+         *
192
+         * @param {object} payload
193
+         */
194
+        public dispatch(payload: {}): void {
195
+            invariant(
196
+                !this._isDispatching,
197
+                'Dispatch.dispatch(...): Cannot dispatch in the middle of a dispatch.'
198
+                );
199
+            this._startDispatching(payload);
200
+            try {
201
+                var id: string;
202
+                for (id in this._callbacks) {
203
+                    if (this._isPending[id]) {
204
+                        continue;
205
+                    }
206
+                    this._invokeCallback(id);
207
+                }
208
+            } finally {
209
+                this._stopDispatching();
210
+            }
211
+        }
212
+
213
+        /**
214
+         * Is this Dispatcher currently dispatching.
215
+         *
216
+         * @return {boolean}
217
+         */
218
+        public isDispatching(): boolean {
219
+            return this._isDispatching;
220
+        }
221
+
222
+        /**
223
+         * Call the callback stored with the given id. Also do some internal
224
+         * bookkeeping.
225
+         *
226
+         * @param {string} id
227
+         * @internal
228
+         */
229
+        private _invokeCallback(id: string): void {
230
+            this._isPending[id] = true;
231
+            this._callbacks[id](this._pendingPayload);
232
+            this._isHandled[id] = true;
233
+        }
234
+
235
+        /**
236
+         * Set up bookkeeping needed when dispatching.
237
+         *
238
+         * @param {object} payload
239
+         * @internal
240
+         */
241
+        private _startDispatching(payload: Action): void {
242
+            for (var id in this._callbacks) {
243
+                this._isPending[id] = false;
244
+                this._isHandled[id] = false;
245
+            }
246
+            this._pendingPayload = payload;
247
+            this._isDispatching = true;
248
+        }
249
+
250
+        /**
251
+         * Clear bookkeeping used for dispatching.
252
+         *
253
+         * @internal
254
+         */
255
+        private _stopDispatching() {
256
+            this._pendingPayload = null;
257
+            this._isDispatching = false;
258
+        }
259
+    }
260
+}

+ 47
- 0
client/src/dispatcher/invariant.ts 查看文件

@@ -0,0 +1,47 @@
1
+/**
2
+* Copyright (c) 2014, Facebook, Inc.
3
+* All rights reserved.
4
+*
5
+* This source code is licensed under the BSD-style license found in the
6
+* LICENSE file in the root directory of this source tree. An additional grant
7
+* of patent rights can be found in the PATENTS file in the same directory.
8
+*
9
+* @providesModule invariant
10
+*/
11
+
12
+// 2014-10-16 Dan Roberts: Minor adjustments for use as TypeScript with support for the option "Allow implicit 'any' types" to be disabled. The copyright message is maintained from the original file at https://github.com/facebook/flux/blob/master/src/invairant.js
13
+
14
+/**
15
+ * Use invariant() to assert state which your program assumes to be true.
16
+ *
17
+ * Provide sprintf-style format (only %s is supported) and arguments
18
+ * to provide information about what broke and what you were
19
+ * expecting.
20
+ *
21
+ * The invariant message will be stripped in production, but the invariant
22
+ * will remain to ensure logic does not differ in production.
23
+ */
24
+
25
+module rpgcards {
26
+    export function invariant(condition: boolean, format: string, a?: any, b?: any, c?: any, d?: any, e?: any, f?: any): void {
27
+        if (!condition) {
28
+            var error: Error;
29
+            if (format === undefined) {
30
+                error = new Error(
31
+                    'Minified exception occurred; use the non-minified dev environment ' +
32
+                    'for the full error message and additional helpful warnings.'
33
+                    );
34
+            } else {
35
+                var args = [a, b, c, d, e, f];
36
+                var argIndex = 0;
37
+                error = new Error(
38
+                    'Invariant Violation: ' +
39
+                    format.replace(/%s/g, function () { return args[argIndex++]; })
40
+                    );
41
+            }
42
+
43
+            (<any>error).framesToPop = 1; // we don't care about invariant's own frame
44
+            throw error;
45
+        }
46
+    };
47
+}

+ 1051
- 0
client/src/external/react/react-addons.d.ts
文件差異過大導致無法顯示
查看文件


+ 785
- 0
client/src/external/react/react.d.ts 查看文件

@@ -0,0 +1,785 @@
1
+// Type definitions for React v0.13.1 (internal and external module)
2
+// Project: http://facebook.github.io/react/
3
+// Definitions by: Asana <https://asana.com>, AssureSign <http://www.assuresign.com>
4
+// Definitions: https://github.com/borisyankov/DefinitelyTyped
5
+
6
+declare module React {
7
+    //
8
+    // React Elements
9
+    // ----------------------------------------------------------------------
10
+
11
+    type ReactType = ComponentClass<any> | string;
12
+
13
+    interface ReactElement<P> {
14
+        type: string | ComponentClass<P>;
15
+        props: P;
16
+        key: string | number;
17
+        ref: string | ((component: Component<P, any>) => any);
18
+    }
19
+
20
+    interface ClassicElement<P> extends ReactElement<P> {
21
+        type: string | ClassicComponentClass<P>;
22
+        ref: string | ((component: ClassicComponent<P, any>) => any);
23
+    }
24
+
25
+    interface DOMElement<P> extends ClassicElement<P> {
26
+        type: string;
27
+        ref: string | ((component: DOMComponent<P>) => any);
28
+    }
29
+
30
+    type HTMLElement = DOMElement<HTMLAttributes>;
31
+    type SVGElement = DOMElement<SVGAttributes>;
32
+
33
+    //
34
+    // Factories
35
+    // ----------------------------------------------------------------------
36
+
37
+    interface Factory<P> {
38
+        (props?: P, ...children: ReactNode[]): ReactElement<P>;
39
+    }
40
+
41
+    interface ClassicFactory<P> extends Factory<P> {
42
+        (props?: P, ...children: ReactNode[]): ClassicElement<P>;
43
+    }
44
+
45
+    interface DOMFactory<P> extends ClassicFactory<P> {
46
+        (props?: P, ...children: ReactNode[]): DOMElement<P>;
47
+    }
48
+
49
+    type HTMLFactory = DOMFactory<HTMLAttributes>;
50
+    type SVGFactory = DOMFactory<SVGAttributes>;
51
+
52
+    //
53
+    // React Nodes
54
+    // http://facebook.github.io/react/docs/glossary.html
55
+    // ----------------------------------------------------------------------
56
+
57
+    type ReactText = string | number;
58
+    type ReactChild = ReactElement<any> | ReactText;
59
+
60
+    // Should be Array<ReactNode> but type aliases cannot be recursive
61
+    type ReactFragment = {} | Array<ReactChild | any[] | boolean>;
62
+    type ReactNode = ReactChild | ReactFragment | boolean;
63
+
64
+    //
65
+    // Top Level API
66
+    // ----------------------------------------------------------------------
67
+
68
+    function createClass<P, S>(spec: ComponentSpec<P, S>): ClassicComponentClass<P>;
69
+
70
+    function createFactory<P>(type: string): DOMFactory<P>;
71
+    function createFactory<P>(type: ClassicComponentClass<P> | string): ClassicFactory<P>;
72
+    function createFactory<P>(type: ComponentClass<P>): Factory<P>;
73
+
74
+    function createElement<P>(
75
+        type: string,
76
+        props?: P,
77
+        ...children: ReactNode[]): DOMElement<P>;
78
+    function createElement<P>(
79
+        type: ClassicComponentClass<P> | string,
80
+        props?: P,
81
+        ...children: ReactNode[]): ClassicElement<P>;
82
+    function createElement<P>(
83
+        type: ComponentClass<P>,
84
+        props?: P,
85
+        ...children: ReactNode[]): ReactElement<P>;
86
+
87
+    function cloneElement<P>(
88
+        element: DOMElement<P>,
89
+        props?: P,
90
+        ...children: ReactNode[]): DOMElement<P>;
91
+    function cloneElement<P>(
92
+        element: ClassicElement<P>,
93
+        props?: P,
94
+        ...children: ReactNode[]): ClassicElement<P>;
95
+    function cloneElement<P>(
96
+        element: ReactElement<P>,
97
+        props?: P,
98
+        ...children: ReactNode[]): ReactElement<P>;
99
+
100
+    function render<P>(
101
+        element: DOMElement<P>,
102
+        container: Element,
103
+        callback?: () => any): DOMComponent<P>;
104
+    function render<P, S>(
105
+        element: ClassicElement<P>,
106
+        container: Element,
107
+        callback?: () => any): ClassicComponent<P, S>;
108
+    function render<P, S>(
109
+        element: ReactElement<P>,
110
+        container: Element,
111
+        callback?: () => any): Component<P, S>;
112
+
113
+    function unmountComponentAtNode(container: Element): boolean;
114
+    function renderToString(element: ReactElement<any>): string;
115
+    function renderToStaticMarkup(element: ReactElement<any>): string;
116
+    function isValidElement(object: {}): boolean;
117
+    function initializeTouchEvents(shouldUseTouch: boolean): void;
118
+
119
+    function findDOMNode<TElement extends Element>(
120
+        componentOrElement: Component<any, any> | Element): TElement;
121
+    function findDOMNode(
122
+        componentOrElement: Component<any, any> | Element): Element;
123
+
124
+    var DOM: ReactDOM;
125
+    var PropTypes: ReactPropTypes;
126
+    var Children: ReactChildren;
127
+
128
+    //
129
+    // Component API
130
+    // ----------------------------------------------------------------------
131
+
132
+    // Base component for plain JS classes
133
+    class Component<P, S> implements ComponentLifecycle<P, S> {
134
+        constructor(props?: P, context?: any);
135
+        setState(f: (prevState: S, props: P) => S, callback?: () => any): void;
136
+        setState(state: S, callback?: () => any): void;
137
+        forceUpdate(): void;
138
+        props: P;
139
+        state: S;
140
+        context: any;
141
+        refs: {
142
+            [key: string]: Component<any, any>
143
+        };
144
+    }
145
+
146
+    interface ClassicComponent<P, S> extends Component<P, S> {
147
+        replaceState(nextState: S, callback?: () => any): void;
148
+        getDOMNode<TElement extends Element>(): TElement;
149
+        getDOMNode(): Element;
150
+        isMounted(): boolean;
151
+        getInitialState?(): S;
152
+        setProps(nextProps: P, callback?: () => any): void;
153
+        replaceProps(nextProps: P, callback?: () => any): void;
154
+    }
155
+
156
+    interface DOMComponent<P> extends ClassicComponent<P, any> {
157
+        tagName: string;
158
+    }
159
+
160
+    type HTMLComponent = DOMComponent<HTMLAttributes>;
161
+    type SVGComponent = DOMComponent<SVGAttributes>;
162
+
163
+    interface ChildContextProvider<CC> {
164
+        getChildContext(): CC;
165
+    }
166
+
167
+    //
168
+    // Class Interfaces
169
+    // ----------------------------------------------------------------------
170
+
171
+    interface ComponentClass<P> {
172
+        new(props?: P, context?: any): Component<P, any>;
173
+        propTypes?: ValidationMap<P>;
174
+        contextTypes?: ValidationMap<any>;
175
+        childContextTypes?: ValidationMap<any>;
176
+        defaultProps?: P;
177
+    }
178
+
179
+    interface ClassicComponentClass<P> extends ComponentClass<P> {
180
+        new(props?: P, context?: any): ClassicComponent<P, any>;
181
+        getDefaultProps?(): P;
182
+        displayName?: string;
183
+    }
184
+
185
+    //
186
+    // Component Specs and Lifecycle
187
+    // ----------------------------------------------------------------------
188
+
189
+    interface ComponentLifecycle<P, S> {
190
+        componentWillMount?(): void;
191
+        componentDidMount?(): void;
192
+        componentWillReceiveProps?(nextProps: P, nextContext: any): void;
193
+        shouldComponentUpdate?(nextProps: P, nextState: S, nextContext: any): boolean;
194
+        componentWillUpdate?(nextProps: P, nextState: S, nextContext: any): void;
195
+        componentDidUpdate?(prevProps: P, prevState: S, prevContext: any): void;
196
+        componentWillUnmount?(): void;
197
+    }
198
+
199
+    interface Mixin<P, S> extends ComponentLifecycle<P, S> {
200
+        mixins?: Mixin<P, S>;
201
+        statics?: {
202
+            [key: string]: any;
203
+        };
204
+
205
+        displayName?: string;
206
+        propTypes?: ValidationMap<any>;
207
+        contextTypes?: ValidationMap<any>;
208
+        childContextTypes?: ValidationMap<any>
209
+
210
+        getDefaultProps?(): P;
211
+        getInitialState?(): S;
212
+    }
213
+
214
+    interface ComponentSpec<P, S> extends Mixin<P, S> {
215
+        render(): ReactElement<any>;
216
+    }
217
+
218
+    //
219
+    // Event System
220
+    // ----------------------------------------------------------------------
221
+
222
+    interface SyntheticEvent {
223
+        bubbles: boolean;
224
+        cancelable: boolean;
225
+        currentTarget: EventTarget;
226
+        defaultPrevented: boolean;
227
+        eventPhase: number;
228
+        isTrusted: boolean;
229
+        nativeEvent: Event;
230
+        preventDefault(): void;
231
+        stopPropagation(): void;
232
+        target: EventTarget;
233
+        timeStamp: Date;
234
+        type: string;
235
+    }
236
+
237
+    interface DragEvent extends SyntheticEvent {
238
+        dataTransfer: DataTransfer;
239
+    }
240
+
241
+    interface ClipboardEvent extends SyntheticEvent {
242
+        clipboardData: DataTransfer;
243
+    }
244
+
245
+    interface KeyboardEvent extends SyntheticEvent {
246
+        altKey: boolean;
247
+        charCode: number;
248
+        ctrlKey: boolean;
249
+        getModifierState(key: string): boolean;
250
+        key: string;
251
+        keyCode: number;
252
+        locale: string;
253
+        location: number;
254
+        metaKey: boolean;
255
+        repeat: boolean;
256
+        shiftKey: boolean;
257
+        which: number;
258
+    }
259
+
260
+    interface FocusEvent extends SyntheticEvent {
261
+        relatedTarget: EventTarget;
262
+    }
263
+
264
+    interface FormEvent extends SyntheticEvent {
265
+    }
266
+
267
+    interface MouseEvent extends SyntheticEvent {
268
+        altKey: boolean;
269
+        button: number;
270
+        buttons: number;
271
+        clientX: number;
272
+        clientY: number;
273
+        ctrlKey: boolean;
274
+        getModifierState(key: string): boolean;
275
+        metaKey: boolean;
276
+        pageX: number;
277
+        pageY: number;
278
+        relatedTarget: EventTarget;
279
+        screenX: number;
280
+        screenY: number;
281
+        shiftKey: boolean;
282
+    }
283
+
284
+    interface TouchEvent extends SyntheticEvent {
285
+        altKey: boolean;
286
+        changedTouches: TouchList;
287
+        ctrlKey: boolean;
288
+        getModifierState(key: string): boolean;
289
+        metaKey: boolean;
290
+        shiftKey: boolean;
291
+        targetTouches: TouchList;
292
+        touches: TouchList;
293
+    }
294
+
295
+    interface UIEvent extends SyntheticEvent {
296
+        detail: number;
297
+        view: AbstractView;
298
+    }
299
+
300
+    interface WheelEvent extends SyntheticEvent {
301
+        deltaMode: number;
302
+        deltaX: number;
303
+        deltaY: number;
304
+        deltaZ: number;
305
+    }
306
+
307
+    //
308
+    // Event Handler Types
309
+    // ----------------------------------------------------------------------
310
+
311
+    interface EventHandler<E extends SyntheticEvent> {
312
+        (event: E): void;
313
+    }
314
+
315
+    interface DragEventHandler extends EventHandler<DragEvent> {}
316
+    interface ClipboardEventHandler extends EventHandler<ClipboardEvent> {}
317
+    interface KeyboardEventHandler extends EventHandler<KeyboardEvent> {}
318
+    interface FocusEventHandler extends EventHandler<FocusEvent> {}
319
+    interface FormEventHandler extends EventHandler<FormEvent> {}
320
+    interface MouseEventHandler extends EventHandler<MouseEvent> {}
321
+    interface TouchEventHandler extends EventHandler<TouchEvent> {}
322
+    interface UIEventHandler extends EventHandler<UIEvent> {}
323
+    interface WheelEventHandler extends EventHandler<WheelEvent> {}
324
+
325
+    //
326
+    // Props / DOM Attributes
327
+    // ----------------------------------------------------------------------
328
+
329
+    interface Props<T> {
330
+        children?: ReactNode;
331
+        key?: string | number;
332
+        ref?: string | ((component: T) => any);
333
+    }
334
+
335
+    interface DOMAttributes extends Props<DOMComponent<any>> {
336
+        onCopy?: ClipboardEventHandler;
337
+        onCut?: ClipboardEventHandler;
338
+        onPaste?: ClipboardEventHandler;
339
+        onKeyDown?: KeyboardEventHandler;
340
+        onKeyPress?: KeyboardEventHandler;
341
+        onKeyUp?: KeyboardEventHandler;
342
+        onFocus?: FocusEventHandler;
343
+        onBlur?: FocusEventHandler;
344
+        onChange?: FormEventHandler;
345
+        onInput?: FormEventHandler;
346
+        onSubmit?: FormEventHandler;
347
+        onClick?: MouseEventHandler;
348
+        onDoubleClick?: MouseEventHandler;
349
+        onDrag?: DragEventHandler;
350
+        onDragEnd?: DragEventHandler;
351
+        onDragEnter?: DragEventHandler;
352
+        onDragExit?: DragEventHandler;
353
+        onDragLeave?: DragEventHandler;
354
+        onDragOver?: DragEventHandler;
355
+        onDragStart?: DragEventHandler;
356
+        onDrop?: DragEventHandler;
357
+        onMouseDown?: MouseEventHandler;
358
+        onMouseEnter?: MouseEventHandler;
359
+        onMouseLeave?: MouseEventHandler;
360
+        onMouseMove?: MouseEventHandler;
361
+        onMouseOut?: MouseEventHandler;
362
+        onMouseOver?: MouseEventHandler;
363
+        onMouseUp?: MouseEventHandler;
364
+        onTouchCancel?: TouchEventHandler;
365
+        onTouchEnd?: TouchEventHandler;
366
+        onTouchMove?: TouchEventHandler;
367
+        onTouchStart?: TouchEventHandler;
368
+        onScroll?: UIEventHandler;
369
+        onWheel?: WheelEventHandler;
370
+
371
+        dangerouslySetInnerHTML?: {
372
+            __html: string;
373
+        };
374
+    }
375
+
376
+    // This interface is not complete. Only properties accepting
377
+    // unitless numbers are listed here (see CSSProperty.js in React)
378
+    interface CSSProperties {
379
+        boxFlex?: number;
380
+        boxFlexGroup?: number;
381
+        columnCount?: number;
382
+        flex?: number | string;
383
+        flexGrow?: number;
384
+        flexShrink?: number;
385
+        fontWeight?: number | string;
386
+        lineClamp?: number;
387
+        lineHeight?: number | string;
388
+        opacity?: number;
389
+        order?: number;
390
+        orphans?: number;
391
+        widows?: number;
392
+        zIndex?: number;
393
+        zoom?: number;
394
+
395
+        // SVG-related properties
396
+        fillOpacity?: number;
397
+        strokeOpacity?: number;
398
+        strokeWidth?: number;
399
+    }
400
+
401
+    interface HTMLAttributes extends DOMAttributes {
402
+        ref?: string | ((component: HTMLComponent) => void);
403
+
404
+        accept?: string;
405
+        acceptCharset?: string;
406
+        accessKey?: string;
407
+        action?: string;
408
+        allowFullScreen?: boolean;
409
+        allowTransparency?: boolean;
410
+        alt?: string;
411
+        async?: boolean;
412
+        autoComplete?: boolean;
413
+        autoFocus?: boolean;
414
+        autoPlay?: boolean;
415
+        cellPadding?: number | string;
416
+        cellSpacing?: number | string;
417
+        charSet?: string;
418
+        checked?: boolean;
419
+        classID?: string;
420
+        className?: string;
421
+        cols?: number;
422
+        colSpan?: number;
423
+        content?: string;
424
+        contentEditable?: boolean;
425
+        contextMenu?: string;
426
+        controls?: any;
427
+        coords?: string;
428
+        crossOrigin?: string;
429
+        data?: string;
430
+        dateTime?: string;
431
+        defer?: boolean;
432
+        dir?: string;
433
+        disabled?: boolean;
434
+        download?: any;
435
+        draggable?: boolean;
436
+        encType?: string;
437
+        form?: string;
438
+        formAction?: string;
439
+        formEncType?: string;
440
+        formMethod?: string;
441
+        formNoValidate?: boolean;
442
+        formTarget?: string;
443
+        frameBorder?: number | string;
444
+        headers?: string;
445
+        height?: number | string;
446
+        hidden?: boolean;
447
+        high?: number;
448
+        href?: string;
449
+        hrefLang?: string;
450
+        htmlFor?: string;
451
+        httpEquiv?: string;
452
+        icon?: string;
453
+        id?: string;
454
+        label?: string;
455
+        lang?: string;
456
+        list?: string;
457
+        loop?: boolean;
458
+        low?: number;
459
+        manifest?: string;
460
+        marginHeight?: number;
461
+        marginWidth?: number;
462
+        max?: number | string;
463
+        maxLength?: number;
464
+        media?: string;
465
+        mediaGroup?: string;
466
+        method?: string;
467
+        min?: number | string;
468
+        multiple?: boolean;
469
+        muted?: boolean;
470
+        name?: string;
471
+        noValidate?: boolean;
472
+        open?: boolean;
473
+        optimum?: number;
474
+        pattern?: string;
475
+        placeholder?: string;
476
+        poster?: string;
477
+        preload?: string;
478
+        radioGroup?: string;
479
+        readOnly?: boolean;
480
+        rel?: string;
481
+        required?: boolean;
482
+        role?: string;
483
+        rows?: number;
484
+        rowSpan?: number;
485
+        sandbox?: string;
486
+        scope?: string;
487
+        scoped?: boolean;
488
+        scrolling?: string;
489
+        seamless?: boolean;
490
+        selected?: boolean;
491
+        shape?: string;
492
+        size?: number;
493
+        sizes?: string;
494
+        span?: number;
495
+        spellCheck?: boolean;
496
+        src?: string;
497
+        srcDoc?: string;
498
+        srcSet?: string;
499
+        start?: number;
500
+        step?: number | string;
501
+        style?: CSSProperties;
502
+        tabIndex?: number;
503
+        target?: string;
504
+        title?: string;
505
+        type?: string;
506
+        useMap?: string;
507
+        value?: string;
508
+        width?: number | string;
509
+        wmode?: string;
510
+
511
+        // Non-standard Attributes
512
+        autoCapitalize?: boolean;
513
+        autoCorrect?: boolean;
514
+        property?: string;
515
+        itemProp?: string;
516
+        itemScope?: boolean;
517
+        itemType?: string;
518
+        unselectable?: boolean;
519
+    }
520
+
521
+    interface SVGElementAttributes extends HTMLAttributes {
522
+        viewBox?: string;
523
+        preserveAspectRatio?: string;
524
+    }
525
+
526
+    interface SVGAttributes extends DOMAttributes {
527
+        ref?: string | ((component: SVGComponent) => void);
528
+
529
+        cx?: number | string;
530
+        cy?: number | string;
531
+        d?: string;
532
+        dx?: number | string;
533
+        dy?: number | string;
534
+        fill?: string;
535
+        fillOpacity?: number | string;
536
+        fontFamily?: string;
537
+        fontSize?: number | string;
538
+        fx?: number | string;
539
+        fy?: number | string;
540
+        gradientTransform?: string;
541
+        gradientUnits?: string;
542
+        markerEnd?: string;
543
+        markerMid?: string;
544
+        markerStart?: string;
545
+        offset?: number | string;
546
+        opacity?: number | string;
547
+        patternContentUnits?: string;
548
+        patternUnits?: string;
549
+        points?: string;
550
+        preserveAspectRatio?: string;
551
+        r?: number | string;
552
+        rx?: number | string;
553
+        ry?: number | string;
554
+        spreadMethod?: string;
555
+        stopColor?: string;
556
+        stopOpacity?: number | string;
557
+        stroke?: string;
558
+        strokeDasharray?: string;
559
+        strokeLinecap?: string;
560
+        strokeOpacity?: number | string;
561
+        strokeWidth?: number | string;
562
+        textAnchor?: string;
563
+        transform?: string;
564
+        version?: string;
565
+        viewBox?: string;
566
+        x1?: number | string;
567
+        x2?: number | string;
568
+        x?: number | string;
569
+        y1?: number | string;
570
+        y2?: number | string
571
+        y?: number | string;
572
+    }
573
+
574
+    //
575
+    // React.DOM
576
+    // ----------------------------------------------------------------------
577
+
578
+    interface ReactDOM {
579
+        // HTML
580
+        a: HTMLFactory;
581
+        abbr: HTMLFactory;
582
+        address: HTMLFactory;
583
+        area: HTMLFactory;
584
+        article: HTMLFactory;
585
+        aside: HTMLFactory;
586
+        audio: HTMLFactory;
587
+        b: HTMLFactory;
588
+        base: HTMLFactory;
589
+        bdi: HTMLFactory;
590
+        bdo: HTMLFactory;
591
+        big: HTMLFactory;
592
+        blockquote: HTMLFactory;
593
+        body: HTMLFactory;
594
+        br: HTMLFactory;
595
+        button: HTMLFactory;
596
+        canvas: HTMLFactory;
597
+        caption: HTMLFactory;
598
+        cite: HTMLFactory;
599
+        code: HTMLFactory;
600
+        col: HTMLFactory;
601
+        colgroup: HTMLFactory;
602
+        data: HTMLFactory;
603
+        datalist: HTMLFactory;
604
+        dd: HTMLFactory;
605
+        del: HTMLFactory;
606
+        details: HTMLFactory;
607
+        dfn: HTMLFactory;
608
+        dialog: HTMLFactory;
609
+        div: HTMLFactory;
610
+        dl: HTMLFactory;
611
+        dt: HTMLFactory;
612
+        em: HTMLFactory;
613
+        embed: HTMLFactory;
614
+        fieldset: HTMLFactory;
615
+        figcaption: HTMLFactory;
616
+        figure: HTMLFactory;
617
+        footer: HTMLFactory;
618
+        form: HTMLFactory;
619
+        h1: HTMLFactory;
620
+        h2: HTMLFactory;
621
+        h3: HTMLFactory;
622
+        h4: HTMLFactory;
623
+        h5: HTMLFactory;
624
+        h6: HTMLFactory;
625
+        head: HTMLFactory;
626
+        header: HTMLFactory;
627
+        hr: HTMLFactory;
628
+        html: HTMLFactory;
629
+        i: HTMLFactory;
630
+        iframe: HTMLFactory;
631
+        img: HTMLFactory;
632
+        input: HTMLFactory;
633
+        ins: HTMLFactory;
634
+        kbd: HTMLFactory;
635
+        keygen: HTMLFactory;
636
+        label: HTMLFactory;
637
+        legend: HTMLFactory;
638
+        li: HTMLFactory;
639
+        link: HTMLFactory;
640
+        main: HTMLFactory;
641
+        map: HTMLFactory;
642
+        mark: HTMLFactory;
643
+        menu: HTMLFactory;
644
+        menuitem: HTMLFactory;
645
+        meta: HTMLFactory;
646
+        meter: HTMLFactory;
647
+        nav: HTMLFactory;
648
+        noscript: HTMLFactory;
649
+        object: HTMLFactory;
650
+        ol: HTMLFactory;
651
+        optgroup: HTMLFactory;
652
+        option: HTMLFactory;
653
+        output: HTMLFactory;
654
+        p: HTMLFactory;
655
+        param: HTMLFactory;
656
+        picture: HTMLFactory;
657
+        pre: HTMLFactory;
658
+        progress: HTMLFactory;
659
+        q: HTMLFactory;
660
+        rp: HTMLFactory;
661
+        rt: HTMLFactory;
662
+        ruby: HTMLFactory;
663
+        s: HTMLFactory;
664
+        samp: HTMLFactory;
665
+        script: HTMLFactory;
666
+        section: HTMLFactory;
667
+        select: HTMLFactory;
668
+        small: HTMLFactory;
669
+        source: HTMLFactory;
670
+        span: HTMLFactory;
671
+        strong: HTMLFactory;
672
+        style: HTMLFactory;
673
+        sub: HTMLFactory;
674
+        summary: HTMLFactory;
675
+        sup: HTMLFactory;
676
+        table: HTMLFactory;
677
+        tbody: HTMLFactory;
678
+        td: HTMLFactory;
679
+        textarea: HTMLFactory;
680
+        tfoot: HTMLFactory;
681
+        th: HTMLFactory;
682
+        thead: HTMLFactory;
683
+        time: HTMLFactory;
684
+        title: HTMLFactory;
685
+        tr: HTMLFactory;
686
+        track: HTMLFactory;
687
+        u: HTMLFactory;
688
+        ul: HTMLFactory;
689
+        "var": HTMLFactory;
690
+        video: HTMLFactory;
691
+        wbr: HTMLFactory;
692
+
693
+        // SVG
694
+        circle: SVGFactory;
695
+        defs: SVGFactory;
696
+        ellipse: SVGFactory;
697
+        g: SVGFactory;
698
+        line: SVGFactory;
699
+        linearGradient: SVGFactory;
700
+        mask: SVGFactory;
701
+        path: SVGFactory;
702
+        pattern: SVGFactory;
703
+        polygon: SVGFactory;
704
+        polyline: SVGFactory;
705
+        radialGradient: SVGFactory;
706
+        rect: SVGFactory;
707
+        stop: SVGFactory;
708
+        svg: SVGFactory;
709
+        text: SVGFactory;
710
+        tspan: SVGFactory;
711
+    }
712
+
713
+    //
714
+    // React.PropTypes
715
+    // ----------------------------------------------------------------------
716
+
717
+    interface Validator<T> {
718
+        (object: T, key: string, componentName: string): Error;
719
+    }
720
+
721
+    interface Requireable<T> extends Validator<T> {
722
+        isRequired: Validator<T>;
723
+    }
724
+
725
+    interface ValidationMap<T> {
726
+        [key: string]: Validator<T>;
727
+    }
728
+
729
+    interface ReactPropTypes {
730
+        any: Requireable<any>;
731
+        array: Requireable<any>;
732
+        bool: Requireable<any>;
733
+        func: Requireable<any>;
734
+        number: Requireable<any>;
735
+        object: Requireable<any>;
736
+        string: Requireable<any>;
737
+        node: Requireable<any>;
738
+        element: Requireable<any>;
739
+        instanceOf(expectedClass: {}): Requireable<any>;
740
+        oneOf(types: any[]): Requireable<any>;
741
+        oneOfType(types: Validator<any>[]): Requireable<any>;
742
+        arrayOf(type: Validator<any>): Requireable<any>;
743
+        objectOf(type: Validator<any>): Requireable<any>;
744
+        shape(type: ValidationMap<any>): Requireable<any>;
745
+    }
746
+
747
+    //
748
+    // React.Children
749
+    // ----------------------------------------------------------------------
750
+
751
+    interface ReactChildren {
752
+        map<T>(children: ReactNode, fn: (child: ReactChild) => T): { [key:string]: T };
753
+        forEach(children: ReactNode, fn: (child: ReactChild) => any): void;
754
+        count(children: ReactNode): number;
755
+        only(children: ReactNode): ReactChild;
756
+    }
757
+
758
+    //
759
+    // Browser Interfaces
760
+    // https://github.com/nikeee/2048-typescript/blob/master/2048/js/touch.d.ts
761
+    // ----------------------------------------------------------------------
762
+
763
+    interface AbstractView {
764
+        styleMedia: StyleMedia;
765
+        document: Document;
766
+    }
767
+
768
+    interface Touch {
769
+        identifier: number;
770
+        target: EventTarget;
771
+        screenX: number;
772
+        screenY: number;
773
+        clientX: number;
774
+        clientY: number;
775
+        pageX: number;
776
+        pageY: number;
777
+    }
778
+
779
+    interface TouchList {
780
+        [index: number]: Touch;
781
+        length: number;
782
+        item(index: number): Touch;
783
+        identifiedTouch(identifier: number): Touch;
784
+    }
785
+}

+ 99
- 0
client/src/stores/asyncT.ts 查看文件

@@ -0,0 +1,99 @@
1
+
2
+
3
+module rpgcards {
4
+
5
+    // Enumerate the different types contained by an AsyncTType object.
6
+    export const enum AsyncTType { Nothing, Pending, Just }
7
+
8
+    // Define a contract to unwrap AsyncTType object using callbacks
9
+    export interface AsyncTPatterns<T,U> {
10
+        just: (t: T) => U;
11
+        pending: () => U;
12
+        nothing: (e: Error) => U;
13
+    }
14
+
15
+    // Encapsulates an optional, ansynchronous value.
16
+    // A value of type AsyncT a either:
17
+    // - contains a value of type a (represented as Just a)
18
+    // - is empty (represented as Nothing error)
19
+    // - is pending (represented as Pending).
20
+    export class AsyncT<T> {
21
+        private value: T;
22
+        private error: Error;
23
+        private type: AsyncTType;
24
+
25
+        // Build a AsyncT object. For internal use only.
26
+        constructor(type: AsyncTType, value: T, error: Error) {
27
+            this.type = type;
28
+            this.value = value;
29
+            this.error = error;
30
+        }
31
+
32
+        // Helper function to build a AsyncT object filled with a Just type.
33
+        static just<T>(t: T) {
34
+            return new AsyncT<T>(AsyncTType.Just, t, null);
35
+        }
36
+
37
+        // Helper function to build a AsyncT object filled with a Pending type.
38
+        static pending<T>() {
39
+            return new AsyncT<T>(AsyncTType.Pending, null, null);
40
+        }
41
+
42
+        // Helper function to build a AsyncT object filled with a Nothing type.
43
+        static nothing<T>(error: Error) {
44
+            return new AsyncT<T>(AsyncTType.Nothing, null, error);
45
+        }
46
+
47
+        // Wrap an object inside a AsyncT.
48
+        unit<U>(u: U) {
49
+            return AsyncT.just<U>(u);
50
+        }
51
+
52
+        // Apply the function passed as parameter on the object.
53
+        bind<U>(f: (t: T) => AsyncT<U>) {
54
+            switch(this.type) {
55
+                case AsyncTType.Just: return f(this.value);
56
+                case AsyncTType.Pending: return AsyncT.pending<U>();
57
+                case AsyncTType.Nothing: return AsyncT.nothing<U>(this.error);
58
+            }
59
+        }
60
+
61
+        // Alias for unit.
62
+        of = this.unit;
63
+
64
+        // Alias for bind.
65
+        chain = this.bind;
66
+
67
+        // Apply the function passed as parameter on the object.
68
+        fmap<U>(f: (t: T) => U) {
69
+            return this.bind(v => this.unit<U>(f(v)));
70
+        }
71
+
72
+        // Alias for fmap.
73
+        lift = this.fmap;
74
+
75
+        // Alias for fmap.
76
+        map = this.fmap;
77
+
78
+        // Execute a function depending on the AsyncT content.
79
+        caseOf<U>(patterns: AsyncTPatterns<T, U>) {
80
+            switch(this.type) {
81
+                case AsyncTType.Just: return patterns.just(this.value);
82
+                case AsyncTType.Pending: return patterns.pending();
83
+                case AsyncTType.Nothing: return patterns.nothing(this.error);
84
+            }
85
+        }
86
+
87
+        static liftA2<T,U,V>(a:AsyncT<T>, b:AsyncT<U>, f:(a:T,b:U)=>V) {
88
+            if (a.type === AsyncTType.Just && b.type === AsyncTType.Just) {
89
+                return AsyncT.just(f(a.value, b.value));
90
+            } else if (a.type === AsyncTType.Nothing) {
91
+                return AsyncT.nothing<V>(a.error);
92
+            } else if (a.type === AsyncTType.Nothing) {
93
+                return AsyncT.nothing<V>(b.error);
94
+            } else {
95
+                return AsyncT.pending<V>();
96
+            }
97
+        }
98
+    }
99
+}

+ 16
- 0
client/src/stores/card.ts 查看文件

@@ -0,0 +1,16 @@
1
+/// <reference path="./entity.ts"/>
2
+
3
+module rpgcards {
4
+
5
+    export class Card extends Entity {
6
+        public data: {[name:string]:any};
7
+        public deckId: string;
8
+
9
+        constructor(id:string) {
10
+            super(id);
11
+            this.data = {};
12
+        }
13
+    }
14
+
15
+    export type CardList = Card[];
16
+}

+ 15
- 0
client/src/stores/deck.ts 查看文件

@@ -0,0 +1,15 @@
1
+/// <reference path="./entity.ts"/>
2
+
3
+module rpgcards {
4
+    export class Deck extends Entity{
5
+        public cards: string[];
6
+
7
+        constructor(id:string) {
8
+            super(id);
9
+            this.cards = [];
10
+        }
11
+
12
+    }
13
+
14
+    export type DeckList = Deck[];
15
+}

+ 15
- 0
client/src/stores/entity.ts 查看文件

@@ -0,0 +1,15 @@
1
+module rpgcards {
2
+
3
+    export class Entity {
4
+        private _id: string;
5
+        public name: string;
6
+
7
+        constructor(id: string) {
8
+            this._id = id;
9
+            this.name = "";
10
+        }
11
+
12
+        get id(): string {return this._id;}
13
+    }
14
+
15
+}

+ 21
- 0
client/src/stores/eventEmitter.ts 查看文件

@@ -0,0 +1,21 @@
1
+module rpgcards {
2
+
3
+    export class ChangeEventEmitter {
4
+        private _callbacks: (() => void)[];
5
+        constructor() {
6
+            this._callbacks = [];
7
+        }
8
+
9
+        protected emitChange() {
10
+            this._callbacks.forEach((callback) => callback());
11
+        }
12
+
13
+        public addChangeListener(callback: ()=>void) {
14
+            this._callbacks.push(callback);
15
+        }
16
+
17
+        public removeChangeListener(callback: ()=>void) {
18
+            this._callbacks = this._callbacks.filter((cb) => cb !== callback);
19
+        }
20
+    }
21
+}

+ 13
- 0
client/src/stores/id.ts 查看文件

@@ -0,0 +1,13 @@
1
+module rpgcards {
2
+    const s = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
3
+    const N = 16;
4
+
5
+    export function randomID(): string {
6
+        var id = "";
7
+        for(var i=0; i<N; ++i) {
8
+            id += s.charAt(Math.floor(Math.random() * s.length));
9
+        }
10
+        return id;
11
+    }
12
+
13
+}

+ 118
- 0
client/src/stores/store.ts 查看文件

@@ -0,0 +1,118 @@
1
+/// <reference path="./card.ts"/>
2
+/// <reference path="./deck.ts"/>
3
+/// <reference path="./eventEmitter.ts"/>
4
+/// <reference path="./asyncT.ts"/>
5
+/// <reference path="../dispatcher/dispatcher.ts"/>
6
+/// <reference path="../actions/actions.ts"/>
7
+
8
+module rpgcards {
9
+    function findEntity<T extends Entity>(elements: T[], id: string):AsyncT<T> {
10
+        for(var i=0; i<elements.length;++i){
11
+            var element = elements[i];
12
+            if (element.id === id) {
13
+                return AsyncT.just(element);
14
+            }
15
+        }
16
+        return AsyncT.nothing<T>(new Error("Entity " + id + " not found"));
17
+    }
18
+
19
+    export class Store extends ChangeEventEmitter {
20
+        private _decks: Deck[];
21
+        private _cards: Card[];
22
+        private _selectedDeck: string;
23
+        private _selectedCard: string;
24
+
25
+        constructor(dispatcher: Dispatcher) {
26
+            super();
27
+            this._decks = [];
28
+            this._cards = [];
29
+            this._selectedCard = "";
30
+            this._selectedDeck = "";
31
+
32
+            dispatcher.register((action) => {
33
+                if(action instanceof ActionNewDeck) {
34
+                    this._newDeck()
35
+                } else if (action instanceof ActionDeleteDeck) {
36
+                    this._deleteDeck(action.id);
37
+                } else if (action instanceof ActionSelectDeck) {
38
+                    this._selectDeck(action.id);
39
+                } else if (action instanceof ActionNewCard) {
40
+                    this._newCard(action.deck_id);
41
+                } else if (action instanceof ActionDeleteCard) {
42
+                    this._deleteCard(action.id);
43
+                } else if (action instanceof ActionSelectCard) {
44
+                    this._selectCard(action.id);
45
+                } else {
46
+                    console.log("Unknown action received");
47
+                }
48
+                this.emitChange();
49
+            });
50
+        }
51
+
52
+        // ---------------------------------------------------------------------
53
+        // Accessing data
54
+        // ---------------------------------------------------------------------
55
+
56
+        getDeckList(): AsyncT<string[]> {
57
+            return AsyncT.just(
58
+                this._decks.map(deck => deck.id)
59
+                );
60
+        }
61
+
62
+        getDeck(id: string): AsyncT<Deck> {
63
+            return findEntity(this._decks, id);
64
+        }
65
+
66
+        getCard(id: string): AsyncT<Card> {
67
+            return findEntity(this._cards, id);
68
+        }
69
+
70
+        getCardsForDeck(id: string): AsyncT<string[]> {
71
+            return this.getDeck(id).lift(deck => deck.cards);
72
+        }
73
+
74
+        getDecksForCard(id: string): AsyncT<string[]> {
75
+            return AsyncT.just(this._decks
76
+                .filter(deck => deck.cards.indexOf(id) !== -1)
77
+                .map(deck => deck.id));
78
+        }
79
+
80
+        // ---------------------------------------------------------------------
81
+        // Pseudo-routes
82
+        // ---------------------------------------------------------------------
83
+        get selectedDeck(): string {return this._selectedDeck}
84
+        get selectedCard(): string {return this._selectedCard}
85
+
86
+        // ---------------------------------------------------------------------
87
+        // Methods for changing the state
88
+        // ---------------------------------------------------------------------
89
+        private _newDeck(): void {
90
+            this._decks.push(new Deck(randomID()));
91
+        }
92
+
93
+        private _deleteDeck(id: string): void {
94
+            this._decks.filter(deck => deck.id !== id);
95
+        }
96
+
97
+        private _selectDeck(id: string): void {
98
+            this._selectedDeck = id;
99
+        }
100
+
101
+        private _newCard(deckId: string): void {
102
+            this.getDeck(deckId)
103
+                .lift(deck => {
104
+                    var cardId = randomID();
105
+                    this._cards.push(new Card(cardId));
106
+                    deck.cards.push(cardId);
107
+                });
108
+        }
109
+
110
+        private _selectCard(id: string): void {
111
+            this.selectedCard = id;
112
+        }
113
+
114
+        private _deleteCard(id: string): void {
115
+            this._cards.filter(card => card.id !== id);
116
+        }
117
+    }
118
+}

+ 5
- 0
client/src/stores/template.ts 查看文件

@@ -0,0 +1,5 @@
1
+/// <reference path="./entity.ts"/>
2
+
3
+module rpgcards {
4
+    
5
+}

+ 29
- 0
client/src/views/decks.ts 查看文件

@@ -0,0 +1,29 @@
1
+/// <reference path="../external/react/react.d.ts"/>
2
+
3
+module rpgcards {
4
+
5
+
6
+    export function renderDecks(store: Store): React.ReactElement<any> {
7
+        var decks = store.getDeckList().fmap(deckIds => deckIds.map(id => {
8
+            let deck = store.getDeck(id);
9
+            let cards = store.getCardsForDeck(id);
10
+
11
+            return AsyncT.liftA2(deck, cards, (deck, cards) => {
12
+                return React.DOM.div({key:deck.id},
13
+                    "Deck " + deck.id + ": " + cards.length + " cards")
14
+            }).caseOf({
15
+                just: (t) => t,
16
+                nothing: (e) => React.DOM.div({}, "error: " + e.message),
17
+                pending: () => React.DOM.div({}, "loading...")
18
+            });
19
+        })).caseOf({
20
+            just: (t) => t,
21
+            nothing: (e) => [React.DOM.div({}, "error: " + e.message)],
22
+            pending: () => [React.DOM.div({}, "loading...")]
23
+        });
24
+
25
+        return React.DOM.div({ className: 'decks' },
26
+            decks
27
+        );
28
+    }
29
+}

+ 37
- 0
client/src/views/header.ts 查看文件

@@ -0,0 +1,37 @@
1
+/// <reference path="../external/react/react.d.ts"/>
2
+
3
+module rpgcards {
4
+
5
+
6
+    var homeItem = React.DOM.div
7
+        ( { className: 'navbar-item', onClick: null }
8
+        , React.DOM.i({ className: 'server icon' })
9
+        , 'Home'
10
+        );
11
+
12
+    var signOutItem = React.DOM.div
13
+        ( { className: 'navbar-item', onClick: null }
14
+        , 'Sign out'
15
+        );
16
+
17
+    var flexItem = React.DOM.div
18
+        ( { className: 'flex-navbar-item' }
19
+        );
20
+
21
+    function accountIdItem() {
22
+        return React.DOM.div
23
+            ( { className: 'navbar-item account-id' }
24
+            , React.DOM.span({ className: 'heading' }, 'Account identifier')
25
+            , React.DOM.span({ className: 'value' }, 'local')
26
+            );
27
+    }
28
+
29
+    export function renderHeader(store: Store): React.ReactElement<any> {
30
+        return React.DOM.div({ className: 'navbar' },
31
+            homeItem,
32
+            flexItem,
33
+            accountIdItem(),
34
+            signOutItem
35
+        );
36
+    }
37
+}

+ 36
- 0
client/tsconfig.json 查看文件

@@ -0,0 +1,36 @@
1
+{
2
+    "version": "1.5.0-alpha",
3
+    "compilerOptions": {
4
+        "target": "es6",
5
+        "module": "commonjs",
6
+        "declaration": false,
7
+        "noImplicitAny": true,
8
+        "removeComments": true,
9
+        "noLib": false,
10
+        "preserveConstEnums": true,
11
+        "suppressImplicitAnyIndexErrors": true,
12
+        "out": "./js/app.js"
13
+    },
14
+    "filesGlob": [
15
+        "./**/*.ts",
16
+        "!./node_modules/**/*.ts"
17
+    ],
18
+    "files": [
19
+        "./src/actions/actions.ts",
20
+        "./src/app.ts",
21
+        "./src/dispatcher/dispatcher.ts",
22
+        "./src/dispatcher/invariant.ts",
23
+        "./src/external/react/react-addons.d.ts",
24
+        "./src/external/react/react.d.ts",
25
+        "./src/stores/asyncT.ts",
26
+        "./src/stores/card.ts",
27
+        "./src/stores/deck.ts",
28
+        "./src/stores/entity.ts",
29
+        "./src/stores/eventEmitter.ts",
30
+        "./src/stores/id.ts",
31
+        "./src/stores/store.ts",
32
+        "./src/stores/template.ts",
33
+        "./src/views/decks.ts",
34
+        "./src/views/header.ts"
35
+    ]
36
+}

Loading…
取消
儲存