├── .gitignore
├── .npmignore
├── README.md
├── index.jsx
├── package.json
└── screenshot.gif
/.gitignore:
--------------------------------------------------------------------------------
1 | npm-debug.log*
2 | node_modules
3 | index.js
4 | .tern-port
5 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | index.jsx
2 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # RagePower
2 |
3 | [](https://npmjs.org/package/rage-power)
4 |
5 | A React wrapper component for your rage typing need.
6 |
7 | > Inspired by this [atom plugin](https://github.com/lukyth/power-mode)
8 |
9 | 
10 |
11 | ## Installation
12 |
13 | ```
14 | npm install --save rage-power
15 | ```
16 |
17 | ## Usage
18 |
19 | Wrap this component around your input component.
20 |
21 | ```
22 | import RagePower from 'rage-power';
23 |
24 | const RageTextarea = () => (
25 |
26 |
27 |
28 | );
29 | ```
30 |
31 | ## License
32 | MIT © [Zero Cho](http://itsze.ro)
33 |
--------------------------------------------------------------------------------
/index.jsx:
--------------------------------------------------------------------------------
1 | import getCaretCoordinates from 'textarea-caret';
2 | import ReactDOM from 'react-dom';
3 | import throttle from 'lodash.throttle';
4 | import React, { PropTypes } from 'react';
5 |
6 | const MAX_PARTICLES = 500;
7 | const PARTICLE_NUM_RANGE = () => 5 + Math.round(Math.random() * 5);
8 | const PARTICLE_GRAVITY = 0.075;
9 | const PARTICLE_ALPHA_FADEOUT = 0.96;
10 | const PARTICLE_VELOCITY_RANGE = {
11 | x: [-1, 1],
12 | y: [-3.5, -1.5]
13 | };
14 | const COLORS = [
15 | '#1f77b4',
16 | '#ff7f0e',
17 | '#2ca02c',
18 | '#d62728',
19 | '#9467bd',
20 | '#8c564b',
21 | '#e377c2',
22 | '#bcbd22',
23 | '#17becf'
24 | ];
25 |
26 | class RagePower extends React.Component {
27 | static propTypes = {
28 | children: PropTypes.node,
29 | onInput: PropTypes.func,
30 | colors: PropTypes.array
31 | }
32 |
33 | static defaultProps = {
34 | colors: COLORS
35 | }
36 |
37 | constructor(props, context) {
38 | super(props, context);
39 | this._drawFrame = this._drawFrame.bind(this);
40 | this._onInput = this._onInput.bind(this);
41 | this._shake = throttle(this._shake.bind(this), 100, { trailing: false });
42 | this._spawnParticles = throttle(this._spawnParticles.bind(this), 25, { trailing: false });
43 | this._particles = [];
44 | }
45 |
46 | componentDidMount() {
47 | this.canvas = document.createElement('canvas');
48 | this.canvas.width = window.innerWidth;
49 | this.canvas.height = window.innerHeight;
50 | this.canvas.style.position = 'absolute';
51 | this.canvas.style.top = '0';
52 | this.canvas.style.pointerEvents = 'none';
53 | this.canvasContext = this.canvas.getContext('2d');
54 | document.body.appendChild(this.canvas);
55 | window.requestAnimationFrame(this._drawFrame);
56 | }
57 |
58 | componentWillUnmount() {
59 | document.body.removeChild(this.canvas);
60 | }
61 |
62 | render() {
63 | const { children, style, colors: _, ...others } = this.props;
64 | const newChildren = React.cloneElement(children, {
65 | onInput: this._onInput
66 | });
67 |
68 | return (
69 |
this.node = ref}
73 | >
74 | { newChildren }
75 |
76 | );
77 | }
78 |
79 | /**
80 | * Following code is ported from: https://atom.io/packages/power-mode
81 | */
82 | _drawFrame() {
83 | this.canvasContext.clearRect(0, 0, this.canvas.width, this.canvas.height);
84 | this._particles.forEach((particle) => {
85 | particle.velocity.y += PARTICLE_GRAVITY;
86 | particle.x += particle.velocity.x;
87 | particle.y += particle.velocity.y;
88 | particle.alpha *= PARTICLE_ALPHA_FADEOUT;
89 |
90 | this.canvasContext.fillStyle = `rgba(${particle.color.join(',')}, ${particle.alpha})`;
91 | this.canvasContext.fillRect(Math.round(particle.x - 1), Math.round(particle.y - 1), 3, 3);
92 | });
93 | this._particles = this._particles
94 | .slice(Math.max(this._particles.length - MAX_PARTICLES, 0))
95 | .filter((particle) => particle.alpha > 0.1);
96 | window.requestAnimationFrame(this._drawFrame);
97 | }
98 |
99 | _shake() {
100 | const intensity = 1 + 2 * Math.random();
101 | const x = intensity * (Math.random() > 0.5 ? -1 : 1);
102 | const y = intensity * (Math.random() > 0.5 ? -1 : 1);
103 |
104 | this.node.style.transform = `translate3d(${x}px, ${y}px, 0)`;
105 |
106 | setTimeout(() => this.node.style.transform = '', 75);
107 | }
108 |
109 | _spawnParticles(x, y) {
110 | const { colors } = this.props;
111 | const numParticles = PARTICLE_NUM_RANGE();
112 | for (let i = 0; i < numParticles; i++) {
113 | const colorCode = colors[i % colors.length];
114 | const r = parseInt(colorCode.slice(1, 3), 16);
115 | const g = parseInt(colorCode.slice(3, 5), 16);
116 | const b = parseInt(colorCode.slice(5, 7), 16);
117 | const color = [r, g, b];
118 | this._particles.push(this._createParticle(x, y, color));
119 | }
120 | }
121 |
122 | _createParticle(x, y, color) {
123 | return {
124 | x,
125 | y: y,
126 | alpha: 1,
127 | color,
128 | velocity: {
129 | x: PARTICLE_VELOCITY_RANGE.x[0] + Math.random() *
130 | (PARTICLE_VELOCITY_RANGE.x[1] - PARTICLE_VELOCITY_RANGE.x[0]),
131 | y: PARTICLE_VELOCITY_RANGE.y[0] + Math.random() *
132 | (PARTICLE_VELOCITY_RANGE.y[1] - PARTICLE_VELOCITY_RANGE.y[0])
133 | }
134 | };
135 | }
136 |
137 | _onInput(...args) {
138 | const { onInput } = this.props;
139 | onInput && onInput(...args);
140 | this._shake();
141 | const target = args[0].target;
142 | const origin = target.getBoundingClientRect();
143 | const { top, left } = getCaretCoordinates(target, target.selectionEnd);
144 | const charHeight = parseInt(getComputedStyle(target)['font-size']);
145 | setTimeout(() => this._spawnParticles(left + origin.left, top + origin.top + charHeight), 0);
146 | }
147 | }
148 |
149 | export default RagePower;
150 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "rage-power",
3 | "version": "1.0.0",
4 | "description": "Activate the raging power mode in the React world",
5 | "jsnext:main": "index.jsx",
6 | "main": "index.js",
7 | "scripts": {
8 | "build": "babel index.jsx -o index.js",
9 | "prepublish": "npm run build"
10 | },
11 | "keywords": [
12 | "power",
13 | "mode",
14 | "rage",
15 | "react"
16 | ],
17 | "author": "Zero Cho ",
18 | "license": "MIT",
19 | "dependencies": {
20 | "lodash.throttle": "^4.0.1",
21 | "textarea-caret": "^3.0.1"
22 | },
23 | "devDependencies": {
24 | "babel-cli": "^6.10.1",
25 | "babel-plugin-syntax-object-rest-spread": "^6.8.0",
26 | "babel-plugin-transform-class-properties": "^6.10.2",
27 | "babel-plugin-transform-es2015-modules-commonjs": "^6.10.3",
28 | "babel-plugin-transform-object-rest-spread": "^6.8.0",
29 | "babel-preset-es2015": "^6.9.0",
30 | "babel-preset-react": "^6.11.1"
31 | },
32 | "peerDependencies": {
33 | "react": ">= 0.14",
34 | "react-dom": ">= 0.14"
35 | },
36 | "repository": {
37 | "type": "git",
38 | "url": "git://github.com/itszero/rage-power.git"
39 | },
40 | "babel": {
41 | "presets": ["es2015", "react"],
42 | "plugins": [
43 | "syntax-object-rest-spread",
44 | "transform-object-rest-spread",
45 | "transform-class-properties",
46 | "transform-es2015-modules-commonjs"
47 | ]
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/screenshot.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/itszero/rage-power/969e4b50fc03ad7669be5e5d33af7eefb55c0ceb/screenshot.gif
--------------------------------------------------------------------------------