makeachoice/src/index.js

307 lines
7.6 KiB
JavaScript

import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
function shuffleArray(array) {
/* https://stackoverflow.com/a/12646864 */
for (let i = array.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[array[i], array[j]] = [array[j], array[i]];
}
}
function choicesToMarkdown(choices) {
const choiceNames = choices.map((c) => c.choice);
const choiceWidth = Math.max(
...choiceNames.concat("Choice").map((x) => x.length + 3)
);
const countWidth = 6;
let r = "| Choice".padEnd(choiceWidth, " ") + "| Count |\n";
r += "|".padEnd(choiceWidth, "-") + "|-------|\n";
for (const c of choices) {
r += ("| " + c.choice).padEnd(choiceWidth, " ") + "| ";
r += c.count.toString().padStart(countWidth, " ") + "|\n";
}
return r;
}
class Decision {
constructor(indexA, indexB, choices) {
this.indexA = indexA;
this.indexB = indexB;
this.choiceA = choices[indexA];
this.choiceB = choices[indexB];
}
static createDecisions(choices) {
let decisions = [];
for (var i = 0; i < choices.length; i++) {
for (var j = 0; j < choices.length; j++) {
if (i !== j) {
let d = new Decision(i, j, choices);
decisions.push(d);
}
}
}
shuffleArray(decisions);
return decisions.slice(0, 50);
}
}
class Options extends React.Component {
constructor(props) {
super(props);
this.state = {
choicesText: "",
choices: [],
decisions: null,
decisionsMade: null,
currentDecision: null,
};
}
startChoosing() {
this.setState({
decisions: Decision.createDecisions(this.state.choices),
decisionsMade: [],
currentDecision: 0,
});
}
choicesTextToChoices(choicesText) {
let choices = choicesText.split("\n");
choices = choices.filter((choice) => choice.length > 0);
return choices;
}
choicesOnChange(event) {
let choicesText = event.target.value;
let choices = this.choicesTextToChoices(choicesText);
this.setState({
choicesText: choicesText,
choices: choices,
});
}
makeChoice(i) {
this.setState({
currentDecision: this.state.currentDecision + 1,
decisionsMade: this.state.decisionsMade.concat([i]),
});
}
startAgain() {
this.setState({
decisions: null,
currentDecision: null,
decisionsMade: null,
});
}
renderChoosing() {
const current = this.state.currentDecision;
const decision = this.state.decisions[current];
const progress = (current / this.state.decisions.length) * 100;
let choiceA = decision.choiceA;
let choiceB = decision.choiceB;
if (choiceA.length > 30) {
choiceA = choiceA.slice(0, 30);
}
if (choiceB.length > 30) {
choiceB = choiceB.slice(0, 40);
}
return (
<div>
<div className="pure-g">
<div className="pure-u-1 space-1">
<button
className="pure-button pure-u-1 pure-u-sm-9-24 pure-button-primary"
onClick={() => this.makeChoice(decision.indexA)}
>
{choiceA}
</button>
<div className="pure-u-1-24" />
<button
className="pure-button pure-u-1 pure-u-sm-9-24 pure-button-primary"
onClick={() => this.makeChoice(decision.indexB)}
>
{choiceB}
</button>
<div className="pure-u-1-24" />
<button
className="pure-button pure-u-1 pure-u-sm-4-24"
onClick={() => this.makeChoice(-1)}
>
skip
</button>
</div>
</div>
<div className="pure-g space-1">
<progress className="pure-u-1" max="100" value={progress}>
{" "}
</progress>
</div>
</div>
);
}
renderButton() {
let choices = this.state.choices;
let button = null;
if (choices.length > 1 && choices[1]) {
button = (
<button
className="pure-button pure-button-primary space-1"
onClick={() => this.startChoosing()}
>
Start choosing
</button>
);
} else {
button = (
<button className="pure-button pure-button-disabled space-1">
Start choosing
</button>
);
}
return button;
}
renderChoiceInputField() {
return (
<div className="pure-u-1">
<h2 className="content-subhead">Type or paste your choices</h2>
<form className="pure-form" onSubmit={() => this.onSubmit}>
<fieldset className="pure-group">
<textarea
onChange={(event) => this.choicesOnChange(event)}
value={this.state.choicesText}
className="pure-input-1"
placeholder="Enter one choice per line"
/>
</fieldset>
</form>
</div>
);
}
renderCurrentChoices() {
let choices = this.state.choices;
let choicesToRender = choices.map((choice, index) => {
return <li key={index}>{choice}</li>;
});
let button = this.renderButton();
return (
<div className="pure-u-1">
<h2 className="content-subhead">Your choices</h2>
<div className="pure-menu">
<ul>{choicesToRender}</ul>
</div>
<div>{button}</div>
</div>
);
}
renderSpecifyChoices() {
let input = this.renderChoiceInputField();
let output = this.renderCurrentChoices();
return (
<div className="pure-g">
{input}
{output}
</div>
);
}
renderResult() {
let choices = this.state.choices.map((choice, index) => {
return {
choice: choice,
count: 0,
relativeCount: 0,
};
});
let totalCount = 0;
for (const i of this.state.decisionsMade) {
if (i !== -1) {
choices[i].count += 1;
totalCount += 1;
}
}
for (let c of choices) {
c.relativeCount = (c.count / totalCount) * 100;
}
choices.sort((a, b) => b.count - a.count);
let choicesToRender = choices.map((choice, index) => {
return (
<tr key={index}>
<td>{choice.choice}</td>
<td>{choice.count}</td>
<td>
<progress max="100" value={choice.relativeCount} />
</td>
</tr>
);
});
let choicesMarkdown = choicesToMarkdown(choices);
return (
<div className="pure-g">
<div className="pure-u-1">
<table className="pure-table space-1">
<thead>
<tr>
<th>Choice</th>
<th>Count</th>
<th></th>
</tr>
</thead>
<tbody>{choicesToRender}</tbody>
</table>
</div>
<div className="pure-u-1 space-1">
<p>
<button
className="pure-button button-warning pure-button-again"
onClick={() => this.startAgain()}
>
Start again
</button>
<button
className="pure-button button-secondary"
onClick={() => {
navigator.clipboard.writeText(choicesMarkdown);
}}
>
Copy result
</button>
</p>
</div>
</div>
);
}
render() {
if (!this.state.decisions) {
return this.renderSpecifyChoices();
} else if (this.state.currentDecision < this.state.decisions.length) {
return this.renderChoosing();
} else {
return this.renderResult();
}
}
}
ReactDOM.render(<Options />, document.getElementById("root"));