Improve usability and efficiency
Allow "saving" a set with no changes Update cleanseVocabString function to be inline with Cloud Function Combine CreateSet and EditSet components Remove redundancy and significantly improve efficiency in the EditSet component
This commit is contained in:
@@ -318,13 +318,13 @@ class App extends React.Component {
|
|||||||
}
|
}
|
||||||
</Route>
|
</Route>
|
||||||
<Route path="/create-set" exact>
|
<Route path="/create-set" exact>
|
||||||
<CreateSet db={db} user={this.state.user} logEvent={analytics.logEvent} page={this.page} />
|
<EditSet db={db} user={this.state.user} logEvent={analytics.logEvent} page={this.page} createSet={true} />
|
||||||
</Route>
|
</Route>
|
||||||
<Route path="/my-sets" exact>
|
<Route path="/my-sets" exact>
|
||||||
<UserSets db={db} functions={functions} user={this.state.user} logEvent={analytics.logEvent} page={this.page} />
|
<UserSets db={db} functions={functions} user={this.state.user} logEvent={analytics.logEvent} page={this.page} />
|
||||||
</Route>
|
</Route>
|
||||||
<Route path="/sets/:setId/edit" exact>
|
<Route path="/sets/:setId/edit" exact>
|
||||||
<EditSet db={db} user={this.state.user} logEvent={analytics.logEvent} page={this.page} />
|
<EditSet db={db} user={this.state.user} logEvent={analytics.logEvent} page={this.page} createSet={false} />
|
||||||
</Route>
|
</Route>
|
||||||
<Route path="/history" exact>
|
<Route path="/history" exact>
|
||||||
<History db={db} user={this.state.user} logEvent={analytics.logEvent} page={this.page} />
|
<History db={db} user={this.state.user} logEvent={analytics.logEvent} page={this.page} />
|
||||||
|
|||||||
295
src/CreateSet.js
295
src/CreateSet.js
@@ -1,295 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import { withRouter } from 'react-router-dom';
|
|
||||||
import NavBar from "./NavBar";
|
|
||||||
import Button from "./Button";
|
|
||||||
import Footer from "./Footer";
|
|
||||||
import { HomeRounded as HomeRoundedIcon, CheckCircleOutlineRounded as CheckCircleOutlineRoundedIcon } from "@material-ui/icons";
|
|
||||||
import Checkbox from '@material-ui/core/Checkbox';
|
|
||||||
|
|
||||||
// import { doc, collection, setDoc, writeBatch } from "firebase/firestore";
|
|
||||||
|
|
||||||
import "./css/Form.css";
|
|
||||||
|
|
||||||
export default withRouter(class CreateSet extends React.Component {
|
|
||||||
constructor(props) {
|
|
||||||
super(props);
|
|
||||||
this.state = {
|
|
||||||
user: props.user,
|
|
||||||
db: props.db,
|
|
||||||
loading: false,
|
|
||||||
canCreateSet: false,
|
|
||||||
inputContents: [],
|
|
||||||
inputs: {
|
|
||||||
title: "",
|
|
||||||
public: false,
|
|
||||||
},
|
|
||||||
navbarItems: [
|
|
||||||
{
|
|
||||||
type: "link",
|
|
||||||
link: "/",
|
|
||||||
icon: <HomeRoundedIcon />,
|
|
||||||
hideTextMobile: true,
|
|
||||||
}
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
let isMounted = true;
|
|
||||||
Object.defineProperty(this, "isMounted", {
|
|
||||||
get: () => isMounted,
|
|
||||||
set: (value) => isMounted = value,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
setState = (state, callback = null) => {
|
|
||||||
if (this.isMounted) super.setState(state, callback);
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidMount() {
|
|
||||||
document.title = "Create Set | Parandum";
|
|
||||||
this.setNameInput.focus();
|
|
||||||
|
|
||||||
this.props.page.load();
|
|
||||||
|
|
||||||
this.props.logEvent("page_view");
|
|
||||||
}
|
|
||||||
|
|
||||||
componentWillUnmount() {
|
|
||||||
this.isMounted = false;
|
|
||||||
this.props.page.unload();
|
|
||||||
}
|
|
||||||
|
|
||||||
stopLoading = () => {
|
|
||||||
this.setState({
|
|
||||||
canCreateSet: true,
|
|
||||||
loading: false,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
cleanseVocabString = (item) => {
|
|
||||||
const chars = " .,()-_'\"";
|
|
||||||
|
|
||||||
let newString = item;
|
|
||||||
|
|
||||||
chars.split("").forEach((char) => {
|
|
||||||
newString = newString.replace(char, "");
|
|
||||||
});
|
|
||||||
|
|
||||||
return newString;
|
|
||||||
}
|
|
||||||
|
|
||||||
handleSetDataChange = () => {
|
|
||||||
let vocabWithTextExists = false;
|
|
||||||
const noInvalidPairs = this.state.inputContents.map(contents => {
|
|
||||||
const cleansedTerm = this.cleanseVocabString(contents.term);
|
|
||||||
const cleansedDefinition = this.cleanseVocabString(contents.definition);
|
|
||||||
const emptyVocabTermPresent = cleansedTerm === "" || cleansedDefinition === "";
|
|
||||||
if (emptyVocabTermPresent && cleansedTerm !== cleansedDefinition) {
|
|
||||||
return true;
|
|
||||||
} else if (!emptyVocabTermPresent) {
|
|
||||||
vocabWithTextExists = true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
})
|
|
||||||
.filter(x => x === true)
|
|
||||||
.length === 0;
|
|
||||||
|
|
||||||
if (this.state.inputs.title.trim() !== "" && noInvalidPairs && vocabWithTextExists) {
|
|
||||||
this.setState({
|
|
||||||
canCreateSet: true,
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
this.setState({
|
|
||||||
canCreateSet: false,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onTermInputChange = (event) => {
|
|
||||||
const index = Number(event.target.name.replace("term_", ""));
|
|
||||||
const input = event.target.value;
|
|
||||||
|
|
||||||
let inputContents = this.state.inputContents;
|
|
||||||
|
|
||||||
if (index >= this.state.inputContents.length && input !== "") {
|
|
||||||
inputContents.push({
|
|
||||||
term: input,
|
|
||||||
definition: "",
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
if (index === this.state.inputContents.length - 1 && input === "" && this.state.inputContents[index].definition === "") {
|
|
||||||
inputContents.splice(-1);
|
|
||||||
} else {
|
|
||||||
inputContents[index].term = input;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setState({
|
|
||||||
inputContents: inputContents,
|
|
||||||
}, this.handleSetDataChange);
|
|
||||||
}
|
|
||||||
|
|
||||||
onDefinitionInputChange = (event) => {
|
|
||||||
const index = Number(event.target.name.replace("definition_", ""));
|
|
||||||
const input = event.target.value;
|
|
||||||
|
|
||||||
let inputContents = this.state.inputContents;
|
|
||||||
|
|
||||||
if (index >= this.state.inputContents.length && input !== "") {
|
|
||||||
inputContents.push({
|
|
||||||
term: "",
|
|
||||||
definition: input,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
if (index === this.state.inputContents.length - 1 && input === "" && this.state.inputContents[index].term === "") {
|
|
||||||
inputContents.splice(-1);
|
|
||||||
} else {
|
|
||||||
inputContents[index].definition = input;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setState({
|
|
||||||
inputContents: inputContents,
|
|
||||||
}, this.handleSetDataChange);
|
|
||||||
}
|
|
||||||
|
|
||||||
onSetTitleInputChange = (event) => {
|
|
||||||
this.setState({
|
|
||||||
inputs: {
|
|
||||||
...this.state.inputs,
|
|
||||||
title: event.target.value,
|
|
||||||
}
|
|
||||||
}, this.handleSetDataChange);
|
|
||||||
}
|
|
||||||
|
|
||||||
onPublicSetInputChange = (event) => {
|
|
||||||
this.setState({
|
|
||||||
inputs: {
|
|
||||||
...this.state.inputs,
|
|
||||||
public: event.target.checked,
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
createSet = () => {
|
|
||||||
if (this.state.canCreateSet) {
|
|
||||||
this.setState({
|
|
||||||
loading: true,
|
|
||||||
canCreateSet: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
const db = this.state.db;
|
|
||||||
const setDocRef = db.collection("sets").doc();
|
|
||||||
|
|
||||||
setDocRef.set({
|
|
||||||
title: this.state.inputs.title,
|
|
||||||
public: this.state.inputs.public,
|
|
||||||
owner: this.state.user.uid,
|
|
||||||
groups: [],
|
|
||||||
})
|
|
||||||
.then(() => {
|
|
||||||
let promises = [];
|
|
||||||
let batches = [db.batch()];
|
|
||||||
|
|
||||||
this.state.inputContents
|
|
||||||
.filter(contents =>
|
|
||||||
this.cleanseVocabString(contents.term) !== "" &&
|
|
||||||
this.cleanseVocabString(contents.definition) !== ""
|
|
||||||
)
|
|
||||||
.map((contents, index) => {
|
|
||||||
if (index % 248 === 0) {
|
|
||||||
promises.push(batches[batches.length - 1].commit());
|
|
||||||
batches.push(db.batch());
|
|
||||||
}
|
|
||||||
const vocabDocRef = setDocRef.collection("vocab").doc();
|
|
||||||
if (contents.term === "") {
|
|
||||||
return batches[batches.length - 1].delete(vocabDocRef);
|
|
||||||
} else {
|
|
||||||
return batches[batches.length - 1].set(vocabDocRef, {
|
|
||||||
term: contents.term,
|
|
||||||
definition: contents.definition,
|
|
||||||
sound: true,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!batches[batches.length - 1]._delegate._committed) promises.push(batches[batches.length - 1].commit().catch(() => null));
|
|
||||||
|
|
||||||
Promise.all(promises).then(() => {
|
|
||||||
this.stopLoading();
|
|
||||||
this.props.history.push("/sets/" + setDocRef.id);
|
|
||||||
}).catch((error) => {
|
|
||||||
console.log("Couldn't save set: " + error);
|
|
||||||
this.stopLoading();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<NavBar items={this.state.navbarItems} />
|
|
||||||
|
|
||||||
<main>
|
|
||||||
<h2 className="page-header">
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
name="set_title"
|
|
||||||
onChange={this.onSetTitleInputChange}
|
|
||||||
placeholder="Set Title"
|
|
||||||
className="set-title-input"
|
|
||||||
ref={inputEl => (this.setNameInput = inputEl)}
|
|
||||||
autoComplete="off"
|
|
||||||
/>
|
|
||||||
</h2>
|
|
||||||
|
|
||||||
<div className="form create-set-vocab-list">
|
|
||||||
<label>
|
|
||||||
<Checkbox
|
|
||||||
checked={this.state.inputs.public}
|
|
||||||
onChange={this.onPublicSetInputChange}
|
|
||||||
inputProps={{ 'aria-label': 'checkbox' }}
|
|
||||||
/>
|
|
||||||
<span>Public</span>
|
|
||||||
</label>
|
|
||||||
|
|
||||||
<div className="create-set-header">
|
|
||||||
<h3>Terms</h3>
|
|
||||||
<h3>Definitions</h3>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{this.state.inputContents.concat({term: "", definition: ""}).map((contents, index) =>
|
|
||||||
<div className="create-set-input-row" key={index}>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
name={`term_${index}`}
|
|
||||||
onChange={this.onTermInputChange}
|
|
||||||
autoComplete="off"
|
|
||||||
autoCapitalize="none"
|
|
||||||
autoCorrect="off"
|
|
||||||
/>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
name={`definition_${index}`}
|
|
||||||
onChange={this.onDefinitionInputChange}
|
|
||||||
autoComplete="off"
|
|
||||||
autoCapitalize="none"
|
|
||||||
autoCorrect="off"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<Button
|
|
||||||
onClick={this.createSet}
|
|
||||||
icon={<CheckCircleOutlineRoundedIcon />}
|
|
||||||
loading={this.state.loading}
|
|
||||||
disabled={!this.state.canCreateSet}
|
|
||||||
>
|
|
||||||
Create
|
|
||||||
</Button>
|
|
||||||
</main>
|
|
||||||
<Footer />
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
308
src/EditSet.js
308
src/EditSet.js
@@ -14,7 +14,7 @@ export default withRouter(class EditSet extends Component {
|
|||||||
user: props.user,
|
user: props.user,
|
||||||
db: props.db,
|
db: props.db,
|
||||||
loading: false,
|
loading: false,
|
||||||
canSaveSet: true,
|
canSaveSet: !(props.createSet === true),
|
||||||
inputs: {
|
inputs: {
|
||||||
title: "",
|
title: "",
|
||||||
public: false,
|
public: false,
|
||||||
@@ -31,6 +31,9 @@ export default withRouter(class EditSet extends Component {
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
canMakeSetNonPublic: true,
|
canMakeSetNonPublic: true,
|
||||||
|
changesMade: false,
|
||||||
|
totalCompliantVocabPairs: 0,
|
||||||
|
totalUncompliantVocabPairs: 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
let isMounted = true;
|
let isMounted = true;
|
||||||
@@ -45,7 +48,7 @@ export default withRouter(class EditSet extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
alertLeavingWithoutSaving = (e = null) => {
|
alertLeavingWithoutSaving = (e = null) => {
|
||||||
if (this.state.canSaveSet) {
|
if (this.state.canSaveSet && this.state.changesMade) {
|
||||||
var confirmationMessage = "Are you sure you want to leave? You will lose any unsaved changes.";
|
var confirmationMessage = "Are you sure you want to leave? You will lose any unsaved changes.";
|
||||||
|
|
||||||
(e || window.event).returnValue = confirmationMessage; //Gecko + IE
|
(e || window.event).returnValue = confirmationMessage; //Gecko + IE
|
||||||
@@ -54,70 +57,69 @@ export default withRouter(class EditSet extends Component {
|
|||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
async componentDidMount() {
|
||||||
window.addEventListener("beforeunload", this.alertLeavingWithoutSaving);
|
window.addEventListener("beforeunload", this.alertLeavingWithoutSaving);
|
||||||
|
|
||||||
const setId = this.props.match.params.setId;
|
if (this.props.createSet !== true) {
|
||||||
const setRef = this.state.db.collection("sets")
|
const setId = this.props.match.params.setId;
|
||||||
.doc(setId);
|
const setRef = this.state.db.collection("sets")
|
||||||
const setVocabRef = setRef.collection("vocab")
|
.doc(setId);
|
||||||
.orderBy("term");
|
const setVocabRef = setRef.collection("vocab")
|
||||||
|
.orderBy("term");
|
||||||
|
|
||||||
setRef.get().then((setDoc) => {
|
await setRef.get().then(async (setDoc) => {
|
||||||
document.title = `Edit | ${setDoc.data().title} | Parandum`;
|
document.title = `Edit | ${setDoc.data().title} | Parandum`;
|
||||||
|
|
||||||
setVocabRef.get().then((querySnapshot) => {
|
await setVocabRef.get().then((querySnapshot) => {
|
||||||
let vocab = [];
|
let vocab = [];
|
||||||
let vocabPairsCount = 0;
|
|
||||||
|
|
||||||
querySnapshot.docs.map((doc) => {
|
querySnapshot.docs.map((doc) => {
|
||||||
const data = doc.data();
|
const data = doc.data();
|
||||||
|
|
||||||
if (this.cleanseVocabString(data.term) !== "" &&
|
return vocab.push({
|
||||||
this.cleanseVocabString(data.definition) !== "") {
|
vocabId: doc.id,
|
||||||
vocabPairsCount++;
|
term: data.term,
|
||||||
|
definition: data.definition,
|
||||||
|
sound: data.sound,
|
||||||
|
validInput: true,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
let newState = {
|
||||||
|
inputs: {
|
||||||
|
title: setDoc.data().title,
|
||||||
|
public: setDoc.data().public,
|
||||||
|
},
|
||||||
|
inputContents: vocab,
|
||||||
|
originalInputContents: JSON.parse(JSON.stringify(vocab)),
|
||||||
|
canMakeSetNonPublic: !(setDoc.data().groups && setDoc.data().groups.length > 0),
|
||||||
|
totalCompliantVocabPairs: vocab.length,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (setDoc.data().owner !== this.state.user.uid) {
|
||||||
|
newState.setInaccessible = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return vocab.push({
|
this.setState(newState);
|
||||||
vocabId: doc.id,
|
});
|
||||||
term: data.term,
|
}).catch(() => {
|
||||||
definition: data.definition,
|
this.setState({
|
||||||
sound: data.sound,
|
setInaccessible: true,
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
let newState = {
|
|
||||||
inputs: {
|
|
||||||
title: setDoc.data().title,
|
|
||||||
public: setDoc.data().public,
|
|
||||||
},
|
|
||||||
inputContents: vocab,
|
|
||||||
originalInputContents: JSON.parse(JSON.stringify(vocab)),
|
|
||||||
canMakeSetNonPublic: !(setDoc.data().groups && setDoc.data().groups.length > 0),
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!(newState.inputs.title !== "" && vocabPairsCount > 0 && vocabPairsCount === this.state.inputContents.length)) {
|
|
||||||
newState.canSaveSet = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (setDoc.data().owner !== this.state.user.uid) {
|
|
||||||
newState.setInaccessible = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setState(newState);
|
|
||||||
this.props.page.load();
|
|
||||||
});
|
});
|
||||||
}).catch(() => {
|
|
||||||
this.setState({
|
|
||||||
setInaccessible: true,
|
|
||||||
});
|
|
||||||
this.props.page.load();
|
|
||||||
});
|
|
||||||
|
|
||||||
this.props.logEvent("select_content", {
|
this.props.logEvent("select_content", {
|
||||||
content_type: "edit_set",
|
content_type: "edit_set",
|
||||||
item_id: this.props.match.params.setId,
|
item_id: this.props.match.params.setId,
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
document.title = "Create Set | Parandum";
|
||||||
|
|
||||||
|
this.props.logEvent("page_view");
|
||||||
|
}
|
||||||
|
|
||||||
|
!this.state.setInaccessible && this.setNameInput.focus();
|
||||||
|
this.props.page.load();
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount = () => {
|
componentWillUnmount = () => {
|
||||||
@@ -126,63 +128,67 @@ export default withRouter(class EditSet extends Component {
|
|||||||
this.props.page.unload();
|
this.props.page.unload();
|
||||||
}
|
}
|
||||||
|
|
||||||
stopLoading = () => {
|
stopLoading = (changesMade=this.state.changesMade) => {
|
||||||
this.setState({
|
this.setState({
|
||||||
canStartTest: true,
|
canSaveSet: true,
|
||||||
loading: false,
|
loading: false,
|
||||||
|
changesMade,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
cleanseVocabString = (item) => {
|
cleanseVocabString = (item) => {
|
||||||
const chars = " .,()-_'\"";
|
const chars = /[\p{P}\p{S} ]+/ug;
|
||||||
|
return item.replace(chars, "");
|
||||||
let newString = item;
|
|
||||||
|
|
||||||
chars.split("").forEach((char) => {
|
|
||||||
newString = newString.replace(char, "");
|
|
||||||
});
|
|
||||||
|
|
||||||
return newString;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
handleSetDataChange = () => {
|
handleSetDataChange = (vocabIndex=null) => {
|
||||||
let vocabWithTextExists = false;
|
let inputContents = [...this.state.inputContents];
|
||||||
const noInvalidPairs = this.state.inputContents.map(contents => {
|
let totalCompliantVocabPairs = this.state.totalCompliantVocabPairs;
|
||||||
const cleansedTerm = this.cleanseVocabString(contents.term);
|
let totalUncompliantVocabPairs = this.state.totalUncompliantVocabPairs;
|
||||||
const cleansedDefinition = this.cleanseVocabString(contents.definition);
|
|
||||||
const emptyVocabTermPresent = cleansedTerm === "" || cleansedDefinition === "";
|
|
||||||
if (emptyVocabTermPresent && cleansedTerm !== cleansedDefinition) {
|
|
||||||
return true;
|
|
||||||
} else if (!emptyVocabTermPresent) {
|
|
||||||
vocabWithTextExists = true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
})
|
|
||||||
.filter(x => x === true)
|
|
||||||
.length === 0;
|
|
||||||
|
|
||||||
if (this.state.inputs.title.trim() !== "" && noInvalidPairs && vocabWithTextExists) {
|
if (vocabIndex !== null) {
|
||||||
this.setState({
|
const emptyTerm = this.cleanseVocabString(inputContents[vocabIndex].term) === "";
|
||||||
canSaveSet: true,
|
const emptyDefinition = this.cleanseVocabString(inputContents[vocabIndex].definition) === "";
|
||||||
})
|
let oldCompliance = inputContents[vocabIndex].validInput;
|
||||||
} else {
|
|
||||||
this.setState({
|
if (oldCompliance === false) {
|
||||||
canSaveSet: false,
|
totalUncompliantVocabPairs--;
|
||||||
})
|
} else if (oldCompliance === true) {
|
||||||
|
totalCompliantVocabPairs--;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (emptyTerm ? !emptyDefinition : emptyDefinition) {
|
||||||
|
inputContents[vocabIndex].validInput = false;
|
||||||
|
totalUncompliantVocabPairs++;
|
||||||
|
} else if (!emptyTerm && !emptyDefinition) {
|
||||||
|
inputContents[vocabIndex].validInput = true;
|
||||||
|
totalCompliantVocabPairs++;
|
||||||
|
} else {
|
||||||
|
inputContents[vocabIndex].validInput = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
canSaveSet: totalUncompliantVocabPairs === 0 && totalCompliantVocabPairs > 0 && this.state.inputs.title.trim() !== "",
|
||||||
|
changesMade: true,
|
||||||
|
inputContents,
|
||||||
|
totalCompliantVocabPairs,
|
||||||
|
totalUncompliantVocabPairs,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
onTermInputChange = (event) => {
|
onTermInputChange = (event, vocabIndex) => {
|
||||||
const index = Number(event.target.name.replace("term_", ""));
|
const index = Number(event.target.name.replace("term_", ""));
|
||||||
const input = event.target.value;
|
const input = event.target.value;
|
||||||
|
|
||||||
let inputContents = this.state.inputContents;
|
let inputContents = [...this.state.inputContents];
|
||||||
|
|
||||||
if (index >= this.state.inputContents.length && input !== "") {
|
if (index >= this.state.inputContents.length && input !== "") {
|
||||||
inputContents.push({
|
inputContents.push({
|
||||||
term: input,
|
term: input,
|
||||||
definition: "",
|
definition: "",
|
||||||
sound: false,
|
sound: false,
|
||||||
|
validInput: null,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
if (index === this.state.inputContents.length - 1 && input === "" && this.state.inputContents[index].definition === "") {
|
if (index === this.state.inputContents.length - 1 && input === "" && this.state.inputContents[index].definition === "") {
|
||||||
@@ -194,10 +200,10 @@ export default withRouter(class EditSet extends Component {
|
|||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
inputContents: inputContents,
|
inputContents: inputContents,
|
||||||
}, this.handleSetDataChange);
|
}, () => this.handleSetDataChange(vocabIndex));
|
||||||
}
|
}
|
||||||
|
|
||||||
onDefinitionInputChange = (event) => {
|
onDefinitionInputChange = (event, vocabIndex) => {
|
||||||
const index = Number(event.target.name.replace("definition_", ""));
|
const index = Number(event.target.name.replace("definition_", ""));
|
||||||
const input = event.target.value;
|
const input = event.target.value;
|
||||||
|
|
||||||
@@ -208,6 +214,7 @@ export default withRouter(class EditSet extends Component {
|
|||||||
term: "",
|
term: "",
|
||||||
definition: input,
|
definition: input,
|
||||||
sound: false,
|
sound: false,
|
||||||
|
validInput: null,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
if (index === this.state.inputContents.length - 1 && input === "" && this.state.inputContents[index].term === "") {
|
if (index === this.state.inputContents.length - 1 && input === "" && this.state.inputContents[index].term === "") {
|
||||||
@@ -219,7 +226,7 @@ export default withRouter(class EditSet extends Component {
|
|||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
inputContents: inputContents,
|
inputContents: inputContents,
|
||||||
}, this.handleSetDataChange);
|
}, () => this.handleSetDataChange(vocabIndex));
|
||||||
}
|
}
|
||||||
|
|
||||||
onSetTitleInputChange = (event) => {
|
onSetTitleInputChange = (event) => {
|
||||||
@@ -228,7 +235,7 @@ export default withRouter(class EditSet extends Component {
|
|||||||
...this.state.inputs,
|
...this.state.inputs,
|
||||||
title: event.target.value,
|
title: event.target.value,
|
||||||
}
|
}
|
||||||
}, this.handleSetDataChange);
|
}, () => this.handleSetDataChange());
|
||||||
}
|
}
|
||||||
|
|
||||||
onPublicSetInputChange = (event) => {
|
onPublicSetInputChange = (event) => {
|
||||||
@@ -240,65 +247,81 @@ export default withRouter(class EditSet extends Component {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getVocabDocRef = (vocabCollectionRef, contents) => {
|
||||||
|
if (this.props.createSet === true) {
|
||||||
|
return vocabCollectionRef.doc();
|
||||||
|
} else {
|
||||||
|
return vocabCollectionRef.doc(contents.vocabId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
saveSet = async () => {
|
saveSet = async () => {
|
||||||
if (this.state.canSaveSet) {
|
if (this.state.canSaveSet) {
|
||||||
|
const noChangesMade = !this.state.changesMade;
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
loading: true,
|
loading: true,
|
||||||
canSaveSet: false,
|
canSaveSet: false,
|
||||||
|
changesMade: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
const db = this.state.db;
|
if (noChangesMade) {
|
||||||
const setId = this.props.match.params.setId;
|
this.props.history.push("/sets/" + this.props.match.params.setId);
|
||||||
const setDocRef = db.collection("sets").doc(setId);
|
} else {
|
||||||
|
const db = this.state.db;
|
||||||
let promises = [];
|
const setCollectionRef = db.collection("sets");
|
||||||
let batches = [db.batch()];
|
let vocabCollectionRef;
|
||||||
|
let setId;
|
||||||
this.state.inputContents.map((contents, index) => {
|
if (this.props.createSet === true) {
|
||||||
if (index % 248 === 0) {
|
let setDocRef = setCollectionRef.doc();
|
||||||
promises.push(batches[batches.length - 1].commit());
|
await setDocRef.set({
|
||||||
batches.push(db.batch());
|
title: this.state.inputs.title,
|
||||||
}
|
public: this.state.inputs.public,
|
||||||
const vocabDocRef = setDocRef.collection("vocab").doc(contents.vocabId);
|
owner: this.state.user.uid,
|
||||||
if (contents.term === "") {
|
groups: [],
|
||||||
return batches[batches.length - 1].delete(vocabDocRef);
|
|
||||||
} else {
|
|
||||||
return batches[batches.length - 1].set(vocabDocRef, {
|
|
||||||
term: contents.term,
|
|
||||||
definition: contents.definition,
|
|
||||||
sound: contents.sound,
|
|
||||||
});
|
});
|
||||||
|
setId = setDocRef.id;
|
||||||
|
vocabCollectionRef = setDocRef.collection("vocab");
|
||||||
|
} else {
|
||||||
|
vocabCollectionRef = setCollectionRef.doc(this.props.match.params.setId).collection("vocab");
|
||||||
|
setId = this.props.match.params.setId;
|
||||||
}
|
}
|
||||||
})
|
|
||||||
|
|
||||||
// TODO: sound files
|
let promises = [];
|
||||||
|
let batches = [db.batch()];
|
||||||
|
|
||||||
if (this.state.inputContents.length < this.state.originalInputContents.length) {
|
this.state.inputContents.map((contents, index) => {
|
||||||
let batchItems = this.state.inputContents.length % 248;
|
if (index % 248 === 0) {
|
||||||
for (let i = this.state.inputContents.length; i < this.state.originalInputContents.length; i++) {
|
|
||||||
if (batchItems + i % 248 === 0) {
|
|
||||||
if (batchItems !== 0) batchItems = 0;
|
|
||||||
promises.push(batches[batches.length - 1].commit());
|
promises.push(batches[batches.length - 1].commit());
|
||||||
batches.push(db.batch());
|
batches.push(db.batch());
|
||||||
}
|
}
|
||||||
|
|
||||||
const vocabDocRef = setDocRef
|
if (this.props.createSet !== true
|
||||||
.collection("vocab")
|
&& this.cleanseVocabString(contents.term) === "") {
|
||||||
.doc(this.state.originalInputContents[i].vocabId);
|
let vocabDocRef = this.getVocabDocRef(vocabCollectionRef, contents);
|
||||||
|
return batches[batches.length - 1].delete(vocabDocRef);
|
||||||
|
} else if (this.props.createSet === true || JSON.stringify(contents) !== JSON.stringify(this.state.originalInputContents[index])) {
|
||||||
|
let vocabDocRef = this.getVocabDocRef(vocabCollectionRef, contents);
|
||||||
|
return batches[batches.length - 1].set(vocabDocRef, {
|
||||||
|
term: contents.term,
|
||||||
|
definition: contents.definition,
|
||||||
|
sound: contents.sound,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
batches[batches.length - 1].delete(vocabDocRef);
|
return true;
|
||||||
}
|
});
|
||||||
|
|
||||||
|
if (!batches[batches.length - 1]._delegate._committed) promises.push(batches[batches.length - 1].commit().catch(() => null));
|
||||||
|
|
||||||
|
Promise.all(promises).then(() => {
|
||||||
|
this.stopLoading();
|
||||||
|
this.props.history.push("/sets/" + setId);
|
||||||
|
}).catch((error) => {
|
||||||
|
console.log("Couldn't update set: " + error);
|
||||||
|
this.stopLoading(true);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!batches[batches.length - 1]._delegate._committed) promises.push(batches[batches.length - 1].commit().catch(() => null));
|
|
||||||
|
|
||||||
Promise.all(promises).then(() => {
|
|
||||||
this.stopLoading();
|
|
||||||
this.props.history.push("/sets/" + setDocRef.id);
|
|
||||||
}).catch((error) => {
|
|
||||||
console.log("Couldn't update set: " + error);
|
|
||||||
this.stopLoading();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -310,7 +333,7 @@ export default withRouter(class EditSet extends Component {
|
|||||||
:
|
:
|
||||||
<div>
|
<div>
|
||||||
<Prompt
|
<Prompt
|
||||||
when={this.state.canSaveSet}
|
when={this.state.changesMade}
|
||||||
message="Are you sure you want to leave? You will lose any unsaved changes."
|
message="Are you sure you want to leave? You will lose any unsaved changes."
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@@ -327,6 +350,7 @@ export default withRouter(class EditSet extends Component {
|
|||||||
value={this.state.inputs.title}
|
value={this.state.inputs.title}
|
||||||
className="set-title-input"
|
className="set-title-input"
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
|
ref={inputEl => (this.setNameInput = inputEl)}
|
||||||
/>
|
/>
|
||||||
</h2>
|
</h2>
|
||||||
<Button
|
<Button
|
||||||
@@ -358,8 +382,8 @@ export default withRouter(class EditSet extends Component {
|
|||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
name={`term_${index}`}
|
name={`term_${index}`}
|
||||||
onChange={this.onTermInputChange}
|
onChange={event => this.onTermInputChange(event, index)}
|
||||||
value={this.state.inputContents[index] ? this.state.inputContents[index].term : ""}
|
value={contents.term}
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
autoCapitalize="none"
|
autoCapitalize="none"
|
||||||
autoCorrect="off"
|
autoCorrect="off"
|
||||||
@@ -367,8 +391,8 @@ export default withRouter(class EditSet extends Component {
|
|||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
name={`definition_${index}`}
|
name={`definition_${index}`}
|
||||||
onChange={this.onDefinitionInputChange}
|
onChange={event => this.onDefinitionInputChange(event, index)}
|
||||||
value={this.state.inputContents[index] ? this.state.inputContents[index].definition : ""}
|
value={contents.definition}
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
autoCapitalize="none"
|
autoCapitalize="none"
|
||||||
autoCorrect="off"
|
autoCorrect="off"
|
||||||
|
|||||||
@@ -355,18 +355,6 @@ export default withRouter(class Progress extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
cleanseVocabString = (item) => {
|
|
||||||
const chars = " .,()-_'\"";
|
|
||||||
|
|
||||||
let newString = item;
|
|
||||||
|
|
||||||
chars.split("").forEach((char) => {
|
|
||||||
newString = newString.replace(char, "");
|
|
||||||
});
|
|
||||||
|
|
||||||
return newString;
|
|
||||||
}
|
|
||||||
|
|
||||||
processAnswer = async (referrer="physical") => {
|
processAnswer = async (referrer="physical") => {
|
||||||
if (this.state.canProceed) {
|
if (this.state.canProceed) {
|
||||||
this.startLoading();
|
this.startLoading();
|
||||||
|
|||||||
Reference in New Issue
Block a user