fixed uneccesary statu updates to prevent flashing rerenders on the wordlist etc

This commit is contained in:
lingdocs 2021-08-31 17:15:36 +04:00
parent 62cf2ef4d4
commit e69fd94d31
10 changed files with 66 additions and 71 deletions

View File

@ -150,8 +150,14 @@ function setupPassport(passport: PassportStatic) {
cb(null, false);
return;
}
const newUser = await updateLingdocsUser(userId, { lastActive: getTimestamp() });
cb(null, newUser);
try {
// skip if there's an update conflict
const newUser = await updateLingdocsUser(userId, { lastActive: getTimestamp() });
cb(null, newUser);
} catch (e) {
console.error(e);
cb(null, user);
}
} catch (err) {
cb(err, null);
}

View File

@ -200,9 +200,9 @@
}
},
"@lingdocs/pashto-inflector": {
"version": "0.9.0",
"resolved": "https://npm.lingdocs.com/@lingdocs%2fpashto-inflector/-/pashto-inflector-0.9.0.tgz",
"integrity": "sha512-kiWVshiMGp/eT0vPTheujD9hJrUXAqLKG48a6iqX8RBURlv5hjk9t+CymKvLGYDO0Zrog0kAYcSo/PqkV0UKIw==",
"version": "0.9.2",
"resolved": "https://npm.lingdocs.com/@lingdocs%2fpashto-inflector/-/pashto-inflector-0.9.2.tgz",
"integrity": "sha512-9tmPPezEvPFR/tPkBuF/bj79dCAviFTGOmCVDbRCymXmv4gWWhH4lCueYOYhyWZ4vXcihMflVpRSmLtUDXmdjQ==",
"requires": {
"classnames": "^2.2.6",
"pbf": "^3.2.1",
@ -1697,9 +1697,9 @@
}
},
"protocol-buffers-schema": {
"version": "3.5.1",
"resolved": "https://registry.npmjs.org/protocol-buffers-schema/-/protocol-buffers-schema-3.5.1.tgz",
"integrity": "sha512-YVCvdhxWNDP8/nJDyXLuM+UFsuPk4+1PB7WGPVDzm3HTHbzFLxQYeW2iZpS4mmnXrQJGBzt230t/BbEb7PrQaw=="
"version": "3.5.2",
"resolved": "https://registry.npmjs.org/protocol-buffers-schema/-/protocol-buffers-schema-3.5.2.tgz",
"integrity": "sha512-LPzSaBYp/TcbuSlpGwqT5jR9kvJ3Zp5ic2N5c2ybx6XB/lSfEHq2D7ja8AgoxHoMD91wXFALJoXsvshKPuXyew=="
},
"proxy-addr": {
"version": "2.0.6",
@ -1745,9 +1745,9 @@
"integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ=="
},
"rambda": {
"version": "6.8.2",
"resolved": "https://registry.npmjs.org/rambda/-/rambda-6.8.2.tgz",
"integrity": "sha512-fIVr6nuqfHfJTguthGsWF930DkNq/ENraeSpYBj1kYvO/ieacux7rj2NW27JwwFrllFJwXkLpGyFMz99EwCJ3Q=="
"version": "6.9.0",
"resolved": "https://registry.npmjs.org/rambda/-/rambda-6.9.0.tgz",
"integrity": "sha512-yosVdGg1hNGkXPzqGiOYNEpXKjEOxzUCg2rB0l+NKdyCaSf4z+i5ojbN0IqDSezMMf71YEglI+ZUTgTffn5afw=="
},
"range-parser": {
"version": "1.2.1",

View File

@ -14,7 +14,7 @@
"main": "lib/functions/src/index.js",
"dependencies": {
"@google-cloud/storage": "^5.8.1",
"@lingdocs/pashto-inflector": "^0.9.0",
"@lingdocs/pashto-inflector": "^0.9.2",
"@types/cors": "^2.8.10",
"@types/google-spreadsheet": "^3.0.2",
"cors": "^2.8.5",

View File

@ -6,7 +6,7 @@
"private": true,
"dependencies": {
"@fortawesome/fontawesome-free": "^5.15.2",
"@lingdocs/pashto-inflector": "^0.9.0",
"@lingdocs/pashto-inflector": "^0.9.2",
"@testing-library/jest-dom": "^5.11.4",
"@testing-library/react": "^11.1.0",
"@testing-library/user-event": "^12.1.10",
@ -32,7 +32,6 @@
"react-bootstrap": "^1.5.1",
"react-dom": "^17.0.1",
"react-dropzone": "^11.3.2",
"react-firebaseui": "^4.1.0",
"react-ga": "^3.3.0",
"react-helmet": "^6.1.0",
"react-image-crop": "^8.6.9",
@ -87,14 +86,14 @@
"@types/lokijs": "^1.5.3",
"@types/mousetrap": "^1.6.8",
"@types/papaparse": "^5.2.5",
"@types/passport-github2": "^1.2.5",
"@types/passport-google-oauth": "^1.0.42",
"@types/passport-twitter": "^1.0.37",
"@types/pouchdb": "^6.4.0",
"@types/react-canvas-draw": "^1.1.0",
"@types/react-helmet": "^6.1.0",
"@types/react-image-crop": "^8.1.2",
"@types/react-router-dom": "^5.1.7",
"@types/passport-github2": "^1.2.5",
"@types/passport-google-oauth": "^1.0.42",
"@types/passport-twitter": "^1.0.37",
"fake-indexeddb": "^3.1.2",
"history": "4",
"jest-fetch-mock": "^3.0.3",

View File

@ -73,6 +73,7 @@ import "./App.css";
import classNames from "classnames";
import { getTextOptions } from "./lib/get-text-options";
import { getTextFromShareTarget } from "./lib/share-target";
import { objIsEqual } from "./lib/misc-helpers";
// to allow Moustrap key combos even when input fields are in focus
Mousetrap.prototype.stopCallback = function () {
@ -290,8 +291,10 @@ class App extends Component<RouteComponentProps, State> {
...userOnServer,
userTextOptionsRecord,
};
this.setState({ user });
saveUser(user);
if (!objIsEqual(prevUser, user)) {
this.setState({ user });
saveUser(user);
}
const textOptionsRecord: TextOptionsRecord = {
lastModified: userTextOptionsRecord.lastModified,
textOptions: {
@ -345,7 +348,7 @@ class App extends Component<RouteComponentProps, State> {
this.setState({ options });
}
} else {
this.setState({ options });
!objIsEqual(this.state.options, options) && this.setState({ options });
}
}
@ -514,7 +517,8 @@ class App extends Component<RouteComponentProps, State> {
</Route>
{wordlistEnabled(this.state.user) && <Route path="/wordlist">
<Wordlist
state={this.state}
options={this.state.options}
wordlist={this.state.wordlist}
isolateEntry={this.handleIsolateEntry}
optionsDispatch={this.handleOptionsUpdate}
/>

View File

@ -8,6 +8,9 @@ const baseUrl: Record<Service, string> = {
functions: "https://functions.lingdocs.com/",
};
// @ts-ignore
const sampleAdminUser: AT.LingdocsUser = {"_id":"5e8dd381-950f-4641-922d-c63c6bf0f8e9","_rev":"1713-1a299e0d66da62fe4c8d059f0f068cb7","userId":"5e8dd381-950f-4641-922d-c63c6bf0f8e9","email":"clay@mailbox.org","admin":true,"emailVerified":true,"name":"Adam D","password":"$2a$10$JR4AHXXGbFP6sKQrqGO9UuMa3tdzNhbdqBvkjn2MVBuIOHkA/Xkf.","level":"editor","tests":[],"lastLogin":1629893763810,"lastActive":1630414108552,"userTextOptionsRecord":{"lastModified":1629983812750,"userTextOptions":{"spelling":"Afghan","diacritics":false,"dialect":"standard","phonetics":"lingdocs"}},"github":{"id":"71590811","nodeId":"MDQ6VXNlcjcxNTkwODEx","displayName":"LingDocs","username":"lingdocs","profileUrl":"https://github.com/lingdocs","photos":[{"value":"https://avatars.githubusercontent.com/u/71590811?v=4"}],"provider":"github","accessToken":"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"},"twitter":{"id":"1307635660451385344","username":"lingdocs","displayName":"LingDocs","photos":[{"value":"https://pbs.twimg.com/profile_images/1315656283928899584/EJIqjI-I_normal.jpg"}],"provider":"twitter","_accessLevel":"read","token":"1307635660451385344-MhpGAaJ6hFmfJtV97N6gy11FgbamVX","tokenSecret":"QjouJ33sWb2MpCkcUqqRxbfC9bqDyRaIODO9cejDvdusN"},"wordlistDbName":"userdb-35653864643338312d393530662d343634312d393232642d633633633662663066386539","couchDbPassword":"oevewi8iptdqyxax3v1x7t2ngi4uloir53g2y4659ada","google":{"id":"111202697753203366308","displayName":"Adam Dueck","name":{"familyName":"Dueck","givenName":"Adam"},"emails":[{"value":"lingdocsdev@gmail.com","verified":true}],"photos":[{"value":"https://lh3.googleusercontent.com/a/AATXAJx3cxPPpUoosqVzyGR-LcJ48EjpVcOBInkW21E=s96-c"}],"provider":"google","accessToken":"ya29.a0ARrdaM8BOOmaKxAPe_tKuVq0VgBncURPeEUUqV85RKksibmdx-DSY0IPOGXLs7UB8vh0yIpULsCQHiBKKg_l1DtuEmfwkN7F1h5YDyn2Zwd_ytVTE4W93YvdVGHVnX8rTdlEVcXLO2apwhJrBi3PuzTzxa6BTw"}};
// FUNCTIONS CALLS - MUST BE RE-ROUTED THROUGH FIREBASE HOSTING IN ../../../firebase.json
export async function publishDictionary(): Promise<FT.PublishDictionaryResponse | FT.FunctionError> {
return await myFetch("functions", "publishDictionary") as FT.PublishDictionaryResponse | FT.FunctionError;
@ -41,6 +44,9 @@ export async function signOut() {
}
export async function getUser(): Promise<undefined | AT.LingdocsUser | "offline"> {
if (process.env.REACT_APP_ENV === "dev") {
return sampleAdminUser;
}
try {
const response = await myFetch("account", "user");
if ("user" in response) {

View File

@ -0,0 +1,4 @@
export function objIsEqual(obj1: any, obj2: any): boolean {
if (!obj1 || !obj2) return false;
return JSON.stringify(obj1) === JSON.stringify(obj2);
}

View File

@ -44,7 +44,6 @@ import AudioPlayButton from "../components/AudioPlayButton";
import dayjs from "dayjs";
import relativeTime from "dayjs/plugin/relativeTime.js";
import hitBottom from "../lib/hitBottom";
import { getTextOptions } from "../lib/get-text-options";
const cleanupIcon = "broom";
@ -72,8 +71,9 @@ function amountOfWords(number: number): string {
return `${number} word${number !== 1 ? "s" : ""}`;
}
function Wordlist({ state, isolateEntry, optionsDispatch }: {
state: State,
function Wordlist({ options, wordlist, isolateEntry, optionsDispatch }: {
options: Options,
wordlist: WordlistWord[],
isolateEntry: (ts: number) => void,
optionsDispatch: (action: OptionsAction) => void,
}) {
@ -89,17 +89,17 @@ function Wordlist({ state, isolateEntry, optionsDispatch }: {
const [showingCleanup, setShowingCleanup] = useState<boolean>(false);
const [monthsBackToKeep, setMonthsBackToKeep] = useState<number>(6);
const [wordsToDelete, setWordsToDelete] = useState<string[]>([]);
const [startedWithWordsToReview] = useState<boolean>(forReview(state.wordlist).length !== 0);
const [startedWithWordsToReview] = useState<boolean>(forReview(wordlist).length !== 0);
useEffect(() => {
window.addEventListener("scroll", handleScroll);
return () => window.removeEventListener("scroll", handleScroll);
// eslint-disable-next-line
}, []);
const toReview = forReview(state.wordlist);
const textOptions = getTextOptions(state);
const toReview = forReview(wordlist);
const textOptions = options.textOptionsRecord.textOptions;
function handleScroll() {
// TODO: DON'T HAVE ENDLESS PAGE INCREASING
if (hitBottom() && state.options.wordlistMode === "browse") {
if (hitBottom() && options.wordlistMode === "browse") {
setPage(page => page + 1);
}
}
@ -119,7 +119,7 @@ function Wordlist({ state, isolateEntry, optionsDispatch }: {
}
function handleSearchValueChange(value: string) {
setWordlistSearchValue(value);
const results = value ? searchWordlist(value, state.wordlist, textOptions) : [];
const results = value ? searchWordlist(value, wordlist, textOptions) : [];
setFilteredWords(results);
}
async function handleGetWordlistCSV() {
@ -137,7 +137,7 @@ function Wordlist({ state, isolateEntry, optionsDispatch }: {
}
function handleShowCleanup() {
setWordsToDelete(
calculateWordsToDelete(state.wordlist, monthsBackToKeep)
calculateWordsToDelete(wordlist, monthsBackToKeep)
);
setShowingCleanup(true);
}
@ -146,7 +146,7 @@ function Wordlist({ state, isolateEntry, optionsDispatch }: {
setWordsToDelete([]);
}
function handleCleanup() {
const toDelete = calculateWordsToDelete(state.wordlist, monthsBackToKeep);
const toDelete = calculateWordsToDelete(wordlist, monthsBackToKeep);
deleteWordFromWordlist(toDelete);
setShowingCleanup(false);
}
@ -219,13 +219,13 @@ function Wordlist({ state, isolateEntry, optionsDispatch }: {
<div className="card mb-3 clickable" onClick={() => handleWordClickReview(word._id)}>
<div className="card-body">
<h6 className="card-title text-center">
{state.options.wordlistReviewLanguage === "Pashto"
{options.wordlistReviewLanguage === "Pashto"
? <InlinePs opts={textOptions}>{{ p: word.entry.p, f: word.entry.f }}</InlinePs>
: word.entry.e
}
</h6>
{beingQuizzed && <div className="card-text text-center">
{state.options.wordlistReviewLanguage === "Pashto"
{options.wordlistReviewLanguage === "Pashto"
? <div>{word.entry.e}</div>
: <InlinePs opts={textOptions}>
{{ p: word.entry.p, f: word.entry.f }}
@ -249,7 +249,7 @@ function Wordlist({ state, isolateEntry, optionsDispatch }: {
</Helmet>
<div className="d-flex flex-row justify-content-between mb-2">
<h4 className="mb-3">Wordlist</h4>
{state.wordlist.length > 0 &&
{wordlist.length > 0 &&
<div className="d-flex flex-row justify-content-between mb-2">
<div>
<button className="btn btn-sm btn-outline-secondary mr-3" onClick={handleShowCleanup}>
@ -264,7 +264,7 @@ function Wordlist({ state, isolateEntry, optionsDispatch }: {
</div>
}
</div>
{!state.wordlist.length ?
{!wordlist.length ?
<EmptyWordlistNotice />
:
<>
@ -280,16 +280,16 @@ function Wordlist({ state, isolateEntry, optionsDispatch }: {
value: "review",
},
]}
value={state.options.wordlistMode || "browse"}
value={options.wordlistMode || "browse"}
handleChange={(p) => {
optionsDispatch({ type: "changeWordlistMode", payload: p as WordlistMode });
}}
/>
</div>
{state.options.wordlistMode === "browse"
{options.wordlistMode === "browse"
? <div className="mt-4">
<WordlistSearchBar value={wordlistSearchValue} handleChange={handleSearchValueChange} />
{paginate(wordlistSearchValue ? filteredWords : state.wordlist, page).map((word) => (
{paginate(wordlistSearchValue ? filteredWords : wordlist, page).map((word) => (
<WordlistBrowsingWord word={word} key={word._id} />
))}
</div>
@ -298,7 +298,7 @@ function Wordlist({ state, isolateEntry, optionsDispatch }: {
<div className="mb-4 text-center" style={{ width: "100%" }}>
<ButtonSelect
options={reviewLanguageOptions}
value={state.options.wordlistReviewLanguage || "Pashto"}
value={options.wordlistReviewLanguage || "Pashto"}
handleChange={(p) => {
optionsDispatch({ type: "changeWordlistReviewLanguage", payload: p as Language });
}}
@ -310,7 +310,7 @@ function Wordlist({ state, isolateEntry, optionsDispatch }: {
? (startedWithWordsToReview
? <p className="lead my-3">All done review 🎉</p>
: (() => {
const nextUp = nextUpForReview(state.wordlist);
const nextUp = nextUpForReview(wordlist);
const { e, ...ps } = nextUp.entry;
return <div>
<div className="lead my-3">None to review</div>
@ -370,7 +370,7 @@ function Wordlist({ state, isolateEntry, optionsDispatch }: {
<Modal.Title><i className={`fas fa-${cleanupIcon} mr-1`} /> Wordlist Cleanup</Modal.Title>
</Modal.Header>
<Modal.Body>
<p>You have {amountOfWords(state.wordlist.length)} in your wordlist.</p>
<p>You have {amountOfWords(wordlist.length)} in your wordlist.</p>
<p>Delete words older than:</p>
<ButtonSelect
options={[
@ -391,7 +391,7 @@ function Wordlist({ state, isolateEntry, optionsDispatch }: {
handleChange={(p: string) => {
const months = parseInt(p);
setWordsToDelete(
calculateWordsToDelete(state.wordlist, months),
calculateWordsToDelete(wordlist, months),
);
setMonthsBackToKeep(months);
}}

View File

@ -10,6 +10,7 @@
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"downlevelIteration": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noFallthroughCasesInSwitch": true,

View File

@ -1483,10 +1483,10 @@
"@types/yargs" "^16.0.0"
chalk "^4.0.0"
"@lingdocs/pashto-inflector@^0.9.0":
version "0.9.0"
resolved "https://npm.lingdocs.com/@lingdocs%2fpashto-inflector/-/pashto-inflector-0.9.0.tgz#d4aa0154f65b27b27cafdbbd1881d00aa1cc43cb"
integrity sha512-kiWVshiMGp/eT0vPTheujD9hJrUXAqLKG48a6iqX8RBURlv5hjk9t+CymKvLGYDO0Zrog0kAYcSo/PqkV0UKIw==
"@lingdocs/pashto-inflector@^0.9.2":
version "0.9.2"
resolved "https://npm.lingdocs.com/@lingdocs%2fpashto-inflector/-/pashto-inflector-0.9.2.tgz#7ef3b3344c3eb3e3d1db77142ef83f0ac7e8e230"
integrity sha512-9tmPPezEvPFR/tPkBuF/bj79dCAviFTGOmCVDbRCymXmv4gWWhH4lCueYOYhyWZ4vXcihMflVpRSmLtUDXmdjQ==
dependencies:
classnames "^2.2.6"
pbf "^3.2.1"
@ -4781,11 +4781,6 @@ detect-port-alt@1.1.6:
address "^1.0.1"
debug "^2.6.0"
dialog-polyfill@^0.4.7:
version "0.4.10"
resolved "https://registry.yarnpkg.com/dialog-polyfill/-/dialog-polyfill-0.4.10.tgz#c4ea68a0deed4abb59a6a2a025c548b278cd532e"
integrity sha512-j5yGMkP8T00UFgyO+78OxiN5vC5dzRQF3BEio+LhNvDbyfxWBsi3sfPArDm54VloaJwy2hm3erEiDWqHRC8rzw==
diff-sequences@^26.6.2:
version "26.6.2"
resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-26.6.2.tgz#48ba99157de1923412eed41db6b6d4aa9ca7c0b1"
@ -5837,14 +5832,6 @@ find-up@^3.0.0:
dependencies:
locate-path "^3.0.0"
firebaseui@^4.7.1:
version "4.8.1"
resolved "https://registry.yarnpkg.com/firebaseui/-/firebaseui-4.8.1.tgz#29ccbc9dfd579c4453725f88e9cf81c8ea62c580"
integrity sha512-Qh8kfqGjMIiVJ2X8MUFsmlf43QFcVc8ungD+kw5T8ACuhQ68IAyUHExlItAfumrcLlqEgyo1MjH0O9fZZAMOKw==
dependencies:
dialog-polyfill "^0.4.7"
material-design-lite "^1.2.0"
flat-cache@^3.0.4:
version "3.0.4"
resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.0.4.tgz#61b0338302b2fe9f957dcc32fc2a87f1c3048b11"
@ -8201,11 +8188,6 @@ map-visit@^1.0.0:
dependencies:
object-visit "^1.0.0"
material-design-lite@^1.2.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/material-design-lite/-/material-design-lite-1.3.0.tgz#d004ce3fee99a1eeb74a78b8a325134a5f1171d3"
integrity sha1-0ATOP+6Zoe63Sni4oyUTSl8RcdM=
md5.js@^1.3.4:
version "1.3.5"
resolved "https://registry.yarnpkg.com/md5.js/-/md5.js-1.3.5.tgz#b5d07b8e3216e3e27cd728d72f70d1e6a342005f"
@ -10527,13 +10509,6 @@ react-fast-compare@^3.1.1:
resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-3.2.0.tgz#641a9da81b6a6320f270e89724fb45a0b39e43bb"
integrity sha512-rtGImPZ0YyLrscKI9xTpV8psd6I8VAtjKCzQDlzyDvqJA8XOW78TXYQwNRNd8g8JZnDu8q9Fu/1v4HPAVwVdHA==
react-firebaseui@^4.1.0:
version "4.2.0"
resolved "https://registry.yarnpkg.com/react-firebaseui/-/react-firebaseui-4.2.0.tgz#13846381daefe31b90af98854f3e79e82d4f81ba"
integrity sha512-KRWfQn7iJMMAcB1zmMrpo5PJ7CBrLW6NXvdEJ/N+SFZUs7ANvC3g8LzgYwsBHUr1OR1MQtGosV7dyQSmYtKyOA==
dependencies:
firebaseui "^4.7.1"
react-ga@^3.3.0:
version "3.3.0"
resolved "https://registry.yarnpkg.com/react-ga/-/react-ga-3.3.0.tgz#c91f407198adcb3b49e2bc5c12b3fe460039b3ca"