├── .babelrc
├── .gitignore
├── README.md
├── __tests__
└── client.js
├── app.json
├── archive
└── textInpuOld.js
├── dist
└── maps.js
├── examples
└── index.vr.js
├── index.js
├── package-lock.json
├── package.json
├── rn-cli.config.js
├── server
└── app.js
├── src
├── keyboard.js
├── keyboardButton.js
├── layout.js
├── scroll.js
└── textInput.js
├── start.js
├── static_assets
├── chess-world.jpg
├── down.png
└── up.png
└── vr
├── build
├── client.bundle.js
├── client.bundle.js.meta
└── index.bundle.js.meta
├── client.bundle.js
├── client.js
├── index.bundle.js
├── index.html
└── static_assets
└── chess-world.jpg
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | "react-native"
4 | ]
5 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # react-vr-textinput
2 |
3 | > Text Input and Virtual Keyboard for React VR
4 |
5 | ## Team
6 |
7 | - Arseniy Kotov
8 | - Sim Kang
9 | - Rishi Raje
10 |
11 | ## Installation
12 |
13 | ```
14 | npm install react-vr-textinput
15 | ```
16 |
17 |
18 | ## Usage
19 |
20 | > ``` import TextInput from 'react-vr-textinput' ```
21 |
22 | - Exposed Props
23 | 1. onSubmit() - provide a call back function that will accept the text from the text input component
24 | 2. rows - provide the number of lines you wish the text box to be
25 | 3. cols - provide the number of columns you wish the tex box to be
26 | 4. x - coordinate for positioning of all 3 elements (all related off the placement of the text input area)
27 | 5. y - coordinate for above
28 | 6. z - coordinate for above
29 | 7. textColor - color for the text inside the input field
30 | 8. backgroundColor - color for the background of the input field
31 | 9. keyboardColor - color for keys on keyboard
32 | 10. keyboardOnHover - color for the keys when the mouse or vr controller hover over.
33 | 11. rotateX - provide the amount of rotate needed on the x axis as a number
34 | 12. rotateY - same as #11
35 | 13. defaultInput - provide a default string to be displayed on the input
36 |
37 | If not specified, all props except onSubmit will use default values.
38 | - Click the TextBox Component to activate it.
39 | - When the user is finished, the keyboard will hide itself after the submit button has been pressed.
40 |
41 |
42 | ## Sample code
43 |
44 | The following example palces the textInput inside the View Component. Note the props that can be passed in. Beyond these, the text input box can be fully customized by directly accessinf the component source code.
45 |
46 | ```js
47 | import React from 'react';
48 | import {View} from 'react-vr';
49 | import TextInput from 'react-vr-textinput'
50 |
51 | export default class Example extends React.Component {
52 |
53 | constructor(props) {
54 | super(props);
55 | }
56 |
57 | submitHandler(string) {
58 | console.log('the text received by the submitHandler is ' + string);
59 | }
60 |
61 | render() {
62 | return (
63 |
64 |
66 |
67 | );
68 | }
69 | };
70 |
71 | AppRegistry.registerComponent('Example', () => Example);
72 |
73 | ```
74 |
75 | ## Requirements
76 |
77 | > React VR
78 |
79 | ## Current Issues
80 |
81 | - This module is fully functional. Our next goal is to refactor for speed and also further enhance customizability through providing users even more props. We are also working on adding auto-complete support for faster typing. This will be an optional component that users can select to enable with their text input box and virtual keyboard. Please feel free to ask for additional functionality and we will try to add features as soon as possible.
82 |
83 | ## Contributing
84 |
85 | - Fork the repository.
86 | - npm install
87 | - Create a pull request
88 | - Provide info on changes/updates on pull request
89 |
90 | ## Tips
91 | - Any additional styling options can be applied to the component. Remember that rotate can be used to help place the entire component anywhere in 3D space.
92 | - Scroll keys show after only if the number of typed in rows exceeds the number of rows specified for the text box
93 | - The cursor can be moved via forward and backward keys.
94 |
--------------------------------------------------------------------------------
/__tests__/client.js:
--------------------------------------------------------------------------------
1 | import 'react-native';
2 | import 'react-vr';
3 | import React from 'react';
4 | import renderer from 'react-test-renderer';
5 | import TextInput from '../src/textInput.js';
6 |
7 | // Note: test renderer must be required after react-native.
8 |
9 | it('renders correctly', () => {
10 | const tree = renderer.create();
11 | });
12 |
13 | describe('handleAllLetters', () => {
14 | it('should add one letter to output string', () => {});
15 | it('should add multiple letters correctly', () => {});
16 | it('should add symbols correctly', () => {});
17 | it('should add character at correct cursor position', () => {});
18 | });
19 | describe('handleDelete', () => {
20 | it('should delete characters when cursor is at last position', () => {});
21 | it('should delete characters when the cursor is located somewhere in the string', () => {});
22 | });
23 | describe('handleShift', () => {
24 | it('should on first press return an all upper case keyboard', () => {});
25 | it('should on re-press give you an all lower case keyboard', () => {});
26 | it('should not fire when pressed when keyboard is showing symbols', () => {});
27 | });
28 | describe('handleBack', () => {
29 | it('should on one press move the cursor one position back', () => {});
30 | it('should not move the cursor past position 0', () => {});
31 | });
32 | describe('handleForward', () => {
33 | it('should on one press move the cursor one position forward', () => {});
34 | it('should not move the cursor beyond the last position', () => {});
35 | });
36 | describe('handleDown', () => {
37 | it('should shows you the correct displayed text', () => {});
38 | });
39 | describe('handleUp', () => {
40 | it('should shows you the correct displayed text', () => {});
41 | });
42 | describe('paginate', () => {
43 | it('should return the right array when text < display area', () => {});
44 | it('should return right array when text > display area', () => {});
45 | });
46 | describe('client', () => {
47 | it('should generate the specified height and width for the textbox', () => {});
48 | it('should fall back to defaults if no specified height or width for the textbox', () => {});
49 | it('should show keyboard only when the textbox is selected', () => {});
50 | it('should call the submithandler callback after the text is submitted', () => {});
51 | it('should only show column items in the text box if there is no cursor on the line', () => {});
52 | it('should have the number of characters equal to the columns if cursor is not present', () => {});
53 | });
54 |
--------------------------------------------------------------------------------
/app.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-vr-textbox",
3 | "scripts": {},
4 | "env": {},
5 | "formation": {},
6 | "addons": [],
7 | "buildpacks": []
8 | }
9 |
--------------------------------------------------------------------------------
/archive/textInpuOld.js:
--------------------------------------------------------------------------------
1 | /*import React, { Component } from 'react';
2 | import { View, VrButton, StyleSheet, Text } from 'react-vr';
3 | import Keyboard from './keyboard';
4 | import Scroll from './scroll'
5 |
6 | class TextInput extends Component {
7 | constructor(props){
8 | super(props);
9 | this.state = {
10 | cursorPosition: 0,
11 | textArrayCursorYes: '|',
12 | textArrayCursorNo: ' ',
13 | text: '',
14 | selected: false,
15 | rows: this.props.rows || 4,
16 | columns: this.props.cols || 50,
17 | submitHandler: this.props.onSubmit || null,
18 | showScroll: false,
19 | toggleCursor: true,
20 | x: -1,
21 | y: 0.2,
22 | z: -1.5,
23 | pages : 0,
24 | start : 0,
25 | end: (this.props.rows || 4) * (this.props.cols || 50),
26 | focus: false,
27 | counter: 0,
28 | opacity: 0
29 | }
30 | }
31 |
32 | focus() {
33 | this.setState({focus: true});
34 | }
35 | generateText() {
36 | var string = '';
37 | for(var i =0; i < this.state.textArrayCursorYes.length; i++) {
38 | if(this.state.textArrayCursorYes[i] !== '|') string += this.state.textArrayCursorYes[i];
39 | }
40 | return string;
41 | }
42 |
43 | handleSpace() {
44 | var newArrYes = this.state.textArrayCursorYes.slice(0, this.state.cursorPosition) + ' ' + this.state.textArrayCursorYes.slice(this.state.cursorPosition);
45 | var newArrNo = this.state.textArrayCursorNo.slice(0, this.state.cursorPosition) + ' ' + this.state.textArrayCursorNo.slice(this.state.cursorPosition);
46 | this.setState({
47 | textArrayCursorYes: newArrYes,
48 | textArrayCursorNo: newArrNo,
49 | cursorPosition: this.state.cursorPosition + 1,
50 | text: this.generateText()
51 | }, () => {
52 | this.handleCursorFollow.bind(this)();
53 | });
54 | }
55 |
56 | handleSubmit() {
57 | var string = JSON.parse(JSON.stringify(this.state.textArrayCursorYes));
58 | string = string.substring(0, string.length-1);
59 | this.setState({cursorPosition: 0,
60 | textArrayCursorYes: '|',
61 | textArrayCursorNo: ' ',
62 | text: '',
63 | selected: false,
64 | rows: this.props.rows || 4,
65 | columns: this.props.cols || 50,
66 | submitHandler: this.props.onSubmit || null,
67 | showScroll: false,
68 | toggleCursor: true,
69 | x: -1,
70 | y: 0.2,
71 | z: -1.5,
72 | pages : 0,
73 | start : 0,
74 | end: (this.props.rows || 4) * (this.props.cols || 50),
75 | focus: false,
76 | opacity: 0
77 | });
78 |
79 | this.props.onSubmit(string);
80 | }
81 |
82 | handleAllLetters(value) {
83 | var newArrYes = this.state.textArrayCursorYes.slice(0, this.state.cursorPosition) + value.trim() + this.state.textArrayCursorYes.slice(this.state.cursorPosition);
84 | var newArrNo = this.state.textArrayCursorNo.slice(0, this.state.cursorPosition) + value.trim() + this.state.textArrayCursorNo.slice(this.state.cursorPosition);
85 | this.setState({
86 | textArrayCursorYes: newArrYes,
87 | textArrayCursorNo: newArrNo,
88 | cursorPosition: this.state.cursorPosition + 1,
89 | text: this.generateText()
90 | }, () => {
91 | this.handleCursorFollow.bind(this)();
92 | });
93 | }
94 |
95 | handleDelete() {
96 | var arr = this.state.textArrayCursorYes.slice(0, this.state.cursorPosition - 1) + this.state.textArrayCursorYes.slice(this.state.cursorPosition);
97 | var arr2 = this.state.textArrayCursorNo.slice(0, this.state.cursorPosition - 1) + this.state.textArrayCursorNo.slice(this.state.cursorPosition);
98 |
99 | this.setState({
100 | textArrayCursorYes: arr,
101 | textArrayCursorNo: arr2,
102 | cursorPosition: this.state.cursorPosition - 1
103 | }, () => {
104 | if (this.paginate(this.state.textArrayCursorYes.slice(this.state.start, this.state.end)).length < this.state.columns * this.state.rows) {
105 | if ((this.state.cursorPosition - this.state.start + 1) % this.state.columns === 0) {
106 | var start = this.state.start - this.state.columns;
107 | var end = this.state.end - this.state.columns;
108 | if (start < 0) {
109 | start = 0;
110 | end = this.state.rows * this.state.columns;
111 | }
112 | this.setState({
113 | start: start,
114 | end: end
115 | });
116 | }
117 | } else {
118 | this.setState({
119 | start: 0,
120 | end: (this.props.rows || 4) * (this.props.cols || 50)
121 | })
122 |
123 | }
124 | });
125 |
126 | }
127 |
128 | handleForward() {
129 | if (this.state.cursorPosition < this.state.columns * this.state.rows - 1) {
130 | var cp = this.state.cursorPosition;
131 | var s = this.state.textArrayCursorYes;
132 | var s2 = this.state.textArrayCursorNo;
133 |
134 | s = s.slice(0, cp) + s.slice(cp + 1, cp + 2) + s.slice(cp, cp + 1) + s.slice(cp + 2);
135 | s2 = s2.slice(0, cp) + s2.slice(cp + 1, cp + 2) + s2.slice(cp, cp + 1) + s2.slice(cp + 2);
136 |
137 | this.setState({
138 | textArrayCursorYes: s,
139 | textArrayCursorNo: s2,
140 | cursorPosition: this.state.cursorPosition + 1
141 | }, () => {
142 | if (this.state.cursorPosition > this.state.rows * this.state.columns) {
143 | if ((this.state.cursorPosition - 1) % this.state.columns === 0) {
144 | this.setState({
145 | start: this.state.start + this.state.columns,
146 | end: this.state.end + this.state.columns
147 | });
148 | }
149 | } else {
150 | this.setState({
151 | start: 0,
152 | end: (this.props.rows || 4) * (this.props.cols || 50)
153 | })
154 | }
155 | });
156 | }
157 | }
158 |
159 | handleBack() {
160 |
161 | if (this.state.cursorPosition !== 0) {
162 | var cp = this.state.cursorPosition;
163 | var s = this.state.textArrayCursorYes;
164 | var s2 = this.state.textArrayCursorNo;
165 |
166 | s = s.slice(0, cp-1) + s.slice(cp, cp+1) + s.slice(cp-1, cp) + s.slice(cp+1);
167 | s2 = s2.slice(0, cp - 1) + s2.slice(cp, cp + 1) + s2.slice(cp - 1, cp) + s2.slice(cp + 1);
168 |
169 | this.setState({
170 | textArrayCursorYes : s,
171 | textArrayCursorNo: s2,
172 | cursorPosition: this.state.cursorPosition - 1
173 | }, () => {
174 | if (this.paginate(this.state.textArrayCursorYes.slice(this.state.start, this.state.end)).length < this.state.columns * this.state.rows) {
175 | if ((this.state.cursorPosition - this.state.start + 3) % this.state.columns === 0) {
176 | var start = this.state.start - this.state.columns;
177 | var end = this.state.end - this.state.columns;
178 | if (start < 0) {
179 | start = 0;
180 | end = this.state.rows * this.state.columns;
181 | }
182 | this.setState({
183 | start: start,
184 | end: end
185 | });
186 | }
187 | } else {
188 | this.setState({
189 | start: 0,
190 | end: (this.props.rows || 4) * (this.props.cols || 50)
191 | })
192 |
193 | }
194 | });
195 | }
196 | }
197 |
198 |
199 |
200 | handleUp() {
201 | if(this.state.start!== 0) {
202 | if (this.state.pages !== 0) {
203 | var pages = this.state.pages - 1;
204 | var start = this.state.start - this.state.columns;
205 | var end = this.state.end - this.state.columns;
206 | this.setState({
207 | pages: pages,
208 | start: start,
209 | end : end
210 | })
211 | }
212 | }
213 | }
214 |
215 | handleDown() {
216 | if(this.state.end < this.state.textArrayCursorYes.length) {
217 | var pages = this.state.pages + 1;
218 | var start = this.state.start + this.state.columns;
219 | var end = this.state.end + this.state.columns;
220 | this.setState({
221 | pages: pages,
222 | start: start,
223 | end: end
224 | })
225 | }
226 | }
227 |
228 |
229 | paginate(s) {
230 |
231 | var columns = this.state.columns;
232 | var cols;
233 | var results = [];
234 | var temp = s.split(' ');
235 | var string = '';
236 | for (var i = 0; i < temp.length; i++) {
237 | if (string.includes('|')) {
238 | cols = columns + 1;
239 | } else {
240 | cols = columns;
241 | }
242 | if (string.length === cols) {
243 | string = string.slice(0, string.length - 1);
244 | results.push(string);
245 | string = temp[i] + ' ';
246 | }
247 |
248 | else if (string.length + temp[i].length > cols) {
249 | string = string.slice(0, string.length - 1);
250 | results.push(string);
251 | string = temp[i] + ' ';
252 | } else {
253 | string += temp[i] + ' '
254 | }
255 | }
256 | if (string !== '') results.push(string.slice(0, string.length - 1));
257 | return results;
258 | }
259 |
260 | handleCursorFollow() {
261 |
262 | // if (this.state.cursorPosition > this.state.end) {
263 | if (this.paginate(this.state.textArrayCursorYes.slice(this.state.start, this.state.end)).length > this.state.rows) {
264 | var start = this.state.start;
265 | var end = this.state.end;
266 | var pages = this.state.pages;
267 |
268 | start = start + this.state.columns;
269 | end = end + this.state.columns;
270 |
271 | while (end <= this.state.cursorPosition) {
272 | pages = pages + 1;
273 | start = start + this.state.columns;
274 | end = end + this.state.columns;
275 | }
276 | if (this.state.textArrayCursorYes[start] === ' ') {
277 | start = start + 2;
278 | end = end + 1;
279 | } else {
280 | var temp = start;
281 | while(this.state.textArrayCursorYes[temp] !== ' ' && temp !== 0) {
282 | temp--
283 | }
284 | if (temp === 0) {
285 | start = this.state.start + this.state.columns
286 | end = this.state.end + this.state.columns
287 | } else {
288 | start = temp + 1;
289 | end = start + (this.state.columns * this.state.rows);
290 | }
291 | }
292 | } else {
293 | var pages = this.state.pages;
294 | start = this.state.start;
295 | end = this.state.end;
296 | }
297 |
298 | this.setState({
299 | start: start,
300 | end: end,
301 | pages: pages
302 | })
303 |
304 | }
305 |
306 | render() {
307 | var arrayCursorYes = this.paginate(this.state.textArrayCursorYes);
308 | var arrayCursorNo = this.paginate(this.state.textArrayCursorNo);
309 | var displayString = '';
310 | var displayArray = this.paginate(this.state.textArrayCursorYes.slice(this.state.start, this.state.end));
311 | displayArray.forEach(function (element, index) {
312 | displayString += element + '\n';
313 | })
314 |
315 | displayString = displayString.slice(0, displayString.length - 1);
316 |
317 | return(
318 |
319 |
320 |
321 |
322 | {displayString}
323 |
324 |
325 |
326 |
327 | (this.state.rows * this.state.columns) + 1 ? 1 : this.state.opacity}
329 | handleUp={this.handleUp.bind(this)}
330 | handleDown={this.handleDown.bind(this)}
331 | />
332 |
333 | {this.state.focus ? (
334 |
335 |
343 | ) : ()}
344 | );
345 | }
346 | }
347 |
348 | export default TextInput;*/
--------------------------------------------------------------------------------
/dist/maps.js:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TeamRocketThesis/react-vr-textinput/51445d1cb6c814720f55e39b3db580c276cfbdea/dist/maps.js
--------------------------------------------------------------------------------
/examples/index.vr.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { AppRegistry, asset, Pano, Text, View} from 'react-vr';
3 | import TextInput from '../src/textInput'
4 |
5 | export default class Example extends React.Component {
6 |
7 | constructor(props) {
8 | super(props);
9 | this.state = {
10 | text: ''
11 | }
12 | }
13 |
14 | submitHandler(string) {
15 | this.setState({
16 | text: string
17 | });
18 | }
19 |
20 | render() {
21 | const { text } = this.state;
22 |
23 | return (
24 |
25 |
26 |
30 | {text ? `The Submit Handler received ${text}` : ''}
31 |
32 | );
33 | }
34 | };
35 |
36 | AppRegistry.registerComponent('Example', () => Example);
37 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | module.exports = require('./src/textInput');
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-vr-textinput",
3 | "version": "1.3.8",
4 | "private": false,
5 | "author": "Arseniy Kotov, Rishi Raje, Sim Kang",
6 | "keywords": [
7 | "react",
8 | "vr",
9 | "text input"
10 | ],
11 | "repository": {
12 | "type": "git",
13 | "url": "https://github.com/TeamRocketThesis/react-vr-textinput"
14 | },
15 | "scripts": {
16 | "start": "node start.js",
17 | "bundle": "node node_modules/react-vr/scripts/bundle.js",
18 | "open": "node -e \"require('xopen')('http://localhost:8081/vr/')\"",
19 | "devtools": "react-devtools",
20 | "test": "jest"
21 | },
22 | "dependencies": {
23 | "chai": "^4.0.2",
24 | "express": "^4.15.3",
25 | "mocha": "^3.4.2",
26 | "nodemon": "^1.11.0",
27 | "ovrui": "^1.1.0",
28 | "path": "^0.12.7",
29 | "prop-types": "15.5.10",
30 | "react": "~15.4.1",
31 | "react-native": "~0.42.0",
32 | "react-vr": "^1.1.0",
33 | "react-vr-web": "^1.1.0",
34 | "shelljs": "^0.7.7",
35 | "three": "^0.80.1"
36 | },
37 | "devDependencies": {
38 | "babel-jest": "^19.0.0",
39 | "babel-preset-react-native": "^1.9.1",
40 | "chai": "^4.0.2",
41 | "eslint": "^4.19.1",
42 | "eslint-config-airbnb": "^17.0.0",
43 | "eslint-plugin-import": "^2.13.0",
44 | "eslint-plugin-jsx-a11y": "^6.1.1",
45 | "eslint-plugin-react": "^7.10.0",
46 | "jest": "^19.0.2",
47 | "mocha": "^3.4.2",
48 | "react-devtools": "^2.1.3",
49 | "react-test-renderer": "~15.4.1",
50 | "xopen": "1.0.0"
51 | },
52 | "jest": {
53 | "preset": "react-vr"
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/rn-cli.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const blacklist = require('./node_modules/react-native/packager/blacklist');
3 |
4 | const config = {
5 | getProjectRoots() {
6 | return getRoots();
7 | },
8 |
9 | getBlacklistRE() {
10 | return blacklist([]);
11 | },
12 |
13 | getAssetExts() {
14 | return ['obj', 'mtl'];
15 | },
16 |
17 | getPlatforms() {
18 | return ['vr'];
19 | },
20 |
21 | getProvidesModuleNodeModules() {
22 | return ['react-native', 'react-vr'];
23 | },
24 | };
25 |
26 | function getRoots() {
27 | const root = process.env.REACT_NATIVE_APP_ROOT;
28 | if (root) {
29 | return [path.resolve(root)];
30 | }
31 | return [path.resolve(__dirname)];
32 | }
33 |
34 | module.exports = config;
35 |
--------------------------------------------------------------------------------
/server/app.js:
--------------------------------------------------------------------------------
1 | const express = require('express');
2 | const path = require('path');
3 |
4 | const app = express();
5 | app.set('port', process.env.PORT || 9000);
6 |
7 | app.use(express.static(path.join(__dirname, '../vr/')));
8 |
9 | if (!module.parent) {
10 | app.listen(app.get('port'));
11 | console.log(`Server listening on port ${app.get('port')}`);
12 | }
13 |
--------------------------------------------------------------------------------
/src/keyboard.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import {
3 | View, VrButton, StyleSheet, Text,
4 | } from 'react-vr';
5 | import KeyboardButton from './keyboardButton';
6 |
7 | import layout from './layout';
8 |
9 | const styles = StyleSheet.create({
10 | container: {},
11 | numbers: {},
12 | row1: {},
13 | row2: {},
14 | row3: {},
15 | bottom: {},
16 | row: {
17 | flexDirection: 'row',
18 | },
19 | });
20 |
21 | class Keyboard extends Component {
22 | constructor(props) {
23 | super(props);
24 | this.state = {
25 | cursorPosition: 0,
26 | textString: '',
27 | isShiftSelected: false,
28 | isSymbolSelected: false,
29 | };
30 | }
31 |
32 | handleAllValues(value) {
33 | this.props.handleAllLetters(value);
34 | }
35 |
36 | handleDelete() {
37 | this.props.handleDelete();
38 | }
39 |
40 | handleShift() {
41 | this.setState({
42 | isShiftSelected: !this.state.isShiftSelected,
43 | });
44 | }
45 |
46 | handleSymbolSelector() {
47 | this.setState({
48 | isSymbolSelected: !this.state.isSymbolSelected,
49 | isShiftSelected: false,
50 | });
51 | }
52 |
53 | handleBack() {
54 | this.props.handleBack();
55 | }
56 |
57 | handleForward() {
58 | this.props.handleForward();
59 | }
60 |
61 | handleSpacebar() {
62 | this.props.handleSpace();
63 | }
64 |
65 | handleSubmit() {
66 | this.props.handleSubmit();
67 | }
68 |
69 | getLayout() {
70 | if (this.state.isSymbolSelected) return layout.symbol.layout;
71 |
72 | if (!this.state.isShiftSelected) return layout.alphabet.layout;
73 |
74 | return layout.alphabet.layout.map(keyRow => keyRow.map(key => key.toUpperCase()));
75 | }
76 |
77 | render() {
78 | const layoutArray = this.getLayout();
79 | numberArray = ['1', '2', '3', '4', '5', '6', '7', '8', '9', '0'];
80 | return (
81 |
82 |
83 | {numberArray.map(number => (
84 |
93 | ))}
94 |
101 |
102 |
103 | {layoutArray[0].map(value => (
104 |
112 | ))}
113 |
114 |
115 | {layoutArray[1].map(value => (
116 |
124 | ))}
125 |
126 |
127 |
134 | {layoutArray[2].map(value => (
135 |
143 | ))}
144 |
155 |
156 |
157 |
164 | '}
168 | clickHandler={this.handleForward.bind(this)}
169 | isDisabled={false}
170 | />
171 |
179 |
186 |
187 |
188 | );
189 | }
190 | }
191 |
192 | export default Keyboard;
193 |
--------------------------------------------------------------------------------
/src/keyboardButton.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import {
3 | View, VrButton, StyleSheet, Text, Animated,
4 | } from 'react-vr';
5 |
6 | const styles = StyleSheet.create({
7 | text: {
8 | fontSize: 0.04,
9 | textAlign: 'center',
10 | color: '#ffffff',
11 | opacity: 3,
12 | fontFamily: 'HelveticaNeue-Light',
13 | fontWeight: 'normal',
14 | },
15 | button: {
16 | height: 0.15,
17 | padding: 0.05,
18 | borderWidth: 0.005,
19 | flex: 1,
20 | alignItems: 'center',
21 | },
22 | });
23 | class KeyboardButton extends Component {
24 | constructor(props) {
25 | super(props);
26 | this.state = {
27 | backgroundColor: this.props.keyboardColor || '#0d0d0d',
28 | opacity: 0.5,
29 | keyboardOnHover: this.props.keyboardOnHover,
30 | keyboardColor: this.props.keyboardColor,
31 | };
32 | }
33 |
34 | handleTheClick() {
35 | this.setState(
36 | { backgroundColor: 'white' },
37 | (() => {
38 | setTimeout((() => this.test.bind(this))(), 1);
39 | })(),
40 | );
41 | if (this.props.isDisabled === false) {
42 | this.props.clickHandler(this.props.value);
43 | }
44 | }
45 |
46 | test() {
47 | this.setState({ backgroundColor: this.state.keyboardColor || '#0d0d0d' });
48 | }
49 |
50 | render() {
51 | return (
52 | this.setState({ backgroundColor: this.state.keyboardOnHover || 'green' })}
60 | onExit={() => this.setState({ backgroundColor: this.state.keyboardColor || '#0d0d0d' })}
61 | >
62 |
63 | {this.props.value}
64 |
65 |
66 | );
67 | }
68 | }
69 |
70 | export default KeyboardButton;
71 |
--------------------------------------------------------------------------------
/src/layout.js:
--------------------------------------------------------------------------------
1 | export default {
2 | symbol: {
3 | displayValue: '.?!&',
4 | layout: [
5 | ['=', '+', '%', ' *', '[', ']', '{', '}', '<', '>'],
6 | ['@', ':', ';', '_', '-', '#', '(', ')', '/', '&'],
7 | ['.', ',', '?', '!', '\\', ' "', '$'],
8 | ],
9 | },
10 | alphabet: {
11 | displayValue: 'Abc',
12 | layout: [
13 | ['q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p'],
14 | ['a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l'],
15 | ['z', 'x', 'c', 'v', 'b', 'n', 'm'],
16 | ],
17 | },
18 | };
19 |
--------------------------------------------------------------------------------
/src/scroll.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import {
3 | View, VrButton, StyleSheet, Text, Image,
4 | } from 'react-vr';
5 |
6 | class Scroll extends Component {
7 | constructor(props) {
8 | super(props);
9 | this.state = {
10 | backgroundColor1: 'rgba(0,0,0,0.5)',
11 | backgroundColor2: 'rgba(0,0,0,0.5)',
12 | };
13 | }
14 |
15 | componentWillMount() {
16 | this.props.coordx;
17 | this.props.coordy;
18 | this.props.coordx;
19 | }
20 |
21 | flash() {
22 | this.setState(
23 | { backgroundColor1: 'green' },
24 | (() => {
25 | setTimeout((() => this.flash1Follow.bind(this))(), 1);
26 | })(),
27 | );
28 | }
29 |
30 | flash1Follow() {
31 | this.setState({ backgroundColor1: 'rgba(0,0,0,0.5)' });
32 | }
33 |
34 | flash2() {
35 | this.setState(
36 | { backgroundColor2: 'green' },
37 | (() => {
38 | setTimeout((() => this.flash2Follow.bind(this))(), 1);
39 | })(),
40 | );
41 | }
42 |
43 | flash2Follow() {
44 | this.setState({ backgroundColor2: 'rgba(0,0,0,0.5)' });
45 | }
46 |
47 | handleClick1() {
48 | this.flash();
49 | this.props.handleUp();
50 | }
51 |
52 | handleClick2() {
53 | this.flash2();
54 | this.props.handleDown();
55 | }
56 |
57 | render() {
58 | return (
59 |
60 |
69 |
73 |
74 |
83 |
87 |
88 |
89 | );
90 | }
91 | }
92 |
93 | export default Scroll;
94 |
--------------------------------------------------------------------------------
/src/textInput.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import PropTypes from 'prop-types';
3 | import {
4 | View, VrButton, StyleSheet, Text,
5 | } from 'react-vr';
6 | import Keyboard from './keyboard';
7 | import Scroll from './scroll';
8 |
9 | class TextInput extends Component {
10 | constructor(props) {
11 | super(props);
12 | this.state = {
13 | start: 0,
14 | end: this.props.rows - 1,
15 | displayArray: [`${props.defaultInput}|`],
16 | text: '',
17 | rows: this.props.rows || 4,
18 | columns: this.props.cols || 50,
19 | submitHandler: this.props.onSubmit || null,
20 | showScroll: false,
21 | toggleCursor: true,
22 | x: this.props.x || -1,
23 | y: this.props.y || 0.2,
24 | z: this.props.z || -1.5,
25 | pages: 0,
26 | focus: false,
27 | counter: 0,
28 | opacity: 0,
29 | textColor: this.props.textColor || 'white',
30 | backgroundColor: this.props.backgroundColor || 'grey',
31 | };
32 | }
33 |
34 | componentWillReceiveProps(nextProps) {
35 | this.setState({
36 | displayArray: [`${nextProps.defaultInput}|`],
37 | });
38 | }
39 |
40 | calculateAdd(s) {
41 | let index;
42 | const results = [];
43 | for (let i = s.length - 1; i >= 0; i--) {
44 | if (s[i] === ' ') {
45 | index = i;
46 | if (s.slice(0, index + 1).length > this.state.columns + 1) {
47 | // 11 = cols + 1
48 | continue;
49 | } else {
50 | index = i;
51 | break;
52 | }
53 | }
54 | }
55 | if (index) {
56 | results[0] = s.slice(0, index + 1);
57 | results[1] = s.slice(index + 1);
58 | } else {
59 | results[0] = `${s.slice(0, s.length - 2)}-`;
60 | results[1] = s.slice(s.length - 2);
61 | }
62 | return results;
63 | }
64 |
65 | // ------------
66 |
67 | calculateDelete(s) {
68 | let index;
69 | const results = [];
70 |
71 | for (let i = 0; i < s.length; i++) {
72 | if (s[i] === ' ') {
73 | index = i;
74 | break;
75 | }
76 | }
77 |
78 | if (index) {
79 | return [s.slice(0, index + 1), s.slice(index + 1)];
80 | }
81 | return [s, null];
82 | }
83 |
84 | // -------------
85 |
86 | findPosition(start, end) {
87 | let index;
88 | // find the string that has the cursor
89 | const arr = this.state.displayArray;
90 | for (var i = 0; i < arr.length; i++) {
91 | if (arr[i]) {
92 | if (arr[i].includes('|')) {
93 | index = i;
94 | break;
95 | }
96 | }
97 | }
98 |
99 | // check if the string with the cursor is in the window of start and end, if not move them in the correct direction till the string with the cursor shows up
100 |
101 | if (index > end) {
102 | var found = false;
103 | while (!found) {
104 | start++;
105 | end++;
106 |
107 | for (i = start; i <= end; i++) {
108 | if (arr[i].includes('|')) {
109 | found = true;
110 | break;
111 | }
112 | }
113 | }
114 | } else if (index < start) {
115 | found = false;
116 | while (!found) {
117 | start--;
118 | end--;
119 |
120 | for (i = start; i <= end; i++) {
121 | if (arr[i].includes('|')) {
122 | found = true;
123 | break;
124 | }
125 | }
126 | }
127 | }
128 |
129 | const results = [];
130 | results[0] = index;
131 | results[1] = start;
132 | results[2] = end;
133 |
134 | return results;
135 | }
136 |
137 | // ---------------
138 |
139 | focus() {
140 | this.setState({ focus: true });
141 | }
142 |
143 | // ---------------
144 |
145 | handleAllLetters(val) {
146 | if (val === ' ') {
147 | val = ' ';
148 | }
149 | let start = this.state.start;
150 | let end = this.state.end;
151 | const cols = this.state.columns;
152 | const arr = this.state.displayArray;
153 |
154 | let index;
155 |
156 | // get the current cursor position string, and move start and end accordingly
157 |
158 | let tempArray = this.findPosition(start, end);
159 |
160 | console.log('item ', tempArray);
161 |
162 | index = tempArray[0];
163 | start = tempArray[1];
164 | end = tempArray[2];
165 |
166 | // add the current character at position CP
167 |
168 | const cp = arr[index].indexOf('|');
169 | arr[index] = arr[index].slice(0, cp) + val + arr[index].slice(cp);
170 | console.log(arr);
171 |
172 | // if the current string has exceeded its length after adding a character ...
173 |
174 | if (arr[index].length > cols + 1) {
175 | // in a loop pass the values of the array to the calculate function to get back properly trimmed strings
176 | let done = false;
177 | while (!done) {
178 | // get back an array called list which will have the two parts that the string is split up in
179 | const list = this.calculateAdd(arr[index]);
180 | // add the first item of list to the array
181 |
182 | arr[index] = list[0];
183 | index++;
184 |
185 | // if there is a second item in list then append the next entry in the array to it and repeat the calculate function
186 | if (list[1]) {
187 | if (arr[index]) {
188 | var s = list[1] + arr[index];
189 | console.log('s ', s);
190 | } else {
191 | s = list[1];
192 | console.log('s in else ', s);
193 | }
194 |
195 | arr.splice(index, 0, s);
196 | tempArray = this.findPosition(start, end);
197 | index = tempArray[0];
198 | start = tempArray[1];
199 | end = tempArray[2];
200 |
201 | if (s.length <= cols + 1) {
202 | done = true;
203 | }
204 | console.log(`done is ${done} for ${s}`);
205 | }
206 | }
207 | }
208 |
209 | this.setState(
210 | {
211 | displayArray: arr,
212 | start,
213 | end,
214 | },
215 | function () {
216 | console.log('new value of start ', this.state.start);
217 | console.log('new value of end ', this.state.end);
218 | },
219 | );
220 | }
221 |
222 | // ------------
223 |
224 | handleDelete() {
225 | console.log('this.state.displayArray');
226 | if (this.state.displayArray[0][0] !== '|') {
227 | let arr = this.state.displayArray;
228 | let start = this.state.start;
229 | let end = this.state.end;
230 | const cols = this.state.columns;
231 | let tempArray = this.findPosition(start, end);
232 | // console.log(tempArray);
233 | let index = tempArray[0];
234 | start = tempArray[1];
235 | end = tempArray[2];
236 |
237 | // find the appropriate string where the cursor is and move start and end accordingly
238 | // find the CP
239 | let cp = arr[index].indexOf('|');
240 |
241 | // if cp is the first character of the string, then move the cursor to the previous string
242 | if (cp === 0) {
243 | arr[index] = arr[index].slice(1);
244 | arr[index - 1] = `${arr[index - 1]}|`;
245 | cp = arr[index - 1].length - 1;
246 | index--;
247 | // see if the display window needs to be moved
248 | tempArray = this.findPosition(start, end);
249 | index = tempArray[0];
250 | start = tempArray[1];
251 | end = tempArray[2];
252 | // console.log(tempArray);
253 | }
254 | // delete the character at cp-1
255 | arr[index] = arr[index].slice(0, cp - 1) + arr[index].slice(cp);
256 | console.log(arr);
257 | // if the string with the cursor is lesser in length than cols + 1 then find the next string, append it etc. till all strings have moved correctly ...
258 |
259 | if (arr[index].length < cols + 1) {
260 | let done = false;
261 | while (!done) {
262 | // if the cursor is in the last string of the array, then there is nothing to do
263 | console.log('index, arr.length -1 ', index, arr.length - 1);
264 | if (!arr[index + 1]) {
265 | done = true;
266 | tempArray = this.findPosition(start, end);
267 | index = tempArray[0];
268 | start = tempArray[1];
269 | end = tempArray[2];
270 | // find the new window
271 | break;
272 | }
273 |
274 | // find the sub-word till after the first space in the string at index +1 and add it to string at index
275 | // console.log('next string ', arr[index + 1]);
276 | const list = this.calculateDelete(arr[index + 1]);
277 | // console.log('calculated list ', list);
278 | // if the length of this new string is = cols + 1 then index++ and find the new window - might not need this
279 |
280 | // if the length of this new string is > cols + 1 then index++ and find the new window
281 | if ((arr[index] + list[0]).length > cols + 1) {
282 | index++;
283 | } else {
284 | arr[index] = arr[index] + list[0];
285 | arr[index + 1] = list[1];
286 | }
287 |
288 | // console.log('arr in last line of while loop ', arr);
289 | // if the length of this new string is < cols + 1, then arr[index] becomes this new string. arr[index + 1]; arr[index + 1] becomes the truncated part of the string
290 | }
291 | }
292 |
293 | if (arr[arr.length - 1] === null) {
294 | arr = arr.slice(0, arr.length - 1);
295 | }
296 |
297 | this.setState({
298 | displayArray: arr,
299 | start,
300 | end,
301 | });
302 | }
303 | }
304 |
305 | // ------
306 |
307 | handleBack() {
308 | if (this.state.displayArray[0][0] !== '|') {
309 | const arr = this.state.displayArray;
310 | let start = this.state.start;
311 | let end = this.state.end;
312 | const cols = this.state.columns;
313 | let index;
314 |
315 | let tempArray = this.findPosition(start, end);
316 |
317 | index = tempArray[0];
318 | start = tempArray[1];
319 | end = tempArray[2];
320 |
321 | const cp = arr[index].indexOf('|');
322 |
323 | if (cp === 0) {
324 | console.log('cp = 0 in handleBack');
325 | arr[index] = arr[index].slice(1);
326 | arr[index - 1] = `${arr[index - 1]}|`;
327 | tempArray = this.findPosition(start, end);
328 | index = tempArray[0];
329 | start = tempArray[1];
330 | end = tempArray[2];
331 | } else {
332 | arr[index] = `${arr[index].slice(0, cp - 1)}|${arr[index][cp - 1]}${arr[index].slice(
333 | cp + 1,
334 | )}`;
335 | }
336 |
337 | this.setState({
338 | displayArray: arr,
339 | start,
340 | end,
341 | });
342 | }
343 | }
344 |
345 | // ------
346 |
347 | handleForward() {
348 | if (
349 | this.state.displayArray[this.state.displayArray.length - 1][
350 | this.state.displayArray[this.state.displayArray.length - 1].length - 1
351 | ] !== '|'
352 | && this.state.displayArray[this.state.end + 1] !== ''
353 | ) {
354 | const arr = this.state.displayArray;
355 | let start = this.state.start;
356 | let end = this.state.end;
357 | const cols = this.state.columns;
358 | let index;
359 |
360 | let tempArray = this.findPosition(start, end);
361 |
362 | index = tempArray[0];
363 | start = tempArray[1];
364 | end = tempArray[2];
365 |
366 | const cp = arr[index].indexOf('|');
367 |
368 | if (cp === arr[index].length - 1) {
369 | console.log('cp = length - 1 in handleForward');
370 | arr[index] = arr[index].slice(0, arr[index].length - 1);
371 | arr[index + 1] = `|${arr[index + 1]}`;
372 | console.log('array[index] ', arr[index]);
373 | tempArray = this.findPosition(start, end);
374 | index = tempArray[0];
375 | start = tempArray[1];
376 | end = tempArray[2];
377 | } else {
378 | arr[index] = `${arr[index].slice(0, cp) + arr[index][cp + 1]}|${arr[index].slice(cp + 2)}`;
379 | }
380 |
381 | this.setState({
382 | displayArray: arr,
383 | start,
384 | end,
385 | });
386 | }
387 | }
388 |
389 | // ------
390 |
391 | handleSubmit() {
392 | const submitArray = this.state.displayArray;
393 | for (let i = 0; i < submitArray.length; i++) {
394 | submitArray[i] = submitArray[i]
395 | .split('')
396 | .filter(element => element !== '|')
397 | .join('');
398 | }
399 | this.setState({
400 | start: 0,
401 | end: this.props.rows - 1,
402 | displayArray: ['|'],
403 | text: '',
404 | submitHandler: this.props.onSubmit || null,
405 | showScroll: false,
406 | toggleCursor: true,
407 | x: -1,
408 | y: 0.2,
409 | z: -1.5,
410 | pages: 0,
411 | focus: false,
412 | counter: 0,
413 | opacity: 0,
414 | });
415 | this.props.onSubmit(submitArray.join(''));
416 | }
417 |
418 | // ------
419 |
420 | handleUp() {
421 | if (this.state.start !== 0) {
422 | this.setState({
423 | start: this.state.start - 1,
424 | end: this.state.end - 1,
425 | });
426 | }
427 | }
428 |
429 | // ------
430 |
431 | handleDown() {
432 | if (this.state.end !== this.state.displayArray.length - 1) {
433 | this.setState({
434 | start: this.state.start + 1,
435 | end: this.state.end + 1,
436 | });
437 | }
438 | }
439 |
440 | // ------
441 |
442 | showScroll() {
443 | let total = 0;
444 | for (let i = 0; i < this.state.displayArray.length; i++) {
445 | if (this.state.displayArray[i] != '') total++;
446 | }
447 |
448 | return total > this.state.rows ? 1 : this.state.opacity;
449 | }
450 |
451 | render() {
452 | const arr = this.state.displayArray;
453 | const start = this.state.start;
454 | const end = this.state.end;
455 | let displayString = '';
456 |
457 | for (let i = start; i <= end; i++) {
458 | if (arr[i]) {
459 | displayString += `${arr[i]}\n`;
460 | }
461 | }
462 |
463 | displayString = displayString.slice(0, displayString.length - 1);
464 |
465 | return (
466 |
471 |
472 |
473 |
486 | {displayString}
487 |
488 |
489 |
490 |
495 |
500 |
501 | {this.state.focus ? (
502 |
510 |
519 |
520 | ) : (
521 |
522 | )}
523 |
524 | );
525 | }
526 | }
527 |
528 | TextInput.propTypes = {
529 | defaultInput: PropTypes.string,
530 | };
531 |
532 | TextInput.defaultProps = {
533 | defaultInput: '',
534 | };
535 |
536 | export default TextInput;
537 |
--------------------------------------------------------------------------------
/start.js:
--------------------------------------------------------------------------------
1 | const shell = require('shelljs');
2 |
3 | shell.exec('node -e "console.log(\'open browser at http://localhost:8081/vr/\\n\\n\');"', {
4 | async: true,
5 | });
6 | shell.exec('nodemon server/app.js', { async: true });
7 | shell.exec('node node_modules/react-native/local-cli/cli.js start', { async: true });
8 |
--------------------------------------------------------------------------------
/static_assets/chess-world.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TeamRocketThesis/react-vr-textinput/51445d1cb6c814720f55e39b3db580c276cfbdea/static_assets/chess-world.jpg
--------------------------------------------------------------------------------
/static_assets/down.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TeamRocketThesis/react-vr-textinput/51445d1cb6c814720f55e39b3db580c276cfbdea/static_assets/down.png
--------------------------------------------------------------------------------
/static_assets/up.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TeamRocketThesis/react-vr-textinput/51445d1cb6c814720f55e39b3db580c276cfbdea/static_assets/up.png
--------------------------------------------------------------------------------
/vr/build/client.bundle.js.meta:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TeamRocketThesis/react-vr-textinput/51445d1cb6c814720f55e39b3db580c276cfbdea/vr/build/client.bundle.js.meta
--------------------------------------------------------------------------------
/vr/build/index.bundle.js.meta:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TeamRocketThesis/react-vr-textinput/51445d1cb6c814720f55e39b3db580c276cfbdea/vr/build/index.bundle.js.meta
--------------------------------------------------------------------------------
/vr/client.js:
--------------------------------------------------------------------------------
1 | // Auto-generated content.
2 | // This file contains the boilerplate to set up your React app.
3 | // If you want to modify your application, start in "index.vr.js"
4 |
5 | // Auto-generated content.
6 | import { VRInstance } from 'react-vr-web';
7 |
8 | function init(bundle, parent, options) {
9 | const vr = new VRInstance(bundle, 'Example', parent, {
10 | // Add custom options here
11 | enableHotReload: true,
12 | ...options,
13 | });
14 | vr.render = function () {
15 | // Any custom behavior you want to perform on each frame goes here
16 | };
17 | // Begin the animation loop
18 | vr.start();
19 | return vr;
20 | }
21 |
22 | window.ReactVR = { init };
23 |
--------------------------------------------------------------------------------
/vr/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | VrTextInput
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/vr/static_assets/chess-world.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TeamRocketThesis/react-vr-textinput/51445d1cb6c814720f55e39b3db580c276cfbdea/vr/static_assets/chess-world.jpg
--------------------------------------------------------------------------------