Compare commits
No commits in common. "db2749bd0a3d1a441c9f6ee6c4687e29b08cff2c" and "c5a238ab05c06eb448878ed87235254760aa6737" have entirely different histories.
db2749bd0a
...
c5a238ab05
|
@ -18,13 +18,12 @@ jobs:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: 20
|
node-version: 16
|
||||||
cache: "yarn"
|
cache: "yarn"
|
||||||
- name: Install, build, test
|
- name: Install, build, test
|
||||||
run: |
|
run: |
|
||||||
yarn install-all
|
yarn install-r
|
||||||
yarn build-lib
|
yarn build-library
|
||||||
yarn build-components
|
|
||||||
yarn build-website
|
yarn build-website
|
||||||
yarn test --silent
|
yarn test --silent
|
||||||
yarn check-all-inflections
|
yarn check-all-inflections
|
||||||
|
|
|
@ -16,7 +16,7 @@ jobs:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: 20
|
node-version: 16
|
||||||
cache: "yarn"
|
cache: "yarn"
|
||||||
- name: Check if version has been updated
|
- name: Check if version has been updated
|
||||||
id: check
|
id: check
|
||||||
|
@ -27,10 +27,10 @@ jobs:
|
||||||
- name: Publish when version changed
|
- name: Publish when version changed
|
||||||
if: steps.check.outputs.changed == 'true'
|
if: steps.check.outputs.changed == 'true'
|
||||||
run: |
|
run: |
|
||||||
yarn install-all
|
yarn install-r
|
||||||
yarn build-lib
|
yarn build-library
|
||||||
yarn build-components
|
|
||||||
yarn test --silent
|
yarn test --silent
|
||||||
|
yarn check-all-inflections
|
||||||
cp .npmrc src/lib
|
cp .npmrc src/lib
|
||||||
cp .npmrc src/components
|
cp .npmrc src/components
|
||||||
cd src/lib
|
cd src/lib
|
||||||
|
|
|
@ -1,15 +1,12 @@
|
||||||
# Logs
|
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||||
logs
|
|
||||||
*.log
|
|
||||||
npm-debug.log*
|
|
||||||
yarn-debug.log*
|
|
||||||
yarn-error.log*
|
|
||||||
pnpm-debug.log*
|
|
||||||
lerna-debug.log*
|
|
||||||
|
|
||||||
# fetched vocab
|
# compiled library files
|
||||||
src/verbs.ts
|
dist
|
||||||
src/nouns-adjs.ts
|
|
||||||
|
# dependencies
|
||||||
|
node_modules
|
||||||
|
/.pnp
|
||||||
|
.pnp.js
|
||||||
|
|
||||||
# testing
|
# testing
|
||||||
/coverage
|
/coverage
|
||||||
|
@ -17,26 +14,20 @@ src/nouns-adjs.ts
|
||||||
dict
|
dict
|
||||||
diac.ts
|
diac.ts
|
||||||
|
|
||||||
node_modules
|
# production
|
||||||
build
|
build
|
||||||
dist
|
dist
|
||||||
dist-ssr
|
|
||||||
*.local
|
|
||||||
/.pnp
|
|
||||||
.pnp.js
|
|
||||||
|
|
||||||
# Editor directories and files
|
# misc
|
||||||
.vscode/*
|
|
||||||
!.vscode/extensions.json
|
|
||||||
.idea
|
|
||||||
.DS_Store
|
|
||||||
.DS_Store
|
.DS_Store
|
||||||
.env.local
|
.env.local
|
||||||
.env.development.local
|
.env.development.local
|
||||||
.env.test.local
|
.env.test.local
|
||||||
.env.production.local
|
.env.production.local
|
||||||
*.suo
|
|
||||||
*.ntvs*
|
npm-debug.log*
|
||||||
*.njsproj
|
yarn-debug.log*
|
||||||
*.sln
|
yarn-error.log*
|
||||||
*.sw?
|
|
||||||
|
src/verbs.ts
|
||||||
|
src/nouns-adjs.ts
|
2
.npmrc
2
.npmrc
|
@ -1,2 +1,2 @@
|
||||||
@lingdocs:registry=https://npm.lingdocs.com
|
@lingdocs:registry=https://npm.lingdocs.com
|
||||||
//npm.lingdocs.com/:_authToken=${LINGDOCS_NPM_TOKEN}
|
//npm.lingdocs.com/:_authToken=${LINGDOCS_NPM_TOKEN}
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
{
|
{
|
||||||
"typescript.preferences.autoImportFileExcludePatterns": [
|
"typescript.preferences.autoImportFileExcludePatterns": [
|
||||||
"library.ts"
|
"../../library.ts"
|
||||||
|
],
|
||||||
|
"cSpell.words": [
|
||||||
|
"کارخانه"
|
||||||
],
|
],
|
||||||
}
|
}
|
|
@ -20,7 +20,7 @@ This library uses a 3-step process to generate gramattically correct Pashto phra
|
||||||
| | Pashto Inflector Function | Chomskian Grammar Level |
|
| | Pashto Inflector Function | Chomskian Grammar Level |
|
||||||
|-|--------------------------| ----------------------- |
|
|-|--------------------------| ----------------------- |
|
||||||
|1.| Assemble the phrase tree | Phrase Structure Rules |
|
|1.| Assemble the phrase tree | Phrase Structure Rules |
|
||||||
|2.| Inflect the words in tree | Morphophonemic Rules |
|
|2.| Inflect the words in tree | Morphophonemic Rules |
|
||||||
|3.| Arrange the inflected words in order | Transformational Rules |
|
|3.| Arrange the inflected words in order | Transformational Rules |
|
||||||
|
|
||||||
### 1. Assemble the phrase tree
|
### 1. Assemble the phrase tree
|
||||||
|
|
|
@ -2,6 +2,7 @@ import * as T from "./src/types";
|
||||||
import { inflectWord } from "./src/lib/src/pashto-inflector";
|
import { inflectWord } from "./src/lib/src/pashto-inflector";
|
||||||
import * as tp from "./src/lib/src/type-predicates";
|
import * as tp from "./src/lib/src/type-predicates";
|
||||||
import { conjugateVerb } from "./src/lib/src/verb-conjugation";
|
import { conjugateVerb } from "./src/lib/src/verb-conjugation";
|
||||||
|
import fetch from "node-fetch";
|
||||||
|
|
||||||
// Script to try inflecting all the words in the dictionary and make sure that
|
// Script to try inflecting all the words in the dictionary and make sure that
|
||||||
// no errors are thrown in the process
|
// no errors are thrown in the process
|
||||||
|
@ -14,8 +15,8 @@ type InflectionError = {
|
||||||
};
|
};
|
||||||
|
|
||||||
async function checkAll() {
|
async function checkAll() {
|
||||||
console.log("Checking inflection functions on all dictionary words");
|
|
||||||
const res = await fetch(process.env.LINGDOCS_DICTIONARY_URL);
|
const res = await fetch(process.env.LINGDOCS_DICTIONARY_URL);
|
||||||
|
// @ts-ignore
|
||||||
const { entries }: T.Dictionary = await res.json();
|
const { entries }: T.Dictionary = await res.json();
|
||||||
const errors: InflectionError[] = [];
|
const errors: InflectionError[] = [];
|
||||||
|
|
||||||
|
|
|
@ -1,26 +0,0 @@
|
||||||
import js from '@eslint/js'
|
|
||||||
import globals from 'globals'
|
|
||||||
import reactHooks from 'eslint-plugin-react-hooks'
|
|
||||||
import reactRefresh from 'eslint-plugin-react-refresh'
|
|
||||||
import tseslint from 'typescript-eslint'
|
|
||||||
|
|
||||||
export default tseslint.config({
|
|
||||||
extends: [js.configs.recommended, ...tseslint.configs.recommended],
|
|
||||||
files: ['**/*.{ts,tsx}'],
|
|
||||||
ignores: ['dist'],
|
|
||||||
languageOptions: {
|
|
||||||
ecmaVersion: 2020,
|
|
||||||
globals: globals.browser,
|
|
||||||
},
|
|
||||||
plugins: {
|
|
||||||
'react-hooks': reactHooks,
|
|
||||||
'react-refresh': reactRefresh,
|
|
||||||
},
|
|
||||||
rules: {
|
|
||||||
...reactHooks.configs.recommended.rules,
|
|
||||||
'react-refresh/only-export-components': [
|
|
||||||
'warn',
|
|
||||||
{ allowConstantExport: true },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
})
|
|
|
@ -6,6 +6,7 @@
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
const fs = require("fs");
|
const fs = require("fs");
|
||||||
|
const fetch = require("node-fetch-commonjs");
|
||||||
const path = require("path");
|
const path = require("path");
|
||||||
const verbCollectionPath = path.join(".", "vocab", "verbs");
|
const verbCollectionPath = path.join(".", "vocab", "verbs");
|
||||||
const nounAdjCollectionPath = path.join(".", "vocab", "nouns-adjs");
|
const nounAdjCollectionPath = path.join(".", "vocab", "nouns-adjs");
|
|
@ -1 +0,0 @@
|
||||||
import "jest-extended";
|
|
|
@ -1,13 +0,0 @@
|
||||||
/** @type {import('ts-jest').JestConfigWithTsJest} **/
|
|
||||||
export default {
|
|
||||||
testEnvironment: "node",
|
|
||||||
transform: {
|
|
||||||
"^.+.tsx?$": [
|
|
||||||
"ts-jest",
|
|
||||||
{
|
|
||||||
tsconfig: "tsconfig.app.json",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
setupFilesAfterEnv: ["./testSetup.ts"],
|
|
||||||
};
|
|
|
@ -1,3 +1,3 @@
|
||||||
[build]
|
[build]
|
||||||
command = "yarn install-all && yarn test --runInBand --no-cache && yarn build-website"
|
command = "yarn install-r && yarn test --silent && yarn build-website"
|
||||||
publish = "dist/"
|
publish = "build/"
|
||||||
|
|
111
package.json
111
package.json
|
@ -1,47 +1,82 @@
|
||||||
{
|
{
|
||||||
"name": "pashto-inflector-website",
|
"name": "pashto-inflector",
|
||||||
"version": "7.6.3",
|
"version": "7.5.1",
|
||||||
"type": "module",
|
"author": "lingdocs.com",
|
||||||
|
"description": "A Pashto inflection and verb conjugation engine, inculding React components for displaying Pashto text, inflections, and conjugations",
|
||||||
|
"homepage": "https://verbs.lingdocs.com",
|
||||||
|
"license": "MIT",
|
||||||
|
"private": false,
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/lingdocs/pashto-inflector.git"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@fortawesome/fontawesome-free": "^5.15.2",
|
||||||
|
"@testing-library/jest-dom": "^5.11.4",
|
||||||
|
"@testing-library/react": "^11.1.0",
|
||||||
|
"@testing-library/user-event": "^12.1.10",
|
||||||
|
"@types/jest": "^26.0.20",
|
||||||
|
"@types/node": "^15.12.1",
|
||||||
|
"@types/react": "^17.0.3",
|
||||||
|
"@types/react-dom": "^17.0.2",
|
||||||
|
"bootstrap": "^4.6.0",
|
||||||
|
"jest-extended": "^4.0.1",
|
||||||
|
"node-fetch": "^3.3.2",
|
||||||
|
"node-fetch-commonjs": "^3.2.4",
|
||||||
|
"pbf": "^3.2.1",
|
||||||
|
"react": "^17.0.1",
|
||||||
|
"react-bootstrap": "^1.5.1",
|
||||||
|
"react-dom": "^17.0.1",
|
||||||
|
"react-scripts": "4.0.3",
|
||||||
|
"tsx": "^3.14.0",
|
||||||
|
"typescript": "^5.1.6",
|
||||||
|
"web-vitals": "^1.0.1"
|
||||||
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"patch": "npm version patch --no-git-tag-version && cd src/lib && npm version patch --no-git-tag-version && cd ../components && npm version patch --no-git-tag-version",
|
"patch": "npm version patch --no-git-tag-version && cd src/lib && npm version patch --no-git-tag-version && cd ../components && npm version patch --no-git-tag-version",
|
||||||
"minor": "npm version minor --no-git-tag-version && cd src/lib && npm version minor --no-git-tag-version && cd ../components && npm version minor --no-git-tag-version",
|
"minor": "npm version minor --no-git-tag-version && cd src/lib && npm version minor --no-git-tag-version && cd ../components && npm version minor --no-git-tag-version",
|
||||||
"major": "npm version major --no-git-tag-version && cd src/lib && npm version major --no-git-tag-version && cd ../components && npm version major --no-git-tag-version",
|
"major": "npm version major --no-git-tag-version && cd src/lib && npm version major --no-git-tag-version && cd ../components && npm version major --no-git-tag-version",
|
||||||
"preinstall": "echo '*** Be sure to use 'yarn install-all' not 'yarn install' ***!'",
|
"preinstall": "echo '*** Be sure to use yarn install-r not yarn install ***!'",
|
||||||
"dev": "vite",
|
"start": "react-scripts start",
|
||||||
"lint": "eslint .",
|
"build": "react-scripts build",
|
||||||
"test": "jest",
|
"test": "react-scripts test",
|
||||||
"preview": "vite preview",
|
"eject": "react-scripts eject",
|
||||||
"install-all": "yarn install && node get-words.cjs && cd src/lib && yarn install && cd ../components && yarn install",
|
"install-r": "yarn install && node get-words.js && cd src/lib && yarn install && cd ../components && yarn install",
|
||||||
"build-website": "tsc -b && vite build",
|
"build-website": "node get-words.js && npm run build",
|
||||||
"build-components": "rm -rf src/components/dist && tsc --project src/components/tsconfig.json && cd src/components && node post-build.cjs",
|
"build-library": "cd src/components && rimraf dist && tsc --project lib-tsconfig.json && node post-build.cjs && cd ../lib && rimraf dist && tsc --project lib-tsconfig.json && node_modules/rollup/dist/bin/rollup -c",
|
||||||
"build-lib": "rm -rf src/lib/dist && tsc --project src/lib/tsconfig.json",
|
"test-ci": "npm run test -- --watchAll=false",
|
||||||
"get-words": "node get-words.cjs",
|
"get-words": "node get-words.js",
|
||||||
"check-all-inflections": "tsx check-all-inflections.ts"
|
"check-all-inflections": "tsx check-all-inflections.ts"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"eslintConfig": {
|
||||||
"react": "^18.3.1",
|
"extends": [
|
||||||
"react-dom": "^18.3.1",
|
"react-app",
|
||||||
"bootstrap": "4.6.1",
|
"react-app/jest"
|
||||||
"react-bootstrap": "1.5.1",
|
],
|
||||||
"@fortawesome/fontawesome-free": "^5.15.2"
|
"rules": {
|
||||||
|
"no-warning-comments": [
|
||||||
|
1,
|
||||||
|
{
|
||||||
|
"terms": [
|
||||||
|
"fixme",
|
||||||
|
"xxx"
|
||||||
|
],
|
||||||
|
"location": "anywhere"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"browserslist": {
|
||||||
"@eslint/js": "^9.8.0",
|
"production": [
|
||||||
"@types/jest": "^29.5.12",
|
">0.2%",
|
||||||
"@types/react": "^18.3.3",
|
"not dead",
|
||||||
"@types/react-dom": "^18.3.0",
|
"not op_mini all"
|
||||||
"@vitejs/plugin-react": "^4.3.1",
|
],
|
||||||
"eslint": "^9.8.0",
|
"development": [
|
||||||
"eslint-plugin-react-hooks": "^5.1.0-rc.0",
|
"last 1 chrome version",
|
||||||
"eslint-plugin-react-refresh": "^0.4.9",
|
"last 1 firefox version",
|
||||||
"globals": "^15.9.0",
|
"last 1 safari version"
|
||||||
"jest": "^29.7.0",
|
]
|
||||||
"jest-environment-jsdom": "^29.7.0",
|
},
|
||||||
"jest-extended": "^4.0.2",
|
"dependencies": {}
|
||||||
"ts-jest": "^29.2.4",
|
|
||||||
"tsx": "^4.17.0",
|
|
||||||
"typescript": "^5.5.3",
|
|
||||||
"typescript-eslint": "^8.0.0",
|
|
||||||
"vite": "^5.4.0"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,11 +2,11 @@
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<link rel="icon" href="/favicon.ico" />
|
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
<meta name="theme-color" content="#000000" />
|
<meta name="theme-color" content="#000000" />
|
||||||
<meta name="description" content="An open source TypeScript/React library for Pashto inflection, verb conjugation, phrase generation, text conversion, and more" />
|
<meta name="description" content="An open source TypeScript/React library for Pashto inflection, verb conjugation, phrase generation, text conversion, and more" />
|
||||||
<link rel="apple-touch-icon" href="/apple-touch-icon.png" />
|
<link rel="apple-touch-icon" href="%PUBLIC_URL%/apple-touch-icon.png" />
|
||||||
<meta name="keywords" content="Pashto, Verbs, Conjugation, Grammar, Linguistics" />
|
<meta name="keywords" content="Pashto, Verbs, Conjugation, Grammar, Linguistics" />
|
||||||
<meta name="author" content="lingdocs.com" />
|
<meta name="author" content="lingdocs.com" />
|
||||||
<link rel="canonical" href="https://pashto-inflector.lingdocs.com/" />
|
<link rel="canonical" href="https://pashto-inflector.lingdocs.com/" />
|
||||||
|
@ -25,11 +25,10 @@
|
||||||
<meta name="twitter:card" content="summary_large_image">
|
<meta name="twitter:card" content="summary_large_image">
|
||||||
<meta name="twitter:creator" content="@lingdocs" />
|
<meta name="twitter:creator" content="@lingdocs" />
|
||||||
|
|
||||||
<link rel="manifest" href="/manifest.json" />
|
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
||||||
<title>Pashto Inflector</title>
|
<title>Pashto Inflector</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body class="d-flex flex-column h-100" id="root">
|
||||||
<div id="root"></div>
|
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||||
<script type="module" src="/src/main.tsx"></script>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
|
@ -0,0 +1,10 @@
|
||||||
|
let
|
||||||
|
nixpkgs = fetchTarball "https://github.com/NixOS/nixpkgs/tarball/nixos-23.05";
|
||||||
|
pkgs = import nixpkgs { config = {}; overlays = []; };
|
||||||
|
in
|
||||||
|
|
||||||
|
pkgs.mkShell {
|
||||||
|
packages = with pkgs; [
|
||||||
|
nodejs_20
|
||||||
|
];
|
||||||
|
}
|
|
@ -8,7 +8,7 @@
|
||||||
|
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
|
|
||||||
import ButtonSelect from "./components/src/selects/ButtonSelect";
|
import ButtonSelect from "./components/src/ButtonSelect";
|
||||||
import { Modal } from "react-bootstrap";
|
import { Modal } from "react-bootstrap";
|
||||||
import * as T from "./types";
|
import * as T from "./types";
|
||||||
import defualtTextOptions from "./lib/src/default-text-options";
|
import defualtTextOptions from "./lib/src/default-text-options";
|
||||||
|
@ -36,7 +36,6 @@ function App() {
|
||||||
function handleHiderClick(label: string) {
|
function handleHiderClick(label: string) {
|
||||||
setShowing((os) => (os === label ? "" : label));
|
setShowing((os) => (os === label ? "" : label));
|
||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
document.documentElement.setAttribute("data-theme", theme);
|
document.documentElement.setAttribute("data-theme", theme);
|
||||||
}, [theme]);
|
}, [theme]);
|
||||||
|
@ -166,9 +165,7 @@ function App() {
|
||||||
show={showingTextOptions}
|
show={showingTextOptions}
|
||||||
onHide={() => setShowingTextOptions(false)}
|
onHide={() => setShowingTextOptions(false)}
|
||||||
>
|
>
|
||||||
{/* @ts-expect-error strange react-bootstrap issue */}
|
|
||||||
<Modal.Header closeButton>
|
<Modal.Header closeButton>
|
||||||
{/* @ts-expect-error strange react-bootstrap issue */}
|
|
||||||
<Modal.Title>Settings</Modal.Title>
|
<Modal.Title>Settings</Modal.Title>
|
||||||
</Modal.Header>
|
</Modal.Header>
|
||||||
<Modal.Body>
|
<Modal.Body>
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="35.93" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 228"><path fill="#00D8FF" d="M210.483 73.824a171.49 171.49 0 0 0-8.24-2.597c.465-1.9.893-3.777 1.273-5.621c6.238-30.281 2.16-54.676-11.769-62.708c-13.355-7.7-35.196.329-57.254 19.526a171.23 171.23 0 0 0-6.375 5.848a155.866 155.866 0 0 0-4.241-3.917C100.759 3.829 77.587-4.822 63.673 3.233C50.33 10.957 46.379 33.89 51.995 62.588a170.974 170.974 0 0 0 1.892 8.48c-3.28.932-6.445 1.924-9.474 2.98C17.309 83.498 0 98.307 0 113.668c0 15.865 18.582 31.778 46.812 41.427a145.52 145.52 0 0 0 6.921 2.165a167.467 167.467 0 0 0-2.01 9.138c-5.354 28.2-1.173 50.591 12.134 58.266c13.744 7.926 36.812-.22 59.273-19.855a145.567 145.567 0 0 0 5.342-4.923a168.064 168.064 0 0 0 6.92 6.314c21.758 18.722 43.246 26.282 56.54 18.586c13.731-7.949 18.194-32.003 12.4-61.268a145.016 145.016 0 0 0-1.535-6.842c1.62-.48 3.21-.974 4.76-1.488c29.348-9.723 48.443-25.443 48.443-41.52c0-15.417-17.868-30.326-45.517-39.844Zm-6.365 70.984c-1.4.463-2.836.91-4.3 1.345c-3.24-10.257-7.612-21.163-12.963-32.432c5.106-11 9.31-21.767 12.459-31.957c2.619.758 5.16 1.557 7.61 2.4c23.69 8.156 38.14 20.213 38.14 29.504c0 9.896-15.606 22.743-40.946 31.14Zm-10.514 20.834c2.562 12.94 2.927 24.64 1.23 33.787c-1.524 8.219-4.59 13.698-8.382 15.893c-8.067 4.67-25.32-1.4-43.927-17.412a156.726 156.726 0 0 1-6.437-5.87c7.214-7.889 14.423-17.06 21.459-27.246c12.376-1.098 24.068-2.894 34.671-5.345a134.17 134.17 0 0 1 1.386 6.193ZM87.276 214.515c-7.882 2.783-14.16 2.863-17.955.675c-8.075-4.657-11.432-22.636-6.853-46.752a156.923 156.923 0 0 1 1.869-8.499c10.486 2.32 22.093 3.988 34.498 4.994c7.084 9.967 14.501 19.128 21.976 27.15a134.668 134.668 0 0 1-4.877 4.492c-9.933 8.682-19.886 14.842-28.658 17.94ZM50.35 144.747c-12.483-4.267-22.792-9.812-29.858-15.863c-6.35-5.437-9.555-10.836-9.555-15.216c0-9.322 13.897-21.212 37.076-29.293c2.813-.98 5.757-1.905 8.812-2.773c3.204 10.42 7.406 21.315 12.477 32.332c-5.137 11.18-9.399 22.249-12.634 32.792a134.718 134.718 0 0 1-6.318-1.979Zm12.378-84.26c-4.811-24.587-1.616-43.134 6.425-47.789c8.564-4.958 27.502 2.111 47.463 19.835a144.318 144.318 0 0 1 3.841 3.545c-7.438 7.987-14.787 17.08-21.808 26.988c-12.04 1.116-23.565 2.908-34.161 5.309a160.342 160.342 0 0 1-1.76-7.887Zm110.427 27.268a347.8 347.8 0 0 0-7.785-12.803c8.168 1.033 15.994 2.404 23.343 4.08c-2.206 7.072-4.956 14.465-8.193 22.045a381.151 381.151 0 0 0-7.365-13.322Zm-45.032-43.861c5.044 5.465 10.096 11.566 15.065 18.186a322.04 322.04 0 0 0-30.257-.006c4.974-6.559 10.069-12.652 15.192-18.18ZM82.802 87.83a323.167 323.167 0 0 0-7.227 13.238c-3.184-7.553-5.909-14.98-8.134-22.152c7.304-1.634 15.093-2.97 23.209-3.984a321.524 321.524 0 0 0-7.848 12.897Zm8.081 65.352c-8.385-.936-16.291-2.203-23.593-3.793c2.26-7.3 5.045-14.885 8.298-22.6a321.187 321.187 0 0 0 7.257 13.246c2.594 4.48 5.28 8.868 8.038 13.147Zm37.542 31.03c-5.184-5.592-10.354-11.779-15.403-18.433c4.902.192 9.899.29 14.978.29c5.218 0 10.376-.117 15.453-.343c-4.985 6.774-10.018 12.97-15.028 18.486Zm52.198-57.817c3.422 7.8 6.306 15.345 8.596 22.52c-7.422 1.694-15.436 3.058-23.88 4.071a382.417 382.417 0 0 0 7.859-13.026a347.403 347.403 0 0 0 7.425-13.565Zm-16.898 8.101a358.557 358.557 0 0 1-12.281 19.815a329.4 329.4 0 0 1-23.444.823c-7.967 0-15.716-.248-23.178-.732a310.202 310.202 0 0 1-12.513-19.846h.001a307.41 307.41 0 0 1-10.923-20.627a310.278 310.278 0 0 1 10.89-20.637l-.001.001a307.318 307.318 0 0 1 12.413-19.761c7.613-.576 15.42-.876 23.31-.876H128c7.926 0 15.743.303 23.354.883a329.357 329.357 0 0 1 12.335 19.695a358.489 358.489 0 0 1 11.036 20.54a329.472 329.472 0 0 1-11 20.722Zm22.56-122.124c8.572 4.944 11.906 24.881 6.52 51.026c-.344 1.668-.73 3.367-1.15 5.09c-10.622-2.452-22.155-4.275-34.23-5.408c-7.034-10.017-14.323-19.124-21.64-27.008a160.789 160.789 0 0 1 5.888-5.4c18.9-16.447 36.564-22.941 44.612-18.3ZM128 90.808c12.625 0 22.86 10.235 22.86 22.86s-10.235 22.86-22.86 22.86s-22.86-10.235-22.86-22.86s10.235-22.86 22.86-22.86Z"></path></svg>
|
|
Before Width: | Height: | Size: 4.0 KiB |
|
@ -0,0 +1,2 @@
|
||||||
|
storybook-static
|
||||||
|
node_modules
|
|
@ -0,0 +1,12 @@
|
||||||
|
module.exports = {
|
||||||
|
"stories": [
|
||||||
|
"../src/**/*.stories.mdx",
|
||||||
|
"../src/**/*.stories.@(js|jsx|ts|tsx)"
|
||||||
|
],
|
||||||
|
"addons": [
|
||||||
|
"@storybook/addon-links",
|
||||||
|
"@storybook/addon-essentials",
|
||||||
|
"@storybook/addon-interactions"
|
||||||
|
],
|
||||||
|
"framework": "@storybook/react"
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
// Bootstrap 4 and fontawesome 5 required for components css
|
||||||
|
import "bootstrap/dist/css/bootstrap.min.css";
|
||||||
|
import "@fortawesome/fontawesome-free/css/all.css";
|
||||||
|
// Plus some custom CSS
|
||||||
|
// TODO: this should be exported with the npm package!
|
||||||
|
import "../../App.css";
|
||||||
|
|
||||||
|
export const parameters = {
|
||||||
|
actions: { argTypesRegex: "^on[A-Z].*" },
|
||||||
|
controls: {
|
||||||
|
matchers: {
|
||||||
|
color: /(background|color)$/i,
|
||||||
|
date: /Date$/,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "es6",
|
||||||
|
"lib": [
|
||||||
|
"dom",
|
||||||
|
"dom.iterable",
|
||||||
|
"esnext"
|
||||||
|
],
|
||||||
|
"allowJs": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"allowSyntheticDefaultImports": true,
|
||||||
|
"strict": true,
|
||||||
|
"downlevelIteration": true,
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"noFallthroughCasesInSwitch": true,
|
||||||
|
"module": "ES6",
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"declaration": true,
|
||||||
|
"jsx": "react-jsx",
|
||||||
|
"outDir": "dist"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"library.ts",
|
||||||
|
"images.d.ts"
|
||||||
|
]
|
||||||
|
}
|
|
@ -1,81 +1,80 @@
|
||||||
import Pashto from "./src/text-display/Pashto";
|
/**
|
||||||
import Phonetics from "./src/text-display/Phonetics";
|
* Copyright (c) 2021 lingdocs.com
|
||||||
import InlinePs from "./src/text-display/InlinePs";
|
*
|
||||||
import Examples from "./src/text-display/Examples";
|
* This source code is licensed under the GPL3 license found in the
|
||||||
import CompiledPTextDisplay from "./src/text-display/CompiledPTextDisplay";
|
* LICENSE file in the root directory of this source tree.
|
||||||
import ButtonSelect from "./src/selects/ButtonSelect";
|
*
|
||||||
import EntrySelect from "./src/selects/EntrySelect";
|
*/
|
||||||
import PersonSelection from "./src/selects/PersonSelection";
|
|
||||||
|
import InflectionsTable from "./src/InflectionsTable";
|
||||||
|
import Pashto from "./src/Pashto";
|
||||||
|
import Phonetics from "./src/Phonetics";
|
||||||
|
import InlinePs from "./src/InlinePs";
|
||||||
|
import ButtonSelect from "./src/ButtonSelect";
|
||||||
|
import VerbFormDisplay from "./src/VerbFormDisplay";
|
||||||
|
import VerbTable from "./src/VerbTable";
|
||||||
|
import EPDisplay from "./src/ep-explorer/EPDisplay";
|
||||||
|
import Examples from "./src/Examples";
|
||||||
import Hider from "./src/Hider";
|
import Hider from "./src/Hider";
|
||||||
import InflectionsTable from "./src/tables/InflectionsTable";
|
import EntrySelect from "./src/EntrySelect";
|
||||||
import VerbTable from "./src/tables/VerbTable";
|
|
||||||
import useStickyState from "./src/useStickyState";
|
|
||||||
import NPPicker from "./src/block-pickers/NPPicker";
|
|
||||||
import SandwichPicker from "./src/block-pickers/SandwichPicker";
|
|
||||||
import VerbFromDisplay from "./src/VerbFormDisplay";
|
|
||||||
import Block from "./src/blocks/Block";
|
|
||||||
import VerbInfo, { RootsAndStems } from "./src/verb-info/VerbInfo";
|
import VerbInfo, { RootsAndStems } from "./src/verb-info/VerbInfo";
|
||||||
import VPExplorer from "./src/vp-explorer/VPExplorer";
|
import VPExplorer from "./src/vp-explorer/VPExplorer";
|
||||||
import EPExplorer from "./src/ep-explorer/EPExplorer";
|
|
||||||
import playAudio from "./src/play-audio";
|
|
||||||
import { roleIcon } from "./src/role-icons";
|
|
||||||
import { vpsReducer } from "../lib/src/phrase-building/vps-reducer";
|
|
||||||
import type { VpsReducerAction } from "../lib/src/phrase-building/vps-reducer";
|
|
||||||
import { makeVPSelectionState } from "../lib/src/phrase-building/verb-selection";
|
import { makeVPSelectionState } from "../lib/src/phrase-building/verb-selection";
|
||||||
|
import { vpsReducer } from "../lib/src/phrase-building/vps-reducer";
|
||||||
import APPicker from "./src/block-pickers/APPicker";
|
import type { VpsReducerAction as VpsA } from "../lib/src/phrase-building/vps-reducer";
|
||||||
|
import useStickyState from "./src/useStickyState";
|
||||||
|
import Block, { NPBlock, APBlock } from "./src/blocks/Block";
|
||||||
|
import { roleIcon } from "./src/vp-explorer/VPExplorerExplanationModal";
|
||||||
|
import CompiledPTextDisplay from "./src/CompiledPTextDisplay";
|
||||||
|
import RenderedBlocksDisplay from "./src/RenderedBlocksDisplay";
|
||||||
|
import NPPicker from "./src/np-picker/NPPicker";
|
||||||
|
import EPPicker from "./src/ep-explorer/EPPicker";
|
||||||
|
import EPExplorer from "./src/ep-explorer/EPExplorer";
|
||||||
|
import APPicker from "./src/ap-picker/APPicker";
|
||||||
import VPDisplay from "./src/vp-explorer/VPDisplay";
|
import VPDisplay from "./src/vp-explorer/VPDisplay";
|
||||||
import VPPicker from "./src/vp-explorer/VPPicker";
|
import VPPicker from "./src/vp-explorer/VPPicker";
|
||||||
import NPDisplay from "./src/vp-explorer/NPDisplay";
|
import NPDisplay from "./src/vp-explorer/NPDisplay";
|
||||||
import HumanReadableInflectionPattern from "./src/tables/HumanReadableInflectionPattern";
|
import HumanReadableInflectionPattern from "./src/HumanReadableInflectionPattern";
|
||||||
import { psJSXMap } from "./src/text-display/jsx-map";
|
import { psJSXMap } from "./src/jsx-map";
|
||||||
import genderColors from "./src/gender-colors";
|
import genderColors from "./src/gender-colors";
|
||||||
|
|
||||||
// this library also includes everything from the core inflect library
|
// this library also includes everything from the core inflect library
|
||||||
export * from "../lib/library";
|
export * from "../lib/library";
|
||||||
|
|
||||||
export {
|
export {
|
||||||
// text-display
|
useStickyState,
|
||||||
InlinePs,
|
roleIcon,
|
||||||
Pashto,
|
vpsReducer,
|
||||||
Phonetics,
|
makeVPSelectionState,
|
||||||
CompiledPTextDisplay,
|
EPExplorer,
|
||||||
Examples,
|
VPExplorer,
|
||||||
|
Examples,
|
||||||
|
VerbFormDisplay,
|
||||||
|
VerbTable,
|
||||||
|
VerbInfo,
|
||||||
|
RootsAndStems,
|
||||||
|
InflectionsTable,
|
||||||
|
Pashto,
|
||||||
|
Phonetics,
|
||||||
|
InlinePs,
|
||||||
|
ButtonSelect,
|
||||||
|
Hider,
|
||||||
|
EntrySelect,
|
||||||
|
NPPicker,
|
||||||
|
APPicker,
|
||||||
|
NPBlock,
|
||||||
|
APBlock,
|
||||||
|
Block,
|
||||||
|
EPDisplay,
|
||||||
|
VPDisplay,
|
||||||
|
NPDisplay,
|
||||||
|
EPPicker,
|
||||||
|
VPPicker,
|
||||||
|
CompiledPTextDisplay,
|
||||||
|
RenderedBlocksDisplay,
|
||||||
|
HumanReadableInflectionPattern,
|
||||||
|
psJSXMap,
|
||||||
|
genderColors,
|
||||||
|
}
|
||||||
|
|
||||||
// selects
|
export type VpsReducerAction = VpsA;
|
||||||
ButtonSelect,
|
|
||||||
EntrySelect,
|
|
||||||
PersonSelection,
|
|
||||||
|
|
||||||
// tables
|
|
||||||
InflectionsTable,
|
|
||||||
VerbTable,
|
|
||||||
|
|
||||||
// block-pickers
|
|
||||||
APPicker,
|
|
||||||
NPPicker,
|
|
||||||
SandwichPicker,
|
|
||||||
|
|
||||||
// blocks
|
|
||||||
Block,
|
|
||||||
|
|
||||||
// misc
|
|
||||||
Hider,
|
|
||||||
useStickyState,
|
|
||||||
VerbFromDisplay,
|
|
||||||
VerbInfo,
|
|
||||||
VPExplorer,
|
|
||||||
EPExplorer,
|
|
||||||
playAudio,
|
|
||||||
roleIcon,
|
|
||||||
vpsReducer,
|
|
||||||
VpsReducerAction,
|
|
||||||
makeVPSelectionState,
|
|
||||||
RootsAndStems,
|
|
||||||
VPDisplay,
|
|
||||||
VPPicker,
|
|
||||||
NPDisplay,
|
|
||||||
HumanReadableInflectionPattern,
|
|
||||||
psJSXMap,
|
|
||||||
genderColors,
|
|
||||||
};
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@lingdocs/ps-react",
|
"name": "@lingdocs/ps-react",
|
||||||
"version": "7.6.3",
|
"version": "7.5.1",
|
||||||
"description": "Pashto inflector library module with React components",
|
"description": "Pashto inflector library module with React components",
|
||||||
"main": "dist/components/library.js",
|
"main": "dist/components/library.js",
|
||||||
"module": "dist/components/library.js",
|
"module": "dist/components/library.js",
|
||||||
|
@ -18,24 +18,35 @@
|
||||||
"LICENSE"
|
"LICENSE"
|
||||||
],
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "echo \"build from repo root\""
|
"build": "echo \"build from repo root\"",
|
||||||
|
"storybook": "start-storybook -p 6006",
|
||||||
|
"build-storybook": "build-storybook"
|
||||||
},
|
},
|
||||||
"author": "lingdocs.com",
|
"author": "lingdocs.com",
|
||||||
"license": "GPL-3.0",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@formkit/auto-animate": "^1.0.0-beta.3",
|
"@formkit/auto-animate": "^1.0.0-beta.3",
|
||||||
"classnames": "^2.5.1",
|
"classnames": "^2.2.6",
|
||||||
"fp-ts": "^2.16.0",
|
"fp-ts": "^2.16.0",
|
||||||
"lokijs": "^1.5.12",
|
"jsurl2": "^2.1.0",
|
||||||
"lz-string": "^1.4.4",
|
"lz-string": "^1.4.4",
|
||||||
"micro-memoize": "^4.1.2",
|
"pbf": "^3.2.1",
|
||||||
"rambda": "^7.3.0",
|
"rambda": "^7.3.0",
|
||||||
"react-bootstrap": "^1.5.1",
|
|
||||||
"react-error-boundary": "^4.0.13",
|
"react-error-boundary": "^4.0.13",
|
||||||
"react-select": "^5.4.0"
|
"react-select": "^5.4.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/lokijs": "^1.5.14",
|
"@babel/core": "^7.21.0",
|
||||||
"fs-extra": "^11.2.0"
|
"@storybook/addon-actions": "^6.5.16",
|
||||||
|
"@storybook/addon-essentials": "^6.5.16",
|
||||||
|
"@storybook/addon-interactions": "^6.5.16",
|
||||||
|
"@storybook/addon-links": "^6.5.16",
|
||||||
|
"@storybook/builder-webpack4": "^6.5.16",
|
||||||
|
"@storybook/manager-webpack4": "^6.5.16",
|
||||||
|
"@storybook/react": "^6.5.16",
|
||||||
|
"@storybook/testing-library": "^0.0.13",
|
||||||
|
"@types/pbf": "^3.0.2",
|
||||||
|
"babel-loader": "^8.3.0",
|
||||||
|
"fs-extra": "^10.1.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,48 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2021 lingdocs.com
|
||||||
|
*
|
||||||
|
* This source code is licensed under the GPL3 license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
import classNames from "classnames";
|
||||||
|
|
||||||
|
type PickerProps<T extends string> = {
|
||||||
|
options: { label: any, value: T, color?: string }[],
|
||||||
|
value: T,
|
||||||
|
handleChange: (payload: T) => void,
|
||||||
|
small?: boolean,
|
||||||
|
xSmall?: boolean,
|
||||||
|
faded?: boolean,
|
||||||
|
}
|
||||||
|
|
||||||
|
function ButtonSelect<L extends string>(props: PickerProps<L>) {
|
||||||
|
return <div className="btn-group">
|
||||||
|
{props.options.map((option) => (
|
||||||
|
<button
|
||||||
|
key={option.value}
|
||||||
|
type="button"
|
||||||
|
className={classNames(
|
||||||
|
"btn",
|
||||||
|
props.faded ? "btn-light" : "btn-outline-secondary",
|
||||||
|
{ active: props.value === option.value },
|
||||||
|
{ "btn-sm": props.small || props.xSmall },
|
||||||
|
)}
|
||||||
|
onClick={() => props.handleChange(option.value)}
|
||||||
|
style={{
|
||||||
|
...props.xSmall ?
|
||||||
|
{ fontSize: "small" }: {},
|
||||||
|
...(option.color && (props.value === option.value)) ?
|
||||||
|
{ backgroundColor: option.color } : {},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span className={classNames([{ "text-on-gender-color": option.color && (props.value === option.value) }])}>
|
||||||
|
{option.label}
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ButtonSelect;
|
|
@ -0,0 +1,37 @@
|
||||||
|
import { getLength, getLong } from "../../lib/src/p-text-helpers";
|
||||||
|
import * as T from "../../types";
|
||||||
|
import Examples from "./Examples";
|
||||||
|
|
||||||
|
function CompiledPTextDisplay({ compiled, opts, justify, onlyOne, length }: {
|
||||||
|
compiled: {
|
||||||
|
ps: T.SingleOrLengthOpts<T.PsString[]>;
|
||||||
|
e?: string[] | undefined;
|
||||||
|
},
|
||||||
|
opts: T.TextOptions,
|
||||||
|
justify?: "left" | "right" | "center",
|
||||||
|
onlyOne?: boolean,
|
||||||
|
length?: "long" | "short",
|
||||||
|
}) {
|
||||||
|
function VariationLayer({ vs }: { vs: T.PsString[] }) {
|
||||||
|
return <div className="mb-2">
|
||||||
|
<Examples opts={opts} lineHeight={0}>{vs}</Examples>
|
||||||
|
</div>;
|
||||||
|
}
|
||||||
|
const ps = length
|
||||||
|
? getLength(compiled.ps, length)
|
||||||
|
: compiled.ps;
|
||||||
|
return <div className={justify === "left" ? "text-left" : justify === "right" ? "text-right" : "text-center"}>
|
||||||
|
{onlyOne
|
||||||
|
? <VariationLayer vs={[getLong(ps)[0]]} />
|
||||||
|
: "long" in ps ?
|
||||||
|
<div>
|
||||||
|
<VariationLayer vs={ps.long} />
|
||||||
|
<VariationLayer vs={ps.short} />
|
||||||
|
{ps.mini && <VariationLayer vs={ps.mini} />}
|
||||||
|
</div>
|
||||||
|
: <VariationLayer vs={ps} />
|
||||||
|
}
|
||||||
|
</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CompiledPTextDisplay;
|
|
@ -1,8 +1,8 @@
|
||||||
import { useState, useEffect } from "react";
|
import { useState, useEffect } from "react";
|
||||||
import * as T from "../../../types";
|
import * as T from "../../types";
|
||||||
import AdjectivePicker from "./AdjectivePicker";
|
import AdjectivePicker from "./np-picker/AdjectivePicker";
|
||||||
import LocativeAdverbPicker from "./LocativeAdverbPicker";
|
import LocativeAdverbPicker from "./ep-explorer/eq-comp-picker/LocativeAdverbPicker";
|
||||||
import SandwichPicker from "./SandwichPicker";
|
import SandwichPicker from "./np-picker/SandwichPicker";
|
||||||
const compTypes: T.ComplementType[] = [
|
const compTypes: T.ComplementType[] = [
|
||||||
"adjective",
|
"adjective",
|
||||||
"loc. adv.",
|
"loc. adv.",
|
|
@ -1,12 +1,31 @@
|
||||||
import * as T from "../../../types";
|
import * as T from "../../types";
|
||||||
import { StyleHTMLAttributes } from "react";
|
import { StyleHTMLAttributes } from "react";
|
||||||
import Select from "react-select";
|
import Select, { StylesConfig } from "react-select";
|
||||||
import AsyncSelect from "react-select/async";
|
import AsyncSelect from "react-select/async";
|
||||||
import {
|
import {
|
||||||
makeSelectOption,
|
makeSelectOption,
|
||||||
makeVerbSelectOption,
|
makeVerbSelectOption,
|
||||||
} from "../block-pickers/picker-tools";
|
} from "./np-picker/picker-tools";
|
||||||
import { customSelectStyles as customStyles } from "./select-styles";
|
|
||||||
|
export const customStyles: StylesConfig = {
|
||||||
|
menuPortal: (base: any) => ({
|
||||||
|
...base,
|
||||||
|
zIndex: 99999,
|
||||||
|
}),
|
||||||
|
menu: (base: any) => ({
|
||||||
|
...base,
|
||||||
|
zIndex: 999999,
|
||||||
|
}),
|
||||||
|
option: (provided: any, state: any) => ({
|
||||||
|
...provided,
|
||||||
|
padding: "10px 5px",
|
||||||
|
color: "#121418",
|
||||||
|
}),
|
||||||
|
input: (base: any) => ({
|
||||||
|
...base,
|
||||||
|
padding: 0,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
function EntrySelect<E extends T.DictionaryEntry | T.VerbEntry>(props: {
|
function EntrySelect<E extends T.DictionaryEntry | T.VerbEntry>(props: {
|
||||||
entryFeeder: T.EntryFeederSingleType<E>;
|
entryFeeder: T.EntryFeederSingleType<E>;
|
||||||
|
@ -62,7 +81,7 @@ function EntrySelect<E extends T.DictionaryEntry | T.VerbEntry>(props: {
|
||||||
isSearchable={true}
|
isSearchable={true}
|
||||||
className="mb-2"
|
className="mb-2"
|
||||||
value={value}
|
value={value}
|
||||||
// @ts-expect-error select issue
|
// @ts-ignore
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
defaultOptions={[]}
|
defaultOptions={[]}
|
||||||
loadOptions={options}
|
loadOptions={options}
|
||||||
|
@ -101,7 +120,7 @@ function EntrySelect<E extends T.DictionaryEntry | T.VerbEntry>(props: {
|
||||||
styles={customStyles}
|
styles={customStyles}
|
||||||
isSearchable={true}
|
isSearchable={true}
|
||||||
value={value || null}
|
value={value || null}
|
||||||
// @ts-expect-error select issue
|
// @ts-ignore - sadly gets messed up when using customStyles
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
className="mb-2"
|
className="mb-2"
|
||||||
options={options}
|
options={options}
|
||||||
|
@ -140,7 +159,7 @@ export function DeterminerSelect(props: {
|
||||||
isSearchable={true}
|
isSearchable={true}
|
||||||
value={value}
|
value={value}
|
||||||
isMulti
|
isMulti
|
||||||
// @ts-expect-error select issue
|
// @ts-ignore - gets messed up when using customStyles
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
className="mb-2"
|
className="mb-2"
|
||||||
options={options}
|
options={options}
|
||||||
|
@ -197,7 +216,7 @@ export function SandwichSelect<E extends T.Sandwich>(props: {
|
||||||
styles={customStyles}
|
styles={customStyles}
|
||||||
isSearchable={true}
|
isSearchable={true}
|
||||||
value={value}
|
value={value}
|
||||||
// @ts-expect-error select issue
|
// @ts-ignore - gets messed up when using customStyles
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
className="mb-2"
|
className="mb-2"
|
||||||
options={options}
|
options={options}
|
|
@ -8,10 +8,9 @@
|
||||||
|
|
||||||
import Pashto from "./Pashto";
|
import Pashto from "./Pashto";
|
||||||
import Phonetics from "./Phonetics";
|
import Phonetics from "./Phonetics";
|
||||||
import * as T from "../../../types";
|
import * as T from "../../types";
|
||||||
import { ReactNode } from "react";
|
|
||||||
|
|
||||||
type PsStringWSub = T.PsString & { sub?: ReactNode };
|
type PsStringWSub = T.PsString & { sub?: any };
|
||||||
|
|
||||||
function EnglishContent({
|
function EnglishContent({
|
||||||
children,
|
children,
|
||||||
|
@ -55,10 +54,10 @@ function Examples(
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
<Pashto opts={props.opts} ps={text} />
|
<Pashto opts={props.opts}>{text}</Pashto>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<Phonetics opts={props.opts} ps={text} />
|
<Phonetics opts={props.opts}>{text}</Phonetics>
|
||||||
</div>
|
</div>
|
||||||
{text.e && <EnglishContent>{text.e}</EnglishContent>}
|
{text.e && <EnglishContent>{text.e}</EnglishContent>}
|
||||||
{text.sub && <div className="small text-muted">{text.sub}</div>}
|
{text.sub && <div className="small text-muted">{text.sub}</div>}
|
|
@ -9,88 +9,65 @@
|
||||||
import { createElement, useEffect, useRef } from "react";
|
import { createElement, useEffect, useRef } from "react";
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
import * as T from "../../types";
|
import * as T from "../../types";
|
||||||
// @ts-expect-error types needed
|
|
||||||
import autoAnimate from "@formkit/auto-animate";
|
import autoAnimate from "@formkit/auto-animate";
|
||||||
|
|
||||||
const caretRight = (
|
const caretRight = <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" className="bi bi-caret-right-fill" viewBox="0 0 16 16">
|
||||||
<svg
|
<path d="M12.14 8.753l-5.482 4.796c-.646.566-1.658.106-1.658-.753V3.204a1 1 0 0 1 1.659-.753l5.48 4.796a1 1 0 0 1 0 1.506z"/>
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
</svg>
|
||||||
width="16"
|
const caretDown = <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" className="bi bi-caret-down-fill" viewBox="0 0 16 16">
|
||||||
height="16"
|
<path d="M7.247 11.14L2.451 5.658C1.885 5.013 2.345 4 3.204 4h9.592a1 1 0 0 1 .753 1.659l-4.796 5.48a1 1 0 0 1-1.506 0z"/>
|
||||||
fill="currentColor"
|
</svg>
|
||||||
className="bi bi-caret-right-fill"
|
|
||||||
viewBox="0 0 16 16"
|
|
||||||
>
|
|
||||||
<path d="M12.14 8.753l-5.482 4.796c-.646.566-1.658.106-1.658-.753V3.204a1 1 0 0 1 1.659-.753l5.48 4.796a1 1 0 0 1 0 1.506z" />
|
|
||||||
</svg>
|
|
||||||
);
|
|
||||||
const caretDown = (
|
|
||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
width="16"
|
|
||||||
height="16"
|
|
||||||
fill="currentColor"
|
|
||||||
className="bi bi-caret-down-fill"
|
|
||||||
viewBox="0 0 16 16"
|
|
||||||
>
|
|
||||||
<path d="M7.247 11.14L2.451 5.658C1.885 5.013 2.345 4 3.204 4h9.592a1 1 0 0 1 .753 1.659l-4.796 5.48a1 1 0 0 1-1.506 0z" />
|
|
||||||
</svg>
|
|
||||||
);
|
|
||||||
|
|
||||||
const defaultLevel = 4;
|
const defaultLevel = 4;
|
||||||
const indentAfterLevel = 5;
|
const indentAfterLevel = 5;
|
||||||
|
|
||||||
function Hider(props: {
|
function Hider(props: {
|
||||||
label: string | JSX.Element;
|
label: string | JSX.Element,
|
||||||
showing: boolean;
|
showing: boolean,
|
||||||
aspect?: T.Aspect;
|
aspect?: T.Aspect,
|
||||||
handleChange: () => void;
|
handleChange: () => void,
|
||||||
children: React.ReactNode;
|
children: React.ReactNode,
|
||||||
hLevel?: number;
|
hLevel?: number,
|
||||||
ignore?: boolean;
|
ignore?: boolean,
|
||||||
}) {
|
}) {
|
||||||
const parent = useRef<HTMLDivElement>(null);
|
const parent = useRef<HTMLDivElement>(null);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (parent.current) {
|
parent.current && autoAnimate(parent.current);
|
||||||
autoAnimate(parent.current);
|
}, [parent]);
|
||||||
|
const hLev = Math.min((props.hLevel ? props.hLevel : defaultLevel), 6);
|
||||||
|
const extraMargin = (props.hLevel && (props.hLevel > indentAfterLevel))
|
||||||
|
? `ml-${(props.hLevel - indentAfterLevel) + 1}`
|
||||||
|
: "";
|
||||||
|
if (props.ignore) {
|
||||||
|
return <>
|
||||||
|
{props.children}
|
||||||
|
</>;
|
||||||
}
|
}
|
||||||
}, [parent]);
|
return <div className="mb-3" ref={parent}>
|
||||||
const hLev = Math.min(props.hLevel ? props.hLevel : defaultLevel, 6);
|
{createElement(
|
||||||
const extraMargin =
|
`h${hLev}`,
|
||||||
props.hLevel && props.hLevel > indentAfterLevel
|
{
|
||||||
? `ml-${props.hLevel - indentAfterLevel + 1}`
|
onClick: props.handleChange,
|
||||||
: "";
|
className: classNames(
|
||||||
if (props.ignore) {
|
"clickable",
|
||||||
return <>{props.children}</>;
|
extraMargin,
|
||||||
}
|
),
|
||||||
return (
|
},
|
||||||
<div className="mb-3" ref={parent}>
|
<div className="d-flex flex-row align-items-center">
|
||||||
{createElement(
|
<div style={{ width: "1rem" }}>
|
||||||
`h${hLev}`,
|
{props.showing ? caretDown : caretRight}
|
||||||
{
|
</div>
|
||||||
onClick: props.handleChange,
|
{` `}
|
||||||
className: classNames("clickable", extraMargin),
|
{props.aspect
|
||||||
},
|
? <i className={`fas fa-${props.aspect === "imperfective" ? "video" : "camera"}`} />
|
||||||
<div className="d-flex flex-row align-items-center">
|
: ""}
|
||||||
<div style={{ width: "1rem" }}>
|
<div className="ml-2">
|
||||||
{props.showing ? caretDown : caretRight}
|
{props.label}
|
||||||
</div>
|
</div>
|
||||||
{` `}
|
</div>,
|
||||||
{props.aspect ? (
|
)}
|
||||||
<i
|
{props.showing && props.children}
|
||||||
className={`fas fa-${
|
|
||||||
props.aspect === "imperfective" ? "video" : "camera"
|
|
||||||
}`}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
""
|
|
||||||
)}
|
|
||||||
<div className="ml-2">{props.label}</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{props.showing && props.children}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Hider;
|
export default Hider;
|
|
@ -1,5 +1,5 @@
|
||||||
import * as T from "../../../types";
|
import * as T from "../../types";
|
||||||
import InlinePs from "./../text-display/InlinePs";
|
import InlinePs from "./InlinePs";
|
||||||
|
|
||||||
export default function HumanReadableInflectionPattern(
|
export default function HumanReadableInflectionPattern(
|
||||||
p: T.InflectionPattern,
|
p: T.InflectionPattern,
|
||||||
|
@ -10,12 +10,12 @@ export default function HumanReadableInflectionPattern(
|
||||||
) : p === 2 ? (
|
) : p === 2 ? (
|
||||||
<span>
|
<span>
|
||||||
#2 Unstressed{" "}
|
#2 Unstressed{" "}
|
||||||
<InlinePs opts={textOptions} ps={{ p: "ی", f: "ay", e: "" }} />
|
<InlinePs opts={textOptions}>{{ p: "ی", f: "ay", e: "" }}</InlinePs>
|
||||||
</span>
|
</span>
|
||||||
) : p === 3 ? (
|
) : p === 3 ? (
|
||||||
<span>
|
<span>
|
||||||
#3 Stressed{" "}
|
#3 Stressed{" "}
|
||||||
<InlinePs opts={textOptions} ps={{ p: "ی", f: "áy", e: "" }} />
|
<InlinePs opts={textOptions}>{{ p: "ی", f: "áy", e: "" }}</InlinePs>
|
||||||
</span>
|
</span>
|
||||||
) : p === 4 ? (
|
) : p === 4 ? (
|
||||||
<span>#4 "Pashtoon"</span>
|
<span>#4 "Pashtoon"</span>
|
||||||
|
@ -24,7 +24,7 @@ export default function HumanReadableInflectionPattern(
|
||||||
) : p === 6 ? (
|
) : p === 6 ? (
|
||||||
<span>
|
<span>
|
||||||
#6 Fem. inan.{" "}
|
#6 Fem. inan.{" "}
|
||||||
<InlinePs opts={textOptions} ps={{ p: "ي", f: "ee", e: "" }} />
|
<InlinePs opts={textOptions}>{{ p: "ي", f: "ee", e: "" }}</InlinePs>
|
||||||
</span>
|
</span>
|
||||||
) : null;
|
) : null;
|
||||||
}
|
}
|
|
@ -13,8 +13,8 @@
|
||||||
// import Pashto from "./Pashto";
|
// import Pashto from "./Pashto";
|
||||||
// import { Modal } from "react-bootstrap";
|
// import { Modal } from "react-bootstrap";
|
||||||
import TableCell from "./TableCell";
|
import TableCell from "./TableCell";
|
||||||
import * as T from "../../../types";
|
import * as T from "../../types";
|
||||||
import { isPluralInflections } from "../../../lib/src/p-text-helpers";
|
import { isPluralInflections } from "../../lib/src/p-text-helpers";
|
||||||
|
|
||||||
// const explanation = (inf: T.Inflections | T.PluralInflections, textOptions: T.TextOptions) => {
|
// const explanation = (inf: T.Inflections | T.PluralInflections, textOptions: T.TextOptions) => {
|
||||||
// const isPluralInfs = isPluralInflections(inf);
|
// const isPluralInfs = isPluralInflections(inf);
|
||||||
|
@ -114,8 +114,6 @@ const InflectionTable = ({
|
||||||
<tr key={title}>
|
<tr key={title}>
|
||||||
<th scope="row">{title}</th>
|
<th scope="row">{title}</th>
|
||||||
{"masc" in inf && (
|
{"masc" in inf && (
|
||||||
// eslint-disable-next-line
|
|
||||||
// @ts-ignore
|
|
||||||
<TableCell
|
<TableCell
|
||||||
item={inf.masc[i]}
|
item={inf.masc[i]}
|
||||||
textOptions={textOptions}
|
textOptions={textOptions}
|
||||||
|
@ -124,8 +122,6 @@ const InflectionTable = ({
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{"fem" in inf && (!("masc" in inf) || i < 2) && (
|
{"fem" in inf && (!("masc" in inf) || i < 2) && (
|
||||||
// eslint-disable-next-line
|
|
||||||
// @ts-ignore
|
|
||||||
<TableCell item={inf.fem[i]} textOptions={textOptions} center />
|
<TableCell item={inf.fem[i]} textOptions={textOptions} center />
|
||||||
)}
|
)}
|
||||||
</tr>
|
</tr>
|
|
@ -0,0 +1,37 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2021 lingdocs.com
|
||||||
|
*
|
||||||
|
* This source code is licensed under the GPL3 license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
import Pashto from "./Pashto";
|
||||||
|
import Phonetics from "./Phonetics";
|
||||||
|
import * as T from "../../types";
|
||||||
|
|
||||||
|
function InlinePs ({
|
||||||
|
children,
|
||||||
|
ps,
|
||||||
|
opts,
|
||||||
|
}: ({
|
||||||
|
ps: T.PsString | (T.PsJSX & { e?: string }),
|
||||||
|
children?: T.PsString | (T.PsJSX & { e?: string }),
|
||||||
|
opts: T.TextOptions,
|
||||||
|
} | {
|
||||||
|
ps?: T.PsString | (T.PsJSX & { e?: string }),
|
||||||
|
children: T.PsString | (T.PsJSX & { e?: string }),
|
||||||
|
opts: T.TextOptions,
|
||||||
|
})) {
|
||||||
|
const text = children || ps as T.PsString | (T.PsJSX & { e?: string });
|
||||||
|
return (
|
||||||
|
<span>
|
||||||
|
<Pashto opts={opts}>{text}</Pashto>
|
||||||
|
{opts.phonetics !== "none" && " - "}
|
||||||
|
<Phonetics opts={opts}>{text}</Phonetics>
|
||||||
|
{text.e && <span className="text-muted"> ({text.e})</span>}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default InlinePs;
|
|
@ -10,8 +10,7 @@ const Keyframes = (props: IProps) => {
|
||||||
typeof cssObject === "string"
|
typeof cssObject === "string"
|
||||||
? cssObject
|
? cssObject
|
||||||
: Object.keys(cssObject).reduce((accumulator, key) => {
|
: Object.keys(cssObject).reduce((accumulator, key) => {
|
||||||
const cssKey = key.replace(/[A-Z]/g, (v) => `-${v.toLowerCase()}`);
|
const cssKey = key.replace(/[A-Z]/g, v => `-${v.toLowerCase()}`);
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
const cssValue = (cssObject as any)[key].toString().replace("'", "");
|
const cssValue = (cssObject as any)[key].toString().replace("'", "");
|
||||||
return `${accumulator}${cssKey}:${cssValue};`;
|
return `${accumulator}${cssKey}:${cssValue};`;
|
||||||
}, "");
|
}, "");
|
||||||
|
@ -20,7 +19,7 @@ const Keyframes = (props: IProps) => {
|
||||||
<style>
|
<style>
|
||||||
{`@keyframes ${props.name} {
|
{`@keyframes ${props.name} {
|
||||||
${Object.keys(props)
|
${Object.keys(props)
|
||||||
.map((key) => {
|
.map(key => {
|
||||||
return ["from", "to"].includes(key)
|
return ["from", "to"].includes(key)
|
||||||
? `${key} { ${toCss(props[key])} }`
|
? `${key} { ${toCss(props[key])} }`
|
||||||
: /^_[0-9]+$/.test(key)
|
: /^_[0-9]+$/.test(key)
|
||||||
|
@ -33,4 +32,4 @@ const Keyframes = (props: IProps) => {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Keyframes;
|
export default Keyframes;
|
|
@ -6,17 +6,17 @@
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { convertSpelling } from "../../../lib/src/convert-spelling";
|
import { convertSpelling } from "../../lib/src/convert-spelling";
|
||||||
import { phoneticsToDiacritics } from "../../../lib/src/phonetics-to-diacritics";
|
import { phoneticsToDiacritics } from "../../lib/src/phonetics-to-diacritics";
|
||||||
import { psJSXMap } from "./jsx-map";
|
import { psJSXMap } from "./jsx-map";
|
||||||
import * as T from "../../../types";
|
import * as T from "../../types";
|
||||||
|
|
||||||
const Pashto = ({
|
const Pashto = ({
|
||||||
opts,
|
opts,
|
||||||
ps,
|
children: text,
|
||||||
}: {
|
}: {
|
||||||
opts: T.TextOptions;
|
opts: T.TextOptions;
|
||||||
ps: T.PsString | T.PsJSX;
|
children: T.PsString | T.PsJSX;
|
||||||
}) => {
|
}) => {
|
||||||
function convertText(ps: T.PsString, opts: T.TextOptions): string {
|
function convertText(ps: T.PsString, opts: T.TextOptions): string {
|
||||||
const p = opts.diacritics
|
const p = opts.diacritics
|
||||||
|
@ -30,11 +30,11 @@ const Pashto = ({
|
||||||
: { fontSize: opts.pTextSize === "larger" ? "large" : "larger" };
|
: { fontSize: opts.pTextSize === "larger" ? "large" : "larger" };
|
||||||
return (
|
return (
|
||||||
<span className="p-text" dir="rtl" style={style} lang="ps">
|
<span className="p-text" dir="rtl" style={style} lang="ps">
|
||||||
{typeof ps.p !== "string" && typeof ps.f !== "string"
|
{typeof text.p !== "string" && typeof text.f !== "string"
|
||||||
? psJSXMap(ps as T.PsJSX, "p", (ps: T.PsString) =>
|
? psJSXMap(text as T.PsJSX, "p", (ps: T.PsString) =>
|
||||||
convertText(ps, opts)
|
convertText(ps, opts)
|
||||||
)
|
)
|
||||||
: convertText(ps as T.PsString, opts)}
|
: convertText(text as T.PsString, opts)}
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
};
|
};
|
|
@ -6,7 +6,7 @@
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import * as T from "../../../types";
|
import * as T from "../../types";
|
||||||
|
|
||||||
const persInfs: {
|
const persInfs: {
|
||||||
label: string;
|
label: string;
|
||||||
|
@ -35,7 +35,7 @@ function PersInfsPicker(props: {
|
||||||
persInf: T.PersonInflectionsField;
|
persInf: T.PersonInflectionsField;
|
||||||
handleChange: (persInf: T.PersonInflectionsField) => void;
|
handleChange: (persInf: T.PersonInflectionsField) => void;
|
||||||
}) {
|
}) {
|
||||||
function hChange(e: React.ChangeEvent<HTMLSelectElement>) {
|
function hChange(e: any) {
|
||||||
const newValue = e.target.value as T.PersonInflectionsField;
|
const newValue = e.target.value as T.PersonInflectionsField;
|
||||||
props.handleChange(newValue);
|
props.handleChange(newValue);
|
||||||
}
|
}
|
|
@ -0,0 +1,108 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2021 lingdocs.com
|
||||||
|
*
|
||||||
|
* This source code is licensed under the GPL3 license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { persons } from "../../lib/src/grammar-units";
|
||||||
|
import InlinePs from "./InlinePs";
|
||||||
|
import * as T from "../../types";
|
||||||
|
|
||||||
|
function PersonSelect(props: {
|
||||||
|
setting: "subject" | "object",
|
||||||
|
value: T.Person,
|
||||||
|
locked?: boolean,
|
||||||
|
handleChange: (person: T.Person) => void,
|
||||||
|
handleRandom: () => void,
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
!props.locked ? <div className="input-group" style={{ maxWidth: "30rem" }}>
|
||||||
|
<select
|
||||||
|
className="custom-select"
|
||||||
|
value={props.value}
|
||||||
|
onChange={(e: any) => props.handleChange(
|
||||||
|
parseInt(e.target.value) as T.Person
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{persons.map((p) => (
|
||||||
|
<option value={p.person} key={"subject"+p.person}>
|
||||||
|
{p.label[props.setting]}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
<div className="input-group-append" onClick={props.handleRandom}>
|
||||||
|
<button className="btn btn-secondary">
|
||||||
|
<i className="fas fa-random" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div> : <input
|
||||||
|
className="form-control"
|
||||||
|
type="text"
|
||||||
|
placeholder={persons[props.value].label[props.setting]}
|
||||||
|
readOnly
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function PersonSelection(props: {
|
||||||
|
subject: T.Person,
|
||||||
|
object: T.Person,
|
||||||
|
info: T.NonComboVerbInfo,
|
||||||
|
handleRandom: (setting: "subject" | "object") => void,
|
||||||
|
handleChange: (payload: { setting: "subject" | "object", person: T.Person }) => void,
|
||||||
|
textOptions: T.TextOptions,
|
||||||
|
}) {
|
||||||
|
function getComp(comp: T.ObjComplement) {
|
||||||
|
const c = comp.plural
|
||||||
|
? comp.plural
|
||||||
|
: comp.entry;
|
||||||
|
return <InlinePs opts={props.textOptions}>{c}</InlinePs>;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<div className="row align-items-baseline">
|
||||||
|
<div className="col">
|
||||||
|
<label className="form-label">
|
||||||
|
{/* TODO: Should I put the Subject/Agent label back in for non-transitive verbs?? */}
|
||||||
|
<strong>Subject</strong>
|
||||||
|
</label>
|
||||||
|
<PersonSelect
|
||||||
|
setting="subject"
|
||||||
|
value={props.subject}
|
||||||
|
handleChange={(person: T.Person) => props.handleChange({ setting: "subject", person })}
|
||||||
|
handleRandom={() => props.handleRandom("subject")}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{(props.info.type === "dynamic compound" || props.info.type === "generative stative compound") ? <div className="col">
|
||||||
|
<label className="form-label"><strong>Object is the complement ({getComp(props.info.objComplement)})</strong></label>
|
||||||
|
<PersonSelect
|
||||||
|
setting="object"
|
||||||
|
value={props.info.objComplement.person}
|
||||||
|
locked
|
||||||
|
handleChange={(person: T.Person) => props.handleChange({ setting: "object", person })}
|
||||||
|
handleRandom={() => props.handleRandom("object")}
|
||||||
|
/>
|
||||||
|
</div> : props.info.transitivity === "transitive" ? <div className="col">
|
||||||
|
<label className="form-label"><strong>Object</strong></label>
|
||||||
|
<PersonSelect
|
||||||
|
setting="object"
|
||||||
|
value={props.object}
|
||||||
|
handleChange={(person: T.Person) => props.handleChange({ setting: "object", person })}
|
||||||
|
handleRandom={() => props.handleRandom("object")}
|
||||||
|
/>
|
||||||
|
</div> : props.info.transitivity === "grammatically transitive" ? <div className="col">
|
||||||
|
<label className="form-label"><strong>Object is unspoken</strong></label>
|
||||||
|
<PersonSelect
|
||||||
|
setting="object"
|
||||||
|
value={10}
|
||||||
|
locked
|
||||||
|
handleChange={(person: T.Person) => props.handleChange({ setting: "object", person })}
|
||||||
|
handleRandom={() => props.handleRandom("object")}
|
||||||
|
/>
|
||||||
|
</div> : null}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default PersonSelection;
|
|
@ -0,0 +1,38 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2021 lingdocs.com
|
||||||
|
*
|
||||||
|
* This source code is licensed under the GPL3 license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {
|
||||||
|
translatePhonetics,
|
||||||
|
} from "../../lib/src/translate-phonetics";
|
||||||
|
import { psJSXMap } from "./jsx-map";
|
||||||
|
import * as T from "../../types";
|
||||||
|
|
||||||
|
const Phonetics = ({ opts, children: text }: {
|
||||||
|
opts: T.TextOptions,
|
||||||
|
children: T.PsJSX | T.PsString | string,
|
||||||
|
}) => {
|
||||||
|
if (opts.phonetics === "none") {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const handleText = (f: string) => (
|
||||||
|
opts.phonetics === "lingdocs"
|
||||||
|
? f
|
||||||
|
: translatePhonetics(f, {
|
||||||
|
dialect: opts.dialect,
|
||||||
|
// @ts-ignore - weird TS not picking up the elimination of "none herre"
|
||||||
|
system: opts.phonetics,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
return <span className="f-text">
|
||||||
|
{(typeof text !== "string" && typeof text.f !== "string")
|
||||||
|
? psJSXMap(text as T.PsJSX, "f", ({f}) => handleText(f))
|
||||||
|
: handleText(typeof text === "string" ? text : text.f as string)}
|
||||||
|
</span>
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Phonetics;
|
|
@ -0,0 +1,67 @@
|
||||||
|
import { useState } from "react";
|
||||||
|
import { filterForVisibleBlocksEP, filterForVisibleBlocksVP } from "../../lib/src/phrase-building/compile";
|
||||||
|
import * as T from "../../types";
|
||||||
|
import Block from "./blocks/Block";
|
||||||
|
import KidDisplay from "./blocks/KidDisplay";
|
||||||
|
|
||||||
|
function RenderedBlocksDisplay({ opts, rendered, justify, script }: {
|
||||||
|
script: "p" | "f",
|
||||||
|
opts: T.TextOptions,
|
||||||
|
rendered: T.EPRendered | T.VPRendered,
|
||||||
|
justify?: "left" | "right" | "center",
|
||||||
|
}) {
|
||||||
|
const [variation, setVariation] = useState<number>(0);
|
||||||
|
// not using autoAnimate here because we need a way to persist the keys in the blocks first
|
||||||
|
// const parent = useRef(null);
|
||||||
|
// useEffect(() => {
|
||||||
|
// parent.current && autoAnimate(parent.current)
|
||||||
|
// }, [parent]);
|
||||||
|
const blocksWVars: T.Block[][] = Array.isArray(rendered)
|
||||||
|
? rendered
|
||||||
|
: ("omitSubject" in rendered)
|
||||||
|
? filterForVisibleBlocksEP(rendered.blocks, rendered.omitSubject)
|
||||||
|
: filterForVisibleBlocksVP(rendered.blocks, rendered.form, rendered.king);
|
||||||
|
const king = "king" in rendered ? rendered.king : undefined;
|
||||||
|
const blocks = blocksWVars[variation];
|
||||||
|
function handleVariationChange() {
|
||||||
|
setVariation(ov => ((ov + 1) % blocksWVars.length));
|
||||||
|
}
|
||||||
|
return <div className={`d-flex flex-row justify-content-${justify ? justify : "center"}`}>
|
||||||
|
<div className={`d-flex flex-row${script === "p" ? "-reverse" : ""} justify-content-left align-items-end mt-3 pb-2`} style={{ overflowX: "auto" }}>
|
||||||
|
<div key={blocks[0].key} className="mr-2">
|
||||||
|
<Block opts={opts} block={blocks[0]} king={king} script={script} />
|
||||||
|
</div>
|
||||||
|
<KidsSection key="kidsSection" opts={opts} kids={rendered.kids} script={script} />
|
||||||
|
{blocks.slice(1).map((block) => (
|
||||||
|
<div key={block.key} className="mr-2">
|
||||||
|
<Block opts={opts} block={block} king={king} script={script} />
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
<div style={{ height: "100%" }} className="d-flex flex-column justify-content-center">
|
||||||
|
{blocksWVars.length > 1 && <button onClick={handleVariationChange} className="btn btn-light btn-sm mx-2">V. {variation + 1}/{blocksWVars.length}</button>}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
function KidsSection({ opts, kids, script }: {
|
||||||
|
opts: T.TextOptions,
|
||||||
|
kids: T.Kid[],
|
||||||
|
script: "p" | "f",
|
||||||
|
}) {
|
||||||
|
// not using autoAnimate here because we need a way to persist the keys in the blocks first
|
||||||
|
// const parent = useRef(null);
|
||||||
|
// useEffect(() => {
|
||||||
|
// parent.current && autoAnimate(parent.current)
|
||||||
|
// }, [parent]);
|
||||||
|
return kids.length > 0 ? <div className="text-center mx-1 mr-3" style={{ paddingBottom: "1rem"}}>
|
||||||
|
<div className={`d-flex flex-row${script === "p" ? "-reverse" : ""} mb-3 justify-content-center`}>
|
||||||
|
{kids.map(kid => (
|
||||||
|
<KidDisplay key={kid.key} opts={opts} kid={kid} script={script} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<div><strong>kids</strong></div>
|
||||||
|
</div> : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default RenderedBlocksDisplay;
|
|
@ -0,0 +1,26 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2021 lingdocs.com
|
||||||
|
*
|
||||||
|
* This source code is licensed under the GPL3 license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
import Pashto from "./Pashto";
|
||||||
|
import Phonetics from "./Phonetics";
|
||||||
|
import * as T from "../../types";
|
||||||
|
|
||||||
|
function SingleItemDisplay({ item, textOptions, english }: {
|
||||||
|
item: T.PsString,
|
||||||
|
textOptions: T.TextOptions,
|
||||||
|
english?: T.EnglishBlock | string,
|
||||||
|
}) {
|
||||||
|
const eng = Array.isArray(english) ? english[0][0] : english;
|
||||||
|
return <div className="text-center mt-3 mb-2">
|
||||||
|
<div><Pashto opts={textOptions}>{item}</Pashto></div>
|
||||||
|
<div><Phonetics opts={textOptions}>{item}</Phonetics></div>
|
||||||
|
{eng && <div className="text-muted">{eng}</div>}
|
||||||
|
</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default SingleItemDisplay;
|
|
@ -1,7 +1,7 @@
|
||||||
import { useState, useEffect } from "react";
|
import { useState, useEffect } from "react";
|
||||||
import * as T from "../../../types";
|
import * as T from "../../types";
|
||||||
import Pashto from "./../text-display/Pashto";
|
import Pashto from "./Pashto";
|
||||||
import Phonetics from "./../text-display/Phonetics";
|
import Phonetics from "./Phonetics";
|
||||||
|
|
||||||
const arrowDown = (
|
const arrowDown = (
|
||||||
<svg
|
<svg
|
||||||
|
@ -19,7 +19,7 @@ const arrowDown = (
|
||||||
</svg>
|
</svg>
|
||||||
);
|
);
|
||||||
|
|
||||||
const TableCell = ({
|
function TableCell({
|
||||||
item,
|
item,
|
||||||
textOptions,
|
textOptions,
|
||||||
center,
|
center,
|
||||||
|
@ -31,7 +31,7 @@ const TableCell = ({
|
||||||
center?: boolean;
|
center?: boolean;
|
||||||
noBorder?: boolean;
|
noBorder?: boolean;
|
||||||
colSpan?: 1 | 2;
|
colSpan?: 1 | 2;
|
||||||
}) => {
|
}) {
|
||||||
const [version, setVersion] = useState(0);
|
const [version, setVersion] = useState(0);
|
||||||
useEffect(() => setVersion(0), [item]);
|
useEffect(() => setVersion(0), [item]);
|
||||||
function advanceVersion() {
|
function advanceVersion() {
|
||||||
|
@ -56,10 +56,10 @@ const TableCell = ({
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
<div>
|
<div>
|
||||||
<Pashto opts={textOptions} ps={w} />
|
<Pashto opts={textOptions}>{w}</Pashto>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<Phonetics opts={textOptions} ps={w} />
|
<Phonetics opts={textOptions}>{w}</Phonetics>
|
||||||
</div>
|
</div>
|
||||||
{w.e &&
|
{w.e &&
|
||||||
(Array.isArray(w.e) ? (
|
(Array.isArray(w.e) ? (
|
||||||
|
@ -83,6 +83,6 @@ const TableCell = ({
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
||||||
export default TableCell;
|
export default TableCell;
|
|
@ -7,11 +7,11 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import PersonInfsPicker from "./selects/PersInfsPicker";
|
import PersonInfsPicker from "./PersInfsPicker";
|
||||||
import InflectionsTable from "./tables/InflectionsTable";
|
import InflectionsTable from "./InflectionsTable";
|
||||||
import SingleItemDisplay from "./text-display/SingleItemDisplay";
|
import SingleItemDisplay from "./SingleItemDisplay";
|
||||||
import ButtonSelect from "./selects/ButtonSelect";
|
import ButtonSelect from "./ButtonSelect";
|
||||||
import VerbTable from "./tables/VerbTable";
|
import VerbTable from "./VerbTable";
|
||||||
import {
|
import {
|
||||||
getEnglishPersonInfo,
|
getEnglishPersonInfo,
|
||||||
isSentenceForm,
|
isSentenceForm,
|
||||||
|
|
|
@ -0,0 +1,111 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2021 lingdocs.com
|
||||||
|
*
|
||||||
|
* This source code is licensed under the GPL3 license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
import TableCell from "./TableCell";
|
||||||
|
import {
|
||||||
|
psStringEquals,
|
||||||
|
isAllOne,
|
||||||
|
addEnglish,
|
||||||
|
} from "../../lib/src/p-text-helpers";
|
||||||
|
import { isSentenceForm } from "../../lib/src/misc-helpers";
|
||||||
|
import * as T from "../../types";
|
||||||
|
import genderColors from "./gender-colors";
|
||||||
|
|
||||||
|
const genderAbbrev = (gender: "masc" | "fem" | undefined): " m." | " f." | "" => (
|
||||||
|
gender === "masc"
|
||||||
|
? " m."
|
||||||
|
: gender === "fem"
|
||||||
|
? " f."
|
||||||
|
: ""
|
||||||
|
);
|
||||||
|
|
||||||
|
const minifyTableGender = (block: T.VerbBlock | T.ImperativeBlock): Array<T.PersonLine | {
|
||||||
|
masc: T.PersonLine,
|
||||||
|
fem: T.PersonLine,
|
||||||
|
}> => {
|
||||||
|
// @ts-ignore
|
||||||
|
return block.reduce((table, person, i, src) => {
|
||||||
|
const isFem = i % 2 !== 0;
|
||||||
|
if (isFem) {
|
||||||
|
return table;
|
||||||
|
}
|
||||||
|
const femPersAhead = src[i+1];
|
||||||
|
const femPersIsTheSame = (
|
||||||
|
psStringEquals(person[0][0], femPersAhead[0][0]) &&
|
||||||
|
psStringEquals(person[1][0], femPersAhead[1][0])
|
||||||
|
);
|
||||||
|
if (femPersAhead && !femPersIsTheSame) {
|
||||||
|
return [...table, {
|
||||||
|
masc: person,
|
||||||
|
fem: femPersAhead,
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
return [...table, person];
|
||||||
|
}, []);
|
||||||
|
};
|
||||||
|
|
||||||
|
function VerbTable({ block, textOptions, english }: {
|
||||||
|
block: T.VerbBlock | T.ImperativeBlock | T.ArrayOneOrMore<T.PsString>,
|
||||||
|
english?: T.EnglishBlock | string,
|
||||||
|
textOptions: T.TextOptions,
|
||||||
|
}) {
|
||||||
|
const blockWEng = english ? addEnglish(english, block) : block;
|
||||||
|
if (isSentenceForm(blockWEng) || isAllOne(blockWEng as T.VerbBlock | T.ImperativeBlock)) {
|
||||||
|
const item = isSentenceForm(blockWEng)
|
||||||
|
? block as unknown as T.ArrayOneOrMore<T.PsString>
|
||||||
|
: (() => {
|
||||||
|
const b = block as T.ImperativeBlock | T.VerbBlock
|
||||||
|
return b[0][0];
|
||||||
|
})();
|
||||||
|
return <table className="table text-center">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<TableCell item={item} textOptions={textOptions} center noBorder />
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
}
|
||||||
|
const bl = blockWEng as T.VerbBlock | T.ImperativeBlock;
|
||||||
|
const b = minifyTableGender(bl);
|
||||||
|
return <table className="table mt-2" style={{ tableLayout: "fixed" }}>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th scope="col" style={{ width: "3rem" }}>Pers.</th>
|
||||||
|
<th scope="col">Singular</th>
|
||||||
|
<th scope="col">Plural</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{b.reduce((rows: React.ReactNode[], person, i, arr) => {
|
||||||
|
function drawRow({ line, gender }: { line: T.PersonLine, gender?: "masc" | "fem" }) {
|
||||||
|
const pers = arr.length > 1 ? ["1st", "2nd", "3rd"] : ["2nd"];
|
||||||
|
const rowLabel = `${pers[i]}${genderAbbrev(gender)}`;
|
||||||
|
const color = !gender
|
||||||
|
? "inherit"
|
||||||
|
: genderColors[gender];
|
||||||
|
return (
|
||||||
|
<tr key={`${i}${gender}`}>
|
||||||
|
<th scope="row" style={{ color }}>{rowLabel}</th>
|
||||||
|
<TableCell item={line[0]} textOptions={textOptions} />
|
||||||
|
<TableCell item={line[1]} textOptions={textOptions} />
|
||||||
|
</tr>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return "masc" in person
|
||||||
|
? [
|
||||||
|
...rows,
|
||||||
|
drawRow({ line: person.masc, gender: "masc" }),
|
||||||
|
drawRow({ line: person.fem, gender: "fem" }),
|
||||||
|
]
|
||||||
|
: [...rows, drawRow({ line: person })];
|
||||||
|
}, [])}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
}
|
||||||
|
|
||||||
|
export default VerbTable;
|
|
@ -1,6 +1,6 @@
|
||||||
import { useState, useEffect } from "react";
|
import { useState, useEffect } from "react";
|
||||||
import * as T from "../../../types";
|
import * as T from "../../../types";
|
||||||
import SandwichPicker from "../block-pickers/SandwichPicker";
|
import SandwichPicker from "../np-picker/SandwichPicker";
|
||||||
import AdverbPicker from "./AdverbPicker";
|
import AdverbPicker from "./AdverbPicker";
|
||||||
type APType = "adverb" | "sandwich";
|
type APType = "adverb" | "sandwich";
|
||||||
const types: APType[] = ["adverb", "sandwich"];
|
const types: APType[] = ["adverb", "sandwich"];
|
|
@ -0,0 +1,31 @@
|
||||||
|
import { makeAdverbSelection } from "../../../lib/src/phrase-building/make-selections";
|
||||||
|
import * as T from "../../../types";
|
||||||
|
import EntrySelect from "../EntrySelect";
|
||||||
|
|
||||||
|
function AdverbPicker(props: {
|
||||||
|
entryFeeder: T.EntryFeederSingleType<T.AdverbEntry>,
|
||||||
|
adjective: T.AdverbSelection | undefined,
|
||||||
|
onChange: (p: T.AdverbSelection | undefined) => void,
|
||||||
|
opts: T.TextOptions,
|
||||||
|
}) {
|
||||||
|
function onEntrySelect(entry: T.AdverbEntry | undefined) {
|
||||||
|
if (!entry) {
|
||||||
|
return props.onChange(undefined);
|
||||||
|
}
|
||||||
|
props.onChange(makeAdverbSelection(entry));
|
||||||
|
}
|
||||||
|
return <div style={{ maxWidth: "225px", minWidth: "125px" }}>
|
||||||
|
<div className="h6">Adverb</div>
|
||||||
|
<div>
|
||||||
|
<EntrySelect
|
||||||
|
value={props.adjective?.entry}
|
||||||
|
entryFeeder={props.entryFeeder}
|
||||||
|
onChange={onEntrySelect}
|
||||||
|
name="Adverb"
|
||||||
|
opts={props.opts}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AdverbPicker;
|
|
@ -1,33 +0,0 @@
|
||||||
import { makeAdverbSelection } from "../../../lib/src/phrase-building/make-selections";
|
|
||||||
import * as T from "../../../types";
|
|
||||||
import EntrySelect from "../selects/EntrySelect";
|
|
||||||
|
|
||||||
function AdverbPicker(props: {
|
|
||||||
entryFeeder: T.EntryFeederSingleType<T.AdverbEntry>;
|
|
||||||
adjective: T.AdverbSelection | undefined;
|
|
||||||
onChange: (p: T.AdverbSelection | undefined) => void;
|
|
||||||
opts: T.TextOptions;
|
|
||||||
}) {
|
|
||||||
function onEntrySelect(entry: T.AdverbEntry | undefined) {
|
|
||||||
if (!entry) {
|
|
||||||
return props.onChange(undefined);
|
|
||||||
}
|
|
||||||
props.onChange(makeAdverbSelection(entry));
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<div style={{ maxWidth: "225px", minWidth: "125px" }}>
|
|
||||||
<div className="h6">Adverb</div>
|
|
||||||
<div>
|
|
||||||
<EntrySelect
|
|
||||||
value={props.adjective?.entry}
|
|
||||||
entryFeeder={props.entryFeeder}
|
|
||||||
onChange={onEntrySelect}
|
|
||||||
name="Adverb"
|
|
||||||
opts={props.opts}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default AdverbPicker;
|
|
|
@ -1,33 +0,0 @@
|
||||||
import { makeLocativeAdverbSelection } from "../../../lib/src/phrase-building/make-selections";
|
|
||||||
import * as T from "../../../types";
|
|
||||||
import EntrySelect from "../selects/EntrySelect";
|
|
||||||
|
|
||||||
function LocativeAdverbPicker(props: {
|
|
||||||
entryFeeder: T.EntryFeederSingleType<T.LocativeAdverbEntry>;
|
|
||||||
adjective: T.LocativeAdverbSelection | undefined;
|
|
||||||
onChange: (p: T.LocativeAdverbSelection | undefined) => void;
|
|
||||||
opts: T.TextOptions;
|
|
||||||
}) {
|
|
||||||
function onEntrySelect(entry: T.LocativeAdverbEntry | undefined) {
|
|
||||||
if (!entry) {
|
|
||||||
return props.onChange(undefined);
|
|
||||||
}
|
|
||||||
props.onChange(makeLocativeAdverbSelection(entry));
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<div style={{ maxWidth: "225px", minWidth: "125px" }}>
|
|
||||||
<div className="h6">Locative Adverb</div>
|
|
||||||
<div>
|
|
||||||
<EntrySelect
|
|
||||||
value={props.adjective?.entry}
|
|
||||||
entryFeeder={props.entryFeeder}
|
|
||||||
onChange={onEntrySelect}
|
|
||||||
name="Locative Adverb"
|
|
||||||
opts={props.opts}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default LocativeAdverbPicker;
|
|
|
@ -1,38 +0,0 @@
|
||||||
import EntrySelect from "../selects/EntrySelect";
|
|
||||||
import * as T from "../../../types";
|
|
||||||
import { makeParticipleSelection } from "../../../lib/src/phrase-building/make-selections";
|
|
||||||
|
|
||||||
function NPParticiplePicker(props: {
|
|
||||||
entryFeeder: T.EntryFeederSingleType<T.VerbEntry>;
|
|
||||||
participle: T.ParticipleSelection | undefined;
|
|
||||||
onChange: (p: T.ParticipleSelection | undefined) => void;
|
|
||||||
opts: T.TextOptions;
|
|
||||||
}) {
|
|
||||||
function onEntrySelect(entry: T.VerbEntry | undefined) {
|
|
||||||
if (!entry) {
|
|
||||||
props.onChange(undefined);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
props.onChange(makeParticipleSelection(entry));
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<div style={{ maxWidth: "225px" }}>
|
|
||||||
<div className="h6">Participle</div>
|
|
||||||
<EntrySelect
|
|
||||||
value={props.participle?.verb}
|
|
||||||
entryFeeder={props.entryFeeder}
|
|
||||||
onChange={onEntrySelect}
|
|
||||||
name="Pariticple"
|
|
||||||
opts={props.opts}
|
|
||||||
/>
|
|
||||||
{props.participle && (
|
|
||||||
<div className="my-2 d-flex flex-row justify-content-around align-items-center">
|
|
||||||
<div>Masc.</div>
|
|
||||||
<div>Plur.</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default NPParticiplePicker;
|
|
|
@ -1,228 +0,0 @@
|
||||||
import * as T from "../../../types";
|
|
||||||
import ButtonSelect from "../selects/ButtonSelect";
|
|
||||||
import useStickyState from "../useStickyState";
|
|
||||||
import classNames from "classnames";
|
|
||||||
import { isSecondPerson } from "../../../lib/src/misc-helpers";
|
|
||||||
|
|
||||||
const gColors = {
|
|
||||||
masc: "LightSkyBlue",
|
|
||||||
fem: "pink",
|
|
||||||
};
|
|
||||||
|
|
||||||
// TODO: better logic on this
|
|
||||||
const labels = (role: "subject" | "object" | "ergative" | "possesor") => ({
|
|
||||||
// persons: [
|
|
||||||
// ["1st", "1st pl."],
|
|
||||||
// ["2nd", "2nd pl."],
|
|
||||||
// ["3rd", "3rd pl."],
|
|
||||||
// ],
|
|
||||||
e:
|
|
||||||
role === "object"
|
|
||||||
? [
|
|
||||||
["me", "us"],
|
|
||||||
["you", "you (pl.)"],
|
|
||||||
[{ masc: "him/it", fem: "her/it" }, "them"],
|
|
||||||
]
|
|
||||||
: role === "possesor"
|
|
||||||
? [
|
|
||||||
["my", "our"],
|
|
||||||
["your", "your (pl.)"],
|
|
||||||
[{ masc: "his/its", fem: "her/its" }, "their"],
|
|
||||||
]
|
|
||||||
: [
|
|
||||||
["I", "We"],
|
|
||||||
["You", "You (pl.)"],
|
|
||||||
[{ masc: "He/It", fem: "She/It" }, "They"],
|
|
||||||
],
|
|
||||||
p:
|
|
||||||
role === "subject"
|
|
||||||
? {
|
|
||||||
far: [
|
|
||||||
["زه", "مونږ"],
|
|
||||||
["ته", "تاسو"],
|
|
||||||
["هغه", "هغوی"],
|
|
||||||
],
|
|
||||||
near: [
|
|
||||||
["زه", "مونږ"],
|
|
||||||
["ته", "تاسو"],
|
|
||||||
[{ masc: "دی", fem: "دا" }, "دوی"],
|
|
||||||
],
|
|
||||||
}
|
|
||||||
: role === "object"
|
|
||||||
? {
|
|
||||||
far: [
|
|
||||||
["زه", "مونږ"],
|
|
||||||
["ته", "تاسو"],
|
|
||||||
[{ masc: "هغهٔ", fem: "هغې" }, "هغوی"],
|
|
||||||
],
|
|
||||||
near: [
|
|
||||||
["زه", "مونږ"],
|
|
||||||
["ته", "تاسو"],
|
|
||||||
[{ masc: "دهٔ", fem: "دې" }, "دوی"],
|
|
||||||
],
|
|
||||||
}
|
|
||||||
: role === "possesor"
|
|
||||||
? {
|
|
||||||
far: [
|
|
||||||
["زما", "زمونږ"],
|
|
||||||
["ستا", "ستاسو"],
|
|
||||||
[{ masc: "د هغهٔ", fem: "د هغې" }, "د هغوی"],
|
|
||||||
],
|
|
||||||
near: [
|
|
||||||
["زما", "زمونږ"],
|
|
||||||
["ستا", "ستاسو"],
|
|
||||||
[{ masc: "د دهٔ", fem: "د دې" }, "د دوی"],
|
|
||||||
],
|
|
||||||
}
|
|
||||||
: {
|
|
||||||
far: [
|
|
||||||
["ما", "مونږ"],
|
|
||||||
["تا", "تاسو"],
|
|
||||||
[{ masc: "هغهٔ", fem: "هغې" }, "هغوی"],
|
|
||||||
],
|
|
||||||
near: [
|
|
||||||
["ما", "مونږ"],
|
|
||||||
["تا", "تاسو"],
|
|
||||||
[{ masc: "دهٔ", fem: "دې" }, "دوی"],
|
|
||||||
],
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
type PickerState = { row: number; col: number; gender: T.Gender };
|
|
||||||
|
|
||||||
function personToPickerState(person: T.Person): PickerState {
|
|
||||||
const col = person > 5 ? 1 : 0;
|
|
||||||
const row = Math.floor((person > 5 ? person - 6 : person) / 2);
|
|
||||||
const gender: T.Gender = person % 2 ? "fem" : "masc";
|
|
||||||
return { col, row, gender };
|
|
||||||
}
|
|
||||||
|
|
||||||
function pickerStateToPerson(s: PickerState): T.Person {
|
|
||||||
return s.row * 2 + (s.gender === "masc" ? 0 : 1) + 6 * s.col;
|
|
||||||
}
|
|
||||||
|
|
||||||
function NPPronounPicker({
|
|
||||||
onChange,
|
|
||||||
pronoun,
|
|
||||||
role,
|
|
||||||
is2ndPersonPicker,
|
|
||||||
}: {
|
|
||||||
pronoun: T.PronounSelection;
|
|
||||||
onChange: (p: T.PronounSelection) => void;
|
|
||||||
role: "object" | "subject" | "ergative" | "possesor";
|
|
||||||
// opts: T.TextOptions,
|
|
||||||
is2ndPersonPicker?: boolean;
|
|
||||||
}) {
|
|
||||||
if (is2ndPersonPicker && !isSecondPerson(pronoun.person)) {
|
|
||||||
throw new Error("can't use 2ndPerson NPProunounPicker without a pronoun");
|
|
||||||
}
|
|
||||||
const [display, setDisplay] = useStickyState<"p" | "e">(
|
|
||||||
"e",
|
|
||||||
"prounoun-picker-display2"
|
|
||||||
);
|
|
||||||
const p = personToPickerState(pronoun.person);
|
|
||||||
function handleClick(row: number, col: number) {
|
|
||||||
const person = pickerStateToPerson({ ...p, row, col });
|
|
||||||
onChange({
|
|
||||||
...pronoun,
|
|
||||||
person,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
function handleGenderChange(gender: T.Gender) {
|
|
||||||
const person = pickerStateToPerson({ ...p, gender });
|
|
||||||
onChange({
|
|
||||||
...pronoun,
|
|
||||||
person,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
function handlePronounTypeChange(distance: "far" | "near") {
|
|
||||||
onChange({
|
|
||||||
...pronoun,
|
|
||||||
distance,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
function handleDisplayChange() {
|
|
||||||
const newPerson = display === "p" ? "e" : "p";
|
|
||||||
setDisplay(newPerson);
|
|
||||||
}
|
|
||||||
const prs = labels(role)[display];
|
|
||||||
const pSpecA = "near" in prs ? prs[pronoun.distance] : prs;
|
|
||||||
const pSpec = is2ndPersonPicker ? [pSpecA[1]] : pSpecA;
|
|
||||||
return (
|
|
||||||
<div style={{ maxWidth: "145px", padding: 0, margin: "0 auto" }}>
|
|
||||||
<div className="d-flex flex-row justify-content-between mb-2">
|
|
||||||
<ButtonSelect
|
|
||||||
xSmall
|
|
||||||
options={[
|
|
||||||
{ label: "Far", value: "far" },
|
|
||||||
{ label: "Near", value: "near" },
|
|
||||||
]}
|
|
||||||
value={pronoun.distance}
|
|
||||||
handleChange={(g) => handlePronounTypeChange(g as "far" | "near")}
|
|
||||||
/>
|
|
||||||
<button
|
|
||||||
className="btn btn-sm btn-outline-secondary"
|
|
||||||
onClick={handleDisplayChange}
|
|
||||||
>
|
|
||||||
{display === "p" ? "PS" : "EN"}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<table
|
|
||||||
className="table table-bordered table-sm"
|
|
||||||
style={{ textAlign: "center", minWidth: "100px", tableLayout: "fixed" }}
|
|
||||||
>
|
|
||||||
<tbody>
|
|
||||||
{pSpec.map((rw, i) => (
|
|
||||||
<tr key={`pronounPickerRow${i}`}>
|
|
||||||
{rw.map((r, j) => {
|
|
||||||
const active = is2ndPersonPicker
|
|
||||||
? p.col === j
|
|
||||||
: p.row === i && p.col === j;
|
|
||||||
const content = typeof r === "string" ? r : r[p.gender];
|
|
||||||
return (
|
|
||||||
<td
|
|
||||||
key={`pronounPickerCell${i}${j}`}
|
|
||||||
onClick={() => {
|
|
||||||
handleClick(is2ndPersonPicker ? 1 : i, j);
|
|
||||||
}}
|
|
||||||
className={classNames({
|
|
||||||
"table-active": active,
|
|
||||||
"text-on-gender-color": active,
|
|
||||||
})}
|
|
||||||
style={{
|
|
||||||
backgroundColor: active ? gColors[p.gender] : "inherit",
|
|
||||||
padding: "0.15rem 0",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div className="my-1">{content}</div>
|
|
||||||
</td>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</tr>
|
|
||||||
))}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
<div className="text-center">
|
|
||||||
<ButtonSelect
|
|
||||||
small
|
|
||||||
options={[
|
|
||||||
{
|
|
||||||
label: <div style={{ margin: "0.15rem" }}>Masc.</div>,
|
|
||||||
value: "masc",
|
|
||||||
color: gColors.masc,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: <div style={{ margin: "0.15rem" }}>Fem.</div>,
|
|
||||||
value: "fem",
|
|
||||||
color: gColors.fem,
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
value={p.gender}
|
|
||||||
handleChange={(g) => handleGenderChange(g as T.Gender)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default NPPronounPicker;
|
|
|
@ -8,7 +8,7 @@ import {
|
||||||
} from "../../../lib/src/misc-helpers";
|
} from "../../../lib/src/misc-helpers";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { getLength } from "../../../lib/src/p-text-helpers";
|
import { getLength } from "../../../lib/src/p-text-helpers";
|
||||||
import { roleIcon } from "../role-icons";
|
import { roleIcon } from "../vp-explorer/VPExplorerExplanationModal";
|
||||||
import { negativeParticle } from "../../../lib/src/grammar-units";
|
import { negativeParticle } from "../../../lib/src/grammar-units";
|
||||||
import { flattenLengths } from "../../library";
|
import { flattenLengths } from "../../library";
|
||||||
|
|
||||||
|
@ -139,7 +139,7 @@ function Border({
|
||||||
}
|
}
|
||||||
|
|
||||||
function VBBlock({
|
function VBBlock({
|
||||||
// opts,
|
opts,
|
||||||
block,
|
block,
|
||||||
script,
|
script,
|
||||||
}: {
|
}: {
|
||||||
|
@ -286,7 +286,7 @@ function WeldedBlock({
|
||||||
// }
|
// }
|
||||||
|
|
||||||
function PerfHeadBlock({
|
function PerfHeadBlock({
|
||||||
// opts,
|
opts,
|
||||||
ps,
|
ps,
|
||||||
script,
|
script,
|
||||||
}: {
|
}: {
|
||||||
|
@ -319,7 +319,7 @@ function PerfHeadBlock({
|
||||||
// }
|
// }
|
||||||
|
|
||||||
function NegBlock({
|
function NegBlock({
|
||||||
// opts,
|
opts,
|
||||||
imperative,
|
imperative,
|
||||||
script,
|
script,
|
||||||
}: {
|
}: {
|
||||||
|
@ -339,7 +339,7 @@ function NegBlock({
|
||||||
}
|
}
|
||||||
|
|
||||||
function EquativeBlock({
|
function EquativeBlock({
|
||||||
// opts,
|
opts,
|
||||||
eq,
|
eq,
|
||||||
script,
|
script,
|
||||||
}: {
|
}: {
|
||||||
|
@ -425,7 +425,7 @@ function ObjectBlock({
|
||||||
}
|
}
|
||||||
|
|
||||||
function NCompBlock({
|
function NCompBlock({
|
||||||
// opts,
|
opts,
|
||||||
comp,
|
comp,
|
||||||
script,
|
script,
|
||||||
}: {
|
}: {
|
||||||
|
@ -465,7 +465,7 @@ function ComplementBlock({
|
||||||
inside?: boolean;
|
inside?: boolean;
|
||||||
}) {
|
}) {
|
||||||
function AdjectiveBlock({
|
function AdjectiveBlock({
|
||||||
// opts,
|
opts,
|
||||||
adj,
|
adj,
|
||||||
}: {
|
}: {
|
||||||
opts: T.TextOptions;
|
opts: T.TextOptions;
|
||||||
|
@ -486,7 +486,7 @@ function ComplementBlock({
|
||||||
}
|
}
|
||||||
|
|
||||||
function LocAdvBlock({
|
function LocAdvBlock({
|
||||||
// opts,
|
opts,
|
||||||
adv,
|
adv,
|
||||||
}: {
|
}: {
|
||||||
opts: T.TextOptions;
|
opts: T.TextOptions;
|
||||||
|
@ -601,7 +601,7 @@ function Sandwich({
|
||||||
}
|
}
|
||||||
|
|
||||||
function Determiners({
|
function Determiners({
|
||||||
// opts,
|
opts,
|
||||||
script,
|
script,
|
||||||
children,
|
children,
|
||||||
}: {
|
}: {
|
||||||
|
@ -628,7 +628,7 @@ function Determiners({
|
||||||
}
|
}
|
||||||
|
|
||||||
function CompNounBlock({
|
function CompNounBlock({
|
||||||
// opts,
|
opts,
|
||||||
noun,
|
noun,
|
||||||
script,
|
script,
|
||||||
}: {
|
}: {
|
||||||
|
@ -703,7 +703,7 @@ export function NPBlock({
|
||||||
{flattenLengths(np.selection.ps)[0][script]}
|
{flattenLengths(np.selection.ps)[0][script]}
|
||||||
</div>,
|
</div>,
|
||||||
].filter((x) => {
|
].filter((x) => {
|
||||||
// @ts-expect-error yes I know
|
// @ts-ignore
|
||||||
return x !== " ";
|
return x !== " ";
|
||||||
});
|
});
|
||||||
const el = script === "p" ? elements.reverse() : elements;
|
const el = script === "p" ? elements.reverse() : elements;
|
||||||
|
@ -793,7 +793,7 @@ function Possesors({
|
||||||
}
|
}
|
||||||
|
|
||||||
function Adjectives({
|
function Adjectives({
|
||||||
// opts,
|
opts,
|
||||||
children,
|
children,
|
||||||
script,
|
script,
|
||||||
}: {
|
}: {
|
||||||
|
|
|
@ -1,27 +1,21 @@
|
||||||
import { baParticle } from "../../../lib/src/grammar-units";
|
import { baParticle } from "../../../lib/src/grammar-units";
|
||||||
import * as T from "../../../types";
|
import * as T from "../../../types";
|
||||||
import Pashto from "../text-display/Pashto";
|
import Pashto from "../Pashto";
|
||||||
import Phonetics from "../text-display/Phonetics";
|
import Phonetics from "../Phonetics";
|
||||||
|
|
||||||
function KidDisplay({
|
function KidDisplay({ opts, kid, script }: {
|
||||||
opts,
|
opts: T.TextOptions,
|
||||||
kid,
|
kid: T.Kid,
|
||||||
script,
|
script: "p" | "f",
|
||||||
}: {
|
|
||||||
opts: T.TextOptions;
|
|
||||||
kid: T.Kid;
|
|
||||||
script: "p" | "f";
|
|
||||||
}) {
|
}) {
|
||||||
const ps = kid.kid.type === "ba" ? baParticle : kid.kid.ps;
|
const ps = kid.kid.type === "ba"
|
||||||
return (
|
? baParticle
|
||||||
<div className="mx-1" key={kid.key}>
|
: kid.kid.ps;
|
||||||
{script === "p" ? (
|
return <div className="mx-1" key={kid.key}>
|
||||||
<Pashto opts={opts} ps={ps} />
|
{script === "p"
|
||||||
) : (
|
? <Pashto opts={opts}>{ps}</Pashto>
|
||||||
<Phonetics opts={opts} ps={ps} />
|
: <Phonetics opts={opts}>{ps}</Phonetics>}
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default KidDisplay;
|
export default KidDisplay;
|
|
@ -1,113 +0,0 @@
|
||||||
import { useState } from "react";
|
|
||||||
import {
|
|
||||||
filterForVisibleBlocksEP,
|
|
||||||
filterForVisibleBlocksVP,
|
|
||||||
} from "../../../lib/src/phrase-building/compile";
|
|
||||||
import * as T from "../../../types";
|
|
||||||
import Block from "./Block";
|
|
||||||
import KidDisplay from "./KidDisplay";
|
|
||||||
|
|
||||||
function RenderedBlocksDisplay({
|
|
||||||
opts,
|
|
||||||
rendered,
|
|
||||||
justify,
|
|
||||||
script,
|
|
||||||
}: {
|
|
||||||
script: "p" | "f";
|
|
||||||
opts: T.TextOptions;
|
|
||||||
rendered: T.EPRendered | T.VPRendered;
|
|
||||||
justify?: "left" | "right" | "center";
|
|
||||||
}) {
|
|
||||||
const [variation, setVariation] = useState<number>(0);
|
|
||||||
// not using autoAnimate here because we need a way to persist the keys in the blocks first
|
|
||||||
// const parent = useRef(null);
|
|
||||||
// useEffect(() => {
|
|
||||||
// parent.current && autoAnimate(parent.current)
|
|
||||||
// }, [parent]);
|
|
||||||
const blocksWVars: T.Block[][] = Array.isArray(rendered)
|
|
||||||
? rendered
|
|
||||||
: "omitSubject" in rendered
|
|
||||||
? filterForVisibleBlocksEP(rendered.blocks, rendered.omitSubject)
|
|
||||||
: filterForVisibleBlocksVP(rendered.blocks, rendered.form, rendered.king);
|
|
||||||
const king = "king" in rendered ? rendered.king : undefined;
|
|
||||||
const blocks = blocksWVars[variation];
|
|
||||||
function handleVariationChange() {
|
|
||||||
setVariation((ov) => (ov + 1) % blocksWVars.length);
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className={`d-flex flex-row justify-content-${
|
|
||||||
justify ? justify : "center"
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className={`d-flex flex-row${
|
|
||||||
script === "p" ? "-reverse" : ""
|
|
||||||
} justify-content-left align-items-end mt-3 pb-2`}
|
|
||||||
style={{ overflowX: "auto" }}
|
|
||||||
>
|
|
||||||
<div key={blocks[0].key} className="mr-2">
|
|
||||||
<Block opts={opts} block={blocks[0]} king={king} script={script} />
|
|
||||||
</div>
|
|
||||||
<KidsSection
|
|
||||||
key="kidsSection"
|
|
||||||
opts={opts}
|
|
||||||
kids={rendered.kids}
|
|
||||||
script={script}
|
|
||||||
/>
|
|
||||||
{blocks.slice(1).map((block) => (
|
|
||||||
<div key={block.key} className="mr-2">
|
|
||||||
<Block opts={opts} block={block} king={king} script={script} />
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
<div
|
|
||||||
style={{ height: "100%" }}
|
|
||||||
className="d-flex flex-column justify-content-center"
|
|
||||||
>
|
|
||||||
{blocksWVars.length > 1 && (
|
|
||||||
<button
|
|
||||||
onClick={handleVariationChange}
|
|
||||||
className="btn btn-light btn-sm mx-2"
|
|
||||||
>
|
|
||||||
V. {variation + 1}/{blocksWVars.length}
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function KidsSection({
|
|
||||||
opts,
|
|
||||||
kids,
|
|
||||||
script,
|
|
||||||
}: {
|
|
||||||
opts: T.TextOptions;
|
|
||||||
kids: T.Kid[];
|
|
||||||
script: "p" | "f";
|
|
||||||
}) {
|
|
||||||
// not using autoAnimate here because we need a way to persist the keys in the blocks first
|
|
||||||
// const parent = useRef(null);
|
|
||||||
// useEffect(() => {
|
|
||||||
// parent.current && autoAnimate(parent.current)
|
|
||||||
// }, [parent]);
|
|
||||||
return kids.length > 0 ? (
|
|
||||||
<div className="text-center mx-1 mr-3" style={{ paddingBottom: "1rem" }}>
|
|
||||||
<div
|
|
||||||
className={`d-flex flex-row${
|
|
||||||
script === "p" ? "-reverse" : ""
|
|
||||||
} mb-3 justify-content-center`}
|
|
||||||
>
|
|
||||||
{kids.map((kid) => (
|
|
||||||
<KidDisplay key={kid.key} opts={opts} kid={kid} script={script} />
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<strong>kids</strong>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
) : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default RenderedBlocksDisplay;
|
|
|
@ -1,151 +1,78 @@
|
||||||
import * as T from "../../../types";
|
import * as T from "../../../types";
|
||||||
import {
|
import { completeEPSelection, renderEP } from "../../../lib/src/phrase-building/render-ep";
|
||||||
completeEPSelection,
|
|
||||||
renderEP,
|
|
||||||
} from "../../../lib/src/phrase-building/render-ep";
|
|
||||||
import { compileEP } from "../../../lib/src/phrase-building/compile";
|
import { compileEP } from "../../../lib/src/phrase-building/compile";
|
||||||
import ButtonSelect from "../selects/ButtonSelect";
|
import ButtonSelect from "../ButtonSelect";
|
||||||
import {
|
import { getPredicateSelectionFromBlocks, getSubjectSelection, getSubjectSelectionFromBlocks } from "../../../lib/src/phrase-building/blocks-utils";
|
||||||
getPredicateSelectionFromBlocks,
|
|
||||||
getSubjectSelection,
|
|
||||||
getSubjectSelectionFromBlocks,
|
|
||||||
} from "../../../lib/src/phrase-building/blocks-utils";
|
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import CompiledPTextDisplay from "../text-display/CompiledPTextDisplay";
|
import CompiledPTextDisplay from "../CompiledPTextDisplay";
|
||||||
import EPBlocksDisplay from "../blocks/RenderedBlocksDisplay";
|
import EPBlocksDisplay from "../RenderedBlocksDisplay";
|
||||||
import ModeSelect, { Mode, ScriptSelect } from "../selects/DisplayModeSelect";
|
import ModeSelect, { Mode, ScriptSelect } from "../DisplayModeSelect";
|
||||||
import useStickyState from "../useStickyState";
|
import useStickyState from "../useStickyState";
|
||||||
|
|
||||||
function EPDisplay({
|
function EPDisplay({ eps, opts, setOmitSubject, justify, onlyOne, length, mode: preferredMode, script: preferredScript }: {
|
||||||
eps,
|
eps: T.EPSelectionState,
|
||||||
opts,
|
opts: T.TextOptions,
|
||||||
setOmitSubject,
|
setOmitSubject: ((value: "true" | "false") => void) | false
|
||||||
justify,
|
justify?: "left" | "right" | "center",
|
||||||
onlyOne,
|
onlyOne?: boolean | "concat",
|
||||||
length,
|
length?: "long" | "short",
|
||||||
mode: preferredMode,
|
mode?: Mode,
|
||||||
script: preferredScript,
|
script?: "p" | "f",
|
||||||
}: {
|
|
||||||
eps: T.EPSelectionState;
|
|
||||||
opts: T.TextOptions;
|
|
||||||
setOmitSubject: ((value: "true" | "false") => void) | false;
|
|
||||||
justify?: "left" | "right" | "center";
|
|
||||||
onlyOne?: boolean | "concat";
|
|
||||||
length?: "long" | "short";
|
|
||||||
mode?: Mode;
|
|
||||||
script?: "p" | "f";
|
|
||||||
}) {
|
}) {
|
||||||
const [mode, setMode] = useState<Mode>(preferredMode || "text");
|
const [mode, setMode] = useState<Mode>(preferredMode || "text");
|
||||||
const [script, setScript] = useStickyState<"p" | "f">(
|
const [script, setScript] = useStickyState<"p" | "f">(preferredScript || "f", "blockScriptChoice");
|
||||||
preferredScript || "f",
|
const EP = completeEPSelection(eps);
|
||||||
"blockScriptChoice"
|
const subject = getSubjectSelection(eps.blocks);
|
||||||
);
|
|
||||||
const EP = completeEPSelection(eps);
|
|
||||||
const subject = getSubjectSelection(eps.blocks);
|
|
||||||
|
|
||||||
if (!EP) {
|
if (!EP) {
|
||||||
return (
|
return <div className="lead text-center my-4">
|
||||||
<div className="lead text-center my-4">
|
{(!subject && !eps.predicate[eps.predicate.type])
|
||||||
{!subject && !eps.predicate[eps.predicate.type]
|
? "Select Subject and Predicate"
|
||||||
? "Select Subject and Predicate"
|
: (subject && !eps.predicate[eps.predicate.type])
|
||||||
: subject && !eps.predicate[eps.predicate.type]
|
? "Select Predicate"
|
||||||
? "Select Predicate"
|
: (!subject && eps.predicate[eps.predicate.type])
|
||||||
: !subject && eps.predicate[eps.predicate.type]
|
? "Select Subject"
|
||||||
? "Select Subject"
|
: ""}
|
||||||
: ""}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
const rendered = renderEP(EP);
|
|
||||||
const result = compileEP(rendered);
|
|
||||||
const renderedSubject = getSubjectSelectionFromBlocks(
|
|
||||||
rendered.blocks
|
|
||||||
).selection;
|
|
||||||
const renderedPredicate = getPredicateSelectionFromBlocks(
|
|
||||||
rendered.blocks
|
|
||||||
).selection;
|
|
||||||
return (
|
|
||||||
<div className="text-center pt-3">
|
|
||||||
<div className="mb-2 d-flex flex-row justify-content-between align-items-center">
|
|
||||||
<div className="d-flex flex-row">
|
|
||||||
<ModeSelect value={mode} onChange={setMode} />
|
|
||||||
{mode === "blocks" && (
|
|
||||||
<ScriptSelect value={script} onChange={setScript} />
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
{setOmitSubject !== false ? (
|
}
|
||||||
<ButtonSelect
|
const rendered = renderEP(EP);
|
||||||
small
|
const result = compileEP(rendered);
|
||||||
value={(eps.omitSubject ? "true" : "false") as "true" | "false"}
|
const renderedSubject = getSubjectSelectionFromBlocks(rendered.blocks).selection;
|
||||||
options={[
|
const renderedPredicate = getPredicateSelectionFromBlocks(rendered.blocks).selection;
|
||||||
{ value: "false", label: "Full" },
|
return <div className="text-center pt-3">
|
||||||
{ value: "true", label: "No Subj." },
|
<div className="mb-2 d-flex flex-row justify-content-between align-items-center">
|
||||||
]}
|
<div className="d-flex flex-row">
|
||||||
handleChange={setOmitSubject}
|
<ModeSelect value={mode} onChange={setMode} />
|
||||||
/>
|
{mode === "blocks" && <ScriptSelect value={script} onChange={setScript} />}
|
||||||
) : (
|
</div>
|
||||||
<div />
|
{setOmitSubject !== false ? <ButtonSelect
|
||||||
)}
|
small
|
||||||
<div />
|
value={(eps.omitSubject ? "true" : "false") as "true" | "false"}
|
||||||
</div>
|
options={[
|
||||||
{mode === "text" ? (
|
{ value: "false", label: "Full"},
|
||||||
<CompiledPTextDisplay
|
{ value: "true", label: "No Subj."},
|
||||||
opts={opts}
|
]}
|
||||||
compiled={result}
|
handleChange={setOmitSubject}
|
||||||
justify={justify}
|
/> : <div />}
|
||||||
onlyOne={!!onlyOne}
|
<div />
|
||||||
length={length || "short"}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<EPBlocksDisplay
|
|
||||||
opts={opts}
|
|
||||||
rendered={rendered}
|
|
||||||
justify={justify}
|
|
||||||
script={script}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{result.e && (
|
|
||||||
<div
|
|
||||||
className={`text-muted mt-2 text-${
|
|
||||||
justify === "left"
|
|
||||||
? "left"
|
|
||||||
: justify === "right"
|
|
||||||
? "right"
|
|
||||||
: "center"
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
{onlyOne === "concat"
|
|
||||||
? result.e.join(" • ")
|
|
||||||
: onlyOne
|
|
||||||
? [result.e[0]]
|
|
||||||
: result.e.map((e, i) => <div key={i}>{e}</div>)}
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
{mode === "text"
|
||||||
{EP.predicate.selection.selection.type === "participle" && (
|
? <CompiledPTextDisplay opts={opts} compiled={result} justify={justify} onlyOne={!!onlyOne} length={length || "short"} />
|
||||||
<div
|
: <EPBlocksDisplay opts={opts} rendered={rendered} justify={justify} script={script} />}
|
||||||
style={{ maxWidth: "6 00px", margin: "0 auto" }}
|
{result.e && <div className={`text-muted mt-2 text-${justify === "left" ? "left" : justify === "right" ? "right" : "center"}`}>
|
||||||
className="alert alert-warning mt-3 pt-4"
|
{onlyOne === "concat"
|
||||||
>
|
? result.e.join(" • ")
|
||||||
<p>
|
: onlyOne
|
||||||
⚠️ NOTE: This means that the subject{" "}
|
? [result.e[0]]
|
||||||
{renderedSubject.selection.e
|
: result.e.map((e, i) => <div key={i}>{e}</div>)}
|
||||||
? `(${renderedSubject.selection.e})`
|
</div>}
|
||||||
: ""}{" "}
|
{EP.predicate.selection.selection.type === "participle" && <div style={{ maxWidth: "6 00px", margin: "0 auto" }} className="alert alert-warning mt-3 pt-4">
|
||||||
is <strong>the action/idea</strong> of
|
<p>⚠️ NOTE: This means that the subject {renderedSubject.selection.e ? `(${renderedSubject.selection.e})` : ""} is <strong>the action/idea</strong> of
|
||||||
{` `}"
|
{` `}
|
||||||
{renderedPredicate.selection.e
|
"{renderedPredicate.selection.e ? renderedPredicate.selection.e : "the particple"}".</p>
|
||||||
? renderedPredicate.selection.e
|
<p>It <strong>does not</strong> mean that the subject is doing the action, which is what the transaltion sounds like in English.</p>
|
||||||
: "the particple"}
|
</div>}
|
||||||
".
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
It <strong>does not</strong> mean that the subject is doing the
|
|
||||||
action, which is what the transaltion sounds like in English.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default EPDisplay;
|
export default EPDisplay;
|
|
@ -1,154 +1,140 @@
|
||||||
import * as T from "../../../types";
|
import * as T from "../../../types";
|
||||||
import useStickyState, { useStickyReducer } from "../useStickyState";
|
import useStickyState, { useStickyReducer } from "../useStickyState";
|
||||||
import EPDisplay from "./EPDisplay";
|
import EPDisplay from "./EPDisplay";
|
||||||
import ButtonSelect from "../selects/ButtonSelect";
|
import ButtonSelect from "../ButtonSelect";
|
||||||
import EqChartsDisplay from "./EqChartsDisplay";
|
import EqChartsDisplay from "./EqChartsDisplay";
|
||||||
import epsReducer from "../../../lib/src/phrase-building/eps-reducer";
|
import epsReducer from "../../../lib/src/phrase-building/eps-reducer";
|
||||||
import { useEffect, useRef, useState } from "react";
|
import {
|
||||||
|
useEffect,
|
||||||
|
useRef,
|
||||||
|
useState,
|
||||||
|
} from "react";
|
||||||
import { completeEPSelection } from "../../../lib/src/phrase-building/render-ep";
|
import { completeEPSelection } from "../../../lib/src/phrase-building/render-ep";
|
||||||
import { makeEPSBlocks } from "../../../lib/src/phrase-building/blocks-utils";
|
import { makeEPSBlocks } from "../../../lib/src/phrase-building/blocks-utils";
|
||||||
// @ts-expect-error types not working for this
|
|
||||||
import autoAnimate from "@formkit/auto-animate";
|
import autoAnimate from "@formkit/auto-animate";
|
||||||
|
// @ts-ignore
|
||||||
import LZString from "lz-string";
|
import LZString from "lz-string";
|
||||||
import EPPicker from "./EPPicker";
|
import EPPicker from "./EPPicker";
|
||||||
const epPhraseURLParam = "ep";
|
const epPhraseURLParam = "ep";
|
||||||
|
|
||||||
const blankEps: T.EPSelectionState = {
|
const blankEps: T.EPSelectionState = {
|
||||||
blocks: makeEPSBlocks(),
|
blocks: makeEPSBlocks(),
|
||||||
predicate: {
|
predicate: {
|
||||||
type: "Complement",
|
type: "Complement",
|
||||||
NP: undefined,
|
NP: undefined,
|
||||||
Complement: undefined,
|
Complement: undefined,
|
||||||
},
|
},
|
||||||
equative: {
|
equative: {
|
||||||
tense: "present",
|
tense: "present",
|
||||||
negative: false,
|
negative: false,
|
||||||
},
|
},
|
||||||
omitSubject: false,
|
omitSubject: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO: put the clear button beside the title in the predicate picker?
|
// TODO: put the clear button beside the title in the predicate picker?
|
||||||
|
|
||||||
function EPExplorer(props: {
|
function EPExplorer(props: {
|
||||||
opts: T.TextOptions;
|
opts: T.TextOptions,
|
||||||
entryFeeder: T.EntryFeeder;
|
entryFeeder: T.EntryFeeder,
|
||||||
}) {
|
}) {
|
||||||
const [mode, setMode] = useStickyState<"charts" | "phrases">(
|
const [mode, setMode] = useStickyState<"charts" | "phrases">("charts", "EPExplorerMode");
|
||||||
"charts",
|
const [eps, adjustEps] = useStickyReducer(
|
||||||
"EPExplorerMode"
|
epsReducer,
|
||||||
);
|
blankEps,
|
||||||
const [eps, adjustEps] = useStickyReducer(
|
"EPState8",
|
||||||
epsReducer,
|
flashMessage,
|
||||||
blankEps,
|
);
|
||||||
"EPState8",
|
// const [
|
||||||
flashMessage
|
// // alertMsg,
|
||||||
);
|
// // setAlertMsg,
|
||||||
// const [
|
// ] = useState<string | undefined>(undefined);
|
||||||
// // alertMsg,
|
const [showClipped, setShowClipped] = useState<string>("");
|
||||||
// // setAlertMsg,
|
const parent = useRef<HTMLDivElement>(null);
|
||||||
// ] = useState<string | undefined>(undefined);
|
useEffect(() => {
|
||||||
const [showClipped, setShowClipped] = useState<string>("");
|
parent.current && autoAnimate(parent.current);
|
||||||
const parent = useRef<HTMLDivElement>(null);
|
}, [parent]);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (parent.current) {
|
const EPSFromUrl = getEPSFromUrl();
|
||||||
autoAnimate(parent.current);
|
if (EPSFromUrl) {
|
||||||
|
setMode("phrases");
|
||||||
|
adjustEps({
|
||||||
|
type: "load EPS",
|
||||||
|
payload: EPSFromUrl,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line
|
||||||
|
}, []);
|
||||||
|
function handleEpsChange(e: T.EPSelectionState) {
|
||||||
|
adjustEps({ type: "load EPS", payload: e });
|
||||||
}
|
}
|
||||||
}, [parent]);
|
function flashMessage(msg: string) {
|
||||||
useEffect(() => {
|
alert(msg);
|
||||||
const EPSFromUrl = getEPSFromUrl();
|
// for some crazy reason using this alert functionality breaks the flow!
|
||||||
if (EPSFromUrl) {
|
// setAlertMsg(msg);
|
||||||
setMode("phrases");
|
// setTimeout(() => {
|
||||||
adjustEps({
|
// setAlertMsg(undefined);
|
||||||
type: "load EPS",
|
// }, 1500);
|
||||||
payload: EPSFromUrl,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
// eslint-disable-next-line
|
function flashClippedMessage(m: string) {
|
||||||
}, []);
|
setShowClipped(m);
|
||||||
function handleEpsChange(e: T.EPSelectionState) {
|
setTimeout(() => {
|
||||||
adjustEps({ type: "load EPS", payload: e });
|
setShowClipped("");
|
||||||
}
|
}, 1250);
|
||||||
function flashMessage(msg: string) {
|
}
|
||||||
alert(msg);
|
function handleCopyCode() {
|
||||||
// for some crazy reason using this alert functionality breaks the flow!
|
const code = getCode(eps);
|
||||||
// setAlertMsg(msg);
|
navigator.clipboard.writeText(code);
|
||||||
// setTimeout(() => {
|
flashClippedMessage("Copied phrase code to clipboard");
|
||||||
// setAlertMsg(undefined);
|
}
|
||||||
// }, 1500);
|
function handleCopyShareLink() {
|
||||||
}
|
const shareUrl = getShareUrl(eps);
|
||||||
function flashClippedMessage(m: string) {
|
navigator.clipboard.writeText(shareUrl);
|
||||||
setShowClipped(m);
|
flashClippedMessage("Copied phrase URL to clipboard");
|
||||||
setTimeout(() => {
|
}
|
||||||
setShowClipped("");
|
const phraseIsComplete = !!completeEPSelection(eps);
|
||||||
}, 1250);
|
return <div>
|
||||||
}
|
<div className="mt-2 mb-3 d-flex flex-row justify-content-between align-items-center">
|
||||||
function handleCopyCode() {
|
<div />
|
||||||
const code = getCode(eps);
|
<ButtonSelect
|
||||||
navigator.clipboard.writeText(code);
|
value={mode}
|
||||||
flashClippedMessage("Copied phrase code to clipboard");
|
options={[
|
||||||
}
|
{ label: "Charts", value: "charts" },
|
||||||
function handleCopyShareLink() {
|
{ label: "Phrases", value: "phrases" },
|
||||||
const shareUrl = getShareUrl(eps);
|
]}
|
||||||
navigator.clipboard.writeText(shareUrl);
|
handleChange={setMode}
|
||||||
flashClippedMessage("Copied phrase URL to clipboard");
|
/>
|
||||||
}
|
{!("eps" in props) && <div className="d-flex flex-row">
|
||||||
const phraseIsComplete = !!completeEPSelection(eps);
|
<div
|
||||||
return (
|
className="clickable mr-4"
|
||||||
<div>
|
onClick={mode === "phrases" ? handleCopyCode : undefined}
|
||||||
<div className="mt-2 mb-3 d-flex flex-row justify-content-between align-items-center">
|
style={{ width: "1rem" }}
|
||||||
<div />
|
>
|
||||||
<ButtonSelect
|
{(mode === "phrases" && phraseIsComplete) ? <i className="fas fa-code" /> : ""}
|
||||||
value={mode}
|
</div>
|
||||||
options={[
|
<div
|
||||||
{ label: "Charts", value: "charts" },
|
className="clickable"
|
||||||
{ label: "Phrases", value: "phrases" },
|
onClick={mode === "phrases" ? handleCopyShareLink : undefined}
|
||||||
]}
|
style={{ width: "1rem" }}
|
||||||
handleChange={setMode}
|
>
|
||||||
/>
|
{mode === "phrases" ? <i className="fas fa-share-alt" /> : ""}
|
||||||
{!("eps" in props) && (
|
</div>
|
||||||
<div className="d-flex flex-row">
|
</div>}
|
||||||
<div
|
</div>
|
||||||
className="clickable mr-4"
|
{mode === "phrases" &&
|
||||||
onClick={mode === "phrases" ? handleCopyCode : undefined}
|
<EPPicker
|
||||||
style={{ width: "1rem" }}
|
opts={props.opts}
|
||||||
>
|
entryFeeder={props.entryFeeder}
|
||||||
{mode === "phrases" && phraseIsComplete ? (
|
eps={eps}
|
||||||
<i className="fas fa-code" />
|
onChange={handleEpsChange}
|
||||||
) : (
|
/>
|
||||||
""
|
}
|
||||||
)}
|
{mode === "charts" && <EqChartsDisplay opts={props.opts} />}
|
||||||
</div>
|
{mode === "phrases" && <EPDisplay
|
||||||
<div
|
opts={props.opts}
|
||||||
className="clickable"
|
eps={eps}
|
||||||
onClick={mode === "phrases" ? handleCopyShareLink : undefined}
|
setOmitSubject={"eps" in props ? false : payload => adjustEps({ type: "set omitSubject", payload })}
|
||||||
style={{ width: "1rem" }}
|
/>}
|
||||||
>
|
{/* {alertMsg && <div className="alert alert-warning text-center" role="alert" style={{
|
||||||
{mode === "phrases" ? <i className="fas fa-share-alt" /> : ""}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
{mode === "phrases" && (
|
|
||||||
<EPPicker
|
|
||||||
opts={props.opts}
|
|
||||||
entryFeeder={props.entryFeeder}
|
|
||||||
eps={eps}
|
|
||||||
onChange={handleEpsChange}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{mode === "charts" && <EqChartsDisplay opts={props.opts} />}
|
|
||||||
{mode === "phrases" && (
|
|
||||||
<EPDisplay
|
|
||||||
opts={props.opts}
|
|
||||||
eps={eps}
|
|
||||||
setOmitSubject={
|
|
||||||
"eps" in props
|
|
||||||
? false
|
|
||||||
: (payload) => adjustEps({ type: "set omitSubject", payload })
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{/* {alertMsg && <div className="alert alert-warning text-center" role="alert" style={{
|
|
||||||
position: "fixed",
|
position: "fixed",
|
||||||
top: "30%",
|
top: "30%",
|
||||||
left: "50%",
|
left: "50%",
|
||||||
|
@ -157,46 +143,39 @@ function EPExplorer(props: {
|
||||||
}}>
|
}}>
|
||||||
{alertMsg}
|
{alertMsg}
|
||||||
</div>} */}
|
</div>} */}
|
||||||
{showClipped && (
|
{showClipped && <div className="alert alert-primary text-center" role="alert" style={{
|
||||||
<div
|
|
||||||
className="alert alert-primary text-center"
|
|
||||||
role="alert"
|
|
||||||
style={{
|
|
||||||
position: "fixed",
|
position: "fixed",
|
||||||
top: "30%",
|
top: "30%",
|
||||||
left: "50%",
|
left: "50%",
|
||||||
transform: "translate(-50%, -50%)",
|
transform: "translate(-50%, -50%)",
|
||||||
zIndex: 9999999999999,
|
zIndex: 9999999999999,
|
||||||
}}
|
}}>
|
||||||
>
|
{showClipped}
|
||||||
{showClipped}
|
</div>}
|
||||||
</div>
|
</div>;
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default EPExplorer;
|
export default EPExplorer;
|
||||||
|
|
||||||
function getShareUrl(eps: T.EPSelectionState): string {
|
function getShareUrl(eps: T.EPSelectionState): string {
|
||||||
const code = getCode(eps);
|
const code = getCode(eps);
|
||||||
const encoded = LZString.compressToEncodedURIComponent(code);
|
const encoded = LZString.compressToEncodedURIComponent(code);
|
||||||
const url = new URL(window.location.href);
|
const url = new URL(window.location.href);
|
||||||
// need to delete or else you could just get a second param written after
|
// need to delete or else you could just get a second param written after
|
||||||
// which gets ignored
|
// which gets ignored
|
||||||
url.searchParams.delete(epPhraseURLParam);
|
url.searchParams.delete(epPhraseURLParam);
|
||||||
url.searchParams.append(epPhraseURLParam, encoded);
|
url.searchParams.append(epPhraseURLParam, encoded);
|
||||||
return url.toString();
|
return url.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
function getCode(eps: T.EPSelectionState): string {
|
function getCode(eps: T.EPSelectionState): string {
|
||||||
return JSON.stringify(eps);
|
return JSON.stringify(eps);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getEPSFromUrl(): T.EPSelectionState | undefined {
|
function getEPSFromUrl(): T.EPSelectionState | undefined {
|
||||||
const params = new URLSearchParams(window.location.search);
|
const params = new URLSearchParams(window.location.search);
|
||||||
const fromParams = params.get(epPhraseURLParam);
|
const fromParams = params.get(epPhraseURLParam);
|
||||||
if (!fromParams) return;
|
if (!fromParams) return;
|
||||||
const decoded = LZString.decompressFromEncodedURIComponent(fromParams);
|
const decoded = LZString.decompressFromEncodedURIComponent(fromParams);
|
||||||
return JSON.parse(decoded) as T.EPSelectionState;
|
return JSON.parse(decoded) as T.EPSelectionState;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,15 +1,14 @@
|
||||||
import * as T from "../../../types";
|
import * as T from "../../../types";
|
||||||
import NPPicker from "../block-pickers/NPPicker";
|
import NPPicker from "../np-picker/NPPicker";
|
||||||
import EquativePicker from "./EquativePicker";
|
import EquativePicker from "./EquativePicker";
|
||||||
import ButtonSelect from "../selects/ButtonSelect";
|
import ButtonSelect from "../ButtonSelect";
|
||||||
import ComplementPicker from "../block-pickers/ComplementPicker";
|
import ComplementPicker from "../ComplementPicker";
|
||||||
import epsReducer, {
|
import epsReducer, {
|
||||||
EpsReducerAction,
|
EpsReducerAction,
|
||||||
} from "../../../lib/src/phrase-building/eps-reducer";
|
} from "../../../lib/src/phrase-building/eps-reducer";
|
||||||
import { useEffect, useRef } from "react";
|
import { useEffect, useRef } from "react";
|
||||||
import { completeEPSelection } from "../../../lib/src/phrase-building/render-ep";
|
import { completeEPSelection } from "../../../lib/src/phrase-building/render-ep";
|
||||||
import APPicker from "../block-pickers/APPicker";
|
import APPicker from "../../src/ap-picker/APPicker";
|
||||||
// @ts-expect-error types not working for this
|
|
||||||
import autoAnimate from "@formkit/auto-animate";
|
import autoAnimate from "@formkit/auto-animate";
|
||||||
|
|
||||||
function EPPicker({
|
function EPPicker({
|
||||||
|
@ -25,9 +24,7 @@ function EPPicker({
|
||||||
}) {
|
}) {
|
||||||
const parent = useRef<HTMLDivElement>(null);
|
const parent = useRef<HTMLDivElement>(null);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (parent.current) {
|
parent.current && autoAnimate(parent.current);
|
||||||
autoAnimate(parent.current);
|
|
||||||
}
|
|
||||||
}, [parent]);
|
}, [parent]);
|
||||||
function adjustEps(action: EpsReducerAction) {
|
function adjustEps(action: EpsReducerAction) {
|
||||||
onChange(epsReducer(eps, action));
|
onChange(epsReducer(eps, action));
|
||||||
|
|
|
@ -1,107 +1,82 @@
|
||||||
import * as T from "../../../types";
|
import * as T from "../../../types"
|
||||||
import Select from "react-select";
|
import Select from "react-select";
|
||||||
import ButtonSelect from "../selects/ButtonSelect";
|
import ButtonSelect from "../ButtonSelect";
|
||||||
import { epTenseOptions as options } from "./epTenseOptions";
|
import { epTenseOptions as options } from "./epTenseOptions";
|
||||||
import { customSelectStyles as customStyles } from "../selects/select-styles";
|
import { customStyles } from "../EntrySelect";
|
||||||
|
|
||||||
function EquativePicker({
|
function EquativePicker({ equative, onChange, hideNegative }: {
|
||||||
equative,
|
equative: { tense: T.EquativeTense, negative: boolean },
|
||||||
onChange,
|
onChange: (e: { tense: T.EquativeTense, negative: boolean }) => void,
|
||||||
hideNegative,
|
hideNegative?: boolean,
|
||||||
}: {
|
|
||||||
equative: { tense: T.EquativeTense; negative: boolean };
|
|
||||||
onChange: (e: { tense: T.EquativeTense; negative: boolean }) => void;
|
|
||||||
hideNegative?: boolean;
|
|
||||||
}) {
|
}) {
|
||||||
function onTenseSelect(o: { value: T.EquativeTense } | null) {
|
function onTenseSelect(o: { value: T.EquativeTense } | null) {
|
||||||
const value = o?.value ? o.value : undefined;
|
const value = o?.value ? o.value : undefined;
|
||||||
if (!value) {
|
if (!value) {
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
onChange({
|
|
||||||
...equative,
|
|
||||||
tense: value,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
function moveTense(dir: "forward" | "back") {
|
|
||||||
return () => {
|
|
||||||
const currIndex = options.findIndex((tn) => tn.value === equative.tense);
|
|
||||||
if (currIndex === -1) {
|
|
||||||
console.error("error moving tense", dir);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const newIndex =
|
|
||||||
dir === "forward"
|
|
||||||
? (currIndex + 1) % options.length
|
|
||||||
: currIndex === 0
|
|
||||||
? options.length - 1
|
|
||||||
: currIndex - 1;
|
|
||||||
const newTense = options[newIndex];
|
|
||||||
onChange({
|
|
||||||
...equative,
|
|
||||||
tense: newTense.value,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
}
|
|
||||||
function onPosNegSelect(value: "true" | "false") {
|
|
||||||
onChange({
|
|
||||||
...equative,
|
|
||||||
negative: value === "true",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<div style={{ maxWidth: "300px", minWidth: "250px", margin: "0 auto" }}>
|
|
||||||
<div className="h5">Tense:</div>
|
|
||||||
<Select
|
|
||||||
isSearchable={false}
|
|
||||||
// for some reason can't use tOptions with find here;
|
|
||||||
value={options.find((o) => o.value === equative.tense)}
|
|
||||||
// @ts-expect-error issues with react-select
|
|
||||||
onChange={onTenseSelect}
|
|
||||||
className="mb-2"
|
|
||||||
options={options}
|
|
||||||
styles={customStyles}
|
|
||||||
/>
|
|
||||||
{
|
|
||||||
<div
|
|
||||||
className="d-flex flex-row justify-content-between align-items-center mt-3 mb-1"
|
|
||||||
style={{ width: "100%" }}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className="btn btn-light clickable"
|
|
||||||
onClick={moveTense("back")}
|
|
||||||
>
|
|
||||||
<i className="fas fa-chevron-left" />
|
|
||||||
</div>
|
|
||||||
{!hideNegative && (
|
|
||||||
<ButtonSelect
|
|
||||||
small
|
|
||||||
value={equative.negative.toString() as "true" | "false"}
|
|
||||||
options={[
|
|
||||||
{
|
|
||||||
label: "Pos.",
|
|
||||||
value: "false",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Neg.",
|
|
||||||
value: "true",
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
handleChange={onPosNegSelect}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<div
|
|
||||||
onClick={moveTense("forward")}
|
|
||||||
className="btn btn-light clickable"
|
|
||||||
>
|
|
||||||
<i className="fas fa-chevron-right" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
}
|
||||||
</div>
|
onChange({
|
||||||
</div>
|
...equative,
|
||||||
);
|
tense: value,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
function moveTense(dir: "forward" | "back") {
|
||||||
|
return () => {
|
||||||
|
const currIndex = options.findIndex(tn => tn.value === equative.tense)
|
||||||
|
if (currIndex === -1) {
|
||||||
|
console.error("error moving tense", dir);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const newIndex = dir === "forward"
|
||||||
|
? ((currIndex + 1) % options.length)
|
||||||
|
: (currIndex === 0 ? (options.length - 1) : (currIndex - 1))
|
||||||
|
const newTense = options[newIndex];
|
||||||
|
onChange({
|
||||||
|
...equative,
|
||||||
|
tense: newTense.value,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
function onPosNegSelect(value: "true" | "false") {
|
||||||
|
onChange({
|
||||||
|
...equative,
|
||||||
|
negative: value === "true",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return <div>
|
||||||
|
<div style={{ maxWidth: "300px", minWidth: "250px", margin: "0 auto" }}>
|
||||||
|
<div className="h5">Tense:</div>
|
||||||
|
<Select
|
||||||
|
isSearchable={false}
|
||||||
|
// for some reason can't use tOptions with find here;
|
||||||
|
value={options.find(o => o.value === equative.tense)}
|
||||||
|
// @ts-ignore
|
||||||
|
onChange={onTenseSelect}
|
||||||
|
className="mb-2"
|
||||||
|
options={options}
|
||||||
|
styles={customStyles}
|
||||||
|
/>
|
||||||
|
{<div className="d-flex flex-row justify-content-between align-items-center mt-3 mb-1" style={{ width: "100%" }}>
|
||||||
|
<div className="btn btn-light clickable" onClick={moveTense("back")}>
|
||||||
|
<i className="fas fa-chevron-left" />
|
||||||
|
</div>
|
||||||
|
{!hideNegative && <ButtonSelect
|
||||||
|
small
|
||||||
|
value={equative.negative.toString() as "true" | "false"}
|
||||||
|
options={[{
|
||||||
|
label: "Pos.",
|
||||||
|
value: "false",
|
||||||
|
}, {
|
||||||
|
label: "Neg.",
|
||||||
|
value: "true",
|
||||||
|
}]}
|
||||||
|
handleChange={onPosNegSelect}
|
||||||
|
/>}
|
||||||
|
<div onClick={moveTense("forward")} className="btn btn-light clickable">
|
||||||
|
<i className="fas fa-chevron-right" />
|
||||||
|
</div>
|
||||||
|
</div>}
|
||||||
|
</div>
|
||||||
|
</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default EquativePicker;
|
export default EquativePicker;
|
|
@ -0,0 +1,31 @@
|
||||||
|
import { makeLocativeAdverbSelection } from "../../../../lib/src/phrase-building/make-selections";
|
||||||
|
import * as T from "../../../../types";
|
||||||
|
import EntrySelect from "../../EntrySelect";
|
||||||
|
|
||||||
|
function LocativeAdverbPicker(props: {
|
||||||
|
entryFeeder: T.EntryFeederSingleType<T.LocativeAdverbEntry>,
|
||||||
|
adjective: T.LocativeAdverbSelection | undefined,
|
||||||
|
onChange: (p: T.LocativeAdverbSelection | undefined) => void,
|
||||||
|
opts: T.TextOptions,
|
||||||
|
}) {
|
||||||
|
function onEntrySelect(entry: T.LocativeAdverbEntry | undefined) {
|
||||||
|
if (!entry) {
|
||||||
|
return props.onChange(undefined);
|
||||||
|
}
|
||||||
|
props.onChange(makeLocativeAdverbSelection(entry));
|
||||||
|
}
|
||||||
|
return <div style={{ maxWidth: "225px", minWidth: "125px" }}>
|
||||||
|
<div className="h6">Locative Adverb</div>
|
||||||
|
<div>
|
||||||
|
<EntrySelect
|
||||||
|
value={props.adjective?.entry}
|
||||||
|
entryFeeder={props.entryFeeder}
|
||||||
|
onChange={onEntrySelect}
|
||||||
|
name="Locative Adverb"
|
||||||
|
opts={props.opts}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default LocativeAdverbPicker;
|
|
@ -0,0 +1,26 @@
|
||||||
|
import { psJSXMap } from "./jsx-map";
|
||||||
|
|
||||||
|
test("psJSXMap should work with p as a target", () => {
|
||||||
|
const input = { p: <>زه کور ته <strong>ځم</strong></>, f: <>zu kor ta <strong>dzum</strong></> };
|
||||||
|
const output = psJSXMap(input, "p", (ps) => ps.p.replace(/ځ/g, "ز"));
|
||||||
|
expect(output).toEqual(<>زه کور ته <strong>زم</strong></>);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("psJSXMap should work with f as a target", () => {
|
||||||
|
const input = { p: <>زه کور ته <strong>ځم</strong></>, f: <>zu kor ta <strong>dzum</strong></> };
|
||||||
|
const output = psJSXMap(input, "f", (ps) => ps.f.replace(/dz/g, "z"));
|
||||||
|
expect(output).toEqual(<>zu kor ta <strong>zum</strong></>);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("psJSXMap should work with single child", () => {
|
||||||
|
const input = { p: <div><span>کور</span></div>, f: <div><span>kor</span></div> };
|
||||||
|
const output = psJSXMap(input, "f", (ps) => ps.f.replace(/k/g, "q"));
|
||||||
|
expect(output).toEqual(<div><span>qor</span></div>);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("psJSXMap will error if given an uneven/unbalanced pair of JSX Elements", () => {
|
||||||
|
expect(() => {
|
||||||
|
const input = { p: <>زه کور ته <strong>ځم</strong></>, f: <>zu kor ta dzum</> };
|
||||||
|
psJSXMap(input, "p", (ps) => ps.p.replace(/ځ/g, "ز"));
|
||||||
|
}).toThrow("error mapping out PsJSX - unbalanced trees");
|
||||||
|
});
|
|
@ -0,0 +1,62 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2021 lingdocs.com
|
||||||
|
*
|
||||||
|
* This source code is licensed under the GPL3 license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
import * as T from "../../types";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allows PsString transforming methods to be applied to a Pashto/Phonetics set of JSX elements
|
||||||
|
* outputs a single part of the pair (p or f) with the transform function applied to all the text
|
||||||
|
* within
|
||||||
|
* eg: { p: <>زه <strong>ځم</strong></>, f : <>zu <strong>dzum</strong></> }
|
||||||
|
*
|
||||||
|
* @param ps
|
||||||
|
* @param target
|
||||||
|
* @param dealWithString
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export function psJSXMap(ps: T.PsJSX, target: "p" | "f", dealWithString: (ps: T.PsString) => string | JSX.Element): JSX.Element {
|
||||||
|
const base = ps[target];
|
||||||
|
const sec = ps[target === "p" ? "f" : "p"];
|
||||||
|
try {
|
||||||
|
return {
|
||||||
|
...base,
|
||||||
|
props: {
|
||||||
|
// TODO: could make this a lot cleaner/less repetitive by following a better recursive formula
|
||||||
|
// see https://adueck.github.io/blog/recursively-modify-text-jsx-react/
|
||||||
|
...base.props,
|
||||||
|
children: typeof base.props.children === "string"
|
||||||
|
? dealWithString({
|
||||||
|
p: (target === "p" ? base : sec).props.children,
|
||||||
|
f: (target === "p" ? sec : base).props.children,
|
||||||
|
})
|
||||||
|
: base.props.children.map
|
||||||
|
? base.props.children.map((x: string | JSX.Element, i: number) => (
|
||||||
|
(typeof x === "string")
|
||||||
|
? dealWithString({
|
||||||
|
p: (target === "p" ? x : sec.props.children[i]),
|
||||||
|
f: (target === "p" ? sec.props.children[i] : x),
|
||||||
|
})
|
||||||
|
: psJSXMap({
|
||||||
|
p: (target === "p" ? x : sec.props.children[i]),
|
||||||
|
f: (target === "p" ? sec.props.children[i] : x),
|
||||||
|
}, target, dealWithString)
|
||||||
|
)) : (typeof base.props.children === "string")
|
||||||
|
? dealWithString({
|
||||||
|
p: (target === "p" ? base.props.children : sec.props.children),
|
||||||
|
f: (target === "p" ? sec.props.children : base.props.children),
|
||||||
|
})
|
||||||
|
: psJSXMap({
|
||||||
|
p: (target === "p" ? base.props.children : sec.props.children),
|
||||||
|
f: (target === "p" ? sec.props.children : base.props.children),
|
||||||
|
}, target, dealWithString)
|
||||||
|
},
|
||||||
|
};
|
||||||
|
} catch (e) {
|
||||||
|
throw new Error("error mapping out PsJSX - unbalanced trees");
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,7 +1,7 @@
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { makeAdjectiveSelection } from "../../../lib/src/phrase-building/make-selections";
|
import { makeAdjectiveSelection } from "../../../lib/src/phrase-building/make-selections";
|
||||||
import * as T from "../../../types";
|
import * as T from "../../../types";
|
||||||
import EntrySelect from "../selects/EntrySelect";
|
import EntrySelect from "../EntrySelect";
|
||||||
import SandwichPicker from "./SandwichPicker";
|
import SandwichPicker from "./SandwichPicker";
|
||||||
|
|
||||||
function AdjectivePicker(props: {
|
function AdjectivePicker(props: {
|
|
@ -1,5 +1,5 @@
|
||||||
import * as T from "../../../types";
|
import * as T from "../../../types";
|
||||||
import { DeterminerSelect } from "../selects/EntrySelect";
|
import { DeterminerSelect } from "../EntrySelect";
|
||||||
|
|
||||||
export default function DeterminersPicker({
|
export default function DeterminersPicker({
|
||||||
determiners,
|
determiners,
|
|
@ -1,10 +1,10 @@
|
||||||
import { makeNounSelection } from "../../../lib/src/phrase-building/make-selections";
|
import { makeNounSelection } from "../../../lib/src/phrase-building/make-selections";
|
||||||
|
|
||||||
import * as T from "../../../types";
|
import * as T from "../../../types";
|
||||||
import ButtonSelect from "../selects/ButtonSelect";
|
import ButtonSelect from "../ButtonSelect";
|
||||||
import InlinePs from "../text-display/InlinePs";
|
import InlinePs from "../InlinePs";
|
||||||
// import { isFemNounEntry, isPattern1Entry, isPattern2Entry, isPattern3Entry, isPattern4Entry, isPattern5Entry, isPattern6FemEntry } from "../../lib/type-predicates";
|
// import { isFemNounEntry, isPattern1Entry, isPattern2Entry, isPattern3Entry, isPattern4Entry, isPattern5Entry, isPattern6FemEntry } from "../../lib/type-predicates";
|
||||||
import EntrySelect from "../selects/EntrySelect";
|
import EntrySelect from "../EntrySelect";
|
||||||
import AdjectiveManager from "./AdjectiveManager";
|
import AdjectiveManager from "./AdjectiveManager";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import DeterminersPicker from "./DeterminersPicker";
|
import DeterminersPicker from "./DeterminersPicker";
|
||||||
|
@ -136,6 +136,7 @@ function NPNounPicker(props: {
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* {showFilter && <div className="mb-2 text-center">
|
{/* {showFilter && <div className="mb-2 text-center">
|
||||||
<div className="d-flex flex-row justify-content-between">
|
<div className="d-flex flex-row justify-content-between">
|
||||||
<div className="text-small mb-1">Filter by inflection pattern</div>
|
<div className="text-small mb-1">Filter by inflection pattern</div>
|
||||||
|
@ -191,10 +192,9 @@ function NPNounPicker(props: {
|
||||||
Compound:
|
Compound:
|
||||||
</div>
|
</div>
|
||||||
<div className="mb-3 text-center">
|
<div className="mb-3 text-center">
|
||||||
<InlinePs
|
<InlinePs opts={props.opts}>
|
||||||
opts={props.opts}
|
{{ p: props.noun.entry.p, f: props.noun.entry.f }}
|
||||||
ps={{ p: props.noun.entry.p, f: props.noun.entry.f }}
|
</InlinePs>
|
||||||
/>
|
|
||||||
<div className="text-muted">{props.noun.entry.e}</div>
|
<div className="text-muted">{props.noun.entry.e}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
|
@ -0,0 +1,34 @@
|
||||||
|
import EntrySelect from "../EntrySelect";
|
||||||
|
import * as T from "../../../types";
|
||||||
|
import { makeParticipleSelection } from "../../../lib/src/phrase-building/make-selections";
|
||||||
|
|
||||||
|
function NPParticiplePicker(props: {
|
||||||
|
entryFeeder: T.EntryFeederSingleType<T.VerbEntry>,
|
||||||
|
participle: T.ParticipleSelection | undefined,
|
||||||
|
onChange: (p: T.ParticipleSelection | undefined) => void,
|
||||||
|
opts: T.TextOptions,
|
||||||
|
}) {
|
||||||
|
function onEntrySelect(entry: T.VerbEntry | undefined) {
|
||||||
|
if (!entry) {
|
||||||
|
props.onChange(undefined);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
props.onChange(makeParticipleSelection(entry));
|
||||||
|
}
|
||||||
|
return <div style={{ maxWidth: "225px" }}>
|
||||||
|
<div className="h6">Participle</div>
|
||||||
|
<EntrySelect
|
||||||
|
value={props.participle?.verb}
|
||||||
|
entryFeeder={props.entryFeeder}
|
||||||
|
onChange={onEntrySelect}
|
||||||
|
name="Pariticple"
|
||||||
|
opts={props.opts}
|
||||||
|
/>
|
||||||
|
{props.participle && <div className="my-2 d-flex flex-row justify-content-around align-items-center">
|
||||||
|
<div>Masc.</div>
|
||||||
|
<div>Plur.</div>
|
||||||
|
</div>}
|
||||||
|
</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default NPParticiplePicker;
|
|
@ -246,7 +246,6 @@ function NPPicker(props: {
|
||||||
pronoun={props.np.selection}
|
pronoun={props.np.selection}
|
||||||
onChange={(p) => onChange({ type: "NP", selection: p })}
|
onChange={(p) => onChange({ type: "NP", selection: p })}
|
||||||
is2ndPersonPicker={props.is2ndPersonPicker}
|
is2ndPersonPicker={props.is2ndPersonPicker}
|
||||||
// @ts-expect-error this is fine
|
|
||||||
opts={props.opts}
|
opts={props.opts}
|
||||||
/>
|
/>
|
||||||
) : npType === "noun" ? (
|
) : npType === "noun" ? (
|
|
@ -0,0 +1,198 @@
|
||||||
|
import * as T from "../../../types";
|
||||||
|
import ButtonSelect from "../ButtonSelect";
|
||||||
|
import useStickyState from "../useStickyState";
|
||||||
|
import classNames from "classnames";
|
||||||
|
import {
|
||||||
|
isSecondPerson,
|
||||||
|
} from "../../../lib/src/misc-helpers";
|
||||||
|
|
||||||
|
const gColors = {
|
||||||
|
masc: "LightSkyBlue",
|
||||||
|
fem: "pink",
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO: better logic on this
|
||||||
|
const labels = (role: "subject" | "object" | "ergative" | "possesor") => ({
|
||||||
|
// persons: [
|
||||||
|
// ["1st", "1st pl."],
|
||||||
|
// ["2nd", "2nd pl."],
|
||||||
|
// ["3rd", "3rd pl."],
|
||||||
|
// ],
|
||||||
|
e: role === "object" ? [
|
||||||
|
["me", "us"],
|
||||||
|
["you", "you (pl.)"],
|
||||||
|
[{ masc: "him/it", fem: "her/it"}, "them"],
|
||||||
|
] : role === "possesor" ? [
|
||||||
|
["my", "our"],
|
||||||
|
["your", "your (pl.)"],
|
||||||
|
[{ masc: "his/its", fem: "her/its"}, "their"],
|
||||||
|
] : [
|
||||||
|
["I", "We"],
|
||||||
|
["You", "You (pl.)"],
|
||||||
|
[{ masc: "He/It", fem: "She/It"}, "They"],
|
||||||
|
],
|
||||||
|
p: role === "subject" ? {
|
||||||
|
far: [
|
||||||
|
["زه", "مونږ"],
|
||||||
|
["ته", "تاسو"],
|
||||||
|
["هغه", "هغوی"],
|
||||||
|
],
|
||||||
|
near: [
|
||||||
|
["زه", "مونږ"],
|
||||||
|
["ته", "تاسو"],
|
||||||
|
[{ masc: "دی", fem: "دا" }, "دوی"],
|
||||||
|
],
|
||||||
|
} : role === "object" ? {
|
||||||
|
far: [
|
||||||
|
["زه", "مونږ"],
|
||||||
|
["ته", "تاسو"],
|
||||||
|
[{ masc: "هغهٔ", fem: "هغې" }, "هغوی"],
|
||||||
|
],
|
||||||
|
near: [
|
||||||
|
["زه", "مونږ"],
|
||||||
|
["ته", "تاسو"],
|
||||||
|
[{ masc: "دهٔ", fem: "دې" }, "دوی"],
|
||||||
|
],
|
||||||
|
} : role === "possesor" ? {
|
||||||
|
far: [
|
||||||
|
["زما", "زمونږ"],
|
||||||
|
["ستا", "ستاسو"],
|
||||||
|
[{ masc: "د هغهٔ", fem: "د هغې" }, "د هغوی"],
|
||||||
|
],
|
||||||
|
near: [
|
||||||
|
["زما", "زمونږ"],
|
||||||
|
["ستا", "ستاسو"],
|
||||||
|
[{ masc: "د دهٔ", fem: "د دې" }, "د دوی"],
|
||||||
|
],
|
||||||
|
} : {
|
||||||
|
far: [
|
||||||
|
["ما", "مونږ"],
|
||||||
|
["تا", "تاسو"],
|
||||||
|
[{ masc: "هغهٔ", fem: "هغې" }, "هغوی"],
|
||||||
|
],
|
||||||
|
near: [
|
||||||
|
["ما", "مونږ"],
|
||||||
|
["تا", "تاسو"],
|
||||||
|
[{ masc: "دهٔ", fem: "دې" }, "دوی"],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
type PickerState = { row: number, col: number, gender: T.Gender };
|
||||||
|
|
||||||
|
function personToPickerState(person: T.Person): PickerState {
|
||||||
|
const col = person > 5 ? 1 : 0;
|
||||||
|
const row = Math.floor((person > 5 ? (person - 6) : person) / 2);
|
||||||
|
const gender: T.Gender = (person % 2) ? "fem" : "masc";
|
||||||
|
return { col, row, gender };
|
||||||
|
}
|
||||||
|
|
||||||
|
function pickerStateToPerson(s: PickerState): T.Person {
|
||||||
|
return (s.row * 2)
|
||||||
|
+ (s.gender === "masc" ? 0 : 1)
|
||||||
|
+ (6 * s.col);
|
||||||
|
}
|
||||||
|
|
||||||
|
function NPPronounPicker({ onChange, pronoun, role, opts, is2ndPersonPicker }: {
|
||||||
|
pronoun: T.PronounSelection,
|
||||||
|
onChange: (p: T.PronounSelection) => void,
|
||||||
|
role: "object" | "subject" | "ergative" | "possesor",
|
||||||
|
opts: T.TextOptions,
|
||||||
|
is2ndPersonPicker?: boolean,
|
||||||
|
}) {
|
||||||
|
if (is2ndPersonPicker && !isSecondPerson(pronoun.person)) {
|
||||||
|
throw new Error("can't use 2ndPerson NPProunounPicker without a pronoun");
|
||||||
|
}
|
||||||
|
const [display, setDisplay] = useStickyState<"p" | "e">("e", "prounoun-picker-display2");
|
||||||
|
const p = personToPickerState(pronoun.person);
|
||||||
|
function handleClick(row: number, col: number) {
|
||||||
|
const person = pickerStateToPerson({ ...p, row, col });
|
||||||
|
onChange({
|
||||||
|
...pronoun,
|
||||||
|
person,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
function handleGenderChange(gender: T.Gender) {
|
||||||
|
const person = pickerStateToPerson({ ...p, gender });
|
||||||
|
onChange({
|
||||||
|
...pronoun,
|
||||||
|
person,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
function handlePronounTypeChange(distance: "far" | "near") {
|
||||||
|
onChange({
|
||||||
|
...pronoun,
|
||||||
|
distance,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
function handleDisplayChange() {
|
||||||
|
const newPerson = display === "p"
|
||||||
|
? "e"
|
||||||
|
: "p";
|
||||||
|
setDisplay(newPerson);
|
||||||
|
}
|
||||||
|
const prs = labels(role)[display];
|
||||||
|
const pSpecA = "near" in prs ? prs[pronoun.distance] : prs;
|
||||||
|
const pSpec = is2ndPersonPicker
|
||||||
|
? [pSpecA[1]]
|
||||||
|
: pSpecA;
|
||||||
|
return <div style={{ maxWidth: "145px", padding: 0, margin: "0 auto" }}>
|
||||||
|
<div className="d-flex flex-row justify-content-between mb-2">
|
||||||
|
<ButtonSelect
|
||||||
|
xSmall
|
||||||
|
options={[
|
||||||
|
{ label: "Far", value: "far" },
|
||||||
|
{ label: "Near", value: "near" },
|
||||||
|
]}
|
||||||
|
value={pronoun.distance}
|
||||||
|
handleChange={(g) => handlePronounTypeChange(g as "far" | "near")}
|
||||||
|
/>
|
||||||
|
<button className="btn btn-sm btn-outline-secondary" onClick={handleDisplayChange}>
|
||||||
|
{display === "p" ? "PS" : "EN"}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<table className="table table-bordered table-sm" style={{ textAlign: "center", minWidth: "100px", tableLayout: "fixed" }}>
|
||||||
|
<tbody>
|
||||||
|
{pSpec.map((rw, i) => (
|
||||||
|
<tr key={`pronounPickerRow${i}`}>
|
||||||
|
{rw.map((r, j) => {
|
||||||
|
const active = is2ndPersonPicker
|
||||||
|
? (p.col === j)
|
||||||
|
: (p.row === i && p.col === j);
|
||||||
|
const content = typeof r === "string" ? r : r[p.gender];
|
||||||
|
return <td
|
||||||
|
key={`pronounPickerCell${i}${j}`}
|
||||||
|
onClick={() => {
|
||||||
|
handleClick(is2ndPersonPicker ? 1 : i, j);
|
||||||
|
}}
|
||||||
|
className={classNames({ "table-active": active, "text-on-gender-color": active })}
|
||||||
|
style={{
|
||||||
|
backgroundColor: active ? gColors[p.gender] : "inherit",
|
||||||
|
padding: "0.15rem 0",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className="my-1">
|
||||||
|
{content}
|
||||||
|
</div>
|
||||||
|
</td>;
|
||||||
|
})}
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<div className="text-center">
|
||||||
|
<ButtonSelect
|
||||||
|
small
|
||||||
|
options={[
|
||||||
|
{ label: <div style={{ margin: "0.15rem"}}>Masc.</div>, value: "masc", color: gColors.masc },
|
||||||
|
{ label: <div style={{ margin: "0.15rem"}}>Fem.</div>, value: "fem", color: gColors.fem },
|
||||||
|
]}
|
||||||
|
value={p.gender}
|
||||||
|
handleChange={(g) => handleGenderChange(g as T.Gender)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default NPPronounPicker;
|
|
@ -1,6 +1,6 @@
|
||||||
import * as T from "../../../types";
|
import * as T from "../../../types";
|
||||||
import { sandwiches } from "../../../lib/src/sandwiches";
|
import { sandwiches } from "../../../lib/src/sandwiches";
|
||||||
import { SandwichSelect } from "../selects/EntrySelect";
|
import { SandwichSelect } from "../EntrySelect";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import NPPicker from "./NPPicker";
|
import NPPicker from "./NPPicker";
|
||||||
|
|
|
@ -1,13 +1,11 @@
|
||||||
export default function playAudio(a: string) {
|
export default function playAudio(a: string) {
|
||||||
if (!a) return;
|
if (!a) return;
|
||||||
const audio = new Audio(
|
let audio = new Audio(`https://verbs.lingdocs.com/sounds/${a}.mp3`);
|
||||||
`https://pashto-inflector.lingdocs.com/sounds/${a}.mp3`
|
audio.addEventListener("ended", () => {
|
||||||
);
|
audio.remove();
|
||||||
audio.addEventListener("ended", () => {
|
audio.srcObject = null;
|
||||||
audio.remove();
|
});
|
||||||
audio.srcObject = null;
|
audio.play().catch((e) => {
|
||||||
});
|
console.error(e);
|
||||||
audio.play().catch((e) => {
|
});
|
||||||
console.error(e);
|
}
|
||||||
});
|
|
||||||
}
|
|
|
@ -1,4 +0,0 @@
|
||||||
export const roleIcon = {
|
|
||||||
king: <i className="mx-1 fas fa-crown" />,
|
|
||||||
servant: <i className="mx-1 fas fa-male" />,
|
|
||||||
};
|
|
|
@ -1,57 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright (c) 2021 lingdocs.com
|
|
||||||
*
|
|
||||||
* This source code is licensed under the GPL3 license found in the
|
|
||||||
* LICENSE file in the root directory of this source tree.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
import classNames from "classnames";
|
|
||||||
|
|
||||||
type PickerProps<T extends string> = {
|
|
||||||
options: { label: React.ReactNode; value: T; color?: string }[];
|
|
||||||
value: T;
|
|
||||||
handleChange: (payload: T) => void;
|
|
||||||
small?: boolean;
|
|
||||||
xSmall?: boolean;
|
|
||||||
faded?: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
function ButtonSelect<L extends string>(props: PickerProps<L>) {
|
|
||||||
return (
|
|
||||||
<div className="btn-group">
|
|
||||||
{props.options.map((option) => (
|
|
||||||
<button
|
|
||||||
key={option.value}
|
|
||||||
type="button"
|
|
||||||
className={classNames(
|
|
||||||
"btn",
|
|
||||||
props.faded ? "btn-light" : "btn-outline-secondary",
|
|
||||||
{ active: props.value === option.value },
|
|
||||||
{ "btn-sm": props.small || props.xSmall }
|
|
||||||
)}
|
|
||||||
onClick={() => props.handleChange(option.value)}
|
|
||||||
style={{
|
|
||||||
...(props.xSmall ? { fontSize: "small" } : {}),
|
|
||||||
...(option.color && props.value === option.value
|
|
||||||
? { backgroundColor: option.color }
|
|
||||||
: {}),
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
className={classNames([
|
|
||||||
{
|
|
||||||
"text-on-gender-color":
|
|
||||||
option.color && props.value === option.value,
|
|
||||||
},
|
|
||||||
])}
|
|
||||||
>
|
|
||||||
{option.label}
|
|
||||||
</span>
|
|
||||||
</button>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default ButtonSelect;
|
|
|
@ -1,134 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright (c) 2021 lingdocs.com
|
|
||||||
*
|
|
||||||
* This source code is licensed under the GPL3 license found in the
|
|
||||||
* LICENSE file in the root directory of this source tree.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { persons } from "../../../lib/src/grammar-units";
|
|
||||||
import InlinePs from "../text-display/InlinePs";
|
|
||||||
import * as T from "../../../types";
|
|
||||||
|
|
||||||
function PersonSelect(props: {
|
|
||||||
setting: "subject" | "object";
|
|
||||||
value: T.Person;
|
|
||||||
locked?: boolean;
|
|
||||||
handleChange: (person: T.Person) => void;
|
|
||||||
handleRandom: () => void;
|
|
||||||
}) {
|
|
||||||
return !props.locked ? (
|
|
||||||
<div className="input-group" style={{ maxWidth: "30rem" }}>
|
|
||||||
<select
|
|
||||||
className="custom-select"
|
|
||||||
value={props.value}
|
|
||||||
onChange={(e) =>
|
|
||||||
props.handleChange(parseInt(e.target.value) as T.Person)
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{persons.map((p) => (
|
|
||||||
<option value={p.person} key={"subject" + p.person}>
|
|
||||||
{p.label[props.setting]}
|
|
||||||
</option>
|
|
||||||
))}
|
|
||||||
</select>
|
|
||||||
<div className="input-group-append" onClick={props.handleRandom}>
|
|
||||||
<button className="btn btn-secondary">
|
|
||||||
<i className="fas fa-random" />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<input
|
|
||||||
className="form-control"
|
|
||||||
type="text"
|
|
||||||
placeholder={persons[props.value].label[props.setting]}
|
|
||||||
readOnly
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function PersonSelection(props: {
|
|
||||||
subject: T.Person;
|
|
||||||
object: T.Person;
|
|
||||||
info: T.NonComboVerbInfo;
|
|
||||||
handleRandom: (setting: "subject" | "object") => void;
|
|
||||||
handleChange: (payload: {
|
|
||||||
setting: "subject" | "object";
|
|
||||||
person: T.Person;
|
|
||||||
}) => void;
|
|
||||||
textOptions: T.TextOptions;
|
|
||||||
}) {
|
|
||||||
function getComp(comp: T.ObjComplement) {
|
|
||||||
const c = comp.plural ? comp.plural : comp.entry;
|
|
||||||
return <InlinePs opts={props.textOptions} ps={c} />;
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<div className="row align-items-baseline">
|
|
||||||
<div className="col">
|
|
||||||
<label className="form-label">
|
|
||||||
{/* TODO: Should I put the Subject/Agent label back in for non-transitive verbs?? */}
|
|
||||||
<strong>Subject</strong>
|
|
||||||
</label>
|
|
||||||
<PersonSelect
|
|
||||||
setting="subject"
|
|
||||||
value={props.subject}
|
|
||||||
handleChange={(person: T.Person) =>
|
|
||||||
props.handleChange({ setting: "subject", person })
|
|
||||||
}
|
|
||||||
handleRandom={() => props.handleRandom("subject")}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
{props.info.type === "dynamic compound" ||
|
|
||||||
props.info.type === "generative stative compound" ? (
|
|
||||||
<div className="col">
|
|
||||||
<label className="form-label">
|
|
||||||
<strong>
|
|
||||||
Object is the complement ({getComp(props.info.objComplement)})
|
|
||||||
</strong>
|
|
||||||
</label>
|
|
||||||
<PersonSelect
|
|
||||||
setting="object"
|
|
||||||
value={props.info.objComplement.person}
|
|
||||||
locked
|
|
||||||
handleChange={(person: T.Person) =>
|
|
||||||
props.handleChange({ setting: "object", person })
|
|
||||||
}
|
|
||||||
handleRandom={() => props.handleRandom("object")}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
) : props.info.transitivity === "transitive" ? (
|
|
||||||
<div className="col">
|
|
||||||
<label className="form-label">
|
|
||||||
<strong>Object</strong>
|
|
||||||
</label>
|
|
||||||
<PersonSelect
|
|
||||||
setting="object"
|
|
||||||
value={props.object}
|
|
||||||
handleChange={(person: T.Person) =>
|
|
||||||
props.handleChange({ setting: "object", person })
|
|
||||||
}
|
|
||||||
handleRandom={() => props.handleRandom("object")}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
) : props.info.transitivity === "grammatically transitive" ? (
|
|
||||||
<div className="col">
|
|
||||||
<label className="form-label">
|
|
||||||
<strong>Object is unspoken</strong>
|
|
||||||
</label>
|
|
||||||
<PersonSelect
|
|
||||||
setting="object"
|
|
||||||
value={10}
|
|
||||||
locked
|
|
||||||
handleChange={(person: T.Person) =>
|
|
||||||
props.handleChange({ setting: "object", person })
|
|
||||||
}
|
|
||||||
handleRandom={() => props.handleRandom("object")}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
) : null}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default PersonSelection;
|
|
|
@ -1,25 +0,0 @@
|
||||||
import { StylesConfig } from "react-select";
|
|
||||||
|
|
||||||
export const customSelectStyles: StylesConfig = {
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
menuPortal: (base: any) => ({
|
|
||||||
...base,
|
|
||||||
zIndex: 99999,
|
|
||||||
}),
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
menu: (base: any) => ({
|
|
||||||
...base,
|
|
||||||
zIndex: 999999,
|
|
||||||
}),
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
option: (provided: any) => ({
|
|
||||||
...provided,
|
|
||||||
padding: "10px 5px",
|
|
||||||
color: "#121418",
|
|
||||||
}),
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
input: (base: any) => ({
|
|
||||||
...base,
|
|
||||||
padding: 0,
|
|
||||||
}),
|
|
||||||
};
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
import React, { useState } from "react";
|
||||||
|
import opts from "../../../lib/src/default-text-options";
|
||||||
|
import ButtonSelect from "../ButtonSelect";
|
||||||
|
import { Story } from "@storybook/react";
|
||||||
|
|
||||||
|
type ButtonSelectProps = Parameters<typeof ButtonSelect>[0];
|
||||||
|
|
||||||
|
export default {
|
||||||
|
title: "ButtonSelect",
|
||||||
|
component: ButtonSelect,
|
||||||
|
};
|
||||||
|
|
||||||
|
const Template: Story<ButtonSelectProps> = (args) => {
|
||||||
|
const [value, setValue] = useState<string>("a");
|
||||||
|
return <div>
|
||||||
|
<ButtonSelect
|
||||||
|
{...args}
|
||||||
|
value={value}
|
||||||
|
handleChange={e => setValue(e)}
|
||||||
|
/>
|
||||||
|
</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Basic = Template.bind({});
|
||||||
|
Basic.args = {
|
||||||
|
options: [
|
||||||
|
{ value: "a", label: "Option A" },
|
||||||
|
{ value: "b", label: "Option B" },
|
||||||
|
{ value: "c", label: "Option C" },
|
||||||
|
],
|
||||||
|
};
|
|
@ -0,0 +1,26 @@
|
||||||
|
import React from "react";
|
||||||
|
import opts from "../../../lib/src/default-text-options";
|
||||||
|
import Examples from "../Examples";
|
||||||
|
import { Story } from "@storybook/react";
|
||||||
|
|
||||||
|
type ExamplesProps = Parameters<typeof Examples>[0];
|
||||||
|
|
||||||
|
export default {
|
||||||
|
title: "Examples",
|
||||||
|
component: Examples,
|
||||||
|
};
|
||||||
|
|
||||||
|
const Template: Story<ExamplesProps> = (args) => <div>
|
||||||
|
<p>Before example</p>
|
||||||
|
<Examples {...args} />
|
||||||
|
<p>After Example</p>
|
||||||
|
</div>;
|
||||||
|
|
||||||
|
export const Basic = Template.bind({});
|
||||||
|
Basic.args = {
|
||||||
|
opts: opts,
|
||||||
|
ex: [
|
||||||
|
{ p: "زه کور ته ځم", f: "zu kor ta dzum", e: "I'm going home" },
|
||||||
|
{ p: "مونږ کور ته ځو", f: "moonG kor ta dzoo", e: "We're going home" },
|
||||||
|
],
|
||||||
|
};
|
|
@ -1,132 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright (c) 2021 lingdocs.com
|
|
||||||
*
|
|
||||||
* This source code is licensed under the GPL3 license found in the
|
|
||||||
* LICENSE file in the root directory of this source tree.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
import TableCell from "./TableCell";
|
|
||||||
import {
|
|
||||||
psStringEquals,
|
|
||||||
isAllOne,
|
|
||||||
addEnglish,
|
|
||||||
} from "../../../lib/src/p-text-helpers";
|
|
||||||
import { isSentenceForm } from "../../../lib/src/misc-helpers";
|
|
||||||
import * as T from "../../../types";
|
|
||||||
import genderColors from "../gender-colors";
|
|
||||||
|
|
||||||
const genderAbbrev = (gender: "masc" | "fem" | undefined): " m." | " f." | "" =>
|
|
||||||
gender === "masc" ? " m." : gender === "fem" ? " f." : "";
|
|
||||||
|
|
||||||
const minifyTableGender = (
|
|
||||||
block: T.VerbBlock | T.ImperativeBlock
|
|
||||||
): Array<
|
|
||||||
| T.PersonLine
|
|
||||||
| {
|
|
||||||
masc: T.PersonLine;
|
|
||||||
fem: T.PersonLine;
|
|
||||||
}
|
|
||||||
> => {
|
|
||||||
// @ts-expect-error because
|
|
||||||
return block.reduce((table, person, i, src) => {
|
|
||||||
const isFem = i % 2 !== 0;
|
|
||||||
if (isFem) {
|
|
||||||
return table;
|
|
||||||
}
|
|
||||||
const femPersAhead = src[i + 1];
|
|
||||||
const femPersIsTheSame =
|
|
||||||
psStringEquals(person[0][0], femPersAhead[0][0]) &&
|
|
||||||
psStringEquals(person[1][0], femPersAhead[1][0]);
|
|
||||||
if (femPersAhead && !femPersIsTheSame) {
|
|
||||||
return [
|
|
||||||
...table,
|
|
||||||
{
|
|
||||||
masc: person,
|
|
||||||
fem: femPersAhead,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
}
|
|
||||||
return [...table, person];
|
|
||||||
}, []);
|
|
||||||
};
|
|
||||||
|
|
||||||
function VerbTable({
|
|
||||||
block,
|
|
||||||
textOptions,
|
|
||||||
english,
|
|
||||||
}: {
|
|
||||||
block: T.VerbBlock | T.ImperativeBlock | T.ArrayOneOrMore<T.PsString>;
|
|
||||||
english?: T.EnglishBlock | string;
|
|
||||||
textOptions: T.TextOptions;
|
|
||||||
}) {
|
|
||||||
const blockWEng = english ? addEnglish(english, block) : block;
|
|
||||||
if (
|
|
||||||
isSentenceForm(blockWEng) ||
|
|
||||||
isAllOne(blockWEng as T.VerbBlock | T.ImperativeBlock)
|
|
||||||
) {
|
|
||||||
const item = isSentenceForm(blockWEng)
|
|
||||||
? (block as unknown as T.ArrayOneOrMore<T.PsString>)
|
|
||||||
: (() => {
|
|
||||||
const b = block as T.ImperativeBlock | T.VerbBlock;
|
|
||||||
return b[0][0];
|
|
||||||
})();
|
|
||||||
return (
|
|
||||||
<table className="table text-center">
|
|
||||||
<tbody>
|
|
||||||
<tr>
|
|
||||||
<TableCell item={item} textOptions={textOptions} center noBorder />
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
const bl = blockWEng as T.VerbBlock | T.ImperativeBlock;
|
|
||||||
const b = minifyTableGender(bl);
|
|
||||||
return (
|
|
||||||
<table className="table mt-2" style={{ tableLayout: "fixed" }}>
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th scope="col" style={{ width: "3rem" }}>
|
|
||||||
Pers.
|
|
||||||
</th>
|
|
||||||
<th scope="col">Singular</th>
|
|
||||||
<th scope="col">Plural</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{b.reduce((rows: React.ReactNode[], person, i, arr) => {
|
|
||||||
function drawRow({
|
|
||||||
line,
|
|
||||||
gender,
|
|
||||||
}: {
|
|
||||||
line: T.PersonLine;
|
|
||||||
gender?: "masc" | "fem";
|
|
||||||
}) {
|
|
||||||
const pers = arr.length > 1 ? ["1st", "2nd", "3rd"] : ["2nd"];
|
|
||||||
const rowLabel = `${pers[i]}${genderAbbrev(gender)}`;
|
|
||||||
const color = !gender ? "inherit" : genderColors[gender];
|
|
||||||
return (
|
|
||||||
<tr key={`${i}${gender}`}>
|
|
||||||
<th scope="row" style={{ color }}>
|
|
||||||
{rowLabel}
|
|
||||||
</th>
|
|
||||||
<TableCell item={line[0]} textOptions={textOptions} />
|
|
||||||
<TableCell item={line[1]} textOptions={textOptions} />
|
|
||||||
</tr>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return "masc" in person
|
|
||||||
? [
|
|
||||||
...rows,
|
|
||||||
drawRow({ line: person.masc, gender: "masc" }),
|
|
||||||
drawRow({ line: person.fem, gender: "fem" }),
|
|
||||||
]
|
|
||||||
: [...rows, drawRow({ line: person })];
|
|
||||||
}, [])}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default VerbTable;
|
|
|
@ -1,56 +0,0 @@
|
||||||
import { getLength, getLong } from "../../../lib/src/p-text-helpers";
|
|
||||||
import * as T from "../../../types";
|
|
||||||
import Examples from "./Examples";
|
|
||||||
|
|
||||||
function CompiledPTextDisplay({
|
|
||||||
compiled,
|
|
||||||
opts,
|
|
||||||
justify,
|
|
||||||
onlyOne,
|
|
||||||
length,
|
|
||||||
}: {
|
|
||||||
compiled: {
|
|
||||||
ps: T.SingleOrLengthOpts<T.PsString[]>;
|
|
||||||
e?: string[] | undefined;
|
|
||||||
};
|
|
||||||
opts: T.TextOptions;
|
|
||||||
justify?: "left" | "right" | "center";
|
|
||||||
onlyOne?: boolean;
|
|
||||||
length?: "long" | "short";
|
|
||||||
}) {
|
|
||||||
function VariationLayer({ vs }: { vs: T.PsString[] }) {
|
|
||||||
return (
|
|
||||||
<div className="mb-2">
|
|
||||||
<Examples opts={opts} lineHeight={0}>
|
|
||||||
{vs}
|
|
||||||
</Examples>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
const ps = length ? getLength(compiled.ps, length) : compiled.ps;
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className={
|
|
||||||
justify === "left"
|
|
||||||
? "text-left"
|
|
||||||
: justify === "right"
|
|
||||||
? "text-right"
|
|
||||||
: "text-center"
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{onlyOne ? (
|
|
||||||
<VariationLayer vs={[getLong(ps)[0]]} />
|
|
||||||
) : "long" in ps ? (
|
|
||||||
<div>
|
|
||||||
<VariationLayer vs={ps.long} />
|
|
||||||
<VariationLayer vs={ps.short} />
|
|
||||||
{ps.mini && <VariationLayer vs={ps.mini} />}
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<VariationLayer vs={ps} />
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default CompiledPTextDisplay;
|
|
|
@ -1,30 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright (c) 2021 lingdocs.com
|
|
||||||
*
|
|
||||||
* This source code is licensed under the GPL3 license found in the
|
|
||||||
* LICENSE file in the root directory of this source tree.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
import Pashto from "./Pashto";
|
|
||||||
import Phonetics from "./Phonetics";
|
|
||||||
import * as T from "../../../types";
|
|
||||||
|
|
||||||
const InlinePs = ({
|
|
||||||
ps,
|
|
||||||
opts,
|
|
||||||
}: {
|
|
||||||
ps: T.PsString | (T.PsJSX & { e?: string });
|
|
||||||
opts: T.TextOptions;
|
|
||||||
}) => {
|
|
||||||
return (
|
|
||||||
<span>
|
|
||||||
<Pashto opts={opts} ps={ps} />
|
|
||||||
{opts.phonetics !== "none" && " - "}
|
|
||||||
<Phonetics opts={opts} ps={ps} />
|
|
||||||
{ps.e && <span className="text-muted"> ({ps.e})</span>}
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default InlinePs;
|
|
|
@ -1,40 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright (c) 2021 lingdocs.com
|
|
||||||
*
|
|
||||||
* This source code is licensed under the GPL3 license found in the
|
|
||||||
* LICENSE file in the root directory of this source tree.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { translatePhonetics } from "../../../lib/src/translate-phonetics";
|
|
||||||
import { psJSXMap } from "./jsx-map";
|
|
||||||
import * as T from "../../../types";
|
|
||||||
|
|
||||||
const Phonetics = ({
|
|
||||||
opts,
|
|
||||||
ps,
|
|
||||||
}: {
|
|
||||||
opts: T.TextOptions;
|
|
||||||
ps: T.PsJSX | T.PsString | string;
|
|
||||||
}) => {
|
|
||||||
if (opts.phonetics === "none") {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
const handleText = (f: string) =>
|
|
||||||
opts.phonetics === "lingdocs"
|
|
||||||
? f
|
|
||||||
: translatePhonetics(f, {
|
|
||||||
dialect: opts.dialect,
|
|
||||||
// @ts-expect-error - weird TS not picking up the elimination of "none herre"
|
|
||||||
system: opts.phonetics,
|
|
||||||
});
|
|
||||||
return (
|
|
||||||
<span className="f-text">
|
|
||||||
{typeof ps !== "string" && typeof ps.f !== "string"
|
|
||||||
? psJSXMap(ps as T.PsJSX, "f", ({ f }) => handleText(f))
|
|
||||||
: handleText(typeof ps === "string" ? ps : (ps.f as string))}
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Phonetics;
|
|
|
@ -1,36 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright (c) 2021 lingdocs.com
|
|
||||||
*
|
|
||||||
* This source code is licensed under the GPL3 license found in the
|
|
||||||
* LICENSE file in the root directory of this source tree.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
import Pashto from "./Pashto";
|
|
||||||
import Phonetics from "./Phonetics";
|
|
||||||
import * as T from "../../../types";
|
|
||||||
|
|
||||||
function SingleItemDisplay({
|
|
||||||
item,
|
|
||||||
textOptions,
|
|
||||||
english,
|
|
||||||
}: {
|
|
||||||
item: T.PsString;
|
|
||||||
textOptions: T.TextOptions;
|
|
||||||
english?: T.EnglishBlock | string;
|
|
||||||
}) {
|
|
||||||
const eng = Array.isArray(english) ? english[0][0] : english;
|
|
||||||
return (
|
|
||||||
<div className="text-center mt-3 mb-2">
|
|
||||||
<div>
|
|
||||||
<Pashto opts={textOptions} ps={item} />
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<Phonetics opts={textOptions} ps={item} />
|
|
||||||
</div>
|
|
||||||
{eng && <div className="text-muted">{eng}</div>}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default SingleItemDisplay;
|
|
|
@ -1,102 +0,0 @@
|
||||||
import { psJSXMap } from "./jsx-map";
|
|
||||||
|
|
||||||
test("psJSXMap should work with p as a target", () => {
|
|
||||||
const input = {
|
|
||||||
p: (
|
|
||||||
<>
|
|
||||||
زه کور ته <strong>ځم</strong>
|
|
||||||
</>
|
|
||||||
),
|
|
||||||
f: (
|
|
||||||
<>
|
|
||||||
zu kor ta <strong>dzum</strong>
|
|
||||||
</>
|
|
||||||
),
|
|
||||||
};
|
|
||||||
const output = psJSXMap(input, "p", (ps) => ps.p.replace(/ځ/g, "ز"));
|
|
||||||
expect(output).toEqual(
|
|
||||||
<>
|
|
||||||
زه کور ته <strong>زم</strong>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("psJSXMap should work with f as a target", () => {
|
|
||||||
const input = {
|
|
||||||
p: (
|
|
||||||
<>
|
|
||||||
زه کور ته <strong>ځم</strong>
|
|
||||||
</>
|
|
||||||
),
|
|
||||||
f: (
|
|
||||||
<>
|
|
||||||
zu kor ta <strong>dzum</strong>
|
|
||||||
</>
|
|
||||||
),
|
|
||||||
};
|
|
||||||
const output = psJSXMap(input, "f", (ps) => ps.f.replace(/dz/g, "z"));
|
|
||||||
expect(output).toEqual(
|
|
||||||
<>
|
|
||||||
zu kor ta <strong>zum</strong>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("psJSXMap should work with single child", () => {
|
|
||||||
const input = {
|
|
||||||
p: (
|
|
||||||
<div>
|
|
||||||
<span>کور</span>
|
|
||||||
</div>
|
|
||||||
),
|
|
||||||
f: (
|
|
||||||
<div>
|
|
||||||
<span>kor</span>
|
|
||||||
</div>
|
|
||||||
),
|
|
||||||
};
|
|
||||||
const output = psJSXMap(input, "f", (ps) => ps.f.replace(/k/g, "q"));
|
|
||||||
expect(output).toEqual(
|
|
||||||
<div>
|
|
||||||
<span>qor</span>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Helps prevent error logs blowing up as a result of expecting an error to be thrown,
|
|
||||||
* when using a library (such as enzyme)
|
|
||||||
*
|
|
||||||
* @param func Function that you would normally pass to `expect(func).toThrow()`
|
|
||||||
*/
|
|
||||||
export const expectToThrow = (
|
|
||||||
func: () => unknown,
|
|
||||||
error?: JestToErrorArg
|
|
||||||
): void => {
|
|
||||||
// Even though the error is caught, it still gets printed to the console
|
|
||||||
// so we mock that out to avoid the wall of red text.
|
|
||||||
const spy = jest.spyOn(console, "error");
|
|
||||||
spy.mockImplementation(() => {});
|
|
||||||
|
|
||||||
expect(func).toThrow(error);
|
|
||||||
|
|
||||||
spy.mockRestore();
|
|
||||||
};
|
|
||||||
|
|
||||||
type JestToErrorArg = Parameters<
|
|
||||||
jest.Matchers<unknown, () => unknown>["toThrow"]
|
|
||||||
>[0];
|
|
||||||
|
|
||||||
test("psJSXMap will error if given an uneven/unbalanced pair of JSX Elements", () => {
|
|
||||||
expectToThrow(() => {
|
|
||||||
const input = {
|
|
||||||
p: (
|
|
||||||
<>
|
|
||||||
زه کور ته <strong>ځم</strong>
|
|
||||||
</>
|
|
||||||
),
|
|
||||||
f: <>zu kor ta dzum</>,
|
|
||||||
};
|
|
||||||
psJSXMap(input, "p", (ps) => ps.p.replace(/ځ/g, "ز"));
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,77 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright (c) 2021 lingdocs.com
|
|
||||||
*
|
|
||||||
* This source code is licensed under the GPL3 license found in the
|
|
||||||
* LICENSE file in the root directory of this source tree.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
import * as T from "../../../types";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Allows PsString transforming methods to be applied to a Pashto/Phonetics set of JSX elements
|
|
||||||
* outputs a single part of the pair (p or f) with the transform function applied to all the text
|
|
||||||
* within
|
|
||||||
* eg: { p: <>زه <strong>ځم</strong></>, f : <>zu <strong>dzum</strong></> }
|
|
||||||
*
|
|
||||||
* @param ps
|
|
||||||
* @param target
|
|
||||||
* @param dealWithString
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
export function psJSXMap(
|
|
||||||
ps: T.PsJSX,
|
|
||||||
target: "p" | "f",
|
|
||||||
dealWithString: (ps: T.PsString) => string | JSX.Element
|
|
||||||
): JSX.Element {
|
|
||||||
const base = ps[target];
|
|
||||||
const sec = ps[target === "p" ? "f" : "p"];
|
|
||||||
try {
|
|
||||||
return {
|
|
||||||
...base,
|
|
||||||
props: {
|
|
||||||
// TODO: could make this a lot cleaner/less repetitive by following a better recursive formula
|
|
||||||
// see https://adueck.github.io/blog/recursively-modify-text-jsx-react/
|
|
||||||
...base.props,
|
|
||||||
children:
|
|
||||||
typeof base.props.children === "string"
|
|
||||||
? dealWithString({
|
|
||||||
p: (target === "p" ? base : sec).props.children,
|
|
||||||
f: (target === "p" ? sec : base).props.children,
|
|
||||||
})
|
|
||||||
: base.props.children.map
|
|
||||||
? base.props.children.map((x: string | JSX.Element, i: number) =>
|
|
||||||
typeof x === "string"
|
|
||||||
? dealWithString({
|
|
||||||
p: target === "p" ? x : sec.props.children[i],
|
|
||||||
f: target === "p" ? sec.props.children[i] : x,
|
|
||||||
})
|
|
||||||
: psJSXMap(
|
|
||||||
{
|
|
||||||
p: target === "p" ? x : sec.props.children[i],
|
|
||||||
f: target === "p" ? sec.props.children[i] : x,
|
|
||||||
},
|
|
||||||
target,
|
|
||||||
dealWithString
|
|
||||||
)
|
|
||||||
)
|
|
||||||
: typeof base.props.children === "string"
|
|
||||||
? dealWithString({
|
|
||||||
p: target === "p" ? base.props.children : sec.props.children,
|
|
||||||
f: target === "p" ? sec.props.children : base.props.children,
|
|
||||||
})
|
|
||||||
: psJSXMap(
|
|
||||||
{
|
|
||||||
p: target === "p" ? base.props.children : sec.props.children,
|
|
||||||
f: target === "p" ? sec.props.children : base.props.children,
|
|
||||||
},
|
|
||||||
target,
|
|
||||||
dealWithString
|
|
||||||
),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
} catch (e) {
|
|
||||||
console.error(e);
|
|
||||||
throw new Error("error mapping out PsJSX - unbalanced trees");
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,22 +1,23 @@
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
|
|
||||||
type SaveableData = string | number | object | boolean | undefined | null;
|
type SaveableData = string | number | object | boolean | undefined | null
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* replacement from the React useState hook that will persist the state in local storage
|
* replacement from the React useState hook that will persist the state in local storage
|
||||||
*
|
*
|
||||||
* @param defaultValue The default value to use if there was nothing saved previously OR
|
* @param defaultValue The default value to use if there was nothing saved previously OR
|
||||||
* a function that will take the saved value and return a modified new value to start with
|
* a function that will take the saved value and return a modified new value to start with
|
||||||
* @param key a key for saving the state in localStorage
|
* @param key a key for saving the state in localStorage
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export default function useStickyState<T extends SaveableData>(
|
export default function useStickyState<T extends SaveableData>(defaultValue: T | ((old: T | undefined) => T), key: string): [
|
||||||
defaultValue: T | ((old: T | undefined) => T),
|
value: T,
|
||||||
key: string
|
setValue: React.Dispatch<React.SetStateAction<T>>,
|
||||||
): [value: T, setValue: React.Dispatch<React.SetStateAction<T>>] {
|
] {
|
||||||
const [value, setValue] = useState<T>(() => {
|
const [value, setValue] = useState<T>(() => {
|
||||||
const v =
|
const v = typeof window === "undefined"
|
||||||
typeof window === "undefined" ? null : window.localStorage.getItem(key);
|
? null
|
||||||
|
: window.localStorage.getItem(key);
|
||||||
// nothing saved
|
// nothing saved
|
||||||
if (v === null) {
|
if (v === null) {
|
||||||
if (typeof defaultValue === "function") {
|
if (typeof defaultValue === "function") {
|
||||||
|
@ -33,16 +34,14 @@ export default function useStickyState<T extends SaveableData>(
|
||||||
return old;
|
return old;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("error parsting saved state from stickState");
|
console.error("error parsting saved state from stickState");
|
||||||
console.error(e);
|
return (typeof defaultValue === "function")
|
||||||
return typeof defaultValue === "function"
|
|
||||||
? defaultValue(undefined)
|
? defaultValue(undefined)
|
||||||
: defaultValue;
|
: defaultValue;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// eslint-disable-next-line
|
if (typeof window !== undefined) {
|
||||||
if (!typeof window !== undefined) {
|
|
||||||
window.localStorage.setItem(key, JSON.stringify(value));
|
window.localStorage.setItem(key, JSON.stringify(value));
|
||||||
}
|
}
|
||||||
}, [key, value]);
|
}, [key, value]);
|
||||||
|
@ -55,11 +54,15 @@ export function useStickyReducer<T extends SaveableData, A>(
|
||||||
defaultValue: T | ((old: T | undefined) => T),
|
defaultValue: T | ((old: T | undefined) => T),
|
||||||
key: string,
|
key: string,
|
||||||
sendAlert?: (msg: string) => void,
|
sendAlert?: (msg: string) => void,
|
||||||
onChange?: (state: T) => void
|
onChange?: (state: T) => void,
|
||||||
): [T, (action: A) => void, ((msg: string) => void) | undefined] {
|
): [
|
||||||
|
T,
|
||||||
|
(action: A) => void,
|
||||||
|
((msg: string) => void) | undefined,
|
||||||
|
] {
|
||||||
const [state, unsafeSetState] = useStickyState<T>(defaultValue, key);
|
const [state, unsafeSetState] = useStickyState<T>(defaultValue, key);
|
||||||
function adjustState(action: A) {
|
function adjustState(action: A) {
|
||||||
unsafeSetState((oldState) => {
|
unsafeSetState(oldState => {
|
||||||
const newState = reducer(oldState, action, sendAlert);
|
const newState = reducer(oldState, action, sendAlert);
|
||||||
if (onChange) {
|
if (onChange) {
|
||||||
onChange(newState);
|
onChange(newState);
|
||||||
|
|
|
@ -13,7 +13,7 @@ import {
|
||||||
noPersInfs,
|
noPersInfs,
|
||||||
} from "../../../lib/src/misc-helpers";
|
} from "../../../lib/src/misc-helpers";
|
||||||
import VerbInfoItemDisplay from "./VerbInfoItemDisplay";
|
import VerbInfoItemDisplay from "./VerbInfoItemDisplay";
|
||||||
import PersonInfsPicker from "../selects/PersInfsPicker";
|
import PersonInfsPicker from "../PersInfsPicker";
|
||||||
import VerbTypeInfo from "./VerbTypeInfo";
|
import VerbTypeInfo from "./VerbTypeInfo";
|
||||||
import Hider from "../Hider";
|
import Hider from "../Hider";
|
||||||
import * as T from "../../../types";
|
import * as T from "../../../types";
|
||||||
|
|
|
@ -7,91 +7,75 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import ButtonSelect from "../selects/ButtonSelect";
|
import ButtonSelect from "../ButtonSelect";
|
||||||
import Pashto from "../text-display/Pashto";
|
import Pashto from "../Pashto";
|
||||||
import Phonetics from "../text-display/Phonetics";
|
import Phonetics from "../Phonetics";
|
||||||
import * as T from "../../../types";
|
import * as T from "../../../types";
|
||||||
import { concatPsString } from "../../../lib/src/p-text-helpers";
|
import { concatPsString } from "../../../lib/src/p-text-helpers";
|
||||||
|
|
||||||
type InputItem = T.SingleOrLengthOpts<T.PsString | [T.PsString, T.PsString]>;
|
type InputItem = T.SingleOrLengthOpts<T.PsString | [T.PsString, T.PsString]>;
|
||||||
|
|
||||||
function VerbInfoItemDisplay({
|
function VerbInfoItemDisplay({ item, textOptions, tails }: {
|
||||||
item,
|
item: InputItem,
|
||||||
textOptions,
|
textOptions: T.TextOptions,
|
||||||
tails,
|
tails?: boolean,
|
||||||
}: {
|
|
||||||
item: InputItem;
|
|
||||||
textOptions: T.TextOptions;
|
|
||||||
tails?: boolean;
|
|
||||||
}) {
|
}) {
|
||||||
const [length, setLength] = useState<T.Length>("long");
|
const [length, setLength] = useState<T.Length>("long");
|
||||||
const getL = (x: InputItem): T.PsString | [T.PsString, T.PsString] =>
|
const getL = (x: InputItem): T.PsString | [T.PsString, T.PsString] => (
|
||||||
"long" in x ? x[length] || x.short : x;
|
"long" in x
|
||||||
useEffect(() => {
|
? x[length] || x.short
|
||||||
setLength((l) => (l === "mini" && !("mini" in item) ? "short" : l));
|
: x
|
||||||
}, [item]);
|
);
|
||||||
// const lengthsAvailable = "long" in item
|
useEffect(() => {
|
||||||
// ? [...["long", "short"], ..."mini" in item ? ["mini"] : []]
|
setLength(l => (l === "mini" && !("mini" in item)) ? "short" : l);
|
||||||
// : [];
|
}, [item]);
|
||||||
const text = getL(item);
|
// const lengthsAvailable = "long" in item
|
||||||
|
// ? [...["long", "short"], ..."mini" in item ? ["mini"] : []]
|
||||||
|
// : [];
|
||||||
|
const text = getL(item);
|
||||||
|
|
||||||
function addTails(text: T.PsString): T.PsString {
|
function addTails(text: T.PsString): T.PsString {
|
||||||
return concatPsString(text, { p: "ـ", f: "–" });
|
return concatPsString(text, { p: "ـ", f: "–"});
|
||||||
}
|
}
|
||||||
return (
|
return <>
|
||||||
<>
|
{Array.isArray(text) ?
|
||||||
{Array.isArray(text) ? (
|
<div>
|
||||||
<div>
|
<div className="text-center" dir="rtl">
|
||||||
<div className="text-center" dir="rtl">
|
<Pashto opts={textOptions}>{text[0]}</Pashto>
|
||||||
<Pashto opts={textOptions} ps={text[0]} />
|
<span className="mx-1"> __ </span>
|
||||||
<span className="mx-1"> __ </span>
|
<Pashto opts={textOptions}>{tails ? addTails(text[1]) : text[1]}</Pashto>
|
||||||
<Pashto
|
</div>
|
||||||
opts={textOptions}
|
<div className="text-center">
|
||||||
ps={tails ? addTails(text[1]) : text[1]}
|
<Phonetics opts={textOptions}>{text[0]}</Phonetics>
|
||||||
|
<span className="mx-1"> __ </span>
|
||||||
|
<Phonetics opts={textOptions}>{tails ? addTails(text[1]) : text[1]}</Phonetics>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
:
|
||||||
|
<div>
|
||||||
|
<div className="text-center">
|
||||||
|
<Pashto opts={textOptions}>{tails ? addTails(text) : text}</Pashto>
|
||||||
|
</div>
|
||||||
|
<div className="text-center">
|
||||||
|
<Phonetics opts={textOptions}>{tails ? addTails(text) : text}</Phonetics>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
{"long" in item && <div className="mt-2 text-center">
|
||||||
|
<ButtonSelect
|
||||||
|
xSmall
|
||||||
|
options={[
|
||||||
|
{ label: "Long", value: "long" },
|
||||||
|
{ label: "Short", value: "short" },
|
||||||
|
..."mini" in item ? [{
|
||||||
|
label: "Mini", value: "mini",
|
||||||
|
}] : [],
|
||||||
|
]}
|
||||||
|
value={length}
|
||||||
|
handleChange={(p) => setLength(p as T.Length)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>}
|
||||||
<div className="text-center">
|
</>;
|
||||||
<Phonetics opts={textOptions} ps={text[0]} />
|
|
||||||
<span className="mx-1"> __ </span>
|
|
||||||
<Phonetics
|
|
||||||
opts={textOptions}
|
|
||||||
ps={tails ? addTails(text[1]) : text[1]}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<div>
|
|
||||||
<div className="text-center">
|
|
||||||
<Pashto opts={textOptions} ps={tails ? addTails(text) : text} />
|
|
||||||
</div>
|
|
||||||
<div className="text-center">
|
|
||||||
<Phonetics opts={textOptions} ps={tails ? addTails(text) : text} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{"long" in item && (
|
|
||||||
<div className="mt-2 text-center">
|
|
||||||
<ButtonSelect
|
|
||||||
xSmall
|
|
||||||
options={[
|
|
||||||
{ label: "Long", value: "long" },
|
|
||||||
{ label: "Short", value: "short" },
|
|
||||||
...("mini" in item
|
|
||||||
? [
|
|
||||||
{
|
|
||||||
label: "Mini",
|
|
||||||
value: "mini",
|
|
||||||
},
|
|
||||||
]
|
|
||||||
: []),
|
|
||||||
]}
|
|
||||||
value={length}
|
|
||||||
handleChange={(p) => setLength(p as T.Length)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default VerbInfoItemDisplay;
|
export default VerbInfoItemDisplay;
|
|
@ -7,10 +7,12 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import InlinePs from "../text-display/InlinePs";
|
import InlinePs from "../InlinePs";
|
||||||
import Pashto from "../text-display/Pashto";
|
import Pashto from "../Pashto";
|
||||||
import Phonetics from "../text-display/Phonetics";
|
import Phonetics from "../Phonetics";
|
||||||
import { makePsString } from "../../../lib/src/accent-and-ps-utils";
|
import {
|
||||||
|
makePsString,
|
||||||
|
} from "../../../lib/src/accent-and-ps-utils";
|
||||||
import { Modal } from "react-bootstrap";
|
import { Modal } from "react-bootstrap";
|
||||||
import stativeCompTrans from "./stative-compound-transitive.svg";
|
import stativeCompTrans from "./stative-compound-transitive.svg";
|
||||||
import stativeCompIntrans from "./stative-compound-intransitive.svg";
|
import stativeCompIntrans from "./stative-compound-intransitive.svg";
|
||||||
|
@ -25,390 +27,271 @@ import gramTransitiveDiagramPresent from "./grammatically-transitive-present.svg
|
||||||
import gramTransitiveDiagramPast from "./grammatically-transitive-past.svg";
|
import gramTransitiveDiagramPast from "./grammatically-transitive-past.svg";
|
||||||
import * as T from "../../../types";
|
import * as T from "../../../types";
|
||||||
|
|
||||||
function CompoundFormula({ a, b }: { a: React.ReactNode; b: React.ReactNode }) {
|
function CompoundFormula({ a, b }: {
|
||||||
return (
|
a: React.ReactNode,
|
||||||
<div className="row align-items-center mb-3">
|
b: React.ReactNode,
|
||||||
<div className="col-5 text-center">{a}</div>
|
}) {
|
||||||
<div className="col-2 text-center" style={{ fontSize: "2.5rem" }}>
|
return (
|
||||||
<strong>+</strong>
|
<div className="row align-items-center mb-3">
|
||||||
</div>
|
<div className="col-5 text-center">
|
||||||
<div className="col-5 text-center">{b}</div>
|
{a}
|
||||||
</div>
|
</div>
|
||||||
);
|
<div className="col-2 text-center" style={{ fontSize: "2.5rem" }}>
|
||||||
|
<strong>+</strong>
|
||||||
|
</div>
|
||||||
|
<div className="col-5 text-center">
|
||||||
|
{b}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function ExplanationImage({ src, alt }: { src: string; alt?: string }) {
|
function ExplanationImage({ src, alt }: {
|
||||||
return (
|
src: any,
|
||||||
<img
|
alt?: string,
|
||||||
src={src}
|
}) {
|
||||||
alt={alt}
|
return (
|
||||||
className="mx-auto d-block mb-2"
|
<img
|
||||||
style={{ maxWidth: "100%" }}
|
src={src}
|
||||||
/>
|
alt={alt}
|
||||||
);
|
className="mx-auto d-block mb-2"
|
||||||
|
style={{ maxWidth: "100%" }}
|
||||||
|
/>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const typeInfo = (type: string, opts: T.TextOptions) => {
|
const typeInfo = (
|
||||||
return type === "simple" ? (
|
type: string,
|
||||||
<>
|
opts: T.TextOptions,
|
||||||
<p>
|
) => {
|
||||||
A <strong>simple verb</strong> is a verb that is just one piece. It
|
return type === "simple"
|
||||||
can't be broken down into different words with different meanings, like
|
? <>
|
||||||
compound verbs can.
|
<p>A <strong>simple verb</strong> is a verb that is just one piece. It can't be broken down into different words with different meanings, like compound verbs can.</p>
|
||||||
</p>
|
|
||||||
</>
|
</>
|
||||||
) : type === "stative compound" ? (
|
: type === "stative compound"
|
||||||
<>
|
? <>
|
||||||
<p>
|
<p>A <strong>stative compound</strong> talks about <strong>something changing from one state to another</strong>.</p>
|
||||||
A <strong>stative compound</strong> talks about{" "}
|
<CompoundFormula
|
||||||
<strong>something changing from one state to another</strong>.
|
a={<>
|
||||||
</p>
|
<div style={{ fontSize: "larger" }}>
|
||||||
<CompoundFormula
|
<strong>Complement</strong>
|
||||||
a={
|
</div>
|
||||||
<>
|
<div>Adjective, Noun, or Adverb</div>
|
||||||
<div style={{ fontSize: "larger" }}>
|
</>}
|
||||||
<strong>Complement</strong>
|
b={<>
|
||||||
</div>
|
<div style={{ fontSize: "larger" }}>
|
||||||
<div>Adjective, Noun, or Adverb</div>
|
<strong>Aux. Verb</strong>
|
||||||
</>
|
</div>
|
||||||
}
|
<div>
|
||||||
b={
|
<div className="small text-muted">With transitive:</div>
|
||||||
<>
|
<div><InlinePs opts={opts}>{{ p: "کول", f: "kawul" }}</InlinePs> (to make)</div>
|
||||||
<div style={{ fontSize: "larger" }}>
|
<div className="small text-muted">With intransitive:</div>
|
||||||
<strong>Aux. Verb</strong>
|
<div><InlinePs opts={opts}>{{ p: "کېدل", f: "kedul" }}</InlinePs> (to become)</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
</>}
|
||||||
<div className="small text-muted">With transitive:</div>
|
/>
|
||||||
<div>
|
<h4>Transitive stative compounds</h4>
|
||||||
<InlinePs opts={opts} ps={{ p: "کول", f: "kawul" }} /> (to make)
|
<ExplanationImage src={stativeCompTrans} />
|
||||||
</div>
|
<h4>Intransitive stative compounds</h4>
|
||||||
<div className="small text-muted">With intransitive:</div>
|
<ExplanationImage src={stativeCompIntrans} />
|
||||||
<div>
|
<p>It's important to remember that the aux. verbs used for these compounds <InlinePs opts={opts}>{{ p: "کول", f: "kawul" }}</InlinePs> (to make) and <InlinePs opts={opts}>{{ p: "کېدل", f: "kedul" }}</InlinePs> (to become) will never, ever take a <InlinePs opts={opts}>{{ p: "و", f: "oo" }}</InlinePs> prefix. 🚫</p>
|
||||||
<InlinePs opts={opts} ps={{ p: "کېدل", f: "kedul" }} /> (to
|
<h5>A couple of other notes:</h5>
|
||||||
become)
|
<p>In the imperfective aspect, the complement and aux. verb are often fused together. For example: <InlinePs opts={opts}>{{ p: "پوخ", f: "pokh" }}</InlinePs> + <InlinePs opts={opts}>{{ p: "کول", f: "kawul" }}</InlinePs> = <InlinePs opts={opts}>{{ p: "بخول", f: "pakhawul" }}</InlinePs>. But they always stay broken apart in the perfective aspect.</p>
|
||||||
</div>
|
<p>When complements are nouns or adverbs, they act (and carry a meaning) almost as if they were adjectives.</p>
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<h4>Transitive stative compounds</h4>
|
|
||||||
<ExplanationImage src={stativeCompTrans} />
|
|
||||||
<h4>Intransitive stative compounds</h4>
|
|
||||||
<ExplanationImage src={stativeCompIntrans} />
|
|
||||||
<p>
|
|
||||||
It's important to remember that the aux. verbs used for these compounds{" "}
|
|
||||||
<InlinePs opts={opts} ps={{ p: "کول", f: "kawul" }} /> (to make) and{" "}
|
|
||||||
<InlinePs opts={opts} ps={{ p: "کېدل", f: "kedul" }} /> (to become) will
|
|
||||||
never, ever take a <InlinePs opts={opts} ps={{ p: "و", f: "oo" }} />{" "}
|
|
||||||
prefix. 🚫
|
|
||||||
</p>
|
|
||||||
<h5>A couple of other notes:</h5>
|
|
||||||
<p>
|
|
||||||
In the imperfective aspect, the complement and aux. verb are often fused
|
|
||||||
together. For example:{" "}
|
|
||||||
<InlinePs opts={opts} ps={{ p: "پوخ", f: "pokh" }} /> +{" "}
|
|
||||||
<InlinePs opts={opts} ps={{ p: "کول", f: "kawul" }} /> ={" "}
|
|
||||||
<InlinePs opts={opts} ps={{ p: "بخول", f: "pakhawul" }} /> . But they
|
|
||||||
always stay broken apart in the perfective aspect.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
When complements are nouns or adverbs, they act (and carry a meaning)
|
|
||||||
almost as if they were adjectives.
|
|
||||||
</p>
|
|
||||||
</>
|
</>
|
||||||
) : type === "dynamic compound" ? (
|
: type === "dynamic compound"
|
||||||
<>
|
? <>
|
||||||
<p>
|
<p>A <strong>dynamic compound</strong> talks about <strong>some action being done</strong>.</p>
|
||||||
A <strong>dynamic compound</strong> talks about{" "}
|
<CompoundFormula
|
||||||
<strong>some action being done</strong>.
|
a={<>
|
||||||
</p>
|
<div style={{ fontSize: "larger" }}>
|
||||||
<CompoundFormula
|
<strong>Complement</strong>
|
||||||
a={
|
</div>
|
||||||
<>
|
<div>Noun (action or activity)</div>
|
||||||
<div style={{ fontSize: "larger" }}>
|
</>}
|
||||||
<strong>Complement</strong>
|
b={<>
|
||||||
</div>
|
<div style={{ fontSize: "larger" }}>
|
||||||
<div>Noun (action or activity)</div>
|
<strong>Aux. Verb</strong>
|
||||||
</>
|
</div>
|
||||||
}
|
</>}
|
||||||
b={
|
/>
|
||||||
<>
|
<h4>Transitive Dynamic Compounds</h4>
|
||||||
<div style={{ fontSize: "larger" }}>
|
<p>These talk about someone doing an action or activity.</p>
|
||||||
<strong>Aux. Verb</strong>
|
<ExplanationImage src={dynamicCompTrans} />
|
||||||
</div>
|
<p>There is a subject/agent doing the action, and <em>the action or activity is the object of the sentence</em>. It's important to remember that with these compounds, the object of the sentence is included inside the compound, and you can't have any other object in the sentence.</p>
|
||||||
</>
|
<h4>Intransitive Dynamic Compounds</h4>
|
||||||
}
|
<p>These talk about an action or activity happening.</p>
|
||||||
/>
|
<ExplanationImage src={dynamicCompIntrans} />
|
||||||
<h4>Transitive Dynamic Compounds</h4>
|
<p>Here the complement, the activity included in the compound, is the subject of the sentence.</p>
|
||||||
<p>These talk about someone doing an action or activity.</p>
|
<h6>Other notes:</h6>
|
||||||
<ExplanationImage src={dynamicCompTrans} />
|
<p>Dynamic compounds made with <InlinePs opts={opts}>{{ p: "کول", f: "kawul" }}</InlinePs> (to do) will also have an intransitive version made with <InlinePs opts={opts}>{{ p: "کېدل", f: "kedul" }}</InlinePs> (to happen).</p>
|
||||||
<p>
|
<p>Dynamic compounds made with <InlinePs opts={opts}>{{ p: "کول", f: "kawul" }}</InlinePs> (to do) or <InlinePs opts={opts}>{{ p: "کېدل", f: "kedul" }}</InlinePs> (to happen) will <em>always</em> take a <InlinePs opts={opts}>{{ p: "و", f: "oo" }}</InlinePs> prefix in the perfective aspect.</p>
|
||||||
There is a subject/agent doing the action, and{" "}
|
|
||||||
<em>the action or activity is the object of the sentence</em>. It's
|
|
||||||
important to remember that with these compounds, the object of the
|
|
||||||
sentence is included inside the compound, and you can't have any other
|
|
||||||
object in the sentence.
|
|
||||||
</p>
|
|
||||||
<h4>Intransitive Dynamic Compounds</h4>
|
|
||||||
<p>These talk about an action or activity happening.</p>
|
|
||||||
<ExplanationImage src={dynamicCompIntrans} />
|
|
||||||
<p>
|
|
||||||
Here the complement, the activity included in the compound, is the
|
|
||||||
subject of the sentence.
|
|
||||||
</p>
|
|
||||||
<h6>Other notes:</h6>
|
|
||||||
<p>
|
|
||||||
Dynamic compounds made with{" "}
|
|
||||||
<InlinePs opts={opts} ps={{ p: "کول", f: "kawul" }} /> (to do) will also
|
|
||||||
have an intransitive version made with{" "}
|
|
||||||
<InlinePs opts={opts} ps={{ p: "کېدل", f: "kedul" }} /> (to happen).
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
Dynamic compounds made with{" "}
|
|
||||||
<InlinePs opts={opts} ps={{ p: "کول", f: "kawul" }} /> (to do) or{" "}
|
|
||||||
<InlinePs opts={opts} ps={{ p: "کېدل", f: "kedul" }} /> (to happen) will{" "}
|
|
||||||
<em>always</em> take a <InlinePs opts={opts} ps={{ p: "و", f: "oo" }} />{" "}
|
|
||||||
prefix in the perfective aspect.
|
|
||||||
</p>
|
|
||||||
</>
|
</>
|
||||||
) : type === "generative stative compound" ? (
|
: type === "generative stative compound"
|
||||||
<>
|
? <>
|
||||||
<p>
|
<p><strong>Generative stative compounds</strong> are strange compounds that seem to behave like a cross between a stative compound and a dynamic compound.</p>
|
||||||
<strong>Generative stative compounds</strong> are strange compounds that
|
<ul>
|
||||||
seem to behave like a cross between a stative compound and a dynamic
|
<li>The object is included in the compound... like with dynamic compounds</li>
|
||||||
compound.
|
<li>But they also use <InlinePs opts={opts}>{{ p: "کول", f: "kawul" }}</InlinePs> (to make), the verb that never gets the <InlinePs opts={opts}>{{ p: "و", f: "oo" }}</InlinePs> prefix... like with stative compounds</li>
|
||||||
</p>
|
</ul>
|
||||||
<ul>
|
<p>This may seem quite confusing at first.</p>
|
||||||
<li>
|
<p>We can think of these verbs as <strong>stative compounds</strong> but <strong>with an implied complement</strong> meaning something like "existing." So they talk about some thing being created or brought out into existence.</p>
|
||||||
The object is included in the compound... like with dynamic compounds
|
<h4>Transitive Generative Stative Compounds</h4>
|
||||||
</li>
|
<ExplanationImage src={generativeStatCompTrans} />
|
||||||
<li>
|
<h4>Intransitive Generative Stative Compounds</h4>
|
||||||
But they also use{" "}
|
<ExplanationImage src={generativeStatCompIntrans} />
|
||||||
<InlinePs opts={opts} ps={{ p: "کول", f: "kawul" }} /> (to make), the
|
|
||||||
verb that never gets the{" "}
|
|
||||||
<InlinePs opts={opts} ps={{ p: "و", f: "oo" }} /> prefix... like with
|
|
||||||
stative compounds
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
<p>This may seem quite confusing at first.</p>
|
|
||||||
<p>
|
|
||||||
We can think of these verbs as <strong>stative compounds</strong> but{" "}
|
|
||||||
<strong>with an implied complement</strong> meaning something like
|
|
||||||
"existing." So they talk about some thing being created or brought out
|
|
||||||
into existence.
|
|
||||||
</p>
|
|
||||||
<h4>Transitive Generative Stative Compounds</h4>
|
|
||||||
<ExplanationImage src={generativeStatCompTrans} />
|
|
||||||
<h4>Intransitive Generative Stative Compounds</h4>
|
|
||||||
<ExplanationImage src={generativeStatCompIntrans} />
|
|
||||||
</>
|
</>
|
||||||
) : null;
|
: null;
|
||||||
};
|
};
|
||||||
|
|
||||||
const transitivityInfo = (type: string) => {
|
const transitivityInfo = (
|
||||||
return type === "transitive" ? (
|
type: string,
|
||||||
<>
|
textOptions: T.TextOptions,
|
||||||
<p>
|
) => {
|
||||||
<strong>Transitive</strong> verbs are{" "}
|
return type === "transitive"
|
||||||
<strong>verbs that take an object</strong>.
|
? <>
|
||||||
</p>
|
<p><strong>Transitive</strong> verbs are <strong>verbs that take an object</strong>.</p>
|
||||||
<p>
|
<p>Transitive verbs are especially difficult because <strong>they work totally differently in the past tense</strong>. (They are ergative in the past tense only.) This takes a lot of hard work for the learner to get used to!</p>
|
||||||
Transitive verbs are especially difficult because{" "}
|
<h4>In all non-past forms</h4>
|
||||||
<strong>they work totally differently in the past tense</strong>. (They
|
<ul>
|
||||||
are ergative in the past tense only.) This takes a lot of hard work for
|
<li>The subject is not inflected</li>
|
||||||
the learner to get used to!
|
<li>The object is not inflected, or it can alse be an enclitic (mini) pronoun (exception: the object is inflected when it's a 1st or 2nd person pronoun)</li>
|
||||||
</p>
|
<li>The verb agrees with the <strong>subject</strong></li>
|
||||||
<h4>In all non-past forms</h4>
|
</ul>
|
||||||
<ul>
|
<ExplanationImage src={transitiveDiagramPresent} />
|
||||||
<li>The subject is not inflected</li>
|
<h4>In the past tense</h4>
|
||||||
<li>
|
<ul>
|
||||||
The object is not inflected, or it can alse be an enclitic (mini)
|
<li>The subject is inflected, or it can be an enclitic (mini) pronoun</li>
|
||||||
pronoun (exception: the object is inflected when it's a 1st or 2nd
|
<li>The object is not inflected</li>
|
||||||
person pronoun)
|
<li>The verb agrees with the <strong>object</strong></li>
|
||||||
</li>
|
</ul>
|
||||||
<li>
|
<ExplanationImage src={transitiveDiagramPast} />
|
||||||
The verb agrees with the <strong>subject</strong>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
<ExplanationImage src={transitiveDiagramPresent} />
|
|
||||||
<h4>In the past tense</h4>
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
The subject is inflected, or it can be an enclitic (mini) pronoun
|
|
||||||
</li>
|
|
||||||
<li>The object is not inflected</li>
|
|
||||||
<li>
|
|
||||||
The verb agrees with the <strong>object</strong>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
<ExplanationImage src={transitiveDiagramPast} />
|
|
||||||
</>
|
</>
|
||||||
) : type === "intransitive" ? (
|
: type === "intransitive"
|
||||||
<>
|
? <>
|
||||||
<p>
|
<p><strong>Intransitive</strong> verbs are <strong>verbs that don't take an object</strong>. They only take a subject, which is a person or thing that experiences the action of the verb.</p>
|
||||||
<strong>Intransitive</strong> verbs are{" "}
|
<p>- The subject is always a <em>uninflected/plain</em> noun or pronoun.</p>
|
||||||
<strong>verbs that don't take an object</strong>. They only take a
|
<p>- The verb always agrees with the subject.</p>
|
||||||
subject, which is a person or thing that experiences the action of the
|
<ExplanationImage src={intransitiveDiagram} />
|
||||||
verb.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
- The subject is always a <em>uninflected/plain</em> noun or pronoun.
|
|
||||||
</p>
|
|
||||||
<p>- The verb always agrees with the subject.</p>
|
|
||||||
<ExplanationImage src={intransitiveDiagram} />
|
|
||||||
</>
|
</>
|
||||||
) : type === "grammatically transitive" ? (
|
: type === "grammatically transitive"
|
||||||
<>
|
? <>
|
||||||
<p>
|
<p><strong>Gramatically transitive</strong> verbs are <strong>verbs that don't appear to have an object, but actually work as if they do</strong>!</p>
|
||||||
<strong>Gramatically transitive</strong> verbs are{" "}
|
<p>These work just like transitive verbs, except that the object is an implied (unspoken) 3rd person masculine plural entity.</p>
|
||||||
<strong>
|
<h4>In all non-past forms</h4>
|
||||||
verbs that don't appear to have an object, but actually work as if
|
<ExplanationImage src={gramTransitiveDiagramPresent} />
|
||||||
they do
|
<h4>In the past tense</h4>
|
||||||
</strong>
|
<ExplanationImage src={gramTransitiveDiagramPast} />
|
||||||
!
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
These work just like transitive verbs, except that the object is an
|
|
||||||
implied (unspoken) 3rd person masculine plural entity.
|
|
||||||
</p>
|
|
||||||
<h4>In all non-past forms</h4>
|
|
||||||
<ExplanationImage src={gramTransitiveDiagramPresent} />
|
|
||||||
<h4>In the past tense</h4>
|
|
||||||
<ExplanationImage src={gramTransitiveDiagramPast} />
|
|
||||||
</>
|
</>
|
||||||
) : null;
|
: null;
|
||||||
};
|
|
||||||
|
|
||||||
function CompoundBreakdown({
|
|
||||||
info,
|
|
||||||
textOptions,
|
|
||||||
}: {
|
|
||||||
info: T.NonComboVerbInfo;
|
|
||||||
textOptions: T.TextOptions;
|
|
||||||
}) {
|
|
||||||
const isComplement = "complement" in info || "objComplement" in info;
|
|
||||||
if (!isComplement) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
const complement = ((): T.PsString => {
|
|
||||||
if ("objComplement" in info) {
|
|
||||||
return info.objComplement.plural
|
|
||||||
? info.objComplement.plural
|
|
||||||
: makePsString(info.objComplement.entry.p, info.objComplement.entry.f);
|
|
||||||
}
|
|
||||||
if ("complement" in info) {
|
|
||||||
return info.complement.masc[0][0];
|
|
||||||
} else return makePsString("aa", "aa");
|
|
||||||
})();
|
|
||||||
const aux = ((): { ps: T.PsString; e: string } => {
|
|
||||||
if (
|
|
||||||
info.type === "stative compound" ||
|
|
||||||
info.type === "generative stative compound"
|
|
||||||
) {
|
|
||||||
return info.transitivity === "transitive"
|
|
||||||
? { ps: { p: "کول", f: "kawul" }, e: "to make" }
|
|
||||||
: { ps: { p: "کېدل", f: "kedul" }, e: "to become" };
|
|
||||||
}
|
|
||||||
if (!("auxVerb" in info)) return { ps: { p: "", f: "" }, e: "" };
|
|
||||||
const kawulDyn =
|
|
||||||
info.type === "dynamic compound" && info.auxVerb.p === "کول";
|
|
||||||
return {
|
|
||||||
ps: makePsString(info.auxVerb.p, info.auxVerb.f),
|
|
||||||
e: kawulDyn ? "to do" : "",
|
|
||||||
};
|
|
||||||
})();
|
|
||||||
return (
|
|
||||||
<div className="d-block mx-auto my-3" style={{ maxWidth: "400px" }}>
|
|
||||||
<CompoundFormula
|
|
||||||
a={
|
|
||||||
<div className="compound-breakdown">
|
|
||||||
<div>
|
|
||||||
<Pashto opts={textOptions} ps={complement} />
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<Phonetics opts={textOptions} ps={complement} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
b={
|
|
||||||
<div className="compound-breakdown">
|
|
||||||
<div>
|
|
||||||
<Pashto opts={textOptions} ps={aux.ps} />
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<Phonetics opts={textOptions} ps={aux.ps} />
|
|
||||||
</div>
|
|
||||||
{aux.e && <div>{aux.e}</div>}
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function VerbTypeInfo({
|
function CompoundBreakdown({ info, textOptions }: {
|
||||||
info,
|
info: T.NonComboVerbInfo,
|
||||||
textOptions,
|
textOptions: T.TextOptions,
|
||||||
}: {
|
|
||||||
info: T.NonComboVerbInfo;
|
|
||||||
textOptions: T.TextOptions;
|
|
||||||
}) {
|
}) {
|
||||||
const [showingTypeModal, setShowingTypeModal] = useState<boolean>(false);
|
const isComplement = ("complement" in info || "objComplement" in info);
|
||||||
const [showingTransModal, setShowingTransModal] = useState<boolean>(false);
|
if (!isComplement) {
|
||||||
return (
|
return null;
|
||||||
<div>
|
}
|
||||||
<div className="text-center my-2">
|
const complement = ((): T.PsString => {
|
||||||
This is a
|
if ("objComplement" in info) {
|
||||||
<button
|
return info.objComplement.plural
|
||||||
className="btn btn-sm btn-light mx-2 my-1"
|
? info.objComplement.plural
|
||||||
onClick={() => setShowingTypeModal(true)}
|
: makePsString(info.objComplement.entry.p, info.objComplement.entry.f)
|
||||||
>
|
}
|
||||||
<strong>{info.type}</strong>
|
if ("complement" in info) {
|
||||||
</button>
|
return info.complement.masc[0][0];
|
||||||
verb and it's
|
}
|
||||||
<button
|
else return makePsString("aa", "aa");
|
||||||
className="btn btn-sm btn-light mx-2 my-1"
|
})();
|
||||||
onClick={() => setShowingTransModal(true)}
|
const aux = ((): { ps: T.PsString, e: string } => {
|
||||||
>
|
if (info.type === "stative compound" || info.type === "generative stative compound") {
|
||||||
<strong>{info.transitivity}</strong>
|
return info.transitivity === "transitive"
|
||||||
</button>
|
? { ps: { p: "کول", f: "kawul" }, e: "to make"}
|
||||||
</div>
|
: { ps: { p: "کېدل", f: "kedul" }, e: "to become"};
|
||||||
<CompoundBreakdown info={info} textOptions={textOptions} />
|
}
|
||||||
<Modal show={showingTypeModal} onHide={() => setShowingTypeModal(false)}>
|
if (!("auxVerb" in info)) return { ps: {p: "", f: ""}, e: ""};
|
||||||
<Modal.Header closeButton>
|
const kawulDyn = info.type === "dynamic compound" && info.auxVerb.p === "کول";
|
||||||
<Modal.Title>About {info.type} verbs</Modal.Title>
|
return {
|
||||||
</Modal.Header>
|
ps: makePsString(info.auxVerb.p, info.auxVerb.f),
|
||||||
<Modal.Body>{typeInfo(info.type, textOptions)}</Modal.Body>
|
e: kawulDyn ? "to do" : "",
|
||||||
<Modal.Footer>
|
}
|
||||||
<button
|
})();
|
||||||
type="button"
|
return (
|
||||||
className="btn btn-primary clb"
|
<div className="d-block mx-auto my-3" style={{ maxWidth: "400px" }}>
|
||||||
onClick={() => setShowingTypeModal(false)}
|
<CompoundFormula
|
||||||
>
|
a={<div className="compound-breakdown">
|
||||||
Close
|
<div>
|
||||||
</button>
|
<Pashto opts={textOptions}>{complement}</Pashto>
|
||||||
</Modal.Footer>
|
</div>
|
||||||
</Modal>
|
<div>
|
||||||
<Modal
|
<Phonetics opts={textOptions}>{complement}</Phonetics>
|
||||||
show={showingTransModal}
|
</div>
|
||||||
onHide={() => setShowingTransModal(false)}
|
</div>}
|
||||||
>
|
b={<div className="compound-breakdown">
|
||||||
<Modal.Header closeButton>
|
<div>
|
||||||
<Modal.Title>What does "{info.transitivity}" mean?</Modal.Title>
|
<Pashto opts={textOptions}>{aux.ps}</Pashto>
|
||||||
</Modal.Header>
|
</div>
|
||||||
<Modal.Body>{transitivityInfo(info.transitivity)}</Modal.Body>
|
<div>
|
||||||
<Modal.Footer>
|
<Phonetics opts={textOptions}>{aux.ps}</Phonetics>
|
||||||
<button
|
</div>
|
||||||
type="button"
|
{aux.e && <div>{aux.e}</div>}
|
||||||
className="btn btn-primary clb"
|
</div>}
|
||||||
onClick={() => setShowingTransModal(false)}
|
/>
|
||||||
>
|
</div>
|
||||||
Close
|
);
|
||||||
</button>
|
|
||||||
</Modal.Footer>
|
|
||||||
</Modal>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default VerbTypeInfo;
|
function VerbTypeInfo({ info, textOptions }: {
|
||||||
|
info: T.NonComboVerbInfo,
|
||||||
|
textOptions: T.TextOptions,
|
||||||
|
}) {
|
||||||
|
const [showingTypeModal, setShowingTypeModal] = useState<boolean>(false);
|
||||||
|
const [showingTransModal, setShowingTransModal] = useState<boolean>(false);
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div className="text-center my-2">
|
||||||
|
This is a
|
||||||
|
<button
|
||||||
|
className="btn btn-sm btn-light mx-2 my-1"
|
||||||
|
onClick={() => setShowingTypeModal(true)}
|
||||||
|
>
|
||||||
|
<strong>{info.type}</strong>
|
||||||
|
</button>
|
||||||
|
verb and it's
|
||||||
|
<button
|
||||||
|
className="btn btn-sm btn-light mx-2 my-1"
|
||||||
|
onClick={() => setShowingTransModal(true)}
|
||||||
|
>
|
||||||
|
<strong>{info.transitivity}</strong>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<CompoundBreakdown info={info} textOptions={textOptions} />
|
||||||
|
<Modal show={showingTypeModal} onHide={() => setShowingTypeModal(false)}>
|
||||||
|
<Modal.Header closeButton>
|
||||||
|
<Modal.Title>About {info.type} verbs</Modal.Title>
|
||||||
|
</Modal.Header>
|
||||||
|
<Modal.Body>{typeInfo(info.type, textOptions)}</Modal.Body>
|
||||||
|
<Modal.Footer>
|
||||||
|
<button type="button" className="btn btn-primary clb" onClick={() => setShowingTypeModal(false)}>
|
||||||
|
Close
|
||||||
|
</button>
|
||||||
|
</Modal.Footer>
|
||||||
|
</Modal>
|
||||||
|
<Modal show={showingTransModal} onHide={() => setShowingTransModal(false)}>
|
||||||
|
<Modal.Header closeButton>
|
||||||
|
<Modal.Title>What does "{info.transitivity}" mean?</Modal.Title>
|
||||||
|
</Modal.Header>
|
||||||
|
<Modal.Body>{transitivityInfo(info.transitivity, textOptions)}</Modal.Body>
|
||||||
|
<Modal.Footer>
|
||||||
|
<button type="button" className="btn btn-primary clb" onClick={() => setShowingTransModal(false)}>
|
||||||
|
Close
|
||||||
|
</button>
|
||||||
|
</Modal.Footer>
|
||||||
|
</Modal>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default VerbTypeInfo;
|
|
@ -1,113 +1,105 @@
|
||||||
import ButtonSelect from "../selects/ButtonSelect";
|
import ButtonSelect from "../ButtonSelect";
|
||||||
import * as T from "../../../types";
|
import * as T from "../../../types";
|
||||||
import { roleIcon } from "../role-icons";
|
import { roleIcon } from "./VPExplorerExplanationModal";
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
|
|
||||||
const options = [
|
const options = [
|
||||||
{
|
{
|
||||||
label: "Full",
|
label: "Full",
|
||||||
value: "full",
|
value: "full",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: <div className="m1-2">No {roleIcon.king}</div>,
|
label: <div className="m1-2">No {roleIcon.king}</div>,
|
||||||
value: "noKing",
|
value: "noKing",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: <div>Mini {roleIcon.servant}</div>,
|
label: <div>Mini {roleIcon.servant}</div>,
|
||||||
value: "shrinkServant",
|
value: "shrinkServant",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: <div>Both</div>,
|
label: <div>Both</div>,
|
||||||
value: "shortest",
|
value: "shortest",
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
function formToValue(f: T.FormVersion) {
|
function formToValue(f: T.FormVersion) {
|
||||||
if (f.removeKing === false && f.shrinkServant === false) {
|
if (f.removeKing === false && f.shrinkServant === false) {
|
||||||
return "full";
|
return "full";
|
||||||
}
|
}
|
||||||
if (f.removeKing === true && f.shrinkServant === false) {
|
if (f.removeKing === true && f.shrinkServant === false) {
|
||||||
return "noKing";
|
return "noKing";
|
||||||
}
|
}
|
||||||
if (f.removeKing === false && f.shrinkServant === true) {
|
if (f.removeKing === false && f.shrinkServant === true) {
|
||||||
return "shrinkServant";
|
return "shrinkServant";
|
||||||
}
|
}
|
||||||
if (f.removeKing === true && f.shrinkServant === true) {
|
if (f.removeKing === true && f.shrinkServant === true) {
|
||||||
return "shortest";
|
return "shortest";
|
||||||
}
|
}
|
||||||
throw new Error("unrecognized abbreviation form");
|
throw new Error("unrecognized abbreviation form");
|
||||||
}
|
}
|
||||||
|
|
||||||
function limitOptions(adjustable: "both" | "king" | "servant") {
|
function limitOptions(adjustable: "both" | "king" | "servant") {
|
||||||
if (adjustable === "both") {
|
if (adjustable === "both") {
|
||||||
return options;
|
return options;
|
||||||
}
|
}
|
||||||
if (adjustable === "king") {
|
if (adjustable === "king") {
|
||||||
return options.filter(
|
return options.filter(o => !["shrinkServant", "shortest"].includes(o.value));
|
||||||
(o) => !["shrinkServant", "shortest"].includes(o.value)
|
}
|
||||||
);
|
if (adjustable === "servant") {
|
||||||
}
|
return options.filter(o => !["noKing", "shortest"].includes(o.value));
|
||||||
if (adjustable === "servant") {
|
}
|
||||||
return options.filter((o) => !["noKing", "shortest"].includes(o.value));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function limitValue(value: string, adjustable: "both" | "king" | "servant") {
|
function limitValue(value: string, adjustable: "both" | "king" | "servant") {
|
||||||
if (adjustable === "both") return value;
|
if (adjustable === "both") return value;
|
||||||
if (adjustable === "king") {
|
if (adjustable === "king") {
|
||||||
return value === "shortest"
|
return (value === "shortest")
|
||||||
? "noKing"
|
? "noKing"
|
||||||
: value === "shrinkServant"
|
: (value === "shrinkServant")
|
||||||
? "full"
|
? "full"
|
||||||
: value;
|
: value;
|
||||||
}
|
|
||||||
if (adjustable === "servant") {
|
|
||||||
return value === "shortest"
|
|
||||||
? "shrinkServant"
|
|
||||||
: value === "noKing"
|
|
||||||
? "full"
|
|
||||||
: value;
|
|
||||||
}
|
|
||||||
throw new Error("unrecognized adjustable value");
|
|
||||||
}
|
|
||||||
|
|
||||||
function AbbreviationFormSelector({
|
|
||||||
form,
|
|
||||||
onChange,
|
|
||||||
adjustable,
|
|
||||||
inline,
|
|
||||||
}: {
|
|
||||||
form: T.FormVersion;
|
|
||||||
onChange: (f: T.FormVersion) => void;
|
|
||||||
adjustable: "both" | "king" | "servant";
|
|
||||||
inline?: boolean;
|
|
||||||
}) {
|
|
||||||
function handleChange(f: "full" | "noKing" | "shrinkServant" | "shortest") {
|
|
||||||
if (f === "full") {
|
|
||||||
onChange({ removeKing: false, shrinkServant: false });
|
|
||||||
} else if (f === "noKing") {
|
|
||||||
onChange({ removeKing: true, shrinkServant: false });
|
|
||||||
} else if (f === "shrinkServant") {
|
|
||||||
onChange({ removeKing: false, shrinkServant: true });
|
|
||||||
} else if (f === "shortest") {
|
|
||||||
onChange({ removeKing: true, shrinkServant: true });
|
|
||||||
}
|
}
|
||||||
}
|
if (adjustable === "servant") {
|
||||||
// TODO: limit display of shrinking options based on the verb type
|
return (value === "shortest")
|
||||||
return (
|
? "shrinkServant"
|
||||||
<div className={classNames("mx-3", { "mb-3": !inline })}>
|
: (value === "noKing")
|
||||||
{/* <div className="text-center text-small mb-2">Abbreviation Options</div> */}
|
? "full"
|
||||||
<ButtonSelect
|
: value;
|
||||||
faded={inline}
|
}
|
||||||
small
|
throw new Error("unrecognized adjustable value");
|
||||||
// @ts-expect-error slight mismatch but its ok
|
|
||||||
value={limitValue(formToValue(form), adjustable)}
|
|
||||||
// @ts-expect-error slight mismatch but its ok
|
|
||||||
options={limitOptions(adjustable)}
|
|
||||||
handleChange={handleChange}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default AbbreviationFormSelector;
|
function AbbreviationFormSelector({ form, onChange, adjustable, inline }: {
|
||||||
|
form: T.FormVersion,
|
||||||
|
onChange: (f: T.FormVersion) => void,
|
||||||
|
adjustable: "both" | "king" | "servant",
|
||||||
|
inline?: boolean,
|
||||||
|
}) {
|
||||||
|
function handleChange(f: "full" | "noKing" | "shrinkServant" | "shortest") {
|
||||||
|
if (f === "full") {
|
||||||
|
onChange({ removeKing: false, shrinkServant: false });
|
||||||
|
} else if (f === "noKing") {
|
||||||
|
onChange({ removeKing: true, shrinkServant: false });
|
||||||
|
} else if (f === "shrinkServant") {
|
||||||
|
onChange({ removeKing: false, shrinkServant: true });
|
||||||
|
} else if (f === "shortest") {
|
||||||
|
onChange({ removeKing: true, shrinkServant: true });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// TODO: limit display of shrinking options based on the verb type
|
||||||
|
return <div className={classNames("mx-3", { "mb-3": !inline })}>
|
||||||
|
{/* <div className="text-center text-small mb-2">Abbreviation Options</div> */}
|
||||||
|
<ButtonSelect
|
||||||
|
faded={inline}
|
||||||
|
small
|
||||||
|
// @ts-ignore
|
||||||
|
value={limitValue(formToValue(form), adjustable)}
|
||||||
|
// @ts-ignore
|
||||||
|
options={limitOptions(adjustable)}
|
||||||
|
// @ts-ignore
|
||||||
|
handleChange={handleChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AbbreviationFormSelector;
|
|
@ -9,7 +9,7 @@ import Hider from "../Hider";
|
||||||
import * as T from "../../../types";
|
import * as T from "../../../types";
|
||||||
import useStickyState from "../useStickyState";
|
import useStickyState from "../useStickyState";
|
||||||
import { isImperativeTense } from "../../../lib/src/type-predicates";
|
import { isImperativeTense } from "../../../lib/src/type-predicates";
|
||||||
import ButtonSelect from "../selects/ButtonSelect";
|
import ButtonSelect from "../ButtonSelect";
|
||||||
import { VpsReducerAction } from "../../../lib/src/phrase-building/vps-reducer";
|
import { VpsReducerAction } from "../../../lib/src/phrase-building/vps-reducer";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { statVerb } from "../../../lib/src/new-verb-engine/roots-and-stems";
|
import { statVerb } from "../../../lib/src/new-verb-engine/roots-and-stems";
|
||||||
|
|
|
@ -1,120 +1,114 @@
|
||||||
import * as T from "../../../types";
|
import * as T from "../../../types";
|
||||||
import Pashto from "../text-display/Pashto";
|
import Pashto from "../Pashto";
|
||||||
import Phonetics from "../text-display/Phonetics";
|
import Phonetics from "../Phonetics";
|
||||||
import { makePsString } from "../../../lib/src/accent-and-ps-utils";
|
import {
|
||||||
|
makePsString,
|
||||||
|
} from "../../../lib/src/accent-and-ps-utils";
|
||||||
import { ReactNode } from "react";
|
import { ReactNode } from "react";
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
import { kawulStat, kedulStat } from "../../../lib/src/irregular-conjugations";
|
import {
|
||||||
|
kawulStat,
|
||||||
|
kedulStat,
|
||||||
|
} from "../../../lib/src/irregular-conjugations";
|
||||||
|
|
||||||
function CompoundFormula({ a, b }: { a: ReactNode; b: ReactNode }) {
|
function CompoundFormula({ a, b }: {
|
||||||
return (
|
a: ReactNode,
|
||||||
<div className="row align-items-center mb-3">
|
b: ReactNode,
|
||||||
<div className="col-5 text-center">{a}</div>
|
|
||||||
<div className="col-2 text-center" style={{ fontSize: "2.5rem" }}>
|
|
||||||
<strong>+</strong>
|
|
||||||
</div>
|
|
||||||
<div className="col-5 text-center">{b}</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function CompoundDisplay({
|
|
||||||
info,
|
|
||||||
opts,
|
|
||||||
handleLinkClick,
|
|
||||||
}: {
|
|
||||||
info: T.NonComboVerbInfo;
|
|
||||||
opts: T.TextOptions;
|
|
||||||
handleLinkClick: ((ts: number) => void) | "none";
|
|
||||||
}) {
|
}) {
|
||||||
const isComplement = "complement" in info || "objComplement" in info;
|
return (
|
||||||
if (!isComplement) {
|
<div className="row align-items-center mb-3">
|
||||||
return null;
|
<div className="col-5 text-center">
|
||||||
}
|
{a}
|
||||||
const complement = ((): T.PsString => {
|
|
||||||
if ("objComplement" in info) {
|
|
||||||
return info.objComplement.plural
|
|
||||||
? info.objComplement.plural
|
|
||||||
: info.objComplement.entry;
|
|
||||||
}
|
|
||||||
if ("complement" in info) {
|
|
||||||
return info.complement.masc[0][0];
|
|
||||||
} else return makePsString("aa", "aa");
|
|
||||||
})();
|
|
||||||
const aux = ((): { ps: T.PsString; e: string } => {
|
|
||||||
if (
|
|
||||||
info.type === "stative compound" ||
|
|
||||||
info.type === "generative stative compound"
|
|
||||||
) {
|
|
||||||
return info.transitivity === "transitive"
|
|
||||||
? { ps: { p: "کول", f: "kawul" }, e: "to make" }
|
|
||||||
: { ps: { p: "کېدل", f: "kedul" }, e: "to become" };
|
|
||||||
}
|
|
||||||
if (!("auxVerb" in info)) return { ps: { p: "", f: "" }, e: "" };
|
|
||||||
const kawulDyn =
|
|
||||||
info.type === "dynamic compound" && info.auxVerb.p === "کول";
|
|
||||||
return {
|
|
||||||
ps: makePsString(info.auxVerb.p, info.auxVerb.f),
|
|
||||||
e: kawulDyn ? "to do" : "",
|
|
||||||
};
|
|
||||||
})();
|
|
||||||
return (
|
|
||||||
<div className="d-block mx-auto my-3" style={{ maxWidth: "400px" }}>
|
|
||||||
<div className="text-center">{info.type}</div>
|
|
||||||
<CompoundFormula
|
|
||||||
a={
|
|
||||||
<div
|
|
||||||
className={classNames([
|
|
||||||
{ clickable: typeof handleLinkClick === "function" },
|
|
||||||
])}
|
|
||||||
onClick={
|
|
||||||
handleLinkClick
|
|
||||||
? // @ts-expect-error - thinks there might not be a complement, but there will be
|
|
||||||
() => handleLinkClick(info.entry.complement?.ts)
|
|
||||||
: undefined
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<div>
|
|
||||||
<Pashto opts={opts} ps={complement} />
|
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div className="col-2 text-center" style={{ fontSize: "2.5rem" }}>
|
||||||
<Phonetics opts={opts} ps={complement} />
|
<strong>+</strong>
|
||||||
</div>
|
</div>
|
||||||
<div className="small text-muted">{info.entry.complement?.c}</div>
|
<div className="col-5 text-center">
|
||||||
{info.type === "dynamic compound" && <div>(Object)</div>}
|
{b}
|
||||||
</div>
|
|
||||||
}
|
|
||||||
b={
|
|
||||||
<div
|
|
||||||
className={classNames([{ clickable: handleLinkClick }])}
|
|
||||||
onClick={
|
|
||||||
handleLinkClick !== "none"
|
|
||||||
? () =>
|
|
||||||
handleLinkClick(
|
|
||||||
"auxVerb" in info
|
|
||||||
? // dyanmic compound auxVerb ts
|
|
||||||
info.auxVerb.ts
|
|
||||||
: // stative compound auxVerb ts
|
|
||||||
(info.transitivity === "intransitive"
|
|
||||||
? kedulStat
|
|
||||||
: kawulStat
|
|
||||||
).info.entry.entry.ts
|
|
||||||
)
|
|
||||||
: undefined
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<div>
|
|
||||||
<Pashto opts={opts} ps={aux.ps} />
|
|
||||||
</div>
|
</div>
|
||||||
<div>
|
</div>
|
||||||
<Phonetics opts={opts} ps={aux.ps} />
|
);
|
||||||
</div>
|
|
||||||
{aux.e && <div>{aux.e}</div>}
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default CompoundDisplay;
|
function CompoundDisplay({ info, opts, handleLinkClick }: {
|
||||||
|
info: T.NonComboVerbInfo,
|
||||||
|
opts: T.TextOptions,
|
||||||
|
handleLinkClick: ((ts: number) => void) | "none",
|
||||||
|
}) {
|
||||||
|
const isComplement = ("complement" in info || "objComplement" in info);
|
||||||
|
if (!isComplement) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const complement = ((): T.PsString => {
|
||||||
|
if ("objComplement" in info) {
|
||||||
|
return info.objComplement.plural
|
||||||
|
? info.objComplement.plural
|
||||||
|
: info.objComplement.entry;
|
||||||
|
}
|
||||||
|
if ("complement" in info) {
|
||||||
|
return info.complement.masc[0][0];
|
||||||
|
}
|
||||||
|
else return makePsString("aa", "aa");
|
||||||
|
})();
|
||||||
|
const aux = ((): { ps: T.PsString, e: string } => {
|
||||||
|
if (info.type === "stative compound" || info.type === "generative stative compound") {
|
||||||
|
return info.transitivity === "transitive"
|
||||||
|
? { ps: { p: "کول", f: "kawul" }, e: "to make"}
|
||||||
|
: { ps: { p: "کېدل", f: "kedul" }, e: "to become"};
|
||||||
|
}
|
||||||
|
if (!("auxVerb" in info)) return { ps: {p: "", f: ""}, e: ""};
|
||||||
|
const kawulDyn = info.type === "dynamic compound" && info.auxVerb.p === "کول";
|
||||||
|
return {
|
||||||
|
ps: makePsString(info.auxVerb.p, info.auxVerb.f),
|
||||||
|
e: kawulDyn ? "to do" : "",
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
return (
|
||||||
|
<div className="d-block mx-auto my-3" style={{ maxWidth: "400px" }}>
|
||||||
|
<div className="text-center">{info.type}</div>
|
||||||
|
<CompoundFormula
|
||||||
|
a={<div
|
||||||
|
className={classNames([{ clickable: typeof handleLinkClick === "function" }])}
|
||||||
|
onClick={(handleLinkClick)
|
||||||
|
// @ts-ignore - thinks there might not be a complement, but there will be
|
||||||
|
? () => handleLinkClick(info.entry.complement?.ts)
|
||||||
|
: undefined}
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<Pashto opts={opts}>{complement}</Pashto>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Phonetics opts={opts}>{complement}</Phonetics>
|
||||||
|
</div>
|
||||||
|
<div className="small text-muted">{info.entry.complement?.c}</div>
|
||||||
|
{info.type === "dynamic compound" && <div>
|
||||||
|
(Object)
|
||||||
|
</div>}
|
||||||
|
</div>}
|
||||||
|
b={<div
|
||||||
|
className={classNames([{ clickable: handleLinkClick }])}
|
||||||
|
onClick={(handleLinkClick !== "none")
|
||||||
|
? () => handleLinkClick(
|
||||||
|
"auxVerb" in info
|
||||||
|
// dyanmic compound auxVerb ts
|
||||||
|
? info.auxVerb.ts
|
||||||
|
// stative compound auxVerb ts
|
||||||
|
: ((info.transitivity === "intransitive"
|
||||||
|
? kedulStat
|
||||||
|
: kawulStat).info.entry.entry.ts)
|
||||||
|
)
|
||||||
|
: undefined}>
|
||||||
|
<div>
|
||||||
|
<Pashto opts={opts}>{aux.ps}</Pashto>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Phonetics opts={opts}>{aux.ps}</Phonetics>
|
||||||
|
</div>
|
||||||
|
{aux.e && <div>{aux.e}</div>}
|
||||||
|
</div>}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CompoundDisplay;
|
|
@ -13,7 +13,7 @@ export function EditIcon() {
|
||||||
function EditableVP({
|
function EditableVP({
|
||||||
children,
|
children,
|
||||||
opts,
|
opts,
|
||||||
// formChoice,
|
formChoice,
|
||||||
noEdit,
|
noEdit,
|
||||||
length,
|
length,
|
||||||
mode,
|
mode,
|
||||||
|
@ -23,7 +23,7 @@ function EditableVP({
|
||||||
}: {
|
}: {
|
||||||
children: T.VPSelectionState;
|
children: T.VPSelectionState;
|
||||||
opts: T.TextOptions;
|
opts: T.TextOptions;
|
||||||
// formChoice?: boolean;
|
formChoice?: boolean;
|
||||||
noEdit?: boolean;
|
noEdit?: boolean;
|
||||||
length?: "long" | "short";
|
length?: "long" | "short";
|
||||||
mode?: "text" | "blocks";
|
mode?: "text" | "blocks";
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import * as T from "../../../types";
|
import * as T from "../../../types";
|
||||||
import ModeSelect, { Mode, ScriptSelect } from "../selects/DisplayModeSelect";
|
import ModeSelect, { Mode, ScriptSelect } from "../DisplayModeSelect";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import CompiledPTextDisplay from "../text-display/CompiledPTextDisplay";
|
import CompiledPTextDisplay from "../CompiledPTextDisplay";
|
||||||
import useStickyState from "../useStickyState";
|
import useStickyState from "../useStickyState";
|
||||||
import {
|
import {
|
||||||
getEnglishFromRendered,
|
getEnglishFromRendered,
|
||||||
|
|
|
@ -1,24 +1,28 @@
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import ButtonSelect from "../selects/ButtonSelect";
|
import ButtonSelect from "../ButtonSelect";
|
||||||
import {
|
import {
|
||||||
combineIntoText,
|
combineIntoText,
|
||||||
flattenLengths,
|
flattenLengths,
|
||||||
} from "../../../lib/src/phrase-building/compile";
|
} from "../../../lib/src/phrase-building/compile";
|
||||||
import { insertNegative } from "../../../lib/src/phrase-building/render-vp";
|
import { insertNegative } from "../../../lib/src/phrase-building/render-vp";
|
||||||
import * as T from "../../../types";
|
import * as T from "../../../types";
|
||||||
import TableCell from "../tables/TableCell";
|
import TableCell from "../TableCell";
|
||||||
import { choosePersInf, getLength } from "../../../lib/src/p-text-helpers";
|
import { choosePersInf, getLength } from "../../../lib/src/p-text-helpers";
|
||||||
import genderColors from "../gender-colors";
|
import genderColors from "../gender-colors";
|
||||||
import { eqPsStringWVars } from "../../../lib/src/fp-ps";
|
import { eqPsStringWVars } from "../../../lib/src/fp-ps";
|
||||||
import PersInfsPicker from "../selects/PersInfsPicker";
|
import PersInfsPicker from "../PersInfsPicker";
|
||||||
import "./form-display.css";
|
import "./form-display.css";
|
||||||
import {
|
import {
|
||||||
makeBlock,
|
makeBlock,
|
||||||
makeKid,
|
makeKid,
|
||||||
} from "../../../lib/src/phrase-building/blocks-utils";
|
} from "../../../lib/src/phrase-building/blocks-utils";
|
||||||
import InlinePs from "../text-display/InlinePs";
|
import InlinePs from "../InlinePs";
|
||||||
import { personToGenNum } from "../../../lib/src/misc-helpers";
|
import { personToGenNum } from "../../../lib/src/misc-helpers";
|
||||||
import { roleIcon } from "./../role-icons";
|
|
||||||
|
export const roleIcon = {
|
||||||
|
king: <i className="mx-1 fas fa-crown" />,
|
||||||
|
servant: <i className="mx-1 fas fa-male" />,
|
||||||
|
};
|
||||||
|
|
||||||
function VerbChartDisplay({
|
function VerbChartDisplay({
|
||||||
chart,
|
chart,
|
||||||
|
@ -326,7 +330,9 @@ function AgreementInfo({
|
||||||
</div>
|
</div>
|
||||||
{transitivity === "transitive" && past && objNP && (
|
{transitivity === "transitive" && past && objNP && (
|
||||||
<div>
|
<div>
|
||||||
<InlinePs opts={opts} ps={flattenLengths(objNP.selection.ps)[0]} />
|
<InlinePs opts={opts}>
|
||||||
|
{flattenLengths(objNP.selection.ps)[0]}
|
||||||
|
</InlinePs>
|
||||||
{` `}({printGenNum(personToGenNum(objNP.selection.person))})
|
{` `}({printGenNum(personToGenNum(objNP.selection.person))})
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -1,248 +1,186 @@
|
||||||
import Select from "react-select";
|
import Select from "react-select";
|
||||||
import * as T from "../../../types";
|
import * as T from "../../../types";
|
||||||
import ButtonSelect from "../selects/ButtonSelect";
|
import ButtonSelect from "../ButtonSelect";
|
||||||
|
import { isImperativeTense, isAbilityTense, isPerfectTense, isVerbTense } from "../../../lib/src/type-predicates";
|
||||||
import useStickyState from "../useStickyState";
|
import useStickyState from "../useStickyState";
|
||||||
import { customSelectStyles as customStyles } from "../selects/select-styles";
|
import { customStyles } from "../EntrySelect";
|
||||||
import { VpsReducerAction } from "../../../lib/src/phrase-building/vps-reducer";
|
|
||||||
import {
|
import {
|
||||||
imperativeTenseOptions,
|
VpsReducerAction,
|
||||||
perfectTenseOptions,
|
} from "../../../lib/src/phrase-building/vps-reducer";
|
||||||
verbTenseOptions,
|
import { imperativeTenseOptions, perfectTenseOptions, verbTenseOptions } from "./verbTenseOptions";
|
||||||
} from "./verbTenseOptions";
|
|
||||||
import { getTenseCategory } from "./vp-explorer-util";
|
|
||||||
|
|
||||||
function composeFormula(
|
function composeFormula(formula: string, prefix: "passive" | "ability"): string {
|
||||||
formula: string,
|
return formula.replace(/^perfective/, `${prefix} perfective`)
|
||||||
prefix: "passive" | "ability"
|
.replace(/^imperfective/, `${prefix} imperfective`)
|
||||||
): string {
|
.replace("continuous", `${prefix} continuous`)
|
||||||
return formula
|
.replace("simple", `${prefix} simple`)
|
||||||
.replace(/^perfective/, `${prefix} perfective`)
|
.replace(/present$/, `${prefix} present`)
|
||||||
.replace(/^imperfective/, `${prefix} imperfective`)
|
.replace(/subjunctive$/, `${prefix} subjunctive`)
|
||||||
.replace("continuous", `${prefix} continuous`)
|
.replace("past participle", `${prefix} past participle`);
|
||||||
.replace("simple", `${prefix} simple`)
|
|
||||||
.replace(/present$/, `${prefix} present`)
|
|
||||||
.replace(/subjunctive$/, `${prefix} subjunctive`)
|
|
||||||
.replace("past participle", `${prefix} past participle`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function TensePicker(
|
export function getRandomTense(o?: T.PerfectTense | T.VerbTense | T.AbilityTense | T.ImperativeTense): T.PerfectTense | T.VerbTense | T.AbilityTense | T.ImperativeTense {
|
||||||
props: (
|
let tns: T.PerfectTense | T.VerbTense | T.AbilityTense | T.ImperativeTense;
|
||||||
| {
|
const oldTenseCategory = !o
|
||||||
vps: T.VPSelectionState;
|
? undefined
|
||||||
}
|
: getTenseCategory(o);
|
||||||
| {
|
const tenseOptions = oldTenseCategory === "perfect"
|
||||||
vpsComplete: T.VPSelectionComplete;
|
? perfectTenseOptions
|
||||||
}
|
: oldTenseCategory === "modal"
|
||||||
) & {
|
? verbTenseOptions.map(x => ({ ...x, value: `${x.value}Modal` as T.AbilityTense }))
|
||||||
onChange: (p: VpsReducerAction) => void;
|
: oldTenseCategory === "imperative"
|
||||||
mode: "charts" | "phrases" | "quiz";
|
? imperativeTenseOptions
|
||||||
}
|
: verbTenseOptions;
|
||||||
) {
|
do {
|
||||||
const [showFormula, setShowFormula] = useStickyState<boolean>(
|
tns = tenseOptions[
|
||||||
false,
|
Math.floor(Math.random()*tenseOptions.length)
|
||||||
"showFormula"
|
].value;
|
||||||
);
|
} while (o === tns);
|
||||||
function onTenseSelect(
|
return tns;
|
||||||
o: { value: T.VerbTense | T.PerfectTense | T.ImperativeTense } | null
|
}
|
||||||
) {
|
|
||||||
if ("vpsComplete" in props) return;
|
function TensePicker(props: ({
|
||||||
const tense = o?.value ? o.value : undefined;
|
vps: T.VPSelectionState,
|
||||||
props.onChange({
|
} | {
|
||||||
type: "set tense",
|
vpsComplete: T.VPSelectionComplete,
|
||||||
payload: tense,
|
}) & {
|
||||||
});
|
onChange: (p: VpsReducerAction) => void,
|
||||||
}
|
mode: "charts" | "phrases" | "quiz",
|
||||||
function moveTense(dir: "forward" | "back") {
|
}) {
|
||||||
if ("vpsComplete" in props) return;
|
const [showFormula, setShowFormula] = useStickyState<boolean>(false, "showFormula");
|
||||||
if (!props.vps.verb) return;
|
function onTenseSelect(o: { value: T.VerbTense | T.PerfectTense | T.ImperativeTense } | null) {
|
||||||
return () => {
|
if ("vpsComplete" in props) return;
|
||||||
// TODO: ABSTRACT THIS - SAFER
|
const tense = o?.value ? o.value : undefined;
|
||||||
const tenses =
|
props.onChange({
|
||||||
props.vps.verb.tenseCategory === "perfect"
|
type: "set tense",
|
||||||
? perfectTenseOptions
|
payload: tense,
|
||||||
: props.vps.verb.tenseCategory === "imperative"
|
});
|
||||||
? imperativeTenseOptions
|
}
|
||||||
: verbTenseOptions;
|
function moveTense(dir: "forward" | "back") {
|
||||||
const currIndex = tenses.findIndex(
|
if ("vpsComplete" in props) return;
|
||||||
(tn) =>
|
if (!props.vps.verb) return;
|
||||||
tn.value ===
|
return () => {
|
||||||
props.vps.verb[
|
// TODO: ABSTRACT THIS - SAFER
|
||||||
// TODO: ABSTRACT THIS? - SAFER
|
const tenses = props.vps.verb.tenseCategory === "perfect"
|
||||||
props.vps.verb.tenseCategory === "perfect"
|
? perfectTenseOptions
|
||||||
? "perfectTense"
|
: props.vps.verb.tenseCategory === "imperative"
|
||||||
: props.vps.verb.tenseCategory === "imperative"
|
? imperativeTenseOptions
|
||||||
? "imperativeTense"
|
: verbTenseOptions;
|
||||||
: "verbTense"
|
const currIndex = tenses.findIndex(tn => tn.value === props.vps.verb[
|
||||||
]
|
// TODO: ABSTRACT THIS? - SAFER
|
||||||
);
|
props.vps.verb.tenseCategory === "perfect"
|
||||||
if (currIndex === -1) {
|
? "perfectTense"
|
||||||
console.error("error moving tense", dir);
|
: props.vps.verb.tenseCategory === "imperative"
|
||||||
return;
|
? "imperativeTense"
|
||||||
}
|
: "verbTense"
|
||||||
const newIndex =
|
]);
|
||||||
dir === "forward"
|
if (currIndex === -1) {
|
||||||
? (currIndex + 1) % tenses.length
|
console.error("error moving tense", dir);
|
||||||
: currIndex === 0
|
return;
|
||||||
? tenses.length - 1
|
}
|
||||||
: currIndex - 1;
|
const newIndex = dir === "forward"
|
||||||
const newTense = tenses[newIndex];
|
? ((currIndex + 1) % tenses.length)
|
||||||
onTenseSelect(newTense);
|
: (currIndex === 0 ? (tenses.length - 1) : (currIndex - 1))
|
||||||
};
|
const newTense = tenses[newIndex];
|
||||||
}
|
onTenseSelect(newTense);
|
||||||
function onPosNegSelect(payload: "true" | "false") {
|
};
|
||||||
if ("vpsComplete" in props) return;
|
}
|
||||||
props.onChange({
|
function onPosNegSelect(payload: "true" | "false") {
|
||||||
type: "set negativity",
|
if ("vpsComplete" in props) return;
|
||||||
payload,
|
props.onChange({
|
||||||
});
|
type: "set negativity",
|
||||||
}
|
payload,
|
||||||
function onTenseCategorySelect(
|
});
|
||||||
payload: "basic" | "modal" | "perfect" | "imperative"
|
}
|
||||||
) {
|
function onTenseCategorySelect(payload: "basic" | "modal" | "perfect" | "imperative") {
|
||||||
if ("vpsComplete" in props) return;
|
if ("vpsComplete" in props) return;
|
||||||
props.onChange({
|
props.onChange({
|
||||||
type: "set tense category",
|
type: "set tense category",
|
||||||
payload,
|
payload,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
const tOptions =
|
const tOptions = ("vps" in props && (props.vps.verb?.tenseCategory === "perfect"))
|
||||||
"vps" in props && props.vps.verb?.tenseCategory === "perfect"
|
? perfectTenseOptions
|
||||||
? perfectTenseOptions
|
: ("vps" in props && (props.vps.verb?.tenseCategory === "imperative"))
|
||||||
: "vps" in props && props.vps.verb?.tenseCategory === "imperative"
|
? imperativeTenseOptions
|
||||||
? imperativeTenseOptions
|
: verbTenseOptions;
|
||||||
: verbTenseOptions;
|
const showImperativeOption = ("vps" in props && props.vps.verb.voice === "active")
|
||||||
const showImperativeOption =
|
|| ("vpsComplete" in props && props.vpsComplete.verb.voice !== "active");
|
||||||
("vps" in props && props.vps.verb.voice === "active") ||
|
const inAllTensesMode = props.mode === "charts";
|
||||||
("vpsComplete" in props && props.vpsComplete.verb.voice !== "active");
|
const canHaveFormula = "vps" in props
|
||||||
const inAllTensesMode = props.mode === "charts";
|
&& props.mode !== "quiz"
|
||||||
const canHaveFormula =
|
&& !inAllTensesMode;
|
||||||
"vps" in props && props.mode !== "quiz" && !inAllTensesMode;
|
return <div>
|
||||||
return (
|
<div style={{ maxWidth: "300px", minWidth: "250px", margin: "0 auto" }}>
|
||||||
<div>
|
<div className="d-flex flex-row justify-content-between align-items-center">
|
||||||
<div style={{ maxWidth: "300px", minWidth: "250px", margin: "0 auto" }}>
|
<div className="h5">
|
||||||
<div className="d-flex flex-row justify-content-between align-items-center">
|
{props.mode === "charts"
|
||||||
<div className="h5">
|
? "Tense Category:"
|
||||||
{props.mode === "charts" ? "Tense Category:" : "Verb Tense:"}
|
: "Verb Tense:"}
|
||||||
</div>
|
</div>
|
||||||
{canHaveFormula && (
|
{canHaveFormula && <div className="clickable mb-2 small" onClick={() => setShowFormula(x => !x)}>
|
||||||
<div
|
🧪 {!showFormula ? "Show" : "Hide"} Formula
|
||||||
className="clickable mb-2 small"
|
</div>}
|
||||||
onClick={() => setShowFormula((x) => !x)}
|
|
||||||
>
|
|
||||||
🧪 {!showFormula ? "Show" : "Hide"} Formula
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
{("vpsComplete" in props || props.vps.verb) && <div className="mb-2">
|
||||||
</div>
|
<ButtonSelect
|
||||||
{("vpsComplete" in props || props.vps.verb) && (
|
small
|
||||||
<div className="mb-2">
|
value={"vpsComplete" in props
|
||||||
<ButtonSelect
|
? getTenseCategory(props.vpsComplete.verb.tense)
|
||||||
small
|
: props.vps.verb.tenseCategory}
|
||||||
value={
|
// @ts-ignore
|
||||||
"vpsComplete" in props
|
options={showImperativeOption ? [{
|
||||||
? getTenseCategory(props.vpsComplete.verb.tense)
|
|
||||||
: props.vps.verb.tenseCategory
|
|
||||||
}
|
|
||||||
options={
|
|
||||||
showImperativeOption
|
|
||||||
? [
|
|
||||||
{
|
|
||||||
label: "Basic",
|
label: "Basic",
|
||||||
value: "basic",
|
value: "basic",
|
||||||
},
|
}, {
|
||||||
{
|
|
||||||
label: "Perfect",
|
label: "Perfect",
|
||||||
value: "perfect",
|
value: "perfect",
|
||||||
},
|
}, {
|
||||||
{
|
|
||||||
label: "Ability",
|
label: "Ability",
|
||||||
value: "modal",
|
value: "modal",
|
||||||
},
|
}, {
|
||||||
{
|
|
||||||
label: "Imperative",
|
label: "Imperative",
|
||||||
value: "imperative",
|
value: "imperative",
|
||||||
},
|
}] : [{
|
||||||
]
|
|
||||||
: [
|
|
||||||
{
|
|
||||||
label: "Basic",
|
label: "Basic",
|
||||||
value: "basic",
|
value: "basic",
|
||||||
},
|
}, {
|
||||||
{
|
|
||||||
label: "Perfect",
|
label: "Perfect",
|
||||||
value: "perfect",
|
value: "perfect",
|
||||||
},
|
}, {
|
||||||
{
|
|
||||||
label: "Ability",
|
label: "Ability",
|
||||||
value: "modal",
|
value: "modal",
|
||||||
},
|
}]}
|
||||||
]
|
handleChange={props.mode !== "quiz" ? onTenseCategorySelect : () => null}
|
||||||
}
|
/>
|
||||||
handleChange={
|
</div>}
|
||||||
props.mode !== "quiz" ? onTenseCategorySelect : () => null
|
{"vpsComplete" in props
|
||||||
}
|
? <div style={{ fontSize: "larger" }} className="mb-3">
|
||||||
/>
|
{[...verbTenseOptions, ...perfectTenseOptions, ...imperativeTenseOptions].find(o => o.value === props.vpsComplete.verb.tense)?.label}
|
||||||
</div>
|
</div>
|
||||||
)}
|
: <>
|
||||||
{"vpsComplete" in props ? (
|
{!inAllTensesMode && <Select
|
||||||
<div style={{ fontSize: "larger" }} className="mb-3">
|
isSearchable={false}
|
||||||
{
|
// for some reason can't use tOptions with find here;
|
||||||
[
|
value={props.vps.verb && ([...verbTenseOptions, ...perfectTenseOptions, ...imperativeTenseOptions].find(o => o.value === props.vps.verb[
|
||||||
...verbTenseOptions,
|
|
||||||
...perfectTenseOptions,
|
|
||||||
...imperativeTenseOptions,
|
|
||||||
].find((o) => o.value === props.vpsComplete.verb.tense)?.label
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
{!inAllTensesMode && (
|
|
||||||
<Select
|
|
||||||
isSearchable={false}
|
|
||||||
// for some reason can't use tOptions with find here;
|
|
||||||
value={
|
|
||||||
props.vps.verb &&
|
|
||||||
[
|
|
||||||
...verbTenseOptions,
|
|
||||||
...perfectTenseOptions,
|
|
||||||
...imperativeTenseOptions,
|
|
||||||
].find(
|
|
||||||
(o) =>
|
|
||||||
o.value ===
|
|
||||||
props.vps.verb[
|
|
||||||
props.vps.verb.tenseCategory === "perfect"
|
props.vps.verb.tenseCategory === "perfect"
|
||||||
? "perfectTense"
|
? "perfectTense"
|
||||||
: props.vps.verb.tenseCategory === "imperative"
|
: props.vps.verb.tenseCategory === "imperative"
|
||||||
? "imperativeTense"
|
? "imperativeTense"
|
||||||
: "verbTense"
|
: "verbTense"
|
||||||
]
|
]))}
|
||||||
)
|
// @ts-ignore - gets messed up when using customStyles
|
||||||
}
|
onChange={onTenseSelect}
|
||||||
// @ts-expect-error slight mismatch but it's ok
|
className="mb-2"
|
||||||
onChange={onTenseSelect}
|
options={tOptions}
|
||||||
className="mb-2"
|
styles={customStyles}
|
||||||
options={tOptions}
|
/>}
|
||||||
styles={customStyles}
|
</>}
|
||||||
/>
|
{"vps" in props && props.vps.verb && (props.mode !== "quiz") && <div className="d-flex flex-row justify-content-between align-items-center mt-2 mb-1" style={{ width: "100%" }}>
|
||||||
)}
|
{!inAllTensesMode ? <div className="btn btn-light clickable" onClick={moveTense("back")}>
|
||||||
</>
|
<i className="fas fa-chevron-left" />
|
||||||
)}
|
</div> : <div />}
|
||||||
{"vps" in props && props.vps.verb && props.mode !== "quiz" && (
|
{/* {props.mode === "charts" && <ButtonSelect
|
||||||
<div
|
|
||||||
className="d-flex flex-row justify-content-between align-items-center mt-2 mb-1"
|
|
||||||
style={{ width: "100%" }}
|
|
||||||
>
|
|
||||||
{!inAllTensesMode ? (
|
|
||||||
<div
|
|
||||||
className="btn btn-light clickable"
|
|
||||||
onClick={moveTense("back")}
|
|
||||||
>
|
|
||||||
<i className="fas fa-chevron-left" />
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<div />
|
|
||||||
)}
|
|
||||||
{/* {props.mode === "charts" && <ButtonSelect
|
|
||||||
small
|
small
|
||||||
value={props.chartMode}
|
value={props.chartMode}
|
||||||
options={[{
|
options={[{
|
||||||
|
@ -254,81 +192,64 @@ function TensePicker(
|
||||||
}]}
|
}]}
|
||||||
handleChange={props.onChartModeChange}
|
handleChange={props.onChartModeChange}
|
||||||
/>} */}
|
/>} */}
|
||||||
{props.mode === "phrases" && (
|
{props.mode === "phrases" && <ButtonSelect
|
||||||
<ButtonSelect
|
small
|
||||||
small
|
value={props.vps.verb.negative.toString() as "true" | "false"}
|
||||||
value={props.vps.verb.negative.toString() as "true" | "false"}
|
options={[{
|
||||||
options={[
|
label: "Pos.",
|
||||||
{
|
value: "false",
|
||||||
label: "Pos.",
|
}, {
|
||||||
value: "false",
|
label: "Neg.",
|
||||||
},
|
value: "true",
|
||||||
{
|
}]}
|
||||||
label: "Neg.",
|
handleChange={onPosNegSelect}
|
||||||
value: "true",
|
/>}
|
||||||
},
|
{!inAllTensesMode ? <div onClick={moveTense("forward")} className="btn btn-light clickable">
|
||||||
]}
|
<i className="fas fa-chevron-right" />
|
||||||
handleChange={onPosNegSelect}
|
</div> : <div />}
|
||||||
/>
|
</div>}
|
||||||
)}
|
{(canHaveFormula && showFormula) && (() => {
|
||||||
{!inAllTensesMode ? (
|
// TODO: Be able to show modal formulas too
|
||||||
<div
|
const curr = (props.vps.verb.tenseCategory === "imperative" && props.vps.verb.negative)
|
||||||
onClick={moveTense("forward")}
|
? imperativeTenseOptions.find(x => x.value === "imperfectiveImperative")
|
||||||
className="btn btn-light clickable"
|
: [...verbTenseOptions, ...perfectTenseOptions, ...imperativeTenseOptions].find(o => o.value === props.vps.verb[
|
||||||
>
|
|
||||||
<i className="fas fa-chevron-right" />
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<div />
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{canHaveFormula &&
|
|
||||||
showFormula &&
|
|
||||||
(() => {
|
|
||||||
// TODO: Be able to show modal formulas too
|
|
||||||
const curr =
|
|
||||||
props.vps.verb.tenseCategory === "imperative" &&
|
|
||||||
props.vps.verb.negative
|
|
||||||
? imperativeTenseOptions.find(
|
|
||||||
(x) => x.value === "imperfectiveImperative"
|
|
||||||
)
|
|
||||||
: [
|
|
||||||
...verbTenseOptions,
|
|
||||||
...perfectTenseOptions,
|
|
||||||
...imperativeTenseOptions,
|
|
||||||
].find(
|
|
||||||
(o) =>
|
|
||||||
o.value ===
|
|
||||||
props.vps.verb[
|
|
||||||
props.vps.verb.tenseCategory === "perfect"
|
props.vps.verb.tenseCategory === "perfect"
|
||||||
? "perfectTense"
|
? "perfectTense"
|
||||||
: props.vps.verb.tenseCategory === "imperative"
|
: props.vps.verb.tenseCategory === "imperative"
|
||||||
? "imperativeTense"
|
? "imperativeTense"
|
||||||
: "verbTense"
|
: "verbTense"
|
||||||
]
|
]);
|
||||||
);
|
const formula = !curr
|
||||||
const formula = !curr
|
? ""
|
||||||
? ""
|
: (props.vps.verb.tenseCategory === "modal")
|
||||||
: props.vps.verb.tenseCategory === "modal"
|
? composeFormula(curr.formula, "ability")
|
||||||
? composeFormula(curr.formula, "ability")
|
: (props.vps.verb.voice === "passive")
|
||||||
: props.vps.verb.voice === "passive"
|
? composeFormula(curr.formula, "passive")
|
||||||
? composeFormula(curr.formula, "passive")
|
: curr.formula;
|
||||||
: curr.formula;
|
if (curr && "formula" in curr) {
|
||||||
if (curr && "formula" in curr) {
|
return <div className="mb-2" style={{ width: "250px", overflowY: "auto" }}>
|
||||||
return (
|
<samp>{formula}</samp>
|
||||||
<div
|
</div>
|
||||||
className="mb-2"
|
}
|
||||||
style={{ width: "250px", overflowY: "auto" }}
|
})()}
|
||||||
>
|
</div>
|
||||||
<samp>{formula}</samp>
|
</div>;
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
})()}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default TensePicker;
|
export default TensePicker;
|
||||||
|
|
||||||
|
function getTenseCategory(tense: T.VerbTense | T.PerfectTense | T.AbilityTense | T.ImperativeTense): "basic" | "perfect" | "modal" | "imperative" {
|
||||||
|
if (isPerfectTense(tense)) {
|
||||||
|
return "perfect";
|
||||||
|
}
|
||||||
|
if (isVerbTense(tense)) {
|
||||||
|
return "basic";
|
||||||
|
}
|
||||||
|
if (isAbilityTense(tense)) {
|
||||||
|
return "modal";
|
||||||
|
}
|
||||||
|
if (isImperativeTense(tense)) {
|
||||||
|
return "imperative";
|
||||||
|
}
|
||||||
|
throw new Error("can't catagorize tense");
|
||||||
|
}
|
|
@ -11,10 +11,10 @@ import ModeSelect, {
|
||||||
LengthSelect,
|
LengthSelect,
|
||||||
Mode,
|
Mode,
|
||||||
ScriptSelect,
|
ScriptSelect,
|
||||||
} from "../selects/DisplayModeSelect";
|
} from "../DisplayModeSelect";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import CompiledPTextDisplay from "../text-display/CompiledPTextDisplay";
|
import CompiledPTextDisplay from "../CompiledPTextDisplay";
|
||||||
import RenderedBlocksDisplay from "../blocks/RenderedBlocksDisplay";
|
import RenderedBlocksDisplay from "../RenderedBlocksDisplay";
|
||||||
import useStickyState from "../useStickyState";
|
import useStickyState from "../useStickyState";
|
||||||
|
|
||||||
function VPDisplay({
|
function VPDisplay({
|
||||||
|
|
|
@ -1,13 +1,14 @@
|
||||||
import VerbPicker from "./VerbPicker";
|
import VerbPicker from "./VerbPicker";
|
||||||
import TensePicker from "./TensePicker";
|
import TensePicker from "./TensePicker";
|
||||||
import VPDisplay from "./VPDisplay";
|
import VPDisplay from "./VPDisplay";
|
||||||
import ButtonSelect from "../selects/ButtonSelect";
|
import ButtonSelect from "../ButtonSelect";
|
||||||
import * as T from "../../../types";
|
import * as T from "../../../types";
|
||||||
import useStickyState, { useStickyReducer } from "../useStickyState";
|
import useStickyState, { useStickyReducer } from "../useStickyState";
|
||||||
import { makeVPSelectionState } from "../../../lib/src/phrase-building/verb-selection";
|
import { makeVPSelectionState } from "../../../lib/src/phrase-building/verb-selection";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { completeVPSelection } from "../../../lib/src/phrase-building/vp-tools";
|
import { completeVPSelection } from "../../../lib/src/phrase-building/vp-tools";
|
||||||
import VPExplorerQuiz from "./VPExplorerQuiz";
|
import VPExplorerQuiz from "./VPExplorerQuiz";
|
||||||
|
// @ts-ignore
|
||||||
import LZString from "lz-string";
|
import LZString from "lz-string";
|
||||||
import {
|
import {
|
||||||
VpsReducerAction,
|
VpsReducerAction,
|
||||||
|
|
|
@ -1,95 +1,56 @@
|
||||||
import { Modal } from "react-bootstrap";
|
import { Modal } from "react-bootstrap";
|
||||||
import structureDiagram from "./vp-structure.svg";
|
import structureDiagram from "./vp-structure.svg";
|
||||||
import { roleIcon } from "../role-icons";
|
|
||||||
|
|
||||||
function VPExplorerExplanationModal({
|
export const roleIcon = {
|
||||||
showing,
|
king: <i className="mx-1 fas fa-crown" />,
|
||||||
setShowing,
|
servant: <i className="mx-1 fas fa-male" />,
|
||||||
}: {
|
};
|
||||||
showing: { role: "servant" | "king"; item: "subject" | "object" } | false;
|
|
||||||
setShowing: (
|
function VPExplorerExplanationModal({ showing, setShowing }: {
|
||||||
s: { role: "servant" | "king"; item: "subject" | "object" } | false
|
showing: { role: "servant" | "king", item: "subject" | "object" } | false,
|
||||||
) => void;
|
setShowing: (s: { role: "servant" | "king", item: "subject" | "object" } | false) => void,
|
||||||
}) {
|
}) {
|
||||||
if (!showing) return null;
|
if (!showing) return null;
|
||||||
return (
|
return <>
|
||||||
<>
|
<Modal show={!!showing} onHide={() => setShowing(false)}>
|
||||||
<Modal show={!!showing} onHide={() => setShowing(false)}>
|
<Modal.Header closeButton>
|
||||||
<Modal.Header closeButton>
|
<Modal.Title>About the {showing.role === "king"
|
||||||
<Modal.Title>
|
? <span>King {roleIcon.king}</span>
|
||||||
About the{" "}
|
: <span>Servant {roleIcon.servant}</span>}
|
||||||
{showing.role === "king" ? (
|
</Modal.Title>
|
||||||
<span>King {roleIcon.king}</span>
|
</Modal.Header>
|
||||||
) : (
|
<Modal.Body>
|
||||||
<span>Servant {roleIcon.servant}</span>
|
<p>In this tense/form, the {showing.item} is the <strong>{showing.role}</strong> {roleIcon[showing.role]} of the phrase.</p>
|
||||||
)}
|
<p>That means that:</p>
|
||||||
</Modal.Title>
|
{showing.role === "king"
|
||||||
</Modal.Header>
|
? <ul className="mt-2">
|
||||||
<Modal.Body>
|
<li>
|
||||||
<p>
|
<div>It <strong>controls the verb conjugation</strong>. The verb agrees with the gender and number of the king.</div>
|
||||||
In this tense/form, the {showing.item} is the{" "}
|
</li>
|
||||||
<strong>{showing.role}</strong> {roleIcon[showing.role]} of the
|
<li>
|
||||||
phrase.
|
<div>It can be removed / left out from the phrase.</div>
|
||||||
</p>
|
<div className="text-muted">(You can <strong>kill the king</strong>)</div>
|
||||||
<p>That means that:</p>
|
</li>
|
||||||
{showing.role === "king" ? (
|
</ul>
|
||||||
<ul className="mt-2">
|
: <ul>
|
||||||
<li>
|
<li>
|
||||||
<div>
|
<div>It can shrink it into a <a target="_blank" rel="noreferrer" href="https://grammar.lingdocs.com/pronouns/pronouns-mini/">mini-pronoun</a>.</div>
|
||||||
It <strong>controls the verb conjugation</strong>. The verb
|
<div className="text-muted">(You can <strong>shrink the servant</strong>)</div>
|
||||||
agrees with the gender and number of the king.
|
</li>
|
||||||
</div>
|
</ul>
|
||||||
</li>
|
}
|
||||||
<li>
|
<h4>Mnemonic for shortening phrases:</h4>
|
||||||
<div>It can be removed / left out from the phrase.</div>
|
<p className="text-muted lead">"🚫 Kill the king 👶 Shrink the servant"</p>
|
||||||
<div className="text-muted">
|
<h4 className="mb-3">Verb Phrase Structure</h4>
|
||||||
(You can <strong>kill the king</strong>)
|
<img className="img-fluid" alt="Pashto verb phrase structure diagram" src={structureDiagram} />
|
||||||
</div>
|
</Modal.Body>
|
||||||
</li>
|
<Modal.Footer>
|
||||||
</ul>
|
<button type="button" className="btn btn-primary clb" onClick={() => setShowing(false)}>
|
||||||
) : (
|
Close
|
||||||
<ul>
|
</button>
|
||||||
<li>
|
</Modal.Footer>
|
||||||
<div>
|
</Modal>
|
||||||
It can shrink it into a{" "}
|
</>;
|
||||||
<a
|
|
||||||
target="_blank"
|
|
||||||
rel="noreferrer"
|
|
||||||
href="https://grammar.lingdocs.com/pronouns/pronouns-mini/"
|
|
||||||
>
|
|
||||||
mini-pronoun
|
|
||||||
</a>
|
|
||||||
.
|
|
||||||
</div>
|
|
||||||
<div className="text-muted">
|
|
||||||
(You can <strong>shrink the servant</strong>)
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
)}
|
|
||||||
<h4>Mnemonic for shortening phrases:</h4>
|
|
||||||
<p className="text-muted lead">
|
|
||||||
"🚫 Kill the king 👶 Shrink the servant"
|
|
||||||
</p>
|
|
||||||
<h4 className="mb-3">Verb Phrase Structure</h4>
|
|
||||||
<img
|
|
||||||
className="img-fluid"
|
|
||||||
alt="Pashto verb phrase structure diagram"
|
|
||||||
src={structureDiagram}
|
|
||||||
/>
|
|
||||||
</Modal.Body>
|
|
||||||
<Modal.Footer>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className="btn btn-primary clb"
|
|
||||||
onClick={() => setShowing(false)}
|
|
||||||
>
|
|
||||||
Close
|
|
||||||
</button>
|
|
||||||
</Modal.Footer>
|
|
||||||
</Modal>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default VPExplorerExplanationModal;
|
export default VPExplorerExplanationModal;
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue