<div id="root"></div>
// copy this into your project
@mixin colorcycle($color-list: #222 #111, $duration: 5s, $name: "colorcycle") {
& {
// target only the element with these params.
background-color: nth($color-list, 1);
animation-name: #{$name};
//animation is named after the element it resides in.
animation-duration: $duration;
animation-timing-function: linear;
animation-iteration-count: infinite;
}
@keyframes #{$name} {
// go through each color in the list
@each $color in $color-list {
// get the current index of the color
$i: index($color-list, $color);
// Set the percent of the animation frames based on how many colors are in the list
$percent: ($i - 1)/length($color-list)*100;
#{$percent}% {
background-color: $color;
}
}
100% {
background-color: nth($color-list,1);
}
}
}
input, select {
border:0;
display:inline;
background-color: rgba(0,0,20,0.4);
color: #DEDEDE;
padding: 3% 4%;
&.invalid {
border-left: 0.2rem solid #FA3433;
}
&:focus {
background-color: #222244;
outline-width: 0;
}
}
.matrix {
width:100%;
input {
cursor: pointer;
border-radius: 0;
background-color: rgba(0,0,20,0.2);
border:0;
display:inline;
font-size:1.2rem;
width: 50%;
text-align:center;
padding: 4% 0%;
transition: background-color 0.2s;
&:hover {
transition: background-color 0.2s;
background-color: rgba(10,10,20,0.5);
}
&:focus {
transition: background-color 0.2s;
@include colorcycle(rgba(0,0,20,0.2) rgba(50,50,200,0.5), 0.5s, "bluecycle");
color: transparent;
outline-width: 0;
text-shadow: 0px 0px 0px #FFFFFF;
}
&::placeholder {
text-shadow: 0px 0px 0.1px rgba(255,255,255,0.3);
}
}
&.matrix-4 input{
width: 50%;
}
&.matrix-9 input {
width: 33%;
}
&.matrix-16 input{
width: 25%;
}
}
body {
display: flex;
align-items: center;
justify-content: center;
width: 100vw;
height: 100vh;
overflow: hidden;
color: #DEDEDE;
background-color: #090915;
}
hr {
height:0.2rem;
border:0;
background-color: #292931;
}
.editor {
margin-top: -1.2rem;
border-radius: 0.4rem;
padding: 1.2rem 1.2rem;
background-color: #333338;
border: 1px solid black;
width: 28rem;
}
.functions {
margin-bottom: 0.5rem;
display:flex;
justify-content: center;
input {
width:80%;
}
select {
border: 0;
margin-left: 0.5rem;
appearance: none
}
}
.code {
display:flex;
justify-content: center;
}
pre {
width:50%;
padding:1rem;
background-color: rgba(0,0,20,0.4);
font-size: 11px;
margin:0;
margin-top: 0.5rem;
&:last-child {
margin-left: 0.5rem;
}
}
View Compiled
const MAT4SIZE = 16;
const MATSIZES = {
MAT4: 16,
MAT3: 9,
MAT2: 4
};
const MATRICES = {
MAT4: "MAT4",
MAT3: "MAT3",
MAT2: "MAT2"
};
const MATLENGTH = {
16: MATRICES.MAT4,
9: MATRICES.MAT3,
4: MATRICES.MAT2
};
const ROWLENGTH = {
16: 4,
9: 3,
4: 2
};
const STRIP_BRACKETS = /[\[\]\s]/g;
let reduceMat = (matrix, size) => {
return matrix.reduce((a, v, i) => {
return (
a +
` ${v}${i < matrix.length - 1 ? "," : ""}` +
`${i % size == size - 1 ? "\n" : ""}`
);
}, "\n");
};
let renderGLSLMatrix = matrix => {
if(MATLENGTH[matrix.length]) {
return `${MATLENGTH[matrix.length].toLowerCase()}(${reduceMat(matrix, ROWLENGTH[matrix.length])})`;
}
};
let renderJSMatrix = matrix => {
if(ROWLENGTH[matrix.length]) {
return `[${reduceMat(matrix, ROWLENGTH[matrix.length])}]`;
}
};
let shrinkMat = (matrix, newSize) => {
let currentRowSize = ROWLENGTH[matrix.length];
let rowSize = ROWLENGTH[newSize];
let sliceSize = matrix.length - newSize;
return matrix
.map((v, i) => {
let remove = i % currentRowSize + 1 > rowSize || i > newSize + 1;
if (remove) {
sliceSize--;
}
return remove ? null : v;
})
.filter(v => v != null);
};
let expandMat = (matrix, newSize) => {
return matrix.concat(new Array(newSize - matrix.length).fill(0));
};
let validMatrix = matrix => {
return ROWLENGTH[matrix.length] != undefined;
};
class OptionSelect extends React.Component {
static defaultProps = {
selected: null,
options: ["none"],
onChange: e => console.log(e)
};
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
}
handleChange(e) {
this.props.onChange(e.target.value);
}
render() {
return (
<select onChange={this.handleChange}>
{this.props.options.map(o => (
<option selected={this.props.selected == o}>{o}</option>
))}
</select>
);
}
}
class MatrixInput extends React.Component {
static defaultProps = {
matrix: new Array(16).fill(0),
onSubmit: e => console.log(e)
};
state = {
focus: false,
valid: null
};
constructor(props) {
super(props);
this.handleFocus = this.handleFocus.bind(this);
this.handleBlur = this.handleBlur.bind(this);
this.submit = this.submit.bind(this);
}
handleBlur(e) {
this.setState({ focus: false });
}
handleFocus(e) {
this.setState({ focus: true });
}
submit(e) {
if(e.target.value != "") {
let matrix = this.parseInput(e.target.value);
if (this.state.focus && validMatrix(matrix)) {
this.props.onSubmit(matrix);
}
this.setState({ valid: validMatrix(matrix) });
return;
}
this.setState({valid: null});
}
parseInput(input) {
let parsedInput = input.replace(STRIP_BRACKETS, "");
return parsedInput.split(",").map(v=>v=="" ? 0 : v);
}
render() {
return (
<input
placeholder="Type / Paste Matrix Here. Comma Delimited."
className={this.state.valid || this.state.valid == null ? "" : "invalid"}
type="text"
onKeyUp={this.submit}
onFocus={this.handleFocus}
onBlur={this.handleBlur}
/>
);
}
}
class MatrixEdit extends React.Component {
static defaultProps = {
matrix: new Array(16).fill(0),
onChange: e => console.log(e)
};
state = {
matrix: [],
};
selected = null;
constructor(props) {
super(props);
this.handleFocus = this.handleFocus.bind(this);
this.handleBlur = this.handleBlur.bind(this);
this.handleChange = this.handleChange.bind(this);
this.handleMouseWheel = this.handleMouseWheel.bind(this);
addEventListener("wheel", this.handleMouseWheel);
}
componentDidMount() {
this.setState({ matrix: this.props.matrix });
}
componentWillReceiveProps(newProps, props) {
if (props.matrix != newProps.matrix) {
this.setState({ matrix: newProps.matrix });
}
}
handleBlur(e) {
if (this.selected.value === "") {
let index = parseInt(e.target.name, 10);
let value = this.state.matrix[index];
this.updateValue(index, value)
}
this.selected = null;
}
handleMouseWheel(e) {
if(this.selected && !isNaN(this.state.matrix[parseInt(this.selected.name, 10)])) {
let index = parseInt(this.selected.name, 10);
let value = parseFloat((this.state.matrix[index]-(e.deltaY/10000)).toFixed(4),10);
this.updateValue(index, value);
}
}
updateValue(index, value) {
let newMatrix = this.setIndex(index, value == "" ? 0 : value);
this.props.onChange(newMatrix);
}
handleFocus(e) {
this.selected = e.target;
this.selected.placeholder = this.selected.value;
this.selected.value = "";
}
handleChange(e) {
let index = parseInt(e.target.name, 10);
this.updateValue(index, e.target.value)
}
setIndex(index, value) {
let matrix = this.state.matrix.slice();
matrix[index] = value;
return matrix;
}
render() {
let components = [];
this.state.matrix.map((v, i) => {
components.push(
<input
value={v}
type="text"
name={i}
onChange={this.handleChange}
onFocus={this.handleFocus}
onBlur={this.handleBlur}
/>
);
});
return (
<div className={`matrix matrix-${this.state.matrix.length}`}>
{components}
</div>
);
}
}
class MatrixDisplay extends React.Component {
static defaultProps = {
matrix: new Array(16).fill(0)
};
state = {
matrix: []
};
constructor(props) {
super(props);
}
componentDidMount() {
this.setState({ matrix: this.props.matrix });
}
componentWillReceiveProps(newProps, props) {
if (props.matrix != newProps.matrix) {
this.setState({ matrix: newProps.matrix });
}
}
render() {
return (
<div className="code">
<pre>{renderGLSLMatrix(this.state.matrix)}</pre>
<pre>{renderJSMatrix(this.state.matrix)}</pre>
</div>
);
}
}
class App extends React.Component {
state = {
size: MATRICES.MAT4,
matrix: new Array(MATSIZES[MATRICES.MAT4]).fill(0)
};
constructor() {
super();
this.changeMatrix = this.changeMatrix.bind(this);
this.pasteMatrix = this.pasteMatrix.bind(this);
}
changeMatrix(newMatrix) {
this.setState({ matrix: newMatrix });
}
pasteMatrix(newMatrix) {
let size = MATLENGTH[newMatrix.length];
this.setState({ matrix: newMatrix, size: size });
}
changeSize(option) {
let size = MATSIZES[option];
let matrix = [];
if (size < this.state.matrix.length) {
matrix = shrinkMat(this.state.matrix, size);
} else {
matrix = expandMat(this.state.matrix, size);
}
this.setState({ matrix: matrix, size: MATRICES[option] });
}
render() {
return (
<div className="editor">
<div className="functions">
<MatrixInput onSubmit={this.pasteMatrix} />
<OptionSelect
onChange={this.changeSize.bind(this)}
options={Object.keys(MATRICES)}
selected={this.state.size}
/>
</div>
<MatrixEdit matrix={this.state.matrix} onChange={this.changeMatrix} />
<MatrixDisplay matrix={this.state.matrix} />
</div>
);
}
}
ReactDOM.render(<App />, window.root);
View Compiled
This Pen doesn't use any external CSS resources.