diff --git a/package.json b/package.json index 626c52b..7031b83 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,7 @@ "react-bootstrap": "1.6.4", "react-countdown-circle-timer": "3.0.9", "react-dom": "^18.2.0", - "react-ga": "3.3.0", + "react-ga4": "^2.1.0", "react-media": "1", "react-player": "2.10.1", "react-rewards": "1.1.2", diff --git a/src/App.tsx b/src/App.tsx index 86e75f3..c521b2d 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -19,19 +19,19 @@ import LandingPage from "./pages/LandingPage"; import AccountPage from "./pages/AccountPage"; import { useEffect } from "react"; import { isProd } from "./lib/isProd"; -import ReactGA from "react-ga"; +import ReactGA from "react-ga4"; import { useUser } from "./user-context"; import PrivacyPolicy from "./pages/PrivacyPolicy"; import SearchPage from "./pages/SearchPage"; -const chapters = content.reduce((chapters, item) => ( - item.content - ? [...chapters, item] - : [...chapters, ...item.chapters] -), []); +const chapters = content.reduce( + (chapters, item) => + item.content ? [...chapters, item] : [...chapters, ...item.chapters], + [] +); if (isProd) { - ReactGA.initialize("UA-196576671-2"); + ReactGA.initialize("387148608"); ReactGA.set({ anonymizeIp: true }); } @@ -41,15 +41,18 @@ function App() { const navigate = useNavigate(); const { user } = useUser(); function logAnalytics() { - if (isProd && !(user?.admin)) { - ReactGA.pageview(window.location.pathname); - }; + if (isProd && !user?.admin) { + ReactGA.send({ + hitType: "pageview", + page: window.location.pathname, + }); + } } useEffect(() => { logAnalytics(); if (window.location.pathname === "/") { if (localStorage.getItem("visitedOnce")) { - navigate("/table-of-contents", { replace: true }) + navigate("/table-of-contents", { replace: true }); } else { localStorage.setItem("visitedOnce", "true"); } @@ -65,7 +68,10 @@ function App() { <>
-
+
- } - /> - } - /> - } - /> + } /> + } /> + } /> } @@ -96,15 +93,12 @@ function App() { element={{chapter}} /> ))} - } - /> + } /> } />
- + ); } diff --git a/src/components/phrase-diagram/EditableEPEx.tsx b/src/components/phrase-diagram/EditableEPEx.tsx index 45203a0..95d3f4d 100644 --- a/src/components/phrase-diagram/EditableEPEx.tsx +++ b/src/components/phrase-diagram/EditableEPEx.tsx @@ -1,64 +1,84 @@ -import { - Types as T, - EPDisplay, - EPPicker, -} from "@lingdocs/ps-react"; +import { Types as T, EPDisplay, EPPicker } from "@lingdocs/ps-react"; import entryFeeder from "../../lib/entry-feeder"; import { useState } from "react"; -import ReactGA from "react-ga"; +import ReactGA from "react-ga4"; import { isProd } from "../../lib/isProd"; import { useUser } from "../../user-context"; export function EditIcon() { - return ; + return ; } -function EditableEPEx({ children, opts, hideOmitSubject, noEdit }: { children: T.EPSelectionState, opts: T.TextOptions, hideOmitSubject?: boolean, noEdit?: boolean }) { - const [editing, setEditing] = useState(false); - const [eps, setEps] = useState(children); - const { user } = useUser(); - function handleReset() { - setEditing(false); - setEps(children); +function EditableEPEx({ + children, + opts, + hideOmitSubject, + noEdit, +}: { + children: T.EPSelectionState; + opts: T.TextOptions; + hideOmitSubject?: boolean; + noEdit?: boolean; +}) { + const [editing, setEditing] = useState(false); + const [eps, setEps] = useState(children); + const { user } = useUser(); + function handleReset() { + setEditing(false); + setEps(children); + } + function logEdit() { + if (isProd && !user?.admin) { + ReactGA.event({ + category: "Example", + action: `edit EPex - ${window.location.pathname}`, + label: "edit EPex", + }); } - function logEdit() { - if (isProd && !(user?.admin)) { - ReactGA.event({ - category: "Example", - action: `edit EPex - ${window.location.pathname}`, - label: "edit EPex" - }); - } - } - return
- {!noEdit &&
{ - setEditing(true); - logEdit(); - }} + } + return ( +
+ {!noEdit && ( +
{ + setEditing(true); + logEdit(); + } + } > - {!editing ? : } -
} - {editing - && } - setEps(o => ({ - ...o, - omitSubject: value === "true", - }))} - justify="left" - onlyOne + {!editing ? : } +
+ )} + {editing && ( + -
; + )} + + setEps((o) => ({ + ...o, + omitSubject: value === "true", + })) + } + justify="left" + onlyOne + /> +
+ ); } -export default EditableEPEx; \ No newline at end of file +export default EditableEPEx; diff --git a/src/components/phrase-diagram/EditableVPEx.tsx b/src/components/phrase-diagram/EditableVPEx.tsx index 372fd74..9e9c5cb 100644 --- a/src/components/phrase-diagram/EditableVPEx.tsx +++ b/src/components/phrase-diagram/EditableVPEx.tsx @@ -1,84 +1,103 @@ import { - Types as T, - VPDisplay, - VPPicker, - vpsReducer, + Types as T, + VPDisplay, + VPPicker, + vpsReducer, } from "@lingdocs/ps-react"; import entryFeeder from "../../lib/entry-feeder"; import { useState } from "react"; -import ReactGA from "react-ga"; +import ReactGA from "react-ga4"; import { isProd } from "../../lib/isProd"; import { useUser } from "../../user-context"; export function EditIcon() { - return ; + return ; } // TODO: Ability to show all variations -function EditableVPEx({ children, opts, formChoice, noEdit, length, mode, sub, allVariations }: { - children: T.VPSelectionState, - opts: T.TextOptions, - formChoice?: boolean, - noEdit?: boolean, - length?: "long" | "short", - mode?: "text" | "blocks", - sub?: string | JSX.Element, - allVariations?: boolean, +function EditableVPEx({ + children, + opts, + formChoice, + noEdit, + length, + mode, + sub, + allVariations, +}: { + children: T.VPSelectionState; + opts: T.TextOptions; + formChoice?: boolean; + noEdit?: boolean; + length?: "long" | "short"; + mode?: "text" | "blocks"; + sub?: string | JSX.Element; + allVariations?: boolean; }) { - const [editing, setEditing] = useState(false); - const [selectedLength, setSelectedLength] = useState<"long" | "short">(length || "short"); - const [vps, setVps] = useState({ ...children }); - const { user } = useUser(); - function logEdit() { - if (isProd && !(user?.admin)) { - ReactGA.event({ - category: "Example", - action: `edit VPex - ${window.location.pathname}`, - label: "edit VPex" - }); - } + const [editing, setEditing] = useState(false); + const [selectedLength, setSelectedLength] = useState<"long" | "short">( + length || "short" + ); + const [vps, setVps] = useState({ ...children }); + const { user } = useUser(); + function logEdit() { + if (isProd && !user?.admin) { + ReactGA.event({ + category: "Example", + action: `edit VPex - ${window.location.pathname}`, + label: "edit VPex", + }); } - function handleReset() { - // TODO: this is crazy, how does children get changed after calling setVps ??? - setVps(children); - setEditing(false); - } - function handleSetForm(form: T.FormVersion) { - setVps(vpsReducer(vps, { type: "set form", payload: form })); - } - return
- {!noEdit &&
{ - setEditing(true); - logEdit(); - }} + } + function handleReset() { + // TODO: this is crazy, how does children get changed after calling setVps ??? + setVps(children); + setEditing(false); + } + function handleSetForm(form: T.FormVersion) { + setVps(vpsReducer(vps, { type: "set form", payload: form })); + } + return ( +
+ {!noEdit && ( +
{ + setEditing(true); + logEdit(); + } + } > - {!editing ? : } -
} - {editing - && - } - : } +
+ )} + {editing && ( + - {sub &&
{sub}
} -
; + )} + + {sub &&
{sub}
} +
+ ); } -export default EditableVPEx; \ No newline at end of file +export default EditableVPEx; diff --git a/src/games/GameCore.tsx b/src/games/GameCore.tsx index db1d3ff..8e2fcca 100644 --- a/src/games/GameCore.tsx +++ b/src/games/GameCore.tsx @@ -1,354 +1,452 @@ import { useState, useRef, useEffect } from "react"; import { CountdownCircleTimer } from "react-countdown-circle-timer"; -import Reward, { RewardElement } from 'react-rewards'; +import Reward, { RewardElement } from "react-rewards"; import Link from "../components/Link"; import { useUser } from "../user-context"; import "./timer.css"; -import { - getPercentageDone, -} from "../lib/game-utils"; -import { - saveResult, - postSavedResults, -} from "../lib/game-results"; -import { - AT, - getTimestamp, -} from "@lingdocs/lingdocs-main"; -import { - randFromArray, - Types, -} from "@lingdocs/ps-react"; -import ReactGA from "react-ga"; +import { getPercentageDone } from "../lib/game-utils"; +import { saveResult, postSavedResults } from "../lib/game-results"; +import { AT, getTimestamp } from "@lingdocs/lingdocs-main"; +import { randFromArray, Types } from "@lingdocs/ps-react"; +import ReactGA from "react-ga4"; import { isProd } from "../lib/isProd"; import autoAnimate from "@formkit/auto-animate"; const errorVibration = 200; const strikesToFail = 3; -type GameState = ({ - mode: "practice", - showAnswer: boolean, -} | { - mode: "intro" | "test" | "fail" | "timeout" | "complete", - showAnswer: false, -}) & { - numberComplete: number, - current: Question, - timerKey: number, - strikes: number, - justStruck: boolean, -} +type GameState = ( + | { + mode: "practice"; + showAnswer: boolean; + } + | { + mode: "intro" | "test" | "fail" | "timeout" | "complete"; + showAnswer: false; + } +) & { + numberComplete: number; + current: Question; + timerKey: number; + strikes: number; + justStruck: boolean; +}; -type GameReducerAction = { - type: "handle question response", - payload: { correct: boolean }, -} | { - type: "start", - payload: "practice" | "test", -} | { - type: "quit", -} | { - type: "timeout", -} | { - type: "toggle show answer", -} | { - type: "skip", -} - -function GameCore({ inChapter, getQuestion, amount, Display, DisplayCorrectAnswer, timeLimit, Instructions, studyLink, id }: { - inChapter: boolean, - id: string, - studyLink: string, - Instructions: (props: { opts?: Types.TextOptions }) => JSX.Element, - getQuestion: () => Question, - DisplayCorrectAnswer: (props: { question: Question }) => JSX.Element, - Display: (props: QuestionDisplayProps) => JSX.Element, - timeLimit: number, - amount: number, -}) { - const initialState: GameState = { - mode: "intro", - numberComplete: 0, - current: getQuestion(), - timerKey: 0, - strikes: 0, - justStruck: false, - showAnswer: false, +type GameReducerAction = + | { + type: "handle question response"; + payload: { correct: boolean }; + } + | { + type: "start"; + payload: "practice" | "test"; + } + | { + type: "quit"; + } + | { + type: "timeout"; + } + | { + type: "toggle show answer"; + } + | { + type: "skip"; }; - // TODO: report pass with id to user info - const rewardRef = useRef(null); - const parent = useRef(null); - const { user, pullUser, setUser } = useUser(); - const [state, setStateDangerous] = useState>(initialState); - useEffect(() => { - parent.current && autoAnimate(parent.current) - }, [parent]); - const gameReducer = (gs: GameState, action: GameReducerAction): GameState => { - if (action.type === "handle question response") { - if (gs.mode === "test") { - if (action.payload.correct) { - const numberComplete = gs.numberComplete + 1; - if (numberComplete === amount) { - logGameEvent("passed"); - rewardRef.current?.rewardMe(); - handleResult(true); - return { - ...gs, - numberComplete, - justStruck: false, - mode: "complete", +function GameCore({ + inChapter, + getQuestion, + amount, + Display, + DisplayCorrectAnswer, + timeLimit, + Instructions, + studyLink, + id, +}: { + inChapter: boolean; + id: string; + studyLink: string; + Instructions: (props: { opts?: Types.TextOptions }) => JSX.Element; + getQuestion: () => Question; + DisplayCorrectAnswer: (props: { question: Question }) => JSX.Element; + Display: (props: QuestionDisplayProps) => JSX.Element; + timeLimit: number; + amount: number; +}) { + const initialState: GameState = { + mode: "intro", + numberComplete: 0, + current: getQuestion(), + timerKey: 0, + strikes: 0, + justStruck: false, + showAnswer: false, + }; + // TODO: report pass with id to user info + const rewardRef = useRef(null); + const parent = useRef(null); + const { user, pullUser, setUser } = useUser(); + const [state, setStateDangerous] = + useState>(initialState); + useEffect(() => { + parent.current && autoAnimate(parent.current); + }, [parent]); - } - } else { - return { - ...gs, - numberComplete, - current: getQuestion(), - justStruck: false, - }; - } - } else { - punish(); - const strikes = gs.strikes + 1; - if (strikes === strikesToFail) { - logGameEvent("fail"); - handleResult(false); - return { - ...gs, - strikes, - mode: "fail", - justStruck: false, - }; - } else { - return { - ...gs, - strikes, - justStruck: true, - }; - } - } - } - else /* (gs.mode === "practice") */ { - if (action.payload.correct) { - const numberComplete = gs.numberComplete + (!gs.showAnswer ? 1 : 0); - return { - ...gs, - numberComplete, - current: getQuestion(), - justStruck: false, - showAnswer: false, - }; - } else { - punish(); - const strikes = gs.strikes + 1; - return { - ...gs, - strikes, - justStruck: true, - showAnswer: false, - }; - } - } - } - if (action.type === "start") { - logGameEvent(`started ${action.payload}`); + const gameReducer = ( + gs: GameState, + action: GameReducerAction + ): GameState => { + if (action.type === "handle question response") { + if (gs.mode === "test") { + if (action.payload.correct) { + const numberComplete = gs.numberComplete + 1; + if (numberComplete === amount) { + logGameEvent("passed"); + rewardRef.current?.rewardMe(); + handleResult(true); return { - ...initialState, - mode: action.payload, - current: getQuestion(), - timerKey: gs.timerKey + 1, - } - } - if (action.type === "quit") { + ...gs, + numberComplete, + justStruck: false, + mode: "complete", + }; + } else { return { - ...initialState, - timerKey: gs.timerKey + 1, - } - } - if (action.type === "timeout") { - logGameEvent("timeout"); + ...gs, + numberComplete, + current: getQuestion(), + justStruck: false, + }; + } + } else { + punish(); + const strikes = gs.strikes + 1; + if (strikes === strikesToFail) { + logGameEvent("fail"); handleResult(false); return { - ...gs, - mode: "timeout", - justStruck: false, - showAnswer: false, + ...gs, + strikes, + mode: "fail", + justStruck: false, }; - } - if (action.type === "toggle show answer") { - if (gs.mode === "practice") { - return { - ...gs, - justStruck: false, - showAnswer: !gs.showAnswer, - }; - } - return gs; - } - if (action.type === "skip") { - if (gs.mode === "practice") { - return { - ...gs, - current: getQuestion(), - justStruck: false, - showAnswer: false, - }; - } - return gs; - } - throw new Error("unknown GameReducerAction"); - } - - function dispatch(action: GameReducerAction) { - setStateDangerous(gs => gameReducer(gs, action)); - } - - function logGameEvent(action: string) { - if (isProd && !(user?.admin)) { - ReactGA.event({ - category: "Game", - action: `${action} - ${id}`, - label: id, - }); - } - } - function punish() { - if (navigator.vibrate) { - navigator.vibrate(errorVibration); - } - } - function handleResult(done: boolean) { - const result: AT.TestResult = { - done, - time: getTimestamp(), - id, - }; - // add the test to the user object - if (!user) return; - setUser((u) => { - // pure type safety with the prevUser - if (!u) return u; + } else { return { - ...u, - tests: [...u.tests, result], + ...gs, + strikes, + justStruck: true, }; - }); - // save the test result in local storage - saveResult(result, user.userId); - // try to post the result - postSavedResults(user.userId).then((r) => { - if (r === "sent") pullUser(); - }).catch(console.error); + } + } + } /* (gs.mode === "practice") */ else { + if (action.payload.correct) { + const numberComplete = gs.numberComplete + (!gs.showAnswer ? 1 : 0); + return { + ...gs, + numberComplete, + current: getQuestion(), + justStruck: false, + showAnswer: false, + }; + } else { + punish(); + const strikes = gs.strikes + 1; + return { + ...gs, + strikes, + justStruck: true, + showAnswer: false, + }; + } + } } - function getProgressWidth(): string { - const num = !state.current - ? 0 - : (state.mode === "complete") - ? 100 - : getPercentageDone(state.numberComplete, amount); - return `${num}%`; + if (action.type === "start") { + logGameEvent(`started ${action.payload}`); + return { + ...initialState, + mode: action.payload, + current: getQuestion(), + timerKey: gs.timerKey + 1, + }; } - const progressColor = state.mode === "complete" - ? "success" - : (state.mode === "fail" || state.mode === "timeout") - ? "danger" - : "primary"; - const gameRunning = state.mode === "practice" || state.mode === "test"; - function ActionButtons() { - return
- {!inChapter && - - } - - -
; + if (action.type === "quit") { + return { + ...initialState, + timerKey: gs.timerKey + 1, + }; } - return <> -
- {(state.mode === "test" || state.mode === "intro") &&
-
-
} -
- {state.mode === "test" - ? - : state.mode === "practice" ? - :
} -
- {state.mode === "test" && dispatch({ type: "timeout" })} - />} - {state.mode !== "intro" && } -
-
-
- {state.justStruck &&
- {getStrikeMessage()} -
} -
- -
- {state.mode === "intro" &&
-
- {/* TODO: ADD IN TEXT DISPLAY OPTIONS HERE TOO - WHEN WE START USING THEM*/} - -
- -
} - {gameRunning && dispatch({ type: "handle question response", payload: { correct }})} - />} - {(state.mode === "practice" && (state.justStruck || state.showAnswer)) &&
- -
} - {(state.showAnswer && state.mode === "practice") &&
-
- -
- -
} - {state.mode === "complete" &&
-

- 🎉 Finished! -

- -
} - {(state.mode === "timeout" || state.mode === "fail") &&
-

{failMessage({ - numberComplete: state.numberComplete, - amount, - type: state.mode, - })}

-
The correct answer was:
-
- -
-
- -
-
} -
-
+ if (action.type === "timeout") { + logGameEvent("timeout"); + handleResult(false); + return { + ...gs, + mode: "timeout", + justStruck: false, + showAnswer: false, + }; + } + if (action.type === "toggle show answer") { + if (gs.mode === "practice") { + return { + ...gs, + justStruck: false, + showAnswer: !gs.showAnswer, + }; + } + return gs; + } + if (action.type === "skip") { + if (gs.mode === "practice") { + return { + ...gs, + current: getQuestion(), + justStruck: false, + showAnswer: false, + }; + } + return gs; + } + throw new Error("unknown GameReducerAction"); + }; + + function dispatch(action: GameReducerAction) { + setStateDangerous((gs) => gameReducer(gs, action)); + } + + function logGameEvent(action: string) { + if (isProd && !user?.admin) { + ReactGA.event({ + category: "Game", + action: `${action} - ${id}`, + label: id, + }); + } + } + function punish() { + if (navigator.vibrate) { + navigator.vibrate(errorVibration); + } + } + function handleResult(done: boolean) { + const result: AT.TestResult = { + done, + time: getTimestamp(), + id, + }; + // add the test to the user object + if (!user) return; + setUser((u) => { + // pure type safety with the prevUser + if (!u) return u; + return { + ...u, + tests: [...u.tests, result], + }; + }); + // save the test result in local storage + saveResult(result, user.userId); + // try to post the result + postSavedResults(user.userId) + .then((r) => { + if (r === "sent") pullUser(); + }) + .catch(console.error); + } + function getProgressWidth(): string { + const num = !state.current + ? 0 + : state.mode === "complete" + ? 100 + : getPercentageDone(state.numberComplete, amount); + return `${num}%`; + } + const progressColor = + state.mode === "complete" + ? "success" + : state.mode === "fail" || state.mode === "timeout" + ? "danger" + : "primary"; + const gameRunning = state.mode === "practice" || state.mode === "test"; + function ActionButtons() { + return ( +
+ {!inChapter && ( + + + + )} + + +
+ ); + } + return ( + <> +
+ {(state.mode === "test" || state.mode === "intro") && ( +
+
+
+ )} +
+ {state.mode === "test" ? ( + + ) : state.mode === "practice" ? ( + + ) : ( +
+ )} +
+ {state.mode === "test" && ( + dispatch({ type: "timeout" })} + /> + )} + {state.mode !== "intro" && ( + + )} +
- {gameRunning &&
+ {state.justStruck && ( +
+ {getStrikeMessage()} +
+ )} +
+ +
+ {state.mode === "intro" && ( +
+
+ {/* TODO: ADD IN TEXT DISPLAY OPTIONS HERE TOO - WHEN WE START USING THEM*/} + +
+ +
+ )} + {gameRunning && ( + + dispatch({ + type: "handle question response", + payload: { correct }, + }) + } + /> + )} + {state.mode === "practice" && + (state.justStruck || state.showAnswer) && ( +
+ +
+ )} + {state.showAnswer && state.mode === "practice" && ( +
+
+ +
+ +
+ )} + {state.mode === "complete" && ( +
+

+ + 🎉 + {" "} + Finished! +

+ +
+ )} + {(state.mode === "timeout" || state.mode === "fail") && ( +
+

+ {failMessage({ + numberComplete: state.numberComplete, + amount, + type: state.mode, + })} +

+
The correct answer was:
+
+ +
+
+ +
+
+ )} +
+
+
+ {gameRunning && ( +
({ inChapter, getQuestion, amount, Display, DisplayCo width: "100%", height: "100%", zIndex: 6, - }}>
} - ; + }} + >
+ )} + + ); } -function PracticeStatusDisplay({ correct, incorrect }: { correct: number, incorrect: number }) { - return
-
Correct: {correct}
-
Incorrect: {incorrect}
+function PracticeStatusDisplay({ + correct, + incorrect, +}: { + correct: number; + incorrect: number; +}) { + return ( +
+
+ ✅ Correct: {correct} +
+
+ ❌ Incorrect: {incorrect} +
+ ); } function StrikesDisplay({ strikes }: { strikes: number }) { - return
- {[...Array(strikes)].map(_ => )} -
; + return ( +
+ {[...Array(strikes)].map((_) => ( + + ❌ + + ))} +
+ ); } function getStrikeMessage() { - return randFromArray([ - "Not quite! Try again.", - "No sorry, try again", - "Umm, no, try again", - "Try again", - "Oooooooo, sorry no...", - ]); + return randFromArray([ + "Not quite! Try again.", + "No sorry, try again", + "Umm, no, try again", + "Try again", + "Oooooooo, sorry no...", + ]); } -function failMessage({ numberComplete, amount, type }: { - numberComplete: number, - amount: number, - type: "timeout" | "fail", +function failMessage({ + numberComplete, + amount, + type, +}: { + numberComplete: number; + amount: number; + type: "timeout" | "fail"; }): string { - const pDone = getPercentageDone(numberComplete, amount); - const { message, face } = pDone < 20 - ? { message: "No, sorry", face: "😑" } - : pDone < 30 - ? { message: "Oops, that's wrong", face: "😟" } - : pDone < 55 - ? { message: "Fail", face: "😕" } - : pDone < 78 - ? { message: "You almost got it!", face: "😩" } - : { message: "Nooo! So close!", face: "😭" }; - return type === "fail" - ? `${message} ${face}` - : `⏳ Time's Up ${face}`; + const pDone = getPercentageDone(numberComplete, amount); + const { message, face } = + pDone < 20 + ? { message: "No, sorry", face: "😑" } + : pDone < 30 + ? { message: "Oops, that's wrong", face: "😟" } + : pDone < 55 + ? { message: "Fail", face: "😕" } + : pDone < 78 + ? { message: "You almost got it!", face: "😩" } + : { message: "Nooo! So close!", face: "😭" }; + return type === "fail" ? `${message} ${face}` : `⏳ Time's Up ${face}`; } -export default GameCore; \ No newline at end of file +export default GameCore; diff --git a/w-grammar.txt b/w-grammar.txt new file mode 100644 index 0000000..c940dec --- /dev/null +++ b/w-grammar.txt @@ -0,0 +1,29 @@ +Metaproductions + +PERSON :: 0 .. 11 + +Hyper-rules + +s () + => es + => vs + +es (subj: PERSON) + => np(subj), adj(subj), eq(subj) +es (subj: PERSON, pred: PERSON) + => np(subj), np(pred), eq(pred) + +vs () + => vs-intrans + => vs-trans-past + => vs-trans-non-past + +vs-trans-non-past (subj: PERSON, obj: PERSON) + => np(subj), np(obj), v-trans-non-past(subj, obj) + => np(obj), np(subj), v-trans-non-past(subj, obj) + +vs-trans-past (subj: PERSON, obj: PERSON) + => np(subj), np(obj), v-trans-past(obj, obj) + => np(obj), np(subj), v-trans-past(obj, obj) + +vs-intrans-past (subj: PERSON) => np(subj), v-intrans(subj) \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index e77e13d..d8e67de 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4523,10 +4523,10 @@ react-fast-compare@^3.0.1: resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-3.2.0.tgz#641a9da81b6a6320f270e89724fb45a0b39e43bb" integrity sha512-rtGImPZ0YyLrscKI9xTpV8psd6I8VAtjKCzQDlzyDvqJA8XOW78TXYQwNRNd8g8JZnDu8q9Fu/1v4HPAVwVdHA== -react-ga@3.3.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/react-ga/-/react-ga-3.3.0.tgz#c91f407198adcb3b49e2bc5c12b3fe460039b3ca" - integrity sha512-o8RScHj6Lb8cwy3GMrVH6NJvL+y0zpJvKtc0+wmH7Bt23rszJmnqEQxRbyrqUzk9DTJIHoP42bfO5rswC9SWBQ== +react-ga4@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/react-ga4/-/react-ga4-2.1.0.tgz#56601f59d95c08466ebd6edfbf8dede55c4678f9" + integrity sha512-ZKS7PGNFqqMd3PJ6+C2Jtz/o1iU9ggiy8Y8nUeksgVuvNISbmrQtJiZNvC/TjDsqD0QlU5Wkgs7i+w9+OjHhhQ== react-is@^16.13.1, react-is@^16.3.2, react-is@^16.7.0: version "16.13.1"