update ga

This commit is contained in:
adueck 2023-07-15 00:49:02 +04:00
parent ab0916aadd
commit 211d9b3446
3 changed files with 745 additions and 614 deletions

View File

@ -31,7 +31,7 @@
"react-bootstrap": "^1.5.1", "react-bootstrap": "^1.5.1",
"react-dom": "^17.0.2", "react-dom": "^17.0.2",
"react-dropzone": "^11.3.2", "react-dropzone": "^11.3.2",
"react-ga": "^3.3.0", "react-ga4": "^2.1.0",
"react-helmet": "^6.1.0", "react-helmet": "^6.1.0",
"react-image-crop": "^8.6.9", "react-image-crop": "^8.6.9",
"react-image-file-resizer": "^0.4.4", "react-image-file-resizer": "^0.4.4",

View File

@ -10,7 +10,12 @@
// sync on initialization and cancel sync on de-initialization // sync on initialization and cancel sync on de-initialization
import { Component } from "react"; import { Component } from "react";
import { defaultTextOptions, revertSpelling, standardizePashto, Types as T } from "@lingdocs/ps-react"; import {
defaultTextOptions,
revertSpelling,
standardizePashto,
Types as T,
} from "@lingdocs/ps-react";
import { withRouter, Route, RouteComponentProps, Link } from "react-router-dom"; import { withRouter, Route, RouteComponentProps, Link } from "react-router-dom";
import Helmet from "react-helmet"; import Helmet from "react-helmet";
import BottomNavItem from "./components/BottomNavItem"; import BottomNavItem from "./components/BottomNavItem";
@ -32,36 +37,23 @@ import {
readUser, readUser,
} from "./lib/local-storage"; } from "./lib/local-storage";
import { allEntries, dictionary, pageSize } from "./lib/dictionary"; import { allEntries, dictionary, pageSize } from "./lib/dictionary";
import { import { optionsReducer, textOptionsReducer } from "./lib/options-reducer";
optionsReducer,
textOptionsReducer,
} from "./lib/options-reducer";
import hitBottom from "./lib/hitBottom"; import hitBottom from "./lib/hitBottom";
import getWordId from "./lib/get-word-id"; import getWordId from "./lib/get-word-id";
import { CronJob } from "cron"; import { CronJob } from "cron";
import Mousetrap from "mousetrap"; import Mousetrap from "mousetrap";
import { import { sendSubmissions } from "./lib/submissions";
sendSubmissions, import { getUser } from "./lib/backend-calls";
} from "./lib/submissions"; import { getWordlist } from "./lib/wordlist-database";
import {
getUser,
} from "./lib/backend-calls";
import {
getWordlist,
} from "./lib/wordlist-database";
import { import {
startLocalDbs, startLocalDbs,
stopLocalDbs, stopLocalDbs,
getAllDocsLocalDb, getAllDocsLocalDb,
} from "./lib/pouch-dbs"; } from "./lib/pouch-dbs";
import { import { forReview } from "./lib/spaced-repetition";
forReview, import { textBadge } from "./lib/badges";
} from "./lib/spaced-repetition";
import {
textBadge,
} from "./lib/badges";
import * as AT from "./types/account-types"; import * as AT from "./types/account-types";
import ReactGA from "react-ga"; import ReactGA from "react-ga4";
// tslint:disable-next-line // tslint:disable-next-line
import "@fortawesome/fontawesome-free/css/all.css"; import "@fortawesome/fontawesome-free/css/all.css";
import "./custom-bootstrap.css"; import "./custom-bootstrap.css";
@ -78,31 +70,35 @@ import {
} from "./types/dictionary-types"; } from "./types/dictionary-types";
import PhraseBuilder from "./screens/PhraseBuilder"; import PhraseBuilder from "./screens/PhraseBuilder";
import { searchAllInflections } from "./lib/search-all-inflections"; import { searchAllInflections } from "./lib/search-all-inflections";
import { import { addToWordlist } from "./lib/wordlist-database";
addToWordlist,
} from "./lib/wordlist-database";
import ScriptToPhonetics from "./screens/ScriptToPhonetics"; import ScriptToPhonetics from "./screens/ScriptToPhonetics";
// to allow Moustrap key combos even when input fields are in focus // to allow Moustrap key combos even when input fields are in focus
Mousetrap.prototype.stopCallback = function () { Mousetrap.prototype.stopCallback = function () {
return false; return false;
} };
const prod = document.location.hostname === "dictionary.lingdocs.com"; const prod = document.location.hostname === "dictionary.lingdocs.com";
if (prod) { if (prod) {
// TODO: migrate to https://www.npmjs.com/package/react-ga4 // TODO: migrate to https://www.npmjs.com/package/react-ga4
ReactGA.initialize("UA-196576671-1"); ReactGA.initialize("386396674");
ReactGA.set({ anonymizeIp: true }); ReactGA.set({ anonymizeIp: true });
} }
const possibleLandingPages = [ const possibleLandingPages = [
"/", "/about", "/settings", "/word", "/account", "/new-entries", "/share-target", "/phrase-builder", "/",
"/privacy", "/script-to-phonetics" "/about",
]; "/settings",
const editorOnlyPages = [ "/word",
"/edit", "/review-tasks", "/account",
"/new-entries",
"/share-target",
"/phrase-builder",
"/privacy",
"/script-to-phonetics",
]; ];
const editorOnlyPages = ["/edit", "/review-tasks"];
class App extends Component<RouteComponentProps, State> { class App extends Component<RouteComponentProps, State> {
constructor(props: RouteComponentProps) { constructor(props: RouteComponentProps) {
@ -112,11 +108,15 @@ class App extends Component<RouteComponentProps, State> {
dictionaryStatus: "loading", dictionaryStatus: "loading",
dictionaryInfo: undefined, dictionaryInfo: undefined,
// TODO: Choose between the saved options and the options in the saved user // TODO: Choose between the saved options and the options in the saved user
options: savedOptions ? savedOptions : { options: savedOptions
? savedOptions
: {
language: "Pashto", language: "Pashto",
searchType: "fuzzy", searchType: "fuzzy",
searchBarStickyFocus: false, searchBarStickyFocus: false,
theme: (window.matchMedia?.("(prefers-color-scheme: dark)").matches) ? "dark" : "light", theme: window.matchMedia?.("(prefers-color-scheme: dark)").matches
? "dark"
: "light",
textOptionsRecord: { textOptionsRecord: {
lastModified: Date.now() as AT.TimeStamp, lastModified: Date.now() as AT.TimeStamp,
textOptions: defaultTextOptions, textOptions: defaultTextOptions,
@ -133,7 +133,7 @@ class App extends Component<RouteComponentProps, State> {
wordlist: [], wordlist: [],
reviewTasks: [], reviewTasks: [],
user: readUser(), user: readUser(),
inflectionSearchResults: undefined inflectionSearchResults: undefined,
}; };
this.handleOptionsUpdate = this.handleOptionsUpdate.bind(this); this.handleOptionsUpdate = this.handleOptionsUpdate.bind(this);
this.handleTextOptionsUpdate = this.handleTextOptionsUpdate.bind(this); this.handleTextOptionsUpdate = this.handleTextOptionsUpdate.bind(this);
@ -153,10 +153,15 @@ class App extends Component<RouteComponentProps, State> {
if (!possibleLandingPages.includes(this.props.location.pathname)) { if (!possibleLandingPages.includes(this.props.location.pathname)) {
this.props.history.replace("/"); this.props.history.replace("/");
} }
if (prod && (!(this.state.user?.level === "editor"))) { if (prod && !(this.state.user?.level === "editor")) {
ReactGA.pageview(window.location.pathname + window.location.search); ReactGA.send({
hitType: "pageview",
page: window.location.pathname + window.location.search,
});
} }
dictionary.initialize().then((r) => { dictionary
.initialize()
.then((r) => {
this.cronJob.start(); this.cronJob.start();
this.setState({ this.setState({
dictionaryStatus: "ready", dictionaryStatus: "ready",
@ -165,7 +170,10 @@ class App extends Component<RouteComponentProps, State> {
this.handleLoadUser(); this.handleLoadUser();
// incase it took forever and timed out - might need to reinitialize the wordlist here ?? // incase it took forever and timed out - might need to reinitialize the wordlist here ??
if (this.state.user) { if (this.state.user) {
startLocalDbs(this.state.user, { wordlist: this.handleRefreshWordlist, reviewTasks: this.handleRefreshReviewTasks }); startLocalDbs(this.state.user, {
wordlist: this.handleRefreshWordlist,
reviewTasks: this.handleRefreshReviewTasks,
});
} }
if (this.props.location.pathname === "/word") { if (this.props.location.pathname === "/word") {
const wordId = getWordId(this.props.location.search); const wordId = getWordId(this.props.location.search);
@ -201,20 +209,28 @@ class App extends Component<RouteComponentProps, State> {
if (r.response === "loaded from saved") { if (r.response === "loaded from saved") {
this.handleDictionaryUpdate(); this.handleDictionaryUpdate();
} }
}).catch((error) => { })
.catch((error) => {
console.error(error); console.error(error);
this.setState({ dictionaryStatus: "error loading" }); this.setState({ dictionaryStatus: "error loading" });
}); });
document.documentElement.setAttribute("data-theme", this.state.options.theme); document.documentElement.setAttribute(
"data-theme",
this.state.options.theme
);
/* istanbul ignore if */ /* istanbul ignore if */
if (window.matchMedia) { if (window.matchMedia) {
const prefersDarkQuery = window.matchMedia("(prefers-color-scheme: dark)"); const prefersDarkQuery = window.matchMedia(
"(prefers-color-scheme: dark)"
);
prefersDarkQuery.addListener((e) => { prefersDarkQuery.addListener((e) => {
if (e.matches) { if (e.matches) {
this.handleOptionsUpdate({ type: "changeTheme", payload: "dark" }); this.handleOptionsUpdate({ type: "changeTheme", payload: "dark" });
} }
}); });
const prefersLightQuery = window.matchMedia("(prefers-color-scheme: light)"); const prefersLightQuery = window.matchMedia(
"(prefers-color-scheme: light)"
);
prefersLightQuery.addListener((e) => { prefersLightQuery.addListener((e) => {
if (e.matches) { if (e.matches) {
this.handleOptionsUpdate({ type: "changeTheme", payload: "light" }); this.handleOptionsUpdate({ type: "changeTheme", payload: "light" });
@ -235,14 +251,17 @@ class App extends Component<RouteComponentProps, State> {
return; return;
} }
this.handleIsolateEntry(toIsolate.ts); this.handleIsolateEntry(toIsolate.ts);
}) });
Mousetrap.bind(["ctrl+down", "ctrl+up", "command+down", "command+up"], (e) => { Mousetrap.bind(
["ctrl+down", "ctrl+up", "command+down", "command+up"],
(e) => {
e.preventDefault(); e.preventDefault();
if (e.repeat) { if (e.repeat) {
return; return;
} }
this.handleOptionsUpdate({ type: "toggleLanguage" }); this.handleOptionsUpdate({ type: "toggleLanguage" });
}); }
);
Mousetrap.bind(["ctrl+b", "command+b"], (e) => { Mousetrap.bind(["ctrl+b", "command+b"], (e) => {
e.preventDefault(); e.preventDefault();
if (e.repeat) { if (e.repeat) {
@ -304,8 +323,11 @@ class App extends Component<RouteComponentProps, State> {
public componentDidUpdate(prevProps: RouteComponentProps) { public componentDidUpdate(prevProps: RouteComponentProps) {
if (this.props.location.pathname !== prevProps.location.pathname) { if (this.props.location.pathname !== prevProps.location.pathname) {
if (prod && (!(this.state.user?.level === "editor"))) { if (prod && !(this.state.user?.level === "editor")) {
ReactGA.pageview(window.location.pathname + window.location.search); ReactGA.send({
hitType: "pageview",
page: window.location.pathname + window.location.search,
});
} }
if (this.props.location.pathname === "/") { if (this.props.location.pathname === "/") {
this.handleSearchValueChange(""); this.handleSearchValueChange("");
@ -316,20 +338,29 @@ class App extends Component<RouteComponentProps, State> {
page: 1, page: 1,
}); });
} }
if (editorOnlyPages.includes(this.props.location.pathname) && !(this.state.user?.level === "editor")) { if (
editorOnlyPages.includes(this.props.location.pathname) &&
!(this.state.user?.level === "editor")
) {
this.props.history.replace("/"); this.props.history.replace("/");
} }
} }
if (getWordId(this.props.location.search) !== getWordId(prevProps.location.search)) { if (
if (prod && ((this.state.user?.level !== "editor"))) { getWordId(this.props.location.search) !==
ReactGA.pageview(window.location.pathname + window.location.search); getWordId(prevProps.location.search)
) {
if (prod && this.state.user?.level !== "editor") {
ReactGA.send({
type: "pageview",
page: window.location.pathname + window.location.search,
});
} }
const wordId = getWordId(this.props.location.search); const wordId = getWordId(this.props.location.search);
/* istanbul ignore else */ /* istanbul ignore else */
if (wordId) { if (wordId) {
this.handleIsolateEntry(wordId, true); this.handleIsolateEntry(wordId, true);
} else { } else {
this.setState({ isolatedEntry: undefined }) this.setState({ isolatedEntry: undefined });
} }
} }
// if (!["/wordlist", "/settings", "/review-tasks"].includes(this.props.location.pathname)) { // if (!["/wordlist", "/settings", "/review-tasks"].includes(this.props.location.pathname)) {
@ -356,12 +387,17 @@ class App extends Component<RouteComponentProps, State> {
return; return;
} }
if (!userObjIsEqual(prevUser, user)) { if (!userObjIsEqual(prevUser, user)) {
console.log("setting state user because something is different about the user") console.log(
"setting state user because something is different about the user"
);
this.setState({ user }); this.setState({ user });
saveUser(user); saveUser(user);
} }
if (user) { if (user) {
startLocalDbs(user, { wordlist: this.handleRefreshWordlist, reviewTasks: this.handleRefreshReviewTasks }); startLocalDbs(user, {
wordlist: this.handleRefreshWordlist,
reviewTasks: this.handleRefreshReviewTasks,
});
} else { } else {
stopLocalDbs(); stopLocalDbs();
} }
@ -372,9 +408,11 @@ class App extends Component<RouteComponentProps, State> {
private handleDictionaryUpdate() { private handleDictionaryUpdate() {
// TODO: fix - what the heck happened and what's going on here // TODO: fix - what the heck happened and what's going on here
dictionary.update(() => { dictionary
.update(() => {
// this.setState({ dictionaryStatus: "updating" }); // this.setState({ dictionaryStatus: "updating" });
}).then(({ dictionaryInfo }) => { })
.then(({ dictionaryInfo }) => {
//if (this.state.dictionaryInfo?.release !== dictionaryInfo?.release) { //if (this.state.dictionaryInfo?.release !== dictionaryInfo?.release) {
// to avoid unnecessary re-rendering that breaks things // to avoid unnecessary re-rendering that breaks things
this.setState({ this.setState({
@ -382,7 +420,8 @@ class App extends Component<RouteComponentProps, State> {
dictionaryInfo, dictionaryInfo,
}); });
//} //}
}).catch(() => { })
.catch(() => {
this.setState({ dictionaryStatus: "error loading" }); this.setState({ dictionaryStatus: "error loading" });
}); });
} }
@ -394,9 +433,12 @@ class App extends Component<RouteComponentProps, State> {
// TODO: use a seperate reducer for changing text options (otherwise you could just be updating the saved text options instead of the user text options that the program is going off of) // TODO: use a seperate reducer for changing text options (otherwise you could just be updating the saved text options instead of the user text options that the program is going off of)
const options = optionsReducer(this.state.options, action); const options = optionsReducer(this.state.options, action);
saveOptions(options); saveOptions(options);
if (action.type === "toggleLanguage" || action.type === "toggleSearchType") { if (
action.type === "toggleLanguage" ||
action.type === "toggleSearchType"
) {
if (this.props.location.pathname !== "/new-entries") { if (this.props.location.pathname !== "/new-entries") {
this.setState(prevState => ({ this.setState((prevState) => ({
options, options,
page: 1, page: 1,
results: dictionary.search({ ...prevState, options }), results: dictionary.search({ ...prevState, options }),
@ -417,7 +459,10 @@ class App extends Component<RouteComponentProps, State> {
lastModified, lastModified,
textOptions, textOptions,
}; };
this.handleOptionsUpdate({ type: "updateTextOptionsRecord", payload: textOptionsRecord }); this.handleOptionsUpdate({
type: "updateTextOptionsRecord",
payload: textOptionsRecord,
});
} }
private handleSearchValueChange(searchValue: string) { private handleSearchValueChange(searchValue: string) {
@ -427,7 +472,7 @@ class App extends Component<RouteComponentProps, State> {
const lastChar = searchValue[searchValue.length - 1]; const lastChar = searchValue[searchValue.length - 1];
// don't let people type in a single digit (to allow for number shortcuts) // don't let people type in a single digit (to allow for number shortcuts)
// but do allow the whole thing to be numbers (to allow for pasting and searching for ts) // but do allow the whole thing to be numbers (to allow for pasting and searching for ts)
if (lastChar >= '0' && lastChar <= '9' && !(/^\d+$/.test(searchValue))) { if (lastChar >= "0" && lastChar <= "9" && !/^\d+$/.test(searchValue)) {
return; return;
} }
if (this.state.dictionaryStatus !== "ready") { if (this.state.dictionaryStatus !== "ready") {
@ -445,7 +490,7 @@ class App extends Component<RouteComponentProps, State> {
} }
return; return;
} }
this.setState(prevState => ({ this.setState((prevState) => ({
searchValue, searchValue,
results: dictionary.search({ ...prevState, searchValue }), results: dictionary.search({ ...prevState, searchValue }),
page: 1, page: 1,
@ -465,7 +510,11 @@ class App extends Component<RouteComponentProps, State> {
return; return;
} }
this.setState({ isolatedEntry }); this.setState({ isolatedEntry });
if (!onlyState && (this.props.location.pathname !== "/word" || (getWordId(this.props.location.search) !== ts))) { if (
!onlyState &&
(this.props.location.pathname !== "/word" ||
getWordId(this.props.location.search) !== ts)
) {
this.props.history.push(`/word?id=${isolatedEntry.ts}`); this.props.history.push(`/word?id=${isolatedEntry.ts}`);
} }
} }
@ -475,11 +524,15 @@ class App extends Component<RouteComponentProps, State> {
private cronJob = new CronJob("1/10 * * * *", () => { private cronJob = new CronJob("1/10 * * * *", () => {
this.handleDictionaryUpdate(); this.handleDictionaryUpdate();
this.handleLoadUser(); this.handleLoadUser();
}) });
/* istanbul ignore next */ /* istanbul ignore next */
private handleScroll() { private handleScroll() {
if (hitBottom() && this.props.location.pathname === "/search" && this.state.results.length >= (pageSize * this.state.page)) { if (
hitBottom() &&
this.props.location.pathname === "/search" &&
this.state.results.length >= pageSize * this.state.page
) {
const page = this.state.page + 1; const page = this.state.page + 1;
const moreResults = dictionary.search({ ...this.state, page }); const moreResults = dictionary.search({ ...this.state, page });
if (moreResults.length > this.state.results.length) { if (moreResults.length > this.state.results.length) {
@ -492,7 +545,10 @@ class App extends Component<RouteComponentProps, State> {
} }
private handleInflectionSearch() { private handleInflectionSearch() {
function prepValueForSearch(searchValue: string, textOptions: T.TextOptions): string { function prepValueForSearch(
searchValue: string,
textOptions: T.TextOptions
): string {
const s = revertSpelling(searchValue, textOptions.spelling); const s = revertSpelling(searchValue, textOptions.spelling);
return standardizePashto(s.trim()); return standardizePashto(s.trim());
} }
@ -501,7 +557,10 @@ class App extends Component<RouteComponentProps, State> {
setTimeout(() => { setTimeout(() => {
const inflectionSearchResults = searchAllInflections( const inflectionSearchResults = searchAllInflections(
allEntries(), allEntries(),
prepValueForSearch(this.state.searchValue, this.state.options.textOptionsRecord.textOptions), prepValueForSearch(
this.state.searchValue,
this.state.options.textOptionsRecord.textOptions
)
); );
this.setState({ inflectionSearchResults }); this.setState({ inflectionSearchResults });
}, 20); }, 20);
@ -525,48 +584,75 @@ class App extends Component<RouteComponentProps, State> {
} }
render() { render() {
return <div style={{ return (
paddingTop: this.state.options.searchBarPosition === "top" ? "75px" : "7px", <div
style={{
paddingTop:
this.state.options.searchBarPosition === "top" ? "75px" : "7px",
paddingBottom: "60px", paddingBottom: "60px",
}}> }}
>
<Helmet> <Helmet>
<title>LingDocs Pashto Dictionary</title> <title>LingDocs Pashto Dictionary</title>
</Helmet> </Helmet>
{this.state.options.searchBarPosition === "top" && <SearchBar {this.state.options.searchBarPosition === "top" && (
<SearchBar
state={this.state} state={this.state}
optionsDispatch={this.handleOptionsUpdate} optionsDispatch={this.handleOptionsUpdate}
handleSearchValueChange={this.handleSearchValueChange} handleSearchValueChange={this.handleSearchValueChange}
/>} />
)}
<div className="container-fluid" data-testid="body"> <div className="container-fluid" data-testid="body">
{this.state.dictionaryStatus !== "ready" ? {this.state.dictionaryStatus !== "ready" ? (
<DictionaryStatusDisplay status={this.state.dictionaryStatus} /> <DictionaryStatusDisplay status={this.state.dictionaryStatus} />
: ) : (
<> <>
<Route path="/" exact={true}> <Route path="/" exact={true}>
<div className="text-center mt-4"> <div className="text-center mt-4">
<h4 className="font-weight-light p-3 mb-4">LingDocs Pashto Dictionary</h4> <h4 className="font-weight-light p-3 mb-4">
LingDocs Pashto Dictionary
</h4>
<div className="mt-4 font-weight-light"> <div className="mt-4 font-weight-light">
<div className="mb-4 small"> <div className="mb-4 small">
{this.state.options.searchType === "alphabetical" {this.state.options.searchType === "alphabetical" ? (
? <><span className="fa fa-book mr-2" /> Alphabetical browsing mode</> <>
: <><span className="fa fa-bolt mr-2" /> Approximate search mode</>} <span className="fa fa-book mr-2" /> Alphabetical
browsing mode
</>
) : (
<>
<span className="fa fa-bolt mr-2" /> Approximate
search mode
</>
)}
</div> </div>
</div> </div>
{this.state.user?.level === "editor" && <div className="mt-4 font-weight-light"> {this.state.user?.level === "editor" && (
<div className="mt-4 font-weight-light">
<div className="mb-3">Editor privileges active</div> <div className="mb-3">Editor privileges active</div>
<Link to="/edit"> <Link to="/edit">
<button className="btn btn-secondary">New Entry</button> <button className="btn btn-secondary">New Entry</button>
</Link> </Link>
</div>} </div>
<Link to="/new-entries" className="plain-link font-weight-light"> )}
<Link
to="/new-entries"
className="plain-link font-weight-light"
>
<div className="my-4">New words this month</div> <div className="my-4">New words this month</div>
</Link> </Link>
<div className="mt-4 pt-3"> <div className="mt-4 pt-3">
<Link to="/phrase-builder" className="plain-link h5 font-weight-light"> <Link
to="/phrase-builder"
className="plain-link h5 font-weight-light"
>
Phrase Builder Phrase Builder
</Link> </Link>
<span className="mx-1"> </span> <span className="mx-1"> </span>
<a href="https://grammar.lingdocs.com" className="plain-link h5 font-weight-light"> <a
href="https://grammar.lingdocs.com"
className="plain-link h5 font-weight-light"
>
Grammar Grammar
</a> </a>
</div> </div>
@ -601,18 +687,21 @@ class App extends Component<RouteComponentProps, State> {
</Route> </Route>
<Route path="/new-entries"> <Route path="/new-entries">
<h4 className="mb-3">New Words This Month</h4> <h4 className="mb-3">New Words This Month</h4>
{this.state.results.length ? {this.state.results.length ? (
<Results <Results
state={this.state} state={this.state}
isolateEntry={this.handleIsolateEntry} isolateEntry={this.handleIsolateEntry}
handleInflectionSearch={this.handleInflectionSearch} handleInflectionSearch={this.handleInflectionSearch}
/> />
: ) : (
<div>No new words added this month 😓</div> <div>No new words added this month 😓</div>
} )}
</Route> </Route>
<Route path="/account"> <Route path="/account">
<Account user={this.state.user} loadUser={this.handleLoadUser} /> <Account
user={this.state.user}
loadUser={this.handleLoadUser}
/>
</Route> </Route>
<Route path="/word"> <Route path="/word">
<IsolatedEntry <IsolatedEntry
@ -634,59 +723,101 @@ class App extends Component<RouteComponentProps, State> {
<Route path="/script-to-phonetics"> <Route path="/script-to-phonetics">
<ScriptToPhonetics /> <ScriptToPhonetics />
</Route> </Route>
{this.state.user?.level === "editor" && <Route path="/edit"> {this.state.user?.level === "editor" && (
<Route path="/edit">
<EntryEditor <EntryEditor
isolatedEntry={this.state.isolatedEntry} isolatedEntry={this.state.isolatedEntry}
user={this.state.user} user={this.state.user}
textOptions={getTextOptions(this.state)} textOptions={getTextOptions(this.state)}
dictionary={dictionary} dictionary={dictionary}
searchParams={new URLSearchParams(this.props.history.location.search)} searchParams={
/> new URLSearchParams(this.props.history.location.search)
</Route>}
{this.state.user?.level === "editor" && <Route path="/review-tasks">
<ReviewTasks state={this.state} />
</Route>}
</>
} }
/>
</Route>
)}
{this.state.user?.level === "editor" && (
<Route path="/review-tasks">
<ReviewTasks state={this.state} />
</Route>
)}
</>
)}
</div> </div>
<footer className={classNames( <footer
className={classNames(
"footer", "footer",
{ "bg-white": !["/search", "/word"].includes(this.props.location.pathname) }, {
{ "footer-thick": this.state.options.searchBarPosition === "bottom" && !["/search", "/word"].includes(this.props.location.pathname) }, "bg-white": !["/search", "/word"].includes(
{ "wee-less-footer": this.state.options.searchBarPosition === "bottom" && ["/search", "/word"].includes(this.props.location.pathname) }, this.props.location.pathname
)}> ),
},
{
"footer-thick":
this.state.options.searchBarPosition === "bottom" &&
!["/search", "/word"].includes(this.props.location.pathname),
},
{
"wee-less-footer":
this.state.options.searchBarPosition === "bottom" &&
["/search", "/word"].includes(this.props.location.pathname),
}
)}
>
<Route path="/" exact={true}> <Route path="/" exact={true}>
<div className="buttons-footer"> <div className="buttons-footer">
<BottomNavItem label="About" icon="info-circle" page="/about" /> <BottomNavItem label="About" icon="info-circle" page="/about" />
<BottomNavItem label="Settings" icon="cog" page="/settings" /> <BottomNavItem label="Settings" icon="cog" page="/settings" />
<BottomNavItem label={this.state.user ? "Account" : "Sign In"} icon="user" page="/account" />
<BottomNavItem <BottomNavItem
label={`Wordlist ${this.state.options.wordlistReviewBadge ? textBadge(forReview(this.state.wordlist).length) : ""}`} label={this.state.user ? "Account" : "Sign In"}
icon="user"
page="/account"
/>
<BottomNavItem
label={`Wordlist ${
this.state.options.wordlistReviewBadge
? textBadge(forReview(this.state.wordlist).length)
: ""
}`}
icon="list" icon="list"
page="/wordlist" page="/wordlist"
/> />
{this.state.user?.level === "editor" && {this.state.user?.level === "editor" && (
<BottomNavItem <BottomNavItem
label={`Tasks ${textBadge(this.state.reviewTasks.length)}`} label={`Tasks ${textBadge(this.state.reviewTasks.length)}`}
icon="edit" icon="edit"
page="/review-tasks" page="/review-tasks"
/> />
} )}
</div> </div>
</Route> </Route>
<Route path={["/about", "/settings", "/new-entries", "/account", "/wordlist", "/edit", "/review-tasks", "/phrase-builder"]}> <Route
path={[
"/about",
"/settings",
"/new-entries",
"/account",
"/wordlist",
"/edit",
"/review-tasks",
"/phrase-builder",
]}
>
<div className="buttons-footer"> <div className="buttons-footer">
<BottomNavItem label="Home" icon="home" page="/" /> <BottomNavItem label="Home" icon="home" page="/" />
</div> </div>
</Route> </Route>
{this.state.options.searchBarPosition === "bottom" && <SearchBar {this.state.options.searchBarPosition === "bottom" && (
<SearchBar
state={this.state} state={this.state}
optionsDispatch={this.handleOptionsUpdate} optionsDispatch={this.handleOptionsUpdate}
handleSearchValueChange={this.handleSearchValueChange} handleSearchValueChange={this.handleSearchValueChange}
onBottom={true} onBottom={true}
/>} />
)}
</footer> </footer>
</div>; </div>
);
} }
} }

View File

@ -11335,10 +11335,10 @@ react-fast-compare@^3.0.1, react-fast-compare@^3.1.1:
resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-3.2.0.tgz#641a9da81b6a6320f270e89724fb45a0b39e43bb" resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-3.2.0.tgz#641a9da81b6a6320f270e89724fb45a0b39e43bb"
integrity sha512-rtGImPZ0YyLrscKI9xTpV8psd6I8VAtjKCzQDlzyDvqJA8XOW78TXYQwNRNd8g8JZnDu8q9Fu/1v4HPAVwVdHA== integrity sha512-rtGImPZ0YyLrscKI9xTpV8psd6I8VAtjKCzQDlzyDvqJA8XOW78TXYQwNRNd8g8JZnDu8q9Fu/1v4HPAVwVdHA==
react-ga@^3.3.0: react-ga4@^2.1.0:
version "3.3.0" version "2.1.0"
resolved "https://registry.yarnpkg.com/react-ga/-/react-ga-3.3.0.tgz#c91f407198adcb3b49e2bc5c12b3fe460039b3ca" resolved "https://registry.yarnpkg.com/react-ga4/-/react-ga4-2.1.0.tgz#56601f59d95c08466ebd6edfbf8dede55c4678f9"
integrity sha512-o8RScHj6Lb8cwy3GMrVH6NJvL+y0zpJvKtc0+wmH7Bt23rszJmnqEQxRbyrqUzk9DTJIHoP42bfO5rswC9SWBQ== integrity sha512-ZKS7PGNFqqMd3PJ6+C2Jtz/o1iU9ggiy8Y8nUeksgVuvNISbmrQtJiZNvC/TjDsqD0QlU5Wkgs7i+w9+OjHhhQ==
react-helmet@^6.1.0: react-helmet@^6.1.0:
version "6.1.0" version "6.1.0"