├── .gitignore ├── src ├── Components │ ├── App.css │ ├── App.tsx │ └── Canvas │ │ ├── Canvas.css │ │ └── Canvas.tsx ├── util │ └── Browser.ts ├── index.css ├── index.tsx └── index.html ├── tslint.json ├── .prettierrc ├── jestconfig.json ├── README.md ├── webpack.config.ts ├── LICENSE ├── package.json └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | # Build 2 | dist 3 | 4 | # Node Modules 5 | node_modules -------------------------------------------------------------------------------- /src/Components/App.css: -------------------------------------------------------------------------------- 1 | #App { 2 | height: 100vh; 3 | width: 100vw; 4 | } 5 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["tslint:recommended", "tslint-config-prettier", "tslint-react"], 3 | "rules": { 4 | "jsx-no-lambda": false 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "arrowParens": "always", 3 | "bracketSpacing": true, 4 | "endOfLine": "lf", 5 | "printWidth": 125, 6 | "semi": true, 7 | "tabWidth": 2, 8 | "singleQuote": true, 9 | "useTabs": false 10 | } 11 | -------------------------------------------------------------------------------- /jestconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "transform": { 3 | "^.+\\.(t|j)sx?$": "ts-jest" 4 | }, 5 | "testRegex": "(/__tests__/.*|(\\.|/)(test|spec))\\.(jsx?|tsx?)$", 6 | "moduleFileExtensions": ["ts", "tsx", "js", "jsx", "json", "node"] 7 | } 8 | -------------------------------------------------------------------------------- /src/util/Browser.ts: -------------------------------------------------------------------------------- 1 | export const getWindowHeight = () => { 2 | const { innerHeight } = window; 3 | 4 | return innerHeight; 5 | }; 6 | 7 | export const getWindowWidth = () => { 8 | const { innerWidth } = window; 9 | 10 | return innerWidth; 11 | }; 12 | -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | * { 2 | margin: 0; 3 | padding: 0; 4 | box-sizing: border-box; 5 | } 6 | 7 | body { 8 | background-color: #1f1b24; 9 | font-size: 10px; 10 | } 11 | 12 | input, 13 | label, 14 | p, 15 | a, 16 | span { 17 | font-family: Arial, Helvetica, sans-serif; 18 | } 19 | -------------------------------------------------------------------------------- /src/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { render } from 'react-dom'; 3 | import App from './Components/App'; 4 | import './index.css'; 5 | 6 | render( 7 | 8 | 9 | , 10 | document.getElementById('root') 11 | ); 12 | -------------------------------------------------------------------------------- /src/Components/App.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import Canvas from './Canvas/Canvas'; 3 | import './App.css'; 4 | 5 | const App = (): JSX.Element => { 6 | return ( 7 |
8 | 9 |
10 | ); 11 | }; 12 | 13 | export default App; 14 | -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Mouse Data Visualizer 7 | 8 | 9 |
10 | 11 | 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Mouse Data Visualizer 2 | 3 | ![screenshot](https://i.imgur.com/cEnMa0o.png) 4 | 5 | 6 | A visual playground for the WindMouse JavaScript library. Edit settings in real time and fine tune your mouse movements, powered by React. 7 | 8 | 9 | Visit live version: https://windmouse-visualizer.netlify.app/ 10 | 11 | ## Getting Started 12 | 13 | ``` 14 | 1. git clone https://github.com/arevi/mouse-data-visualizer.git 15 | 2. npm install 16 | 3. npm run dev:react 17 | ``` 18 | 19 | The application will then be visitable on localhost:3000 20 | 21 | ## WindMouse Library 22 | 23 | The WindMouse library is available here: https://github.com/arevi/wind-mouse/ 24 | 25 | ### Contributng 26 | 27 | Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change. 28 | 29 | ### License 30 | 31 | https://opensource.org/licenses/MIT 32 | 33 | ### Credits 34 | 35 | The original WindMouse library was created by https://github.com/BenLand100 for Java. 36 | -------------------------------------------------------------------------------- /webpack.config.ts: -------------------------------------------------------------------------------- 1 | import HtmlWebPackPlugin from 'html-webpack-plugin'; 2 | import path from 'path'; 3 | import Webpack from 'webpack'; 4 | 5 | const config: Webpack.Configuration = { 6 | name: 'react', 7 | entry: './src/index.tsx', 8 | module: { 9 | rules: [ 10 | { 11 | test: /\.(ts|tsx)$/, 12 | use: 'ts-loader', 13 | exclude: /node_modules/, 14 | }, 15 | { 16 | test: /\.css$/, 17 | use: ['style-loader', 'css-loader'], 18 | }, 19 | ], 20 | }, 21 | resolve: { 22 | extensions: ['.tsx', '.ts', '.js', '.css'], 23 | }, 24 | output: { 25 | path: __dirname + '/dist', 26 | publicPath: './', 27 | filename: 'app.js', 28 | }, 29 | devServer: { 30 | contentBase: path.join(__dirname, 'dist'), 31 | compress: true, 32 | port: 3000, 33 | hot: true, 34 | publicPath: '/', 35 | }, 36 | plugins: [ 37 | new HtmlWebPackPlugin({ 38 | filename: 'index.html', 39 | template: 'src/index.html', 40 | }), 41 | ], 42 | }; 43 | 44 | export default config; 45 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Armin Dizdarevic 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /src/Components/Canvas/Canvas.css: -------------------------------------------------------------------------------- 1 | canvas { 2 | height: 100%; 3 | width: 100%; 4 | border: 1px solid #525252; 5 | } 6 | 7 | #mouse-data { 8 | position: absolute; 9 | top: 1rem; 10 | left: 1rem; 11 | background-color: rgba(12, 12, 12, 0.4); 12 | padding: 1rem; 13 | } 14 | 15 | .mouse-data-row { 16 | display: flex; 17 | flex-flow: row; 18 | } 19 | 20 | .mouse-data-row:not(:first-of-type) { 21 | margin-top: 0.6rem; 22 | } 23 | 24 | .mouse-data-field:nth-of-type(even) { 25 | margin-left: 1rem; 26 | } 27 | 28 | .mouse-data-field { 29 | display: flex; 30 | flex-flow: column; 31 | width: 6.4rem; 32 | } 33 | 34 | .mouse-data-label { 35 | color: #525252; 36 | font-size: 1.2rem; 37 | } 38 | 39 | .mouse-data-input { 40 | outline: none; 41 | background-color: rgba(12, 12, 12, 0.4); 42 | padding: 0.4rem; 43 | border: 1px solid #525252; 44 | border-radius: 0.2rem; 45 | color: #747474; 46 | } 47 | 48 | .mouse-data-input::-webkit-outer-spin-button, 49 | .mouse-data-input::-webkit-inner-spin-button { 50 | -webkit-appearance: none; 51 | margin: 0; 52 | } 53 | 54 | .btn { 55 | margin-top: 0.6rem; 56 | width: 6.4rem; 57 | height: 2.2rem; 58 | background-color: rgba(12, 12, 12, 0.4); 59 | border: 1px solid #525252; 60 | border-radius: 0.2rem; 61 | outline: none; 62 | color: #747474; 63 | font-weight: bold; 64 | } 65 | 66 | .btn:hover { 67 | cursor: pointer; 68 | background-color: rgba(12, 12, 12, 1); 69 | border: 1px solid #424242; 70 | color: #747474; 71 | transition: all 0.2s ease-in; 72 | } 73 | 74 | .btn:not(:first-of-type) { 75 | margin-left: 1rem; 76 | } 77 | 78 | #credits { 79 | position: absolute; 80 | bottom: 0.4rem; 81 | left: 0.4rem; 82 | color: white; 83 | font-size: 1rem; 84 | font-weight: bolder; 85 | } 86 | 87 | a { 88 | text-decoration: none; 89 | font-family: Arial, Helvetica, sans-serif; 90 | } 91 | 92 | .red { 93 | color: red; 94 | } 95 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mouse-data-visualizer", 3 | "version": "1.0.0", 4 | "description": "A visual playground for the WindMouse JavaScript library. Edit settings in real time and fine tune your mouse movements.", 5 | "main": "src/index.tsx", 6 | "scripts": { 7 | "dev:webpack": "webpack --mode development", 8 | "dev:react": "webpack-dev-server --mode development --port 3000", 9 | "prod:webpack": "webpack --mode production", 10 | "format": "prettier --write \"src/**/*.tsx\"", 11 | "lint": "tslint -p tsconfig.json" 12 | }, 13 | "repository": { 14 | "type": "git", 15 | "directory": "https://github.com/arevi/mouse-data-visualizer.git" 16 | }, 17 | "bugs": { 18 | "url": "https://github.com/arevi/mouse-data-visualizer/issues" 19 | }, 20 | "keywords": [ 21 | "windmouse", 22 | "mouse movement", 23 | "visualizer", 24 | "javascript", 25 | "typescript", 26 | "react" 27 | ], 28 | "homepage": "https://github.com/arevi/mouse-data-visualizer", 29 | "author": "Armin Dizdarevic", 30 | "license": "MIT", 31 | "dependencies": { 32 | "@fortawesome/fontawesome-svg-core": "^1.2.30", 33 | "@fortawesome/free-solid-svg-icons": "^5.14.0", 34 | "@fortawesome/react-fontawesome": "^0.1.11", 35 | "@hookform/resolvers": "^0.1.1", 36 | "react": "^16.13.1", 37 | "react-dom": "^16.13.1", 38 | "react-hook-form": "^6.8.3", 39 | "windmouse": "^1.0.5", 40 | "yup": "^0.29.3" 41 | }, 42 | "devDependencies": { 43 | "@types/html-webpack-plugin": "^3.2.3", 44 | "@types/jest": "^26.0.14", 45 | "@types/node": "^14.11.1", 46 | "@types/react": "^16.9.49", 47 | "@types/react-dom": "^16.9.8", 48 | "@types/webpack": "^4.41.22", 49 | "@types/webpack-dev-server": "^3.11.0", 50 | "@types/yup": "^0.29.7", 51 | "css-loader": "^4.3.0", 52 | "html-webpack-plugin": "^4.4.1", 53 | "jest": "^26.4.2", 54 | "prettier": "^2.1.2", 55 | "style-loader": "^1.2.1", 56 | "ts-jest": "^26.4.0", 57 | "ts-loader": "^8.0.4", 58 | "ts-node": "^9.0.0", 59 | "tslint": "^6.1.3", 60 | "tslint-config-prettier": "^1.18.0", 61 | "tslint-react": "^5.0.0", 62 | "typescript": "^4.0.3", 63 | "webpack": "^4.44.2", 64 | "webpack-cli": "^3.3.12", 65 | "webpack-dev-server": "^3.11.0" 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Visit https://aka.ms/tsconfig.json to read more about this file */ 4 | 5 | /* Basic Options */ 6 | // "incremental": true, /* Enable incremental compilation */ 7 | "target": "es5" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */, 8 | "module": "commonjs" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */, 9 | // "lib": [], /* Specify library files to be included in the compilation. */ 10 | // "allowJs": true, /* Allow javascript files to be compiled. */ 11 | // "checkJs": true, /* Report errors in .js files. */ 12 | "jsx": "react" /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */, 13 | // "declaration": true, /* Generates corresponding '.d.ts' file. */ 14 | // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ 15 | // "sourceMap": true, /* Generates corresponding '.map' file. */ 16 | // "outFile": "./", /* Concatenate and emit output to single file. */ 17 | "outDir": "./dist" /* Redirect output structure to the directory. */, 18 | // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ 19 | // "composite": true, /* Enable project compilation */ 20 | // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ 21 | // "removeComments": true, /* Do not emit comments to output. */ 22 | // "noEmit": true, /* Do not emit outputs. */ 23 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */ 24 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ 25 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ 26 | 27 | /* Strict Type-Checking Options */ 28 | "strict": true /* Enable all strict type-checking options. */, 29 | // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ 30 | // "strictNullChecks": true, /* Enable strict null checks. */ 31 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */ 32 | // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ 33 | // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ 34 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ 35 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ 36 | 37 | /* Additional Checks */ 38 | // "noUnusedLocals": true, /* Report errors on unused locals. */ 39 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 40 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 41 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 42 | 43 | /* Module Resolution Options */ 44 | // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ 45 | // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ 46 | // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ 47 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ 48 | // "typeRoots": [], /* List of folders to include type definitions from. */ 49 | // "types": [], /* Type declaration files to be included in compilation. */ 50 | // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ 51 | "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */, 52 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ 53 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 54 | 55 | /* Source Map Options */ 56 | // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ 57 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 58 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ 59 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ 60 | 61 | /* Experimental Options */ 62 | // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ 63 | // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ 64 | 65 | /* Advanced Options */ 66 | "skipLibCheck": true /* Skip type checking of declaration files. */, 67 | "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ 68 | }, 69 | "include": ["./src/**/*"], 70 | "exclude": ["./node_modules", "./dist"] 71 | } 72 | -------------------------------------------------------------------------------- /src/Components/Canvas/Canvas.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { useForm } from 'react-hook-form'; 3 | import { MouseSettings } from 'windmouse/lib/Types'; 4 | import { getWindowWidth, getWindowHeight } from '../../util/Browser'; 5 | import { yupResolver } from '@hookform/resolvers'; 6 | import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; 7 | import { faPlay, faSync } from '@fortawesome/free-solid-svg-icons'; 8 | import WindMouse from 'windmouse'; 9 | import * as yup from 'yup'; 10 | import './Canvas.css'; 11 | 12 | const schema = yup.object().shape({ 13 | startX: yup.number().required(), 14 | startY: yup.number().required(), 15 | endX: yup.number().required(), 16 | endY: yup.number().required(), 17 | gravity: yup.number().required(), 18 | wind: yup.number().required(), 19 | minWait: yup.number().required(), 20 | maxWait: yup.number().required(), 21 | maxStep: yup.number().required(), 22 | targetArea: yup.number().required(), 23 | }); 24 | 25 | const Canvas = (): JSX.Element => { 26 | const canvasRef = React.useRef(null); 27 | const [context, setContext] = React.useState(null); 28 | const { register, handleSubmit, setValue } = useForm({ 29 | resolver: yupResolver(schema), 30 | }); 31 | 32 | React.useEffect(() => { 33 | if (canvasRef.current) { 34 | const renderCtx = canvasRef.current.getContext('2d'); 35 | 36 | if (renderCtx) { 37 | setContext(renderCtx); 38 | randomizeValues(); 39 | } 40 | } 41 | }, []); 42 | 43 | const onSubmit = async (values: MouseSettings) => { 44 | let points = await generatePoints(values); 45 | await drawCanvas(points); 46 | }; 47 | 48 | const drawCanvas = (points: number[][]): Promise => { 49 | if (!context) return Promise.reject(); 50 | 51 | context.clearRect(0, 0, context.canvas.width, context.canvas.height); 52 | 53 | context.beginPath(); 54 | context.lineCap = 'round'; 55 | context.strokeStyle = '#ffffff'; 56 | context.fillStyle = 'red'; 57 | 58 | points.forEach((point, index) => { 59 | const [x, y, time] = point; 60 | 61 | setTimeout(() => { 62 | context.lineTo(x, y); 63 | context.stroke(); 64 | if (index === 0 || index === points.length - 1) { 65 | context.fillRect(x, y, 5, 5); 66 | context.fillText(`(${x}, ${y})`, x + 10, y + 10); 67 | } 68 | }, time); 69 | }); 70 | 71 | return Promise.resolve(); 72 | }; 73 | 74 | const generatePoints = async (settings: MouseSettings): Promise => { 75 | const windMouse: WindMouse = new WindMouse(Math.ceil(Math.random() * 10)); 76 | const points: number[][] = await windMouse.GeneratePoints({ ...settings }); 77 | return Promise.resolve(points); 78 | }; 79 | 80 | const randomizeValues = () => { 81 | setValue('startX', Math.ceil(Math.random() * getWindowWidth())); 82 | setValue('startY', Math.ceil(Math.random() * getWindowHeight())); 83 | setValue('endX', Math.ceil(Math.random() * getWindowWidth())); 84 | setValue('endY', Math.ceil(Math.random() * getWindowHeight())); 85 | setValue('gravity', Math.ceil(Math.random() * 10)); 86 | setValue('wind', Math.ceil(Math.random() * 10)); 87 | setValue('minWait', 1); 88 | setValue('maxWait', Math.ceil(Math.random() * 5)); 89 | setValue('maxStep', Math.ceil(Math.random() * 3)); 90 | setValue('targetArea', Math.ceil(Math.random() * 10)); 91 | }; 92 | 93 | return ( 94 | 95 |
96 |
97 |
98 | 101 | 102 |
103 |
104 | 107 | 108 |
109 |
110 |
111 |
112 | 115 | 116 |
117 |
118 | 121 | 122 |
123 |
124 | 125 |
126 |
127 | 130 | 131 |
132 |
133 | 136 | 137 |
138 |
139 | 140 |
141 |
142 | 145 | 146 |
147 | 148 |
149 | 152 | 153 |
154 |
155 | 156 |
157 |
158 | 161 | 162 |
163 |
164 | 167 | 168 |
169 |
170 |
171 | 174 | 177 |
178 |
179 | 180 | 181 |

182 | Made with by{' '} 183 | 184 | Arevi 185 | 186 |

187 |
188 | ); 189 | }; 190 | 191 | export default Canvas; 192 | --------------------------------------------------------------------------------