├── .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 | 
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 |
179 |
180 |
181 |
182 | Made with ❤ by{' '}
183 |
184 | Arevi
185 |
186 |
187 |
188 | );
189 | };
190 |
191 | export default Canvas;
192 |
--------------------------------------------------------------------------------