towards new verb explorer form

This commit is contained in:
lingdocs 2022-04-05 00:40:55 +05:00
parent 0467080343
commit 9f9236ba9b
6 changed files with 367 additions and 354 deletions

View File

@ -5,7 +5,7 @@
"dependencies": {
"@fortawesome/fontawesome-free": "^5.15.4",
"@lingdocs/lingdocs-main": "^0.2.0",
"@lingdocs/pashto-inflector": "^1.6.8",
"@lingdocs/pashto-inflector": "^1.6.9",
"@testing-library/jest-dom": "^5.11.4",
"@testing-library/react": "^11.1.0",
"@testing-library/user-event": "^12.1.10",

View File

@ -1,73 +1,12 @@
import Select from "react-select";
import {
makeNounSelection,
zIndexProps,
} from "./np-picker/picker-tools";
import {
Types as T,
ButtonSelect,
getVerbInfo,
} from "@lingdocs/pashto-inflector";
import { isPerfectTense } from "../lib/phrase-building/vp-tools";
import {
makeVerbSelection,
} from "./phrase-builder/verb-selection";
import EntrySelect from "./EntrySelect";
// import { useState } from "react";
const tenseOptions: { label: string | JSX.Element, value: VerbTense }[] = [{
label: <div><i className="fas fa-video mr-2" />present</div>,
value: "presentVerb",
}, {
label: <div><i className="fas fa-camera mr-2" />subjunctive</div>,
value: "subjunctiveVerb",
}, {
label: <div><i className="fas fa-video mr-2" />imperf. future</div>,
value: "imperfectiveFuture",
}, {
label: <div><i className="fas fa-camera mr-2" />perf. future</div>,
value: "perfectiveFuture",
}, {
label: <div><i className="fas fa-video mr-2" />continuous past</div>,
value: "imperfectivePast",
}, {
label: <div><i className="fas fa-camera mr-2" />simple past</div>,
value: "perfectivePast",
}, {
label: <div><i className="fas fa-video mr-2" />habitual cont. past.</div>,
value: "habitualImperfectivePast",
}, {
label: <div><i className="fas fa-camera mr-2" />habitual simp. past.</div>,
value: "habitualPerfectivePast",
}];
const perfectTenseOptions: { label: string | JSX.Element, value: PerfectTense }[] = [{
label: "Present Perfect",
value: "present perfect",
}, {
label: "Habitual Perfect",
value: "habitual perfect",
}, {
label: "Subjunctive Perfect",
value: "subjunctive perfect",
}, {
label: "Future Perfect",
value: "future perfect",
}, {
label: "Past Perfect",
value: "past perfect",
}, {
label: `"Would Be" Perfect`,
value: "wouldBe perfect",
}, {
label: "Past Subjunctive Perfect",
value: "pastSubjunctive perfect",
}];
// type Filters = {
// stative: boolean,
// dynamic: boolean,
// transitive: boolean,
// intransitive: boolean,
// grammaticallyTransitive: boolean,
// }
// TODO: dark on past tense selecitons
function VerbPicker({ onChange, subject, changeSubject, verb, verbs }: {
verbs: VerbEntry[],
@ -90,65 +29,6 @@ function VerbPicker({ onChange, subject, changeSubject, verb, verbs }: {
}
onChange(makeVerbSelection(v, changeSubject, verb));
}
function onTenseSelect(o: { value: VerbTense | PerfectTense } | null) {
const value = o?.value ? o.value : undefined;
if (verb && value) {
if (isPerfectTense(value)) {
onChange({
...verb,
tense: value,
tenseCategory: "perfect",
});
} else {
onChange({
...verb,
tense: value,
tenseCategory: verb.tenseCategory === "perfect" ? "basic" : verb.tenseCategory,
});
}
}
}
function moveTense(dir: "forward" | "back") {
if (!verb) return;
return () => {
const tenses = verb.tenseCategory === "perfect" ? perfectTenseOptions : tenseOptions;
const currIndex = tenses.findIndex(tn => tn.value === verb.tense)
if (currIndex === -1) {
console.error("error moving tense", dir);
return;
}
const newIndex = dir === "forward"
? ((currIndex + 1) % tenses.length)
: (currIndex === 0 ? (tenses.length - 1) : (currIndex - 1))
const newTense = tenses[newIndex];
onTenseSelect(newTense);
};
}
function onPosNegSelect(value: string) {
if (verb) {
onChange({
...verb,
negative: value === "true",
});
}
}
function onTenseCategorySelect(value: "basic" | "modal" | "perfect") {
if (verb) {
if (value === "perfect") {
onChange({
...verb,
tenseCategory: value,
tense: isPerfectTense(verb.tense) ? verb.tense : "present perfect",
});
} else {
onChange({
...verb,
tenseCategory: value,
tense: isPerfectTense(verb.tense) ? "presentVerb" : verb.tense,
});
}
}
}
function onVoiceSelect(value: "active" | "passive") {
if (verb && verb.changeVoice) {
if (value === "passive" && (typeof verb.object === "object")) {
@ -173,218 +53,63 @@ function VerbPicker({ onChange, subject, changeSubject, verb, verbs }: {
onChange(verb.changeStatDyn(c));
}
}
const tOptions = (verb?.tenseCategory === "perfect") ? perfectTenseOptions : tenseOptions;
return <div style={{ maxWidth: "225px", minWidth: "175px" }}>
<div>Verb:</div>
<EntrySelect
entries={verbs}
value={verb?.verb}
onChange={onVerbSelect}
name="Verb"
isVerbSelect
/>
{/* <Select
value={verb && verb.verb.entry.ts.toString()}
// @ts-ignore
onChange={onEntrySelect}
className="mb-2"
// @ts-ignore
options={options}
isSearchable
// // @ts-ignore
placeholder={verb ? options.find(o => o.value === (verb.verb.entry).ts.toString())?.label : "Select Verb..."}
{...zIndexProps}
/> */}
{verb && verb.changeTransitivity && <div className="text-center mt-3">
<ButtonSelect
small
options={[{
label: "gramm. trans.",
value: "grammatically transitive",
}, {
label: "trans.",
value: "transitive",
}]}
value={notInstransitive(verb.transitivity)}
handleChange={handleChangeTransitivity}
return <div className="mb-3">
<div style={{ maxWidth: "300px", margin: "0 auto" }}>
<div>Verb:</div>
<EntrySelect
entries={verbs}
value={verb?.verb}
onChange={onVerbSelect}
name="Verb"
isVerbSelect
/>
</div>}
{verb && <div className="col text-center my-3">
<ButtonSelect
small
value={verb.tenseCategory}
options={[{
label: "Basic",
value: "basic",
}, {
label: "Perfect",
value: "perfect",
}, {
label: "Modal",
value: "modal",
}]}
handleChange={onTenseCategorySelect}
/>
</div>}
{verb && verb.changeVoice && <div className="col text-center my-3">
<ButtonSelect
small
value={verb.voice}
options={[{
label: "Active",
value: "active",
}, {
label: "Passive",
value: "passive",
}]}
handleChange={onVoiceSelect}
/>
</div>}
<div>Tense:</div>
<Select
isSearchable={false}
// for some reason can't use tOptions with find here;
value={verb && ([...tenseOptions, ...perfectTenseOptions].find(o => o.value === verb.tense))}
onChange={onTenseSelect}
className="mb-2"
options={tOptions}
{...zIndexProps}
/>
{verb && verb.changeStatDyn && <div className="text-center">
<ButtonSelect
small
options={[{
label: "stative",
value: "stative",
}, {
label: "dynamic",
value: "dynamic",
}]}
value={verb.isCompound ? verb.isCompound : "stative"}
handleChange={handleChangeStatDyn}
/>
</div>}
{verb && <div className="d-flex flex-row justify-content-between align-items-center my-3" style={{ width: "100%" }}>
<div onClick={moveTense("back")} className="clickable">
<i className="fas fa-chevron-left" />
</div>
<ButtonSelect
small
value={verb.negative.toString()}
options={[{
label: "Pos.",
value: "false",
}, {
label: "Neg.",
value: "true",
}]}
handleChange={onPosNegSelect}
/>
<div onClick={moveTense("forward")} className="clickable">
<i className="fas fa-chevron-right" />
</div>
</div>}
</div>
<div className="d-flex flex-row justify-content-around flex-wrap" style={{ maxWidth: "400px", margin: "0 auto" }}>
{verb && verb.changeTransitivity && <div className="text-center my-2">
<ButtonSelect
small
options={[{
label: "gramm. trans.",
value: "grammatically transitive",
}, {
label: "trans.",
value: "transitive",
}]}
value={notInstransitive(verb.transitivity)}
handleChange={handleChangeTransitivity}
/>
</div>}
{verb && verb.changeVoice && <div className="text-center my-2">
<ButtonSelect
small
value={verb.voice}
options={[{
label: "Active",
value: "active",
}, {
label: "Passive",
value: "passive",
}]}
handleChange={onVoiceSelect}
/>
</div>}
{verb && verb.changeStatDyn && <div className="text-center my-2">
<ButtonSelect
small
options={[{
label: "stative",
value: "stative",
}, {
label: "dynamic",
value: "dynamic",
}]}
value={verb.isCompound ? verb.isCompound : "stative"}
handleChange={handleChangeStatDyn}
/>
</div>}
</div>
</div>;
}
function makeVerbSelection(verb: VerbEntry, changeSubject: (s: NPSelection | undefined) => void, oldVerbSelection?: VerbSelection): VerbSelection {
const info = getVerbInfo(verb.entry, verb.complement);
function getTransObjFromOldVerbSelection() {
if (
!oldVerbSelection ||
oldVerbSelection.object === "none" ||
typeof oldVerbSelection.object === "number" ||
oldVerbSelection.isCompound === "dynamic" ||
(oldVerbSelection.object?.type === "noun" && oldVerbSelection.object.dynamicComplement)
) return undefined;
return oldVerbSelection.object;
}
const transitivity: T.Transitivity = "grammaticallyTransitive" in info
? "transitive"
: info.transitivity;
const object = (transitivity === "grammatically transitive")
? T.Person.ThirdPlurMale
: (info.type === "dynamic compound" && oldVerbSelection?.voice !== "passive")
? makeNounSelection(info.objComplement.entry as NounEntry, true)
: (transitivity === "transitive" && oldVerbSelection?.voice !== "passive")
? getTransObjFromOldVerbSelection()
: "none";
if (oldVerbSelection?.voice === "passive" && info.type === "dynamic compound") {
changeSubject(makeNounSelection(info.objComplement.entry as NounEntry, true));
}
const isCompound = ("stative" in info || info.type === "stative compound")
? "stative"
: info.type === "dynamic compound"
? "dynamic"
: false;
// TODO: here and below in the changeStatDyn function ... allow for entries with complement
const dynAuxVerb: VerbEntry | undefined = isCompound !== "dynamic"
? undefined
: info.type === "dynamic compound"
? { entry: info.auxVerb } as VerbEntry
: "dynamic" in info
? { entry: info.dynamic.auxVerb } as VerbEntry
: undefined;
const tenseSelection = ((): { tenseCategory: "perfect", tense: PerfectTense } | {
tenseCategory: "basic" | "modal",
tense: VerbTense,
} => {
if (!oldVerbSelection) {
return { tense: "presentVerb", tenseCategory: "basic" };
}
if (oldVerbSelection.tenseCategory === "modal") {
return { tenseCategory: "modal", tense: isPerfectTense(oldVerbSelection.tense) ? "presentVerb" : oldVerbSelection.tense };
}
if (oldVerbSelection.tenseCategory === "basic") {
return { tenseCategory: "basic", tense: isPerfectTense(oldVerbSelection.tense) ? "presentVerb" : oldVerbSelection.tense };
}
return { tenseCategory: "perfect", tense: isPerfectTense(oldVerbSelection.tense) ? oldVerbSelection.tense : "present perfect" };
})();
return {
type: "verb",
verb: verb,
dynAuxVerb,
...tenseSelection,
object,
transitivity,
isCompound,
voice: transitivity === "transitive"
? (oldVerbSelection?.voice || "active")
: "active",
negative: oldVerbSelection ? oldVerbSelection.negative : false,
...("grammaticallyTransitive" in info) ? {
changeTransitivity: function(t) {
return {
...this,
transitivity: t,
object: t === "grammatically transitive" ? T.Person.ThirdPlurMale : undefined,
};
},
} : {},
...("stative" in info) ? {
changeStatDyn: function(c) {
return {
...this,
isCompound: c,
object: c === "dynamic"
? makeNounSelection(info.dynamic.objComplement.entry as NounEntry, true)
: undefined,
dynAuxVerb: c === "dynamic"
? { entry: info.dynamic.auxVerb } as VerbEntry
: undefined,
};
}
} : {},
...(transitivity === "transitive") ? {
changeVoice: function(v, s) {
return {
...this,
voice: v,
object: v === "active" ? s : "none",
};
},
} : {},
};
}
export default VerbPicker;

View File

@ -0,0 +1,177 @@
import Select from "react-select";
import {
zIndexProps,
} from "./np-picker/picker-tools";
import {
ButtonSelect,
} from "@lingdocs/pashto-inflector";
import { isPerfectTense } from "../lib/phrase-building/vp-tools";
const tenseOptions: { label: string | JSX.Element, value: VerbTense }[] = [{
label: <div><i className="fas fa-video mr-2" />present</div>,
value: "presentVerb",
}, {
label: <div><i className="fas fa-camera mr-2" />subjunctive</div>,
value: "subjunctiveVerb",
}, {
label: <div><i className="fas fa-video mr-2" />imperf. future</div>,
value: "imperfectiveFuture",
}, {
label: <div><i className="fas fa-camera mr-2" />perf. future</div>,
value: "perfectiveFuture",
}, {
label: <div><i className="fas fa-video mr-2" />continuous past</div>,
value: "imperfectivePast",
}, {
label: <div><i className="fas fa-camera mr-2" />simple past</div>,
value: "perfectivePast",
}, {
label: <div><i className="fas fa-video mr-2" />habitual cont. past.</div>,
value: "habitualImperfectivePast",
}, {
label: <div><i className="fas fa-camera mr-2" />habitual simp. past.</div>,
value: "habitualPerfectivePast",
}];
const perfectTenseOptions: { label: string | JSX.Element, value: PerfectTense }[] = [{
label: "Present Perfect",
value: "present perfect",
}, {
label: "Habitual Perfect",
value: "habitual perfect",
}, {
label: "Subjunctive Perfect",
value: "subjunctive perfect",
}, {
label: "Future Perfect",
value: "future perfect",
}, {
label: "Past Perfect",
value: "past perfect",
}, {
label: `"Would Be" Perfect`,
value: "wouldBe perfect",
}, {
label: "Past Subjunctive Perfect",
value: "pastSubjunctive perfect",
}];
function VerbPickerBelow({ onChange, verb }: {
verbs: VerbEntry[],
verb: VerbSelection | undefined,
onChange: (p: VerbSelection | undefined) => void,
}) {
function onTenseSelect(o: { value: VerbTense | PerfectTense } | null) {
const value = o?.value ? o.value : undefined;
if (verb && value) {
if (isPerfectTense(value)) {
onChange({
...verb,
tense: value,
tenseCategory: "perfect",
});
} else {
onChange({
...verb,
tense: value,
tenseCategory: verb.tenseCategory === "perfect" ? "basic" : verb.tenseCategory,
});
}
}
}
function moveTense(dir: "forward" | "back") {
if (!verb) return;
return () => {
const tenses = verb.tenseCategory === "perfect" ? perfectTenseOptions : tenseOptions;
const currIndex = tenses.findIndex(tn => tn.value === verb.tense)
if (currIndex === -1) {
console.error("error moving tense", dir);
return;
}
const newIndex = dir === "forward"
? ((currIndex + 1) % tenses.length)
: (currIndex === 0 ? (tenses.length - 1) : (currIndex - 1))
const newTense = tenses[newIndex];
onTenseSelect(newTense);
};
}
function onPosNegSelect(value: string) {
if (verb) {
onChange({
...verb,
negative: value === "true",
});
}
}
function onTenseCategorySelect(value: "basic" | "modal" | "perfect") {
if (verb) {
if (value === "perfect") {
onChange({
...verb,
tenseCategory: value,
tense: isPerfectTense(verb.tense) ? verb.tense : "present perfect",
});
} else {
onChange({
...verb,
tenseCategory: value,
tense: isPerfectTense(verb.tense) ? "presentVerb" : verb.tense,
});
}
}
}
const tOptions = (verb?.tenseCategory === "perfect") ? perfectTenseOptions : tenseOptions;
return <div className="mb-4 mt-3">
{verb && <div className="col text-center mb-2">
<ButtonSelect
small
value={verb.tenseCategory}
options={[{
label: "Basic",
value: "basic",
}, {
label: "Perfect",
value: "perfect",
}, {
label: "Modal",
value: "modal",
}]}
handleChange={onTenseCategorySelect}
/>
</div>}
<div style={{ maxWidth: "300px", margin: "0 auto" }}>
<div>Tense:</div>
<Select
isSearchable={false}
// for some reason can't use tOptions with find here;
value={verb && ([...tenseOptions, ...perfectTenseOptions].find(o => o.value === verb.tense))}
onChange={onTenseSelect}
className="mb-2"
options={tOptions}
{...zIndexProps}
/>
{verb && <div className="d-flex flex-row justify-content-between align-items-center my-3" style={{ width: "100%" }}>
<div onClick={moveTense("back")} className="clickable">
<i className="fas fa-chevron-left" />
</div>
<ButtonSelect
small
value={verb.negative.toString()}
options={[{
label: "Pos.",
value: "false",
}, {
label: "Neg.",
value: "true",
}]}
handleChange={onPosNegSelect}
/>
<div onClick={moveTense("forward")} className="clickable">
<i className="fas fa-chevron-right" />
</div>
</div>}
</div>
</div>;
}
export default VerbPickerBelow;

View File

@ -1,6 +1,7 @@
import { useState } from "react";
import NPPicker from "../np-picker/NPPicker";
import VerbPicker from "../VerbPicker";
import VerbPickerBelow from "../VerbPickerBelow";
import VPDisplay from "./VPDisplay";
import { verbs } from "../../words/words";
import { renderVP } from "../../lib/phrase-building";
@ -47,13 +48,20 @@ export function PhraseBuilder() {
<div>{kingEmoji} = <abbr title="controls the verb conjugation, can be removed">king</abbr> of phrase</div>
<div>{servantEmoji} = <abbr title="can be shrunken into a mini-pronoun">servant</abbr> of phrase</div>
</div>
<VerbPicker
verbs={verbs}
verb={verb}
subject={subject}
changeSubject={(s) => handleSubjectChange(s, true)}
onChange={setVerb}
/>
{(verb && (typeof verb.object === "object") && (verb.isCompound !== "dynamic")) &&
<div className="d-flex flex-row justify-content-around flex-wrap mb-2">
<button onClick={handleSubjObjSwap} className="btn btn-sm btn-light">
<i className="fas fa-exchange-alt mr-2" /> subj/obj
</button>
<div>{` `}</div>
</div>}
<div className="d-flex flex-row justify-content-around flex-wrap mt-4">
<button onClick={handleSubjObjSwap} className="btn btn-sm btn-light">
<i className="fas fa-exchange-alt mr-2" /> subj/obj
</button>
{/* <div>{` `}</div> */}
</div>}
<div className="d-flex flex-row justify-content-around flex-wrap">
<div className="my-2">
<div className="h4">Subject {showRole(VPRendered, "subject")}</div>
@ -74,17 +82,12 @@ export function PhraseBuilder() {
onChange={handleObjectChange}
/>}
</div>}
<div className="my-2">
<div className="h4">Verb</div>
<VerbPicker
verbs={verbs}
verb={verb}
subject={subject}
changeSubject={(s) => handleSubjectChange(s, true)}
onChange={setVerb}
/>
</div>
</div>
<VerbPickerBelow
verbs={verbs}
verb={verb}
onChange={setVerb}
/>
{verbPhrase && <div>
<VPDisplay VP={verbPhrase} />
</div>}

View File

@ -0,0 +1,108 @@
import {
makeNounSelection,
} from "../np-picker/picker-tools";
import {
getVerbInfo,
Types as T,
} from "@lingdocs/pashto-inflector";
import { isPerfectTense } from "../../lib/phrase-building/vp-tools";
export function makeVerbSelection(verb: VerbEntry, changeSubject: (s: NPSelection | undefined) => void, oldVerbSelection?: VerbSelection): VerbSelection {
const info = getVerbInfo(verb.entry, verb.complement);
function getTransObjFromOldVerbSelection() {
if (
!oldVerbSelection ||
oldVerbSelection.object === "none" ||
typeof oldVerbSelection.object === "number" ||
oldVerbSelection.isCompound === "dynamic" ||
(oldVerbSelection.object?.type === "noun" && oldVerbSelection.object.dynamicComplement)
) return undefined;
return oldVerbSelection.object;
}
const transitivity: T.Transitivity = "grammaticallyTransitive" in info
? "transitive"
: info.transitivity;
const object = (transitivity === "grammatically transitive")
? T.Person.ThirdPlurMale
: (info.type === "dynamic compound" && oldVerbSelection?.voice !== "passive")
? makeNounSelection(info.objComplement.entry as NounEntry, true)
: (transitivity === "transitive" && oldVerbSelection?.voice !== "passive")
? getTransObjFromOldVerbSelection()
: "none";
if (oldVerbSelection?.voice === "passive" && info.type === "dynamic compound") {
changeSubject(makeNounSelection(info.objComplement.entry as NounEntry, true));
}
const isCompound = ("stative" in info || info.type === "stative compound")
? "stative"
: info.type === "dynamic compound"
? "dynamic"
: false;
// TODO: here and below in the changeStatDyn function ... allow for entries with complement
const dynAuxVerb: VerbEntry | undefined = isCompound !== "dynamic"
? undefined
: info.type === "dynamic compound"
? { entry: info.auxVerb } as VerbEntry
: "dynamic" in info
? { entry: info.dynamic.auxVerb } as VerbEntry
: undefined;
const tenseSelection = ((): { tenseCategory: "perfect", tense: PerfectTense } | {
tenseCategory: "basic" | "modal",
tense: VerbTense,
} => {
if (!oldVerbSelection) {
return { tense: "presentVerb", tenseCategory: "basic" };
}
if (oldVerbSelection.tenseCategory === "modal") {
return { tenseCategory: "modal", tense: isPerfectTense(oldVerbSelection.tense) ? "presentVerb" : oldVerbSelection.tense };
}
if (oldVerbSelection.tenseCategory === "basic") {
return { tenseCategory: "basic", tense: isPerfectTense(oldVerbSelection.tense) ? "presentVerb" : oldVerbSelection.tense };
}
return { tenseCategory: "perfect", tense: isPerfectTense(oldVerbSelection.tense) ? oldVerbSelection.tense : "present perfect" };
})();
return {
type: "verb",
verb: verb,
dynAuxVerb,
...tenseSelection,
object,
transitivity,
isCompound,
voice: transitivity === "transitive"
? (oldVerbSelection?.voice || "active")
: "active",
negative: oldVerbSelection ? oldVerbSelection.negative : false,
...("grammaticallyTransitive" in info) ? {
changeTransitivity: function(t) {
return {
...this,
transitivity: t,
object: t === "grammatically transitive" ? T.Person.ThirdPlurMale : undefined,
};
},
} : {},
...("stative" in info) ? {
changeStatDyn: function(c) {
return {
...this,
isCompound: c,
object: c === "dynamic"
? makeNounSelection(info.dynamic.objComplement.entry as NounEntry, true)
: undefined,
dynAuxVerb: c === "dynamic"
? { entry: info.dynamic.auxVerb } as VerbEntry
: undefined,
};
}
} : {},
...(transitivity === "transitive") ? {
changeVoice: function(v, s) {
return {
...this,
voice: v,
object: v === "active" ? s : "none",
};
},
} : {},
};
}

View File

@ -1684,10 +1684,10 @@
pbf "^3.2.1"
rambda "^6.7.0"
"@lingdocs/pashto-inflector@^1.6.8":
version "1.6.8"
resolved "https://npm.lingdocs.com/@lingdocs%2fpashto-inflector/-/pashto-inflector-1.6.8.tgz#adc94c84c49cc067e26fb3066fae2a13b9d25d03"
integrity sha512-BGzMP0URWm6fEGdUUX653oImSMEkn11S/3vxeQILrQDCUNjGpNPQlORF27OlzCs8+n9lDBMDQHVjfhBlAZFN4w==
"@lingdocs/pashto-inflector@^1.6.9":
version "1.6.9"
resolved "https://npm.lingdocs.com/@lingdocs%2fpashto-inflector/-/pashto-inflector-1.6.9.tgz#cd66b8afe1c55ce609ba25952984cd5c329355ac"
integrity sha512-ipssrtenaDr9k/VFz6WAxZ/spVb62IFgxczSVsd8Zc/fdIqzy9UKV4R4efJhBp2d7h5QadqBkfEk81PUZu48UQ==
dependencies:
classnames "^2.2.6"
pbf "^3.2.1"