Compare commits

..

No commits in common. "db2749bd0a3d1a441c9f6ee6c4687e29b08cff2c" and "c5a238ab05c06eb448878ed87235254760aa6737" have entirely different histories.

186 changed files with 60706 additions and 8312 deletions

1
.env Normal file
View File

@ -0,0 +1 @@
SKIP_PREFLIGHT_CHECK=true

View File

@ -18,13 +18,12 @@ jobs:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
node-version: 16
cache: "yarn"
- name: Install, build, test
run: |
yarn install-all
yarn build-lib
yarn build-components
yarn install-r
yarn build-library
yarn build-website
yarn test --silent
yarn check-all-inflections

View File

@ -16,7 +16,7 @@ jobs:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
node-version: 16
cache: "yarn"
- name: Check if version has been updated
id: check
@ -27,10 +27,10 @@ jobs:
- name: Publish when version changed
if: steps.check.outputs.changed == 'true'
run: |
yarn install-all
yarn build-lib
yarn build-components
yarn install-r
yarn build-library
yarn test --silent
yarn check-all-inflections
cp .npmrc src/lib
cp .npmrc src/components
cd src/lib

43
.gitignore vendored
View File

@ -1,15 +1,12 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# fetched vocab
src/verbs.ts
src/nouns-adjs.ts
# compiled library files
dist
# dependencies
node_modules
/.pnp
.pnp.js
# testing
/coverage
@ -17,26 +14,20 @@ src/nouns-adjs.ts
dict
diac.ts
node_modules
# production
build
dist
dist-ssr
*.local
/.pnp
.pnp.js
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
npm-debug.log*
yarn-debug.log*
yarn-error.log*
src/verbs.ts
src/nouns-adjs.ts

2
.nvmrc
View File

@ -1 +1 @@
20
16

View File

@ -1,5 +1,8 @@
{
"typescript.preferences.autoImportFileExcludePatterns": [
"library.ts"
"../../library.ts"
],
"cSpell.words": [
"کارخانه"
],
}

View File

@ -2,6 +2,7 @@ import * as T from "./src/types";
import { inflectWord } from "./src/lib/src/pashto-inflector";
import * as tp from "./src/lib/src/type-predicates";
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
// no errors are thrown in the process
@ -14,8 +15,8 @@ type InflectionError = {
};
async function checkAll() {
console.log("Checking inflection functions on all dictionary words");
const res = await fetch(process.env.LINGDOCS_DICTIONARY_URL);
// @ts-ignore
const { entries }: T.Dictionary = await res.json();
const errors: InflectionError[] = [];

2
dev Executable file
View File

@ -0,0 +1,2 @@
nix-shell --command return
node --version

View File

@ -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 },
],
},
})

View File

@ -6,6 +6,7 @@
*
*/
const fs = require("fs");
const fetch = require("node-fetch-commonjs");
const path = require("path");
const verbCollectionPath = path.join(".", "vocab", "verbs");
const nounAdjCollectionPath = path.join(".", "vocab", "nouns-adjs");

1
global.d.ts vendored
View File

@ -1 +0,0 @@
import "jest-extended";

View File

@ -1,13 +0,0 @@
/** @type {import('ts-jest').JestConfigWithTsJest} **/
export default {
testEnvironment: "node",
transform: {
"^.+.tsx?$": [
"ts-jest",
{
tsconfig: "tsconfig.app.json",
},
],
},
setupFilesAfterEnv: ["./testSetup.ts"],
};

View File

@ -1,3 +1,3 @@
[build]
command = "yarn install-all && yarn test --runInBand --no-cache && yarn build-website"
publish = "dist/"
command = "yarn install-r && yarn test --silent && yarn build-website"
publish = "build/"

View File

@ -1,47 +1,82 @@
{
"name": "pashto-inflector-website",
"version": "7.6.3",
"type": "module",
"name": "pashto-inflector",
"version": "7.5.1",
"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": {
"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",
"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' ***!'",
"dev": "vite",
"lint": "eslint .",
"test": "jest",
"preview": "vite preview",
"install-all": "yarn install && node get-words.cjs && cd src/lib && yarn install && cd ../components && yarn install",
"build-website": "tsc -b && vite build",
"build-components": "rm -rf src/components/dist && tsc --project src/components/tsconfig.json && cd src/components && node post-build.cjs",
"build-lib": "rm -rf src/lib/dist && tsc --project src/lib/tsconfig.json",
"get-words": "node get-words.cjs",
"preinstall": "echo '*** Be sure to use yarn install-r not yarn install ***!'",
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject",
"install-r": "yarn install && node get-words.js && cd src/lib && yarn install && cd ../components && yarn install",
"build-website": "node get-words.js && npm run build",
"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",
"test-ci": "npm run test -- --watchAll=false",
"get-words": "node get-words.js",
"check-all-inflections": "tsx check-all-inflections.ts"
},
"dependencies": {
"react": "^18.3.1",
"react-dom": "^18.3.1",
"bootstrap": "4.6.1",
"react-bootstrap": "1.5.1",
"@fortawesome/fontawesome-free": "^5.15.2"
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
],
"rules": {
"no-warning-comments": [
1,
{
"terms": [
"fixme",
"xxx"
],
"location": "anywhere"
}
]
}
},
"devDependencies": {
"@eslint/js": "^9.8.0",
"@types/jest": "^29.5.12",
"@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0",
"@vitejs/plugin-react": "^4.3.1",
"eslint": "^9.8.0",
"eslint-plugin-react-hooks": "^5.1.0-rc.0",
"eslint-plugin-react-refresh": "^0.4.9",
"globals": "^15.9.0",
"jest": "^29.7.0",
"jest-environment-jsdom": "^29.7.0",
"jest-extended": "^4.0.2",
"ts-jest": "^29.2.4",
"tsx": "^4.17.0",
"typescript": "^5.5.3",
"typescript-eslint": "^8.0.0",
"vite": "^5.4.0"
}
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
},
"dependencies": {}
}

View File

@ -2,11 +2,11 @@
<html lang="en">
<head>
<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="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" />
<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="author" content="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:creator" content="@lingdocs" />
<link rel="manifest" href="/manifest.json" />
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<title>Pashto Inflector</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
<body class="d-flex flex-column h-100" id="root">
<noscript>You need to enable JavaScript to run this app.</noscript>
</body>
</html>

10
shell.nix Normal file
View File

@ -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
];
}

View File

@ -8,7 +8,7 @@
import { useEffect, useState } from "react";
import ButtonSelect from "./components/src/selects/ButtonSelect";
import ButtonSelect from "./components/src/ButtonSelect";
import { Modal } from "react-bootstrap";
import * as T from "./types";
import defualtTextOptions from "./lib/src/default-text-options";
@ -36,7 +36,6 @@ function App() {
function handleHiderClick(label: string) {
setShowing((os) => (os === label ? "" : label));
}
useEffect(() => {
document.documentElement.setAttribute("data-theme", theme);
}, [theme]);
@ -166,9 +165,7 @@ function App() {
show={showingTextOptions}
onHide={() => setShowingTextOptions(false)}
>
{/* @ts-expect-error strange react-bootstrap issue */}
<Modal.Header closeButton>
{/* @ts-expect-error strange react-bootstrap issue */}
<Modal.Title>Settings</Modal.Title>
</Modal.Header>
<Modal.Body>

View File

@ -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

2
src/components/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
storybook-static
node_modules

View File

@ -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"
}

View File

@ -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$/,
},
},
}

View File

@ -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"
]
}

View File

@ -1,81 +1,80 @@
import Pashto from "./src/text-display/Pashto";
import Phonetics from "./src/text-display/Phonetics";
import InlinePs from "./src/text-display/InlinePs";
import Examples from "./src/text-display/Examples";
import CompiledPTextDisplay from "./src/text-display/CompiledPTextDisplay";
import ButtonSelect from "./src/selects/ButtonSelect";
import EntrySelect from "./src/selects/EntrySelect";
import PersonSelection from "./src/selects/PersonSelection";
/**
* 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 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 InflectionsTable from "./src/tables/InflectionsTable";
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 EntrySelect from "./src/EntrySelect";
import VerbInfo, { RootsAndStems } from "./src/verb-info/VerbInfo";
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 APPicker from "./src/block-pickers/APPicker";
import { vpsReducer } from "../lib/src/phrase-building/vps-reducer";
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 VPPicker from "./src/vp-explorer/VPPicker";
import NPDisplay from "./src/vp-explorer/NPDisplay";
import HumanReadableInflectionPattern from "./src/tables/HumanReadableInflectionPattern";
import { psJSXMap } from "./src/text-display/jsx-map";
import HumanReadableInflectionPattern from "./src/HumanReadableInflectionPattern";
import { psJSXMap } from "./src/jsx-map";
import genderColors from "./src/gender-colors";
// this library also includes everything from the core inflect library
export * from "../lib/library";
export {
// text-display
InlinePs,
Pashto,
Phonetics,
CompiledPTextDisplay,
Examples,
// selects
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,
EPExplorer,
VPExplorer,
Examples,
VerbFormDisplay,
VerbTable,
VerbInfo,
RootsAndStems,
InflectionsTable,
Pashto,
Phonetics,
InlinePs,
ButtonSelect,
Hider,
EntrySelect,
NPPicker,
APPicker,
NPBlock,
APBlock,
Block,
EPDisplay,
VPDisplay,
VPPicker,
NPDisplay,
EPPicker,
VPPicker,
CompiledPTextDisplay,
RenderedBlocksDisplay,
HumanReadableInflectionPattern,
psJSXMap,
genderColors,
};
}
export type VpsReducerAction = VpsA;

35162
src/components/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{
"name": "@lingdocs/ps-react",
"version": "7.6.3",
"version": "7.5.1",
"description": "Pashto inflector library module with React components",
"main": "dist/components/library.js",
"module": "dist/components/library.js",
@ -18,24 +18,35 @@
"LICENSE"
],
"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",
"license": "GPL-3.0",
"license": "MIT",
"dependencies": {
"@formkit/auto-animate": "^1.0.0-beta.3",
"classnames": "^2.5.1",
"classnames": "^2.2.6",
"fp-ts": "^2.16.0",
"lokijs": "^1.5.12",
"jsurl2": "^2.1.0",
"lz-string": "^1.4.4",
"micro-memoize": "^4.1.2",
"pbf": "^3.2.1",
"rambda": "^7.3.0",
"react-bootstrap": "^1.5.1",
"react-error-boundary": "^4.0.13",
"react-select": "^5.4.0"
},
"devDependencies": {
"@types/lokijs": "^1.5.14",
"fs-extra": "^11.2.0"
"@babel/core": "^7.21.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"
}
}

View File

@ -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;

View File

@ -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;

View File

@ -1,8 +1,8 @@
import { useState, useEffect } from "react";
import * as T from "../../../types";
import AdjectivePicker from "./AdjectivePicker";
import LocativeAdverbPicker from "./LocativeAdverbPicker";
import SandwichPicker from "./SandwichPicker";
import * as T from "../../types";
import AdjectivePicker from "./np-picker/AdjectivePicker";
import LocativeAdverbPicker from "./ep-explorer/eq-comp-picker/LocativeAdverbPicker";
import SandwichPicker from "./np-picker/SandwichPicker";
const compTypes: T.ComplementType[] = [
"adjective",
"loc. adv.",

View File

@ -1,12 +1,31 @@
import * as T from "../../../types";
import * as T from "../../types";
import { StyleHTMLAttributes } from "react";
import Select from "react-select";
import Select, { StylesConfig } from "react-select";
import AsyncSelect from "react-select/async";
import {
makeSelectOption,
makeVerbSelectOption,
} from "../block-pickers/picker-tools";
import { customSelectStyles as customStyles } from "./select-styles";
} from "./np-picker/picker-tools";
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: {
entryFeeder: T.EntryFeederSingleType<E>;
@ -62,7 +81,7 @@ function EntrySelect<E extends T.DictionaryEntry | T.VerbEntry>(props: {
isSearchable={true}
className="mb-2"
value={value}
// @ts-expect-error select issue
// @ts-ignore
onChange={onChange}
defaultOptions={[]}
loadOptions={options}
@ -101,7 +120,7 @@ function EntrySelect<E extends T.DictionaryEntry | T.VerbEntry>(props: {
styles={customStyles}
isSearchable={true}
value={value || null}
// @ts-expect-error select issue
// @ts-ignore - sadly gets messed up when using customStyles
onChange={onChange}
className="mb-2"
options={options}
@ -140,7 +159,7 @@ export function DeterminerSelect(props: {
isSearchable={true}
value={value}
isMulti
// @ts-expect-error select issue
// @ts-ignore - gets messed up when using customStyles
onChange={onChange}
className="mb-2"
options={options}
@ -197,7 +216,7 @@ export function SandwichSelect<E extends T.Sandwich>(props: {
styles={customStyles}
isSearchable={true}
value={value}
// @ts-expect-error select issue
// @ts-ignore - gets messed up when using customStyles
onChange={onChange}
className="mb-2"
options={options}

View File

@ -8,10 +8,9 @@
import Pashto from "./Pashto";
import Phonetics from "./Phonetics";
import * as T from "../../../types";
import { ReactNode } from "react";
import * as T from "../../types";
type PsStringWSub = T.PsString & { sub?: ReactNode };
type PsStringWSub = T.PsString & { sub?: any };
function EnglishContent({
children,
@ -55,10 +54,10 @@ function Examples(
}
>
<div>
<Pashto opts={props.opts} ps={text} />
<Pashto opts={props.opts}>{text}</Pashto>
</div>
<div>
<Phonetics opts={props.opts} ps={text} />
<Phonetics opts={props.opts}>{text}</Phonetics>
</div>
{text.e && <EnglishContent>{text.e}</EnglishContent>}
{text.sub && <div className="small text-muted">{text.sub}</div>}

View File

@ -9,88 +9,65 @@
import { createElement, useEffect, useRef } from "react";
import classNames from "classnames";
import * as T from "../../types";
// @ts-expect-error types needed
import autoAnimate from "@formkit/auto-animate";
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"
>
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">
<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"
>
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 indentAfterLevel = 5;
function Hider(props: {
label: string | JSX.Element;
showing: boolean;
aspect?: T.Aspect;
handleChange: () => void;
children: React.ReactNode;
hLevel?: number;
ignore?: boolean;
label: string | JSX.Element,
showing: boolean,
aspect?: T.Aspect,
handleChange: () => void,
children: React.ReactNode,
hLevel?: number,
ignore?: boolean,
}) {
const parent = useRef<HTMLDivElement>(null);
useEffect(() => {
if (parent.current) {
autoAnimate(parent.current);
}
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}`
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}</>;
return <>
{props.children}
</>;
}
return (
<div className="mb-3" ref={parent}>
return <div className="mb-3" ref={parent}>
{createElement(
`h${hLev}`,
{
onClick: props.handleChange,
className: classNames("clickable", extraMargin),
className: classNames(
"clickable",
extraMargin,
),
},
<div className="d-flex flex-row align-items-center">
<div style={{ width: "1rem" }}>
{props.showing ? caretDown : caretRight}
</div>
{` `}
{props.aspect ? (
<i
className={`fas fa-${
props.aspect === "imperfective" ? "video" : "camera"
}`}
/>
) : (
""
)}
<div className="ml-2">{props.label}</div>
{props.aspect
? <i className={`fas fa-${props.aspect === "imperfective" ? "video" : "camera"}`} />
: ""}
<div className="ml-2">
{props.label}
</div>
</div>,
)}
{props.showing && props.children}
</div>
);
}
export default Hider;

View File

@ -1,5 +1,5 @@
import * as T from "../../../types";
import InlinePs from "./../text-display/InlinePs";
import * as T from "../../types";
import InlinePs from "./InlinePs";
export default function HumanReadableInflectionPattern(
p: T.InflectionPattern,
@ -10,12 +10,12 @@ export default function HumanReadableInflectionPattern(
) : p === 2 ? (
<span>
#2 Unstressed{" "}
<InlinePs opts={textOptions} ps={{ p: "ی", f: "ay", e: "" }} />
<InlinePs opts={textOptions}>{{ p: "ی", f: "ay", e: "" }}</InlinePs>
</span>
) : p === 3 ? (
<span>
#3 Stressed{" "}
<InlinePs opts={textOptions} ps={{ p: "ی", f: "áy", e: "" }} />
<InlinePs opts={textOptions}>{{ p: "ی", f: "áy", e: "" }}</InlinePs>
</span>
) : p === 4 ? (
<span>#4 "Pashtoon"</span>
@ -24,7 +24,7 @@ export default function HumanReadableInflectionPattern(
) : p === 6 ? (
<span>
#6 Fem. inan.{" "}
<InlinePs opts={textOptions} ps={{ p: "ي", f: "ee", e: "" }} />
<InlinePs opts={textOptions}>{{ p: "ي", f: "ee", e: "" }}</InlinePs>
</span>
) : null;
}

View File

@ -13,8 +13,8 @@
// import Pashto from "./Pashto";
// import { Modal } from "react-bootstrap";
import TableCell from "./TableCell";
import * as T from "../../../types";
import { isPluralInflections } from "../../../lib/src/p-text-helpers";
import * as T from "../../types";
import { isPluralInflections } from "../../lib/src/p-text-helpers";
// const explanation = (inf: T.Inflections | T.PluralInflections, textOptions: T.TextOptions) => {
// const isPluralInfs = isPluralInflections(inf);
@ -114,8 +114,6 @@ const InflectionTable = ({
<tr key={title}>
<th scope="row">{title}</th>
{"masc" in inf && (
// eslint-disable-next-line
// @ts-ignore
<TableCell
item={inf.masc[i]}
textOptions={textOptions}
@ -124,8 +122,6 @@ const InflectionTable = ({
/>
)}
{"fem" in inf && (!("masc" in inf) || i < 2) && (
// eslint-disable-next-line
// @ts-ignore
<TableCell item={inf.fem[i]} textOptions={textOptions} center />
)}
</tr>

View File

@ -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;

View File

@ -10,8 +10,7 @@ const Keyframes = (props: IProps) => {
typeof cssObject === "string"
? cssObject
: Object.keys(cssObject).reduce((accumulator, key) => {
const cssKey = key.replace(/[A-Z]/g, (v) => `-${v.toLowerCase()}`);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const cssKey = key.replace(/[A-Z]/g, v => `-${v.toLowerCase()}`);
const cssValue = (cssObject as any)[key].toString().replace("'", "");
return `${accumulator}${cssKey}:${cssValue};`;
}, "");
@ -20,7 +19,7 @@ const Keyframes = (props: IProps) => {
<style>
{`@keyframes ${props.name} {
${Object.keys(props)
.map((key) => {
.map(key => {
return ["from", "to"].includes(key)
? `${key} { ${toCss(props[key])} }`
: /^_[0-9]+$/.test(key)

View File

@ -6,17 +6,17 @@
*
*/
import { convertSpelling } from "../../../lib/src/convert-spelling";
import { phoneticsToDiacritics } from "../../../lib/src/phonetics-to-diacritics";
import { convertSpelling } from "../../lib/src/convert-spelling";
import { phoneticsToDiacritics } from "../../lib/src/phonetics-to-diacritics";
import { psJSXMap } from "./jsx-map";
import * as T from "../../../types";
import * as T from "../../types";
const Pashto = ({
opts,
ps,
children: text,
}: {
opts: T.TextOptions;
ps: T.PsString | T.PsJSX;
children: T.PsString | T.PsJSX;
}) => {
function convertText(ps: T.PsString, opts: T.TextOptions): string {
const p = opts.diacritics
@ -30,11 +30,11 @@ const Pashto = ({
: { fontSize: opts.pTextSize === "larger" ? "large" : "larger" };
return (
<span className="p-text" dir="rtl" style={style} lang="ps">
{typeof ps.p !== "string" && typeof ps.f !== "string"
? psJSXMap(ps as T.PsJSX, "p", (ps: T.PsString) =>
{typeof text.p !== "string" && typeof text.f !== "string"
? psJSXMap(text as T.PsJSX, "p", (ps: T.PsString) =>
convertText(ps, opts)
)
: convertText(ps as T.PsString, opts)}
: convertText(text as T.PsString, opts)}
</span>
);
};

View File

@ -6,7 +6,7 @@
*
*/
import * as T from "../../../types";
import * as T from "../../types";
const persInfs: {
label: string;
@ -35,7 +35,7 @@ function PersInfsPicker(props: {
persInf: T.PersonInflectionsField;
handleChange: (persInf: T.PersonInflectionsField) => void;
}) {
function hChange(e: React.ChangeEvent<HTMLSelectElement>) {
function hChange(e: any) {
const newValue = e.target.value as T.PersonInflectionsField;
props.handleChange(newValue);
}

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -1,7 +1,7 @@
import { useState, useEffect } from "react";
import * as T from "../../../types";
import Pashto from "./../text-display/Pashto";
import Phonetics from "./../text-display/Phonetics";
import * as T from "../../types";
import Pashto from "./Pashto";
import Phonetics from "./Phonetics";
const arrowDown = (
<svg
@ -19,7 +19,7 @@ const arrowDown = (
</svg>
);
const TableCell = ({
function TableCell({
item,
textOptions,
center,
@ -31,7 +31,7 @@ const TableCell = ({
center?: boolean;
noBorder?: boolean;
colSpan?: 1 | 2;
}) => {
}) {
const [version, setVersion] = useState(0);
useEffect(() => setVersion(0), [item]);
function advanceVersion() {
@ -56,10 +56,10 @@ const TableCell = ({
>
<div>
<div>
<Pashto opts={textOptions} ps={w} />
<Pashto opts={textOptions}>{w}</Pashto>
</div>
<div>
<Phonetics opts={textOptions} ps={w} />
<Phonetics opts={textOptions}>{w}</Phonetics>
</div>
{w.e &&
(Array.isArray(w.e) ? (
@ -83,6 +83,6 @@ const TableCell = ({
</div>
</td>
);
};
}
export default TableCell;

View File

@ -7,11 +7,11 @@
*/
import { useEffect, useState } from "react";
import PersonInfsPicker from "./selects/PersInfsPicker";
import InflectionsTable from "./tables/InflectionsTable";
import SingleItemDisplay from "./text-display/SingleItemDisplay";
import ButtonSelect from "./selects/ButtonSelect";
import VerbTable from "./tables/VerbTable";
import PersonInfsPicker from "./PersInfsPicker";
import InflectionsTable from "./InflectionsTable";
import SingleItemDisplay from "./SingleItemDisplay";
import ButtonSelect from "./ButtonSelect";
import VerbTable from "./VerbTable";
import {
getEnglishPersonInfo,
isSentenceForm,

View File

@ -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;

View File

@ -1,6 +1,6 @@
import { useState, useEffect } from "react";
import * as T from "../../../types";
import SandwichPicker from "../block-pickers/SandwichPicker";
import SandwichPicker from "../np-picker/SandwichPicker";
import AdverbPicker from "./AdverbPicker";
type APType = "adverb" | "sandwich";
const types: APType[] = ["adverb", "sandwich"];

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -8,7 +8,7 @@ import {
} from "../../../lib/src/misc-helpers";
import { useState } from "react";
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 { flattenLengths } from "../../library";
@ -139,7 +139,7 @@ function Border({
}
function VBBlock({
// opts,
opts,
block,
script,
}: {
@ -286,7 +286,7 @@ function WeldedBlock({
// }
function PerfHeadBlock({
// opts,
opts,
ps,
script,
}: {
@ -319,7 +319,7 @@ function PerfHeadBlock({
// }
function NegBlock({
// opts,
opts,
imperative,
script,
}: {
@ -339,7 +339,7 @@ function NegBlock({
}
function EquativeBlock({
// opts,
opts,
eq,
script,
}: {
@ -425,7 +425,7 @@ function ObjectBlock({
}
function NCompBlock({
// opts,
opts,
comp,
script,
}: {
@ -465,7 +465,7 @@ function ComplementBlock({
inside?: boolean;
}) {
function AdjectiveBlock({
// opts,
opts,
adj,
}: {
opts: T.TextOptions;
@ -486,7 +486,7 @@ function ComplementBlock({
}
function LocAdvBlock({
// opts,
opts,
adv,
}: {
opts: T.TextOptions;
@ -601,7 +601,7 @@ function Sandwich({
}
function Determiners({
// opts,
opts,
script,
children,
}: {
@ -628,7 +628,7 @@ function Determiners({
}
function CompNounBlock({
// opts,
opts,
noun,
script,
}: {
@ -703,7 +703,7 @@ export function NPBlock({
{flattenLengths(np.selection.ps)[0][script]}
</div>,
].filter((x) => {
// @ts-expect-error yes I know
// @ts-ignore
return x !== " ";
});
const el = script === "p" ? elements.reverse() : elements;
@ -793,7 +793,7 @@ function Possesors({
}
function Adjectives({
// opts,
opts,
children,
script,
}: {

View File

@ -1,27 +1,21 @@
import { baParticle } from "../../../lib/src/grammar-units";
import * as T from "../../../types";
import Pashto from "../text-display/Pashto";
import Phonetics from "../text-display/Phonetics";
import Pashto from "../Pashto";
import Phonetics from "../Phonetics";
function KidDisplay({
opts,
kid,
script,
}: {
opts: T.TextOptions;
kid: T.Kid;
script: "p" | "f";
function KidDisplay({ opts, kid, script }: {
opts: T.TextOptions,
kid: T.Kid,
script: "p" | "f",
}) {
const ps = kid.kid.type === "ba" ? baParticle : kid.kid.ps;
return (
<div className="mx-1" key={kid.key}>
{script === "p" ? (
<Pashto opts={opts} ps={ps} />
) : (
<Phonetics opts={opts} ps={ps} />
)}
const ps = kid.kid.type === "ba"
? baParticle
: kid.kid.ps;
return <div className="mx-1" key={kid.key}>
{script === "p"
? <Pashto opts={opts}>{ps}</Pashto>
: <Phonetics opts={opts}>{ps}</Phonetics>}
</div>
);
}
export default KidDisplay;

View File

@ -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;

View File

@ -1,80 +1,51 @@
import * as T from "../../../types";
import {
completeEPSelection,
renderEP,
} from "../../../lib/src/phrase-building/render-ep";
import { completeEPSelection, renderEP } from "../../../lib/src/phrase-building/render-ep";
import { compileEP } from "../../../lib/src/phrase-building/compile";
import ButtonSelect from "../selects/ButtonSelect";
import {
getPredicateSelectionFromBlocks,
getSubjectSelection,
getSubjectSelectionFromBlocks,
} from "../../../lib/src/phrase-building/blocks-utils";
import ButtonSelect from "../ButtonSelect";
import { getPredicateSelectionFromBlocks, getSubjectSelection, getSubjectSelectionFromBlocks } from "../../../lib/src/phrase-building/blocks-utils";
import { useState } from "react";
import CompiledPTextDisplay from "../text-display/CompiledPTextDisplay";
import EPBlocksDisplay from "../blocks/RenderedBlocksDisplay";
import ModeSelect, { Mode, ScriptSelect } from "../selects/DisplayModeSelect";
import CompiledPTextDisplay from "../CompiledPTextDisplay";
import EPBlocksDisplay from "../RenderedBlocksDisplay";
import ModeSelect, { Mode, ScriptSelect } from "../DisplayModeSelect";
import useStickyState from "../useStickyState";
function EPDisplay({
eps,
opts,
setOmitSubject,
justify,
onlyOne,
length,
mode: preferredMode,
script: preferredScript,
}: {
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";
function EPDisplay({ eps, opts, setOmitSubject, justify, onlyOne, length, mode: preferredMode, script: preferredScript }: {
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 [script, setScript] = useStickyState<"p" | "f">(
preferredScript || "f",
"blockScriptChoice"
);
const [script, setScript] = useStickyState<"p" | "f">(preferredScript || "f", "blockScriptChoice");
const EP = completeEPSelection(eps);
const subject = getSubjectSelection(eps.blocks);
if (!EP) {
return (
<div className="lead text-center my-4">
{!subject && !eps.predicate[eps.predicate.type]
return <div className="lead text-center my-4">
{(!subject && !eps.predicate[eps.predicate.type])
? "Select Subject and Predicate"
: subject && !eps.predicate[eps.predicate.type]
: (subject && !eps.predicate[eps.predicate.type])
? "Select Predicate"
: !subject && eps.predicate[eps.predicate.type]
: (!subject && eps.predicate[eps.predicate.type])
? "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">
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} />
)}
{mode === "blocks" && <ScriptSelect value={script} onChange={setScript} />}
</div>
{setOmitSubject !== false ? (
<ButtonSelect
{setOmitSubject !== false ? <ButtonSelect
small
value={(eps.omitSubject ? "true" : "false") as "true" | "false"}
options={[
@ -82,70 +53,26 @@ function EPDisplay({
{ value: "true", label: "No Subj."},
]}
handleChange={setOmitSubject}
/>
) : (
<div />
)}
/> : <div />}
<div />
</div>
{mode === "text" ? (
<CompiledPTextDisplay
opts={opts}
compiled={result}
justify={justify}
onlyOne={!!onlyOne}
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"
}`}
>
{mode === "text"
? <CompiledPTextDisplay opts={opts} compiled={result} justify={justify} onlyOne={!!onlyOne} 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>}
{EP.predicate.selection.selection.type === "participle" && <div style={{ maxWidth: "6 00px", margin: "0 auto" }} className="alert alert-warning mt-3 pt-4">
<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 : "the particple"}".</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>
)}
{EP.predicate.selection.selection.type === "participle" && (
<div
style={{ maxWidth: "6 00px", margin: "0 auto" }}
className="alert alert-warning mt-3 pt-4"
>
<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
: "the particple"}
".
</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>
);
}
export default EPDisplay;

View File

@ -1,14 +1,18 @@
import * as T from "../../../types";
import useStickyState, { useStickyReducer } from "../useStickyState";
import EPDisplay from "./EPDisplay";
import ButtonSelect from "../selects/ButtonSelect";
import ButtonSelect from "../ButtonSelect";
import EqChartsDisplay from "./EqChartsDisplay";
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 { makeEPSBlocks } from "../../../lib/src/phrase-building/blocks-utils";
// @ts-expect-error types not working for this
import autoAnimate from "@formkit/auto-animate";
// @ts-ignore
import LZString from "lz-string";
import EPPicker from "./EPPicker";
const epPhraseURLParam = "ep";
@ -30,18 +34,15 @@ const blankEps: T.EPSelectionState = {
// TODO: put the clear button beside the title in the predicate picker?
function EPExplorer(props: {
opts: T.TextOptions;
entryFeeder: T.EntryFeeder;
opts: T.TextOptions,
entryFeeder: T.EntryFeeder,
}) {
const [mode, setMode] = useStickyState<"charts" | "phrases">(
"charts",
"EPExplorerMode"
);
const [mode, setMode] = useStickyState<"charts" | "phrases">("charts", "EPExplorerMode");
const [eps, adjustEps] = useStickyReducer(
epsReducer,
blankEps,
"EPState8",
flashMessage
flashMessage,
);
// const [
// // alertMsg,
@ -50,9 +51,7 @@ function EPExplorer(props: {
const [showClipped, setShowClipped] = useState<string>("");
const parent = useRef<HTMLDivElement>(null);
useEffect(() => {
if (parent.current) {
autoAnimate(parent.current);
}
parent.current && autoAnimate(parent.current);
}, [parent]);
useEffect(() => {
const EPSFromUrl = getEPSFromUrl();
@ -93,8 +92,7 @@ function EPExplorer(props: {
flashClippedMessage("Copied phrase URL to clipboard");
}
const phraseIsComplete = !!completeEPSelection(eps);
return (
<div>
return <div>
<div className="mt-2 mb-3 d-flex flex-row justify-content-between align-items-center">
<div />
<ButtonSelect
@ -105,18 +103,13 @@ function EPExplorer(props: {
]}
handleChange={setMode}
/>
{!("eps" in props) && (
<div className="d-flex flex-row">
{!("eps" in props) && <div className="d-flex flex-row">
<div
className="clickable mr-4"
onClick={mode === "phrases" ? handleCopyCode : undefined}
style={{ width: "1rem" }}
>
{mode === "phrases" && phraseIsComplete ? (
<i className="fas fa-code" />
) : (
""
)}
{(mode === "phrases" && phraseIsComplete) ? <i className="fas fa-code" /> : ""}
</div>
<div
className="clickable"
@ -125,29 +118,22 @@ function EPExplorer(props: {
>
{mode === "phrases" ? <i className="fas fa-share-alt" /> : ""}
</div>
</div>}
</div>
)}
</div>
{mode === "phrases" && (
{mode === "phrases" &&
<EPPicker
opts={props.opts}
entryFeeder={props.entryFeeder}
eps={eps}
onChange={handleEpsChange}
/>
)}
}
{mode === "charts" && <EqChartsDisplay opts={props.opts} />}
{mode === "phrases" && (
<EPDisplay
{mode === "phrases" && <EPDisplay
opts={props.opts}
eps={eps}
setOmitSubject={
"eps" in props
? false
: (payload) => adjustEps({ type: "set omitSubject", payload })
}
/>
)}
setOmitSubject={"eps" in props ? false : payload => adjustEps({ type: "set omitSubject", payload })}
/>}
{/* {alertMsg && <div className="alert alert-warning text-center" role="alert" style={{
position: "fixed",
top: "30%",
@ -157,23 +143,16 @@ function EPExplorer(props: {
}}>
{alertMsg}
</div>} */}
{showClipped && (
<div
className="alert alert-primary text-center"
role="alert"
style={{
{showClipped && <div className="alert alert-primary text-center" role="alert" style={{
position: "fixed",
top: "30%",
left: "50%",
transform: "translate(-50%, -50%)",
zIndex: 9999999999999,
}}
>
}}>
{showClipped}
</div>
)}
</div>
);
</div>}
</div>;
}
export default EPExplorer;

View File

@ -1,15 +1,14 @@
import * as T from "../../../types";
import NPPicker from "../block-pickers/NPPicker";
import NPPicker from "../np-picker/NPPicker";
import EquativePicker from "./EquativePicker";
import ButtonSelect from "../selects/ButtonSelect";
import ComplementPicker from "../block-pickers/ComplementPicker";
import ButtonSelect from "../ButtonSelect";
import ComplementPicker from "../ComplementPicker";
import epsReducer, {
EpsReducerAction,
} from "../../../lib/src/phrase-building/eps-reducer";
import { useEffect, useRef } from "react";
import { completeEPSelection } from "../../../lib/src/phrase-building/render-ep";
import APPicker from "../block-pickers/APPicker";
// @ts-expect-error types not working for this
import APPicker from "../../src/ap-picker/APPicker";
import autoAnimate from "@formkit/auto-animate";
function EPPicker({
@ -25,9 +24,7 @@ function EPPicker({
}) {
const parent = useRef<HTMLDivElement>(null);
useEffect(() => {
if (parent.current) {
autoAnimate(parent.current);
}
parent.current && autoAnimate(parent.current);
}, [parent]);
function adjustEps(action: EpsReducerAction) {
onChange(epsReducer(eps, action));

View File

@ -1,17 +1,13 @@
import * as T from "../../../types";
import * as T from "../../../types"
import Select from "react-select";
import ButtonSelect from "../selects/ButtonSelect";
import ButtonSelect from "../ButtonSelect";
import { epTenseOptions as options } from "./epTenseOptions";
import { customSelectStyles as customStyles } from "../selects/select-styles";
import { customStyles } from "../EntrySelect";
function EquativePicker({
equative,
onChange,
hideNegative,
}: {
equative: { tense: T.EquativeTense; negative: boolean };
onChange: (e: { tense: T.EquativeTense; negative: boolean }) => void;
hideNegative?: boolean;
function EquativePicker({ equative, onChange, hideNegative }: {
equative: { tense: T.EquativeTense, negative: boolean },
onChange: (e: { tense: T.EquativeTense, negative: boolean }) => void,
hideNegative?: boolean,
}) {
function onTenseSelect(o: { value: T.EquativeTense } | null) {
const value = o?.value ? o.value : undefined;
@ -25,17 +21,14 @@ function EquativePicker({
}
function moveTense(dir: "forward" | "back") {
return () => {
const currIndex = options.findIndex((tn) => tn.value === equative.tense);
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 newIndex = dir === "forward"
? ((currIndex + 1) % options.length)
: (currIndex === 0 ? (options.length - 1) : (currIndex - 1))
const newTense = options[newIndex];
onChange({
...equative,
@ -49,59 +42,41 @@ function EquativePicker({
negative: value === "true",
});
}
return (
<div>
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
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")}
>
{<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
{!hideNegative && <ButtonSelect
small
value={equative.negative.toString() as "true" | "false"}
options={[
{
options={[{
label: "Pos.",
value: "false",
},
{
}, {
label: "Neg.",
value: "true",
},
]}
}]}
handleChange={onPosNegSelect}
/>
)}
<div
onClick={moveTense("forward")}
className="btn btn-light clickable"
>
/>}
<div onClick={moveTense("forward")} className="btn btn-light clickable">
<i className="fas fa-chevron-right" />
</div>
</div>}
</div>
}
</div>
</div>
);
</div>;
}
export default EquativePicker;

View File

@ -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;

View File

@ -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");
});

View File

@ -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");
}
}

View File

@ -1,7 +1,7 @@
import { useState } from "react";
import { makeAdjectiveSelection } from "../../../lib/src/phrase-building/make-selections";
import * as T from "../../../types";
import EntrySelect from "../selects/EntrySelect";
import EntrySelect from "../EntrySelect";
import SandwichPicker from "./SandwichPicker";
function AdjectivePicker(props: {

View File

@ -1,5 +1,5 @@
import * as T from "../../../types";
import { DeterminerSelect } from "../selects/EntrySelect";
import { DeterminerSelect } from "../EntrySelect";
export default function DeterminersPicker({
determiners,

View File

@ -1,10 +1,10 @@
import { makeNounSelection } from "../../../lib/src/phrase-building/make-selections";
import * as T from "../../../types";
import ButtonSelect from "../selects/ButtonSelect";
import InlinePs from "../text-display/InlinePs";
import ButtonSelect from "../ButtonSelect";
import InlinePs from "../InlinePs";
// 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 { useState } from "react";
import DeterminersPicker from "./DeterminersPicker";
@ -136,6 +136,7 @@ function NPNounPicker(props: {
/>
</div>
)}
{/* {showFilter && <div className="mb-2 text-center">
<div className="d-flex flex-row justify-content-between">
<div className="text-small mb-1">Filter by inflection pattern</div>
@ -191,10 +192,9 @@ function NPNounPicker(props: {
Compound:
</div>
<div className="mb-3 text-center">
<InlinePs
opts={props.opts}
ps={{ p: props.noun.entry.p, f: props.noun.entry.f }}
/>
<InlinePs opts={props.opts}>
{{ p: props.noun.entry.p, f: props.noun.entry.f }}
</InlinePs>
<div className="text-muted">{props.noun.entry.e}</div>
</div>
</div>

View File

@ -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;

View File

@ -246,7 +246,6 @@ function NPPicker(props: {
pronoun={props.np.selection}
onChange={(p) => onChange({ type: "NP", selection: p })}
is2ndPersonPicker={props.is2ndPersonPicker}
// @ts-expect-error this is fine
opts={props.opts}
/>
) : npType === "noun" ? (

View File

@ -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;

View File

@ -1,6 +1,6 @@
import * as T from "../../../types";
import { sandwiches } from "../../../lib/src/sandwiches";
import { SandwichSelect } from "../selects/EntrySelect";
import { SandwichSelect } from "../EntrySelect";
import { useEffect, useState } from "react";
import NPPicker from "./NPPicker";

View File

@ -1,8 +1,6 @@
export default function playAudio(a: string) {
if (!a) return;
const audio = new Audio(
`https://pashto-inflector.lingdocs.com/sounds/${a}.mp3`
);
let audio = new Audio(`https://verbs.lingdocs.com/sounds/${a}.mp3`);
audio.addEventListener("ended", () => {
audio.remove();
audio.srcObject = null;

View File

@ -1,4 +0,0 @@
export const roleIcon = {
king: <i className="mx-1 fas fa-crown" />,
servant: <i className="mx-1 fas fa-male" />,
};

View File

@ -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;

View File

@ -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;

View File

@ -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,
}),
};

View File

@ -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" },
],
};

View File

@ -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" },
],
};

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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, "ز"));
});
});

View File

@ -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");
}
}

View File

@ -1,6 +1,6 @@
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
@ -10,13 +10,14 @@ type SaveableData = string | number | object | boolean | undefined | null;
* @param key a key for saving the state in localStorage
* @returns
*/
export default function useStickyState<T extends SaveableData>(
defaultValue: T | ((old: T | undefined) => T),
key: string
): [value: T, setValue: React.Dispatch<React.SetStateAction<T>>] {
export default function useStickyState<T extends SaveableData>(defaultValue: T | ((old: T | undefined) => T), key: string): [
value: T,
setValue: React.Dispatch<React.SetStateAction<T>>,
] {
const [value, setValue] = useState<T>(() => {
const v =
typeof window === "undefined" ? null : window.localStorage.getItem(key);
const v = typeof window === "undefined"
? null
: window.localStorage.getItem(key);
// nothing saved
if (v === null) {
if (typeof defaultValue === "function") {
@ -33,16 +34,14 @@ export default function useStickyState<T extends SaveableData>(
return old;
} catch (e) {
console.error("error parsting saved state from stickState");
console.error(e);
return typeof defaultValue === "function"
return (typeof defaultValue === "function")
? defaultValue(undefined)
: defaultValue;
}
});
useEffect(() => {
// eslint-disable-next-line
if (!typeof window !== undefined) {
if (typeof window !== undefined) {
window.localStorage.setItem(key, JSON.stringify(value));
}
}, [key, value]);
@ -55,11 +54,15 @@ export function useStickyReducer<T extends SaveableData, A>(
defaultValue: T | ((old: T | undefined) => T),
key: string,
sendAlert?: (msg: string) => void,
onChange?: (state: T) => void
): [T, (action: A) => void, ((msg: string) => void) | undefined] {
onChange?: (state: T) => void,
): [
T,
(action: A) => void,
((msg: string) => void) | undefined,
] {
const [state, unsafeSetState] = useStickyState<T>(defaultValue, key);
function adjustState(action: A) {
unsafeSetState((oldState) => {
unsafeSetState(oldState => {
const newState = reducer(oldState, action, sendAlert);
if (onChange) {
onChange(newState);

View File

@ -13,7 +13,7 @@ import {
noPersInfs,
} from "../../../lib/src/misc-helpers";
import VerbInfoItemDisplay from "./VerbInfoItemDisplay";
import PersonInfsPicker from "../selects/PersInfsPicker";
import PersonInfsPicker from "../PersInfsPicker";
import VerbTypeInfo from "./VerbTypeInfo";
import Hider from "../Hider";
import * as T from "../../../types";

View File

@ -7,28 +7,27 @@
*/
import { useEffect, useState } from "react";
import ButtonSelect from "../selects/ButtonSelect";
import Pashto from "../text-display/Pashto";
import Phonetics from "../text-display/Phonetics";
import ButtonSelect from "../ButtonSelect";
import Pashto from "../Pashto";
import Phonetics from "../Phonetics";
import * as T from "../../../types";
import { concatPsString } from "../../../lib/src/p-text-helpers";
type InputItem = T.SingleOrLengthOpts<T.PsString | [T.PsString, T.PsString]>;
function VerbInfoItemDisplay({
item,
textOptions,
tails,
}: {
item: InputItem;
textOptions: T.TextOptions;
tails?: boolean;
function VerbInfoItemDisplay({ item, textOptions, tails }: {
item: InputItem,
textOptions: T.TextOptions,
tails?: boolean,
}) {
const [length, setLength] = useState<T.Length>("long");
const getL = (x: InputItem): T.PsString | [T.PsString, T.PsString] =>
"long" in x ? x[length] || x.short : x;
const getL = (x: InputItem): T.PsString | [T.PsString, T.PsString] => (
"long" in x
? x[length] || x.short
: x
);
useEffect(() => {
setLength((l) => (l === "mini" && !("mini" in item) ? "short" : l));
setLength(l => (l === "mini" && !("mini" in item)) ? "short" : l);
}, [item]);
// const lengthsAvailable = "long" in item
// ? [...["long", "short"], ..."mini" in item ? ["mini"] : []]
@ -38,60 +37,45 @@ function VerbInfoItemDisplay({
function addTails(text: T.PsString): T.PsString {
return concatPsString(text, { p: "ـ", f: ""});
}
return (
<>
{Array.isArray(text) ? (
return <>
{Array.isArray(text) ?
<div>
<div className="text-center" dir="rtl">
<Pashto opts={textOptions} ps={text[0]} />
<Pashto opts={textOptions}>{text[0]}</Pashto>
<span className="mx-1"> __ </span>
<Pashto
opts={textOptions}
ps={tails ? addTails(text[1]) : text[1]}
/>
<Pashto opts={textOptions}>{tails ? addTails(text[1]) : text[1]}</Pashto>
</div>
<div className="text-center">
<Phonetics opts={textOptions} ps={text[0]} />
<Phonetics opts={textOptions}>{text[0]}</Phonetics>
<span className="mx-1"> __ </span>
<Phonetics
opts={textOptions}
ps={tails ? addTails(text[1]) : text[1]}
/>
<Phonetics opts={textOptions}>{tails ? addTails(text[1]) : text[1]}</Phonetics>
</div>
</div>
) : (
:
<div>
<div className="text-center">
<Pashto opts={textOptions} ps={tails ? addTails(text) : text} />
<Pashto opts={textOptions}>{tails ? addTails(text) : text}</Pashto>
</div>
<div className="text-center">
<Phonetics opts={textOptions} ps={tails ? addTails(text) : text} />
<Phonetics opts={textOptions}>{tails ? addTails(text) : text}</Phonetics>
</div>
</div>
)}
{"long" in item && (
<div className="mt-2 text-center">
}
{"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",
},
]
: []),
..."mini" in item ? [{
label: "Mini", value: "mini",
}] : [],
]}
value={length}
handleChange={(p) => setLength(p as T.Length)}
/>
</div>
)}
</>
);
</div>}
</>;
}
export default VerbInfoItemDisplay;

View File

@ -7,10 +7,12 @@
*/
import { useState } from "react";
import InlinePs from "../text-display/InlinePs";
import Pashto from "../text-display/Pashto";
import Phonetics from "../text-display/Phonetics";
import { makePsString } from "../../../lib/src/accent-and-ps-utils";
import InlinePs from "../InlinePs";
import Pashto from "../Pashto";
import Phonetics from "../Phonetics";
import {
makePsString,
} from "../../../lib/src/accent-and-ps-utils";
import { Modal } from "react-bootstrap";
import stativeCompTrans from "./stative-compound-transitive.svg";
import stativeCompIntrans from "./stative-compound-intransitive.svg";
@ -25,19 +27,29 @@ import gramTransitiveDiagramPresent from "./grammatically-transitive-present.svg
import gramTransitiveDiagramPast from "./grammatically-transitive-past.svg";
import * as T from "../../../types";
function CompoundFormula({ a, b }: { a: React.ReactNode; b: React.ReactNode }) {
function CompoundFormula({ a, b }: {
a: React.ReactNode,
b: React.ReactNode,
}) {
return (
<div className="row align-items-center mb-3">
<div className="col-5 text-center">{a}</div>
<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 className="col-5 text-center">
{b}
</div>
</div>
);
}
function ExplanationImage({ src, alt }: { src: string; alt?: string }) {
function ExplanationImage({ src, alt }: {
src: any,
alt?: string,
}) {
return (
<img
src={src}
@ -48,245 +60,137 @@ function ExplanationImage({ src, alt }: { src: string; alt?: string }) {
);
}
const typeInfo = (type: string, opts: T.TextOptions) => {
return type === "simple" ? (
<>
<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>
const typeInfo = (
type: string,
opts: T.TextOptions,
) => {
return type === "simple"
? <>
<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>
</>
) : type === "stative compound" ? (
<>
<p>
A <strong>stative compound</strong> talks about{" "}
<strong>something changing from one state to another</strong>.
</p>
: type === "stative compound"
? <>
<p>A <strong>stative compound</strong> talks about <strong>something changing from one state to another</strong>.</p>
<CompoundFormula
a={
<>
a={<>
<div style={{ fontSize: "larger" }}>
<strong>Complement</strong>
</div>
<div>Adjective, Noun, or Adverb</div>
</>
}
b={
<>
</>}
b={<>
<div style={{ fontSize: "larger" }}>
<strong>Aux. Verb</strong>
</div>
<div>
<div className="small text-muted">With transitive:</div>
<div>
<InlinePs opts={opts} ps={{ p: "کول", f: "kawul" }} /> (to make)
</div>
<div><InlinePs opts={opts}>{{ p: "کول", f: "kawul" }}</InlinePs> (to make)</div>
<div className="small text-muted">With intransitive:</div>
<div>
<InlinePs opts={opts} ps={{ p: "کېدل", f: "kedul" }} /> (to
become)
<div><InlinePs opts={opts}>{{ p: "کېدل", f: "kedul" }}</InlinePs> (to become)</div>
</div>
</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>
<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>
<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>
<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>
<p>When complements are nouns or adverbs, they act (and carry a meaning) almost as if they were adjectives.</p>
</>
) : type === "dynamic compound" ? (
<>
<p>
A <strong>dynamic compound</strong> talks about{" "}
<strong>some action being done</strong>.
</p>
: type === "dynamic compound"
? <>
<p>A <strong>dynamic compound</strong> talks about <strong>some action being done</strong>.</p>
<CompoundFormula
a={
<>
a={<>
<div style={{ fontSize: "larger" }}>
<strong>Complement</strong>
</div>
<div>Noun (action or activity)</div>
</>
}
b={
<>
</>}
b={<>
<div style={{ fontSize: "larger" }}>
<strong>Aux. Verb</strong>
</div>
</>
}
</>}
/>
<h4>Transitive Dynamic Compounds</h4>
<p>These talk about someone doing an action or activity.</p>
<ExplanationImage src={dynamicCompTrans} />
<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>
<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>
<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>
<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>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>
</>
) : type === "generative stative compound" ? (
<>
<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>
: type === "generative stative compound"
? <>
<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>
<ul>
<li>
The object is included in the compound... like with dynamic compounds
</li>
<li>
But they also use{" "}
<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>
<li>The object is included in the compound... like with dynamic compounds</li>
<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>
</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>
<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) => {
return type === "transitive" ? (
<>
<p>
<strong>Transitive</strong> verbs are{" "}
<strong>verbs that take an object</strong>.
</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>
const transitivityInfo = (
type: string,
textOptions: T.TextOptions,
) => {
return type === "transitive"
? <>
<p><strong>Transitive</strong> verbs are <strong>verbs that take an object</strong>.</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>
<h4>In all non-past forms</h4>
<ul>
<li>The subject is not inflected</li>
<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>
<li>
The verb agrees with the <strong>subject</strong>
</li>
<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>
<li>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 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>
<li>The verb agrees with the <strong>object</strong></li>
</ul>
<ExplanationImage src={transitiveDiagramPast} />
</>
) : type === "intransitive" ? (
<>
<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>
<p>
- The subject is always a <em>uninflected/plain</em> noun or pronoun.
</p>
: type === "intransitive"
? <>
<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>
<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" ? (
<>
<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>
<p>
These work just like transitive verbs, except that the object is an
implied (unspoken) 3rd person masculine plural entity.
</p>
: type === "grammatically transitive"
? <>
<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>
<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;
function CompoundBreakdown({ info, textOptions }: {
info: T.NonComboVerbInfo,
textOptions: T.TextOptions,
}) {
const isComplement = "complement" in info || "objComplement" in info;
const isComplement = ("complement" in info || "objComplement" in info);
if (!isComplement) {
return null;
}
@ -294,64 +198,54 @@ function CompoundBreakdown({
if ("objComplement" in info) {
return info.objComplement.plural
? info.objComplement.plural
: makePsString(info.objComplement.entry.p, info.objComplement.entry.f);
: makePsString(info.objComplement.entry.p, info.objComplement.entry.f)
}
if ("complement" in info) {
return info.complement.masc[0][0];
} else return makePsString("aa", "aa");
}
else return makePsString("aa", "aa");
})();
const aux = ((): { ps: T.PsString; e: string } => {
if (
info.type === "stative compound" ||
info.type === "generative stative compound"
) {
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 === "کول";
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">
a={<div className="compound-breakdown">
<div>
<Pashto opts={textOptions} ps={complement} />
<Pashto opts={textOptions}>{complement}</Pashto>
</div>
<div>
<Phonetics opts={textOptions} ps={complement} />
<Phonetics opts={textOptions}>{complement}</Phonetics>
</div>
</div>
}
b={
<div className="compound-breakdown">
</div>}
b={<div className="compound-breakdown">
<div>
<Pashto opts={textOptions} ps={aux.ps} />
<Pashto opts={textOptions}>{aux.ps}</Pashto>
</div>
<div>
<Phonetics opts={textOptions} ps={aux.ps} />
<Phonetics opts={textOptions}>{aux.ps}</Phonetics>
</div>
{aux.e && <div>{aux.e}</div>}
</div>
}
</div>}
/>
</div>
);
}
function VerbTypeInfo({
info,
textOptions,
}: {
info: T.NonComboVerbInfo;
textOptions: T.TextOptions;
function VerbTypeInfo({ info, textOptions }: {
info: T.NonComboVerbInfo,
textOptions: T.TextOptions,
}) {
const [showingTypeModal, setShowingTypeModal] = useState<boolean>(false);
const [showingTransModal, setShowingTransModal] = useState<boolean>(false);
@ -380,29 +274,18 @@ function VerbTypeInfo({
</Modal.Header>
<Modal.Body>{typeInfo(info.type, textOptions)}</Modal.Body>
<Modal.Footer>
<button
type="button"
className="btn btn-primary clb"
onClick={() => setShowingTypeModal(false)}
>
<button type="button" className="btn btn-primary clb" onClick={() => setShowingTypeModal(false)}>
Close
</button>
</Modal.Footer>
</Modal>
<Modal
show={showingTransModal}
onHide={() => setShowingTransModal(false)}
>
<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)}</Modal.Body>
<Modal.Body>{transitivityInfo(info.transitivity, textOptions)}</Modal.Body>
<Modal.Footer>
<button
type="button"
className="btn btn-primary clb"
onClick={() => setShowingTransModal(false)}
>
<button type="button" className="btn btn-primary clb" onClick={() => setShowingTransModal(false)}>
Close
</button>
</Modal.Footer>

View File

@ -1,6 +1,6 @@
import ButtonSelect from "../selects/ButtonSelect";
import ButtonSelect from "../ButtonSelect";
import * as T from "../../../types";
import { roleIcon } from "../role-icons";
import { roleIcon } from "./VPExplorerExplanationModal";
import classNames from "classnames";
const options = [
@ -43,44 +43,37 @@ function limitOptions(adjustable: "both" | "king" | "servant") {
return options;
}
if (adjustable === "king") {
return options.filter(
(o) => !["shrinkServant", "shortest"].includes(o.value)
);
return options.filter(o => !["shrinkServant", "shortest"].includes(o.value));
}
if (adjustable === "servant") {
return options.filter((o) => !["noKing", "shortest"].includes(o.value));
return options.filter(o => !["noKing", "shortest"].includes(o.value));
}
}
function limitValue(value: string, adjustable: "both" | "king" | "servant") {
if (adjustable === "both") return value;
if (adjustable === "king") {
return value === "shortest"
return (value === "shortest")
? "noKing"
: value === "shrinkServant"
: (value === "shrinkServant")
? "full"
: value;
}
if (adjustable === "servant") {
return value === "shortest"
return (value === "shortest")
? "shrinkServant"
: value === "noKing"
: (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 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") {
@ -94,20 +87,19 @@ function AbbreviationFormSelector({
}
}
// TODO: limit display of shrinking options based on the verb type
return (
<div className={classNames("mx-3", { "mb-3": !inline })}>
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-expect-error slight mismatch but its ok
// @ts-ignore
value={limitValue(formToValue(form), adjustable)}
// @ts-expect-error slight mismatch but its ok
// @ts-ignore
options={limitOptions(adjustable)}
// @ts-ignore
handleChange={handleChange}
/>
</div>
);
}
export default AbbreviationFormSelector;

View File

@ -9,7 +9,7 @@ import Hider from "../Hider";
import * as T from "../../../types";
import useStickyState from "../useStickyState";
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 { useState } from "react";
import { statVerb } from "../../../lib/src/new-verb-engine/roots-and-stems";

View File

@ -1,33 +1,41 @@
import * as T from "../../../types";
import Pashto from "../text-display/Pashto";
import Phonetics from "../text-display/Phonetics";
import { makePsString } from "../../../lib/src/accent-and-ps-utils";
import Pashto from "../Pashto";
import Phonetics from "../Phonetics";
import {
makePsString,
} from "../../../lib/src/accent-and-ps-utils";
import { ReactNode } from "react";
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 }: {
a: ReactNode,
b: ReactNode,
}) {
return (
<div className="row align-items-center mb-3">
<div className="col-5 text-center">{a}</div>
<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 className="col-5 text-center">
{b}
</div>
</div>
);
}
function CompoundDisplay({
info,
opts,
handleLinkClick,
}: {
info: T.NonComboVerbInfo;
opts: T.TextOptions;
handleLinkClick: ((ts: number) => void) | "none";
function CompoundDisplay({ info, opts, handleLinkClick }: {
info: T.NonComboVerbInfo,
opts: T.TextOptions,
handleLinkClick: ((ts: number) => void) | "none",
}) {
const isComplement = "complement" in info || "objComplement" in info;
const isComplement = ("complement" in info || "objComplement" in info);
if (!isComplement) {
return null;
}
@ -39,79 +47,65 @@ function CompoundDisplay({
}
if ("complement" in info) {
return info.complement.masc[0][0];
} else return makePsString("aa", "aa");
}
else return makePsString("aa", "aa");
})();
const aux = ((): { ps: T.PsString; e: string } => {
if (
info.type === "stative compound" ||
info.type === "generative stative compound"
) {
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 === "کول";
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
}
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} ps={complement} />
<Pashto opts={opts}>{complement}</Pashto>
</div>
<div>
<Phonetics opts={opts} ps={complement} />
<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
{info.type === "dynamic compound" && <div>
(Object)
</div>}
</div>}
b={<div
className={classNames([{ clickable: handleLinkClick }])}
onClick={
handleLinkClick !== "none"
? () =>
handleLinkClick(
onClick={(handleLinkClick !== "none")
? () => handleLinkClick(
"auxVerb" in info
? // dyanmic compound auxVerb ts
info.auxVerb.ts
: // stative compound auxVerb ts
(info.transitivity === "intransitive"
// dyanmic compound auxVerb ts
? info.auxVerb.ts
// stative compound auxVerb ts
: ((info.transitivity === "intransitive"
? kedulStat
: kawulStat
).info.entry.entry.ts
: kawulStat).info.entry.entry.ts)
)
: undefined
}
>
: undefined}>
<div>
<Pashto opts={opts} ps={aux.ps} />
<Pashto opts={opts}>{aux.ps}</Pashto>
</div>
<div>
<Phonetics opts={opts} ps={aux.ps} />
<Phonetics opts={opts}>{aux.ps}</Phonetics>
</div>
{aux.e && <div>{aux.e}</div>}
</div>
}
</div>}
/>
</div>
);

View File

@ -13,7 +13,7 @@ export function EditIcon() {
function EditableVP({
children,
opts,
// formChoice,
formChoice,
noEdit,
length,
mode,
@ -23,7 +23,7 @@ function EditableVP({
}: {
children: T.VPSelectionState;
opts: T.TextOptions;
// formChoice?: boolean;
formChoice?: boolean;
noEdit?: boolean;
length?: "long" | "short";
mode?: "text" | "blocks";

View File

@ -1,7 +1,7 @@
import * as T from "../../../types";
import ModeSelect, { Mode, ScriptSelect } from "../selects/DisplayModeSelect";
import ModeSelect, { Mode, ScriptSelect } from "../DisplayModeSelect";
import { useState } from "react";
import CompiledPTextDisplay from "../text-display/CompiledPTextDisplay";
import CompiledPTextDisplay from "../CompiledPTextDisplay";
import useStickyState from "../useStickyState";
import {
getEnglishFromRendered,

View File

@ -1,24 +1,28 @@
import { useEffect, useState } from "react";
import ButtonSelect from "../selects/ButtonSelect";
import ButtonSelect from "../ButtonSelect";
import {
combineIntoText,
flattenLengths,
} from "../../../lib/src/phrase-building/compile";
import { insertNegative } from "../../../lib/src/phrase-building/render-vp";
import * as T from "../../../types";
import TableCell from "../tables/TableCell";
import TableCell from "../TableCell";
import { choosePersInf, getLength } from "../../../lib/src/p-text-helpers";
import genderColors from "../gender-colors";
import { eqPsStringWVars } from "../../../lib/src/fp-ps";
import PersInfsPicker from "../selects/PersInfsPicker";
import PersInfsPicker from "../PersInfsPicker";
import "./form-display.css";
import {
makeBlock,
makeKid,
} 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 { 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({
chart,
@ -326,7 +330,9 @@ function AgreementInfo({
</div>
{transitivity === "transitive" && past && objNP && (
<div>
<InlinePs opts={opts} ps={flattenLengths(objNP.selection.ps)[0]} />
<InlinePs opts={opts}>
{flattenLengths(objNP.selection.ps)[0]}
</InlinePs>
{` `}({printGenNum(personToGenNum(objNP.selection.person))})
</div>
)}

View File

@ -1,23 +1,16 @@
import Select from "react-select";
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 { customSelectStyles as customStyles } from "../selects/select-styles";
import { VpsReducerAction } from "../../../lib/src/phrase-building/vps-reducer";
import { customStyles } from "../EntrySelect";
import {
imperativeTenseOptions,
perfectTenseOptions,
verbTenseOptions,
} from "./verbTenseOptions";
import { getTenseCategory } from "./vp-explorer-util";
VpsReducerAction,
} from "../../../lib/src/phrase-building/vps-reducer";
import { imperativeTenseOptions, perfectTenseOptions, verbTenseOptions } from "./verbTenseOptions";
function composeFormula(
formula: string,
prefix: "passive" | "ability"
): string {
return formula
.replace(/^perfective/, `${prefix} perfective`)
function composeFormula(formula: string, prefix: "passive" | "ability"): string {
return formula.replace(/^perfective/, `${prefix} perfective`)
.replace(/^imperfective/, `${prefix} imperfective`)
.replace("continuous", `${prefix} continuous`)
.replace("simple", `${prefix} simple`)
@ -26,26 +19,36 @@ function composeFormula(
.replace("past participle", `${prefix} past participle`);
}
function TensePicker(
props: (
| {
vps: T.VPSelectionState;
export function getRandomTense(o?: T.PerfectTense | T.VerbTense | T.AbilityTense | T.ImperativeTense): T.PerfectTense | T.VerbTense | T.AbilityTense | T.ImperativeTense {
let tns: T.PerfectTense | T.VerbTense | T.AbilityTense | T.ImperativeTense;
const oldTenseCategory = !o
? undefined
: getTenseCategory(o);
const tenseOptions = oldTenseCategory === "perfect"
? perfectTenseOptions
: oldTenseCategory === "modal"
? verbTenseOptions.map(x => ({ ...x, value: `${x.value}Modal` as T.AbilityTense }))
: oldTenseCategory === "imperative"
? imperativeTenseOptions
: verbTenseOptions;
do {
tns = tenseOptions[
Math.floor(Math.random()*tenseOptions.length)
].value;
} while (o === tns);
return tns;
}
| {
vpsComplete: T.VPSelectionComplete;
}
) & {
onChange: (p: VpsReducerAction) => void;
mode: "charts" | "phrases" | "quiz";
}
) {
const [showFormula, setShowFormula] = useStickyState<boolean>(
false,
"showFormula"
);
function onTenseSelect(
o: { value: T.VerbTense | T.PerfectTense | T.ImperativeTense } | null
) {
function TensePicker(props: ({
vps: T.VPSelectionState,
} | {
vpsComplete: T.VPSelectionComplete,
}) & {
onChange: (p: VpsReducerAction) => void,
mode: "charts" | "phrases" | "quiz",
}) {
const [showFormula, setShowFormula] = useStickyState<boolean>(false, "showFormula");
function onTenseSelect(o: { value: T.VerbTense | T.PerfectTense | T.ImperativeTense } | null) {
if ("vpsComplete" in props) return;
const tense = o?.value ? o.value : undefined;
props.onChange({
@ -58,34 +61,26 @@ function TensePicker(
if (!props.vps.verb) return;
return () => {
// TODO: ABSTRACT THIS - SAFER
const tenses =
props.vps.verb.tenseCategory === "perfect"
const tenses = props.vps.verb.tenseCategory === "perfect"
? perfectTenseOptions
: props.vps.verb.tenseCategory === "imperative"
? imperativeTenseOptions
: verbTenseOptions;
const currIndex = tenses.findIndex(
(tn) =>
tn.value ===
props.vps.verb[
const currIndex = tenses.findIndex(tn => tn.value === props.vps.verb[
// TODO: ABSTRACT THIS? - SAFER
props.vps.verb.tenseCategory === "perfect"
? "perfectTense"
: props.vps.verb.tenseCategory === "imperative"
? "imperativeTense"
: "verbTense"
]
);
]);
if (currIndex === -1) {
console.error("error moving tense", dir);
return;
}
const newIndex =
dir === "forward"
? (currIndex + 1) % tenses.length
: currIndex === 0
? tenses.length - 1
: currIndex - 1;
const newIndex = dir === "forward"
? ((currIndex + 1) % tenses.length)
: (currIndex === 0 ? (tenses.length - 1) : (currIndex - 1))
const newTense = tenses[newIndex];
onTenseSelect(newTense);
};
@ -97,151 +92,94 @@ function TensePicker(
payload,
});
}
function onTenseCategorySelect(
payload: "basic" | "modal" | "perfect" | "imperative"
) {
function onTenseCategorySelect(payload: "basic" | "modal" | "perfect" | "imperative") {
if ("vpsComplete" in props) return;
props.onChange({
type: "set tense category",
payload,
});
}
const tOptions =
"vps" in props && props.vps.verb?.tenseCategory === "perfect"
const tOptions = ("vps" in props && (props.vps.verb?.tenseCategory === "perfect"))
? perfectTenseOptions
: "vps" in props && props.vps.verb?.tenseCategory === "imperative"
: ("vps" in props && (props.vps.verb?.tenseCategory === "imperative"))
? imperativeTenseOptions
: verbTenseOptions;
const showImperativeOption =
("vps" in props && props.vps.verb.voice === "active") ||
("vpsComplete" in props && props.vpsComplete.verb.voice !== "active");
const showImperativeOption = ("vps" in props && props.vps.verb.voice === "active")
|| ("vpsComplete" in props && props.vpsComplete.verb.voice !== "active");
const inAllTensesMode = props.mode === "charts";
const canHaveFormula =
"vps" in props && props.mode !== "quiz" && !inAllTensesMode;
return (
<div>
const canHaveFormula = "vps" in props
&& props.mode !== "quiz"
&& !inAllTensesMode;
return <div>
<div style={{ maxWidth: "300px", minWidth: "250px", margin: "0 auto" }}>
<div className="d-flex flex-row justify-content-between align-items-center">
<div className="h5">
{props.mode === "charts" ? "Tense Category:" : "Verb Tense:"}
{props.mode === "charts"
? "Tense Category:"
: "Verb Tense:"}
</div>
{canHaveFormula && (
<div
className="clickable mb-2 small"
onClick={() => setShowFormula((x) => !x)}
>
{canHaveFormula && <div className="clickable mb-2 small" onClick={() => setShowFormula(x => !x)}>
🧪 {!showFormula ? "Show" : "Hide"} Formula
</div>}
</div>
)}
</div>
{("vpsComplete" in props || props.vps.verb) && (
<div className="mb-2">
{("vpsComplete" in props || props.vps.verb) && <div className="mb-2">
<ButtonSelect
small
value={
"vpsComplete" in props
value={"vpsComplete" in props
? getTenseCategory(props.vpsComplete.verb.tense)
: props.vps.verb.tenseCategory
}
options={
showImperativeOption
? [
{
: props.vps.verb.tenseCategory}
// @ts-ignore
options={showImperativeOption ? [{
label: "Basic",
value: "basic",
},
{
}, {
label: "Perfect",
value: "perfect",
},
{
}, {
label: "Ability",
value: "modal",
},
{
}, {
label: "Imperative",
value: "imperative",
},
]
: [
{
}] : [{
label: "Basic",
value: "basic",
},
{
}, {
label: "Perfect",
value: "perfect",
},
{
}, {
label: "Ability",
value: "modal",
},
]
}
handleChange={
props.mode !== "quiz" ? onTenseCategorySelect : () => null
}
}]}
handleChange={props.mode !== "quiz" ? onTenseCategorySelect : () => null}
/>
</div>}
{"vpsComplete" in props
? <div style={{ fontSize: "larger" }} className="mb-3">
{[...verbTenseOptions, ...perfectTenseOptions, ...imperativeTenseOptions].find(o => o.value === props.vpsComplete.verb.tense)?.label}
</div>
)}
{"vpsComplete" in props ? (
<div style={{ fontSize: "larger" }} className="mb-3">
{
[
...verbTenseOptions,
...perfectTenseOptions,
...imperativeTenseOptions,
].find((o) => o.value === props.vpsComplete.verb.tense)?.label
}
</div>
) : (
<>
{!inAllTensesMode && (
<Select
: <>
{!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[
value={props.vps.verb && ([...verbTenseOptions, ...perfectTenseOptions, ...imperativeTenseOptions].find(o => o.value === props.vps.verb[
props.vps.verb.tenseCategory === "perfect"
? "perfectTense"
: props.vps.verb.tenseCategory === "imperative"
? "imperativeTense"
: "verbTense"
]
)
}
// @ts-expect-error slight mismatch but it's ok
]))}
// @ts-ignore - gets messed up when using customStyles
onChange={onTenseSelect}
className="mb-2"
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")}
>
/>}
</>}
{"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 />
)}
</div> : <div />}
{/* {props.mode === "charts" && <ButtonSelect
small
value={props.chartMode}
@ -254,81 +192,64 @@ function TensePicker(
}]}
handleChange={props.onChartModeChange}
/>} */}
{props.mode === "phrases" && (
<ButtonSelect
{props.mode === "phrases" && <ButtonSelect
small
value={props.vps.verb.negative.toString() as "true" | "false"}
options={[
{
options={[{
label: "Pos.",
value: "false",
},
{
}, {
label: "Neg.",
value: "true",
},
]}
}]}
handleChange={onPosNegSelect}
/>
)}
{!inAllTensesMode ? (
<div
onClick={moveTense("forward")}
className="btn btn-light clickable"
>
/>}
{!inAllTensesMode ? <div onClick={moveTense("forward")} className="btn btn-light clickable">
<i className="fas fa-chevron-right" />
</div>
) : (
<div />
)}
</div>
)}
{canHaveFormula &&
showFormula &&
(() => {
</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[
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"
? "perfectTense"
: props.vps.verb.tenseCategory === "imperative"
? "imperativeTense"
: "verbTense"
]
);
]);
const formula = !curr
? ""
: props.vps.verb.tenseCategory === "modal"
: (props.vps.verb.tenseCategory === "modal")
? composeFormula(curr.formula, "ability")
: props.vps.verb.voice === "passive"
: (props.vps.verb.voice === "passive")
? composeFormula(curr.formula, "passive")
: curr.formula;
if (curr && "formula" in curr) {
return (
<div
className="mb-2"
style={{ width: "250px", overflowY: "auto" }}
>
return <div className="mb-2" style={{ width: "250px", overflowY: "auto" }}>
<samp>{formula}</samp>
</div>
);
}
})()}
</div>
</div>
);
</div>;
}
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");
}

View File

@ -11,10 +11,10 @@ import ModeSelect, {
LengthSelect,
Mode,
ScriptSelect,
} from "../selects/DisplayModeSelect";
} from "../DisplayModeSelect";
import { useState } from "react";
import CompiledPTextDisplay from "../text-display/CompiledPTextDisplay";
import RenderedBlocksDisplay from "../blocks/RenderedBlocksDisplay";
import CompiledPTextDisplay from "../CompiledPTextDisplay";
import RenderedBlocksDisplay from "../RenderedBlocksDisplay";
import useStickyState from "../useStickyState";
function VPDisplay({

View File

@ -1,13 +1,14 @@
import VerbPicker from "./VerbPicker";
import TensePicker from "./TensePicker";
import VPDisplay from "./VPDisplay";
import ButtonSelect from "../selects/ButtonSelect";
import ButtonSelect from "../ButtonSelect";
import * as T from "../../../types";
import useStickyState, { useStickyReducer } from "../useStickyState";
import { makeVPSelectionState } from "../../../lib/src/phrase-building/verb-selection";
import { useEffect, useState } from "react";
import { completeVPSelection } from "../../../lib/src/phrase-building/vp-tools";
import VPExplorerQuiz from "./VPExplorerQuiz";
// @ts-ignore
import LZString from "lz-string";
import {
VpsReducerAction,

View File

@ -1,95 +1,56 @@
import { Modal } from "react-bootstrap";
import structureDiagram from "./vp-structure.svg";
import { roleIcon } from "../role-icons";
function VPExplorerExplanationModal({
showing,
setShowing,
}: {
showing: { role: "servant" | "king"; item: "subject" | "object" } | false;
setShowing: (
s: { role: "servant" | "king"; item: "subject" | "object" } | false
) => void;
export const roleIcon = {
king: <i className="mx-1 fas fa-crown" />,
servant: <i className="mx-1 fas fa-male" />,
};
function VPExplorerExplanationModal({ showing, setShowing }: {
showing: { role: "servant" | "king", item: "subject" | "object" } | false,
setShowing: (s: { role: "servant" | "king", item: "subject" | "object" } | false) => void,
}) {
if (!showing) return null;
return (
<>
return <>
<Modal show={!!showing} onHide={() => setShowing(false)}>
<Modal.Header closeButton>
<Modal.Title>
About the{" "}
{showing.role === "king" ? (
<span>King {roleIcon.king}</span>
) : (
<span>Servant {roleIcon.servant}</span>
)}
<Modal.Title>About the {showing.role === "king"
? <span>King {roleIcon.king}</span>
: <span>Servant {roleIcon.servant}</span>}
</Modal.Title>
</Modal.Header>
<Modal.Body>
<p>
In this tense/form, the {showing.item} is the{" "}
<strong>{showing.role}</strong> {roleIcon[showing.role]} of the
phrase.
</p>
<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>
{showing.role === "king" ? (
<ul className="mt-2">
{showing.role === "king"
? <ul className="mt-2">
<li>
<div>
It <strong>controls the verb conjugation</strong>. The verb
agrees with the gender and number of the king.
</div>
<div>It <strong>controls the verb conjugation</strong>. The verb agrees with the gender and number of the king.</div>
</li>
<li>
<div>It can be removed / left out from the phrase.</div>
<div className="text-muted">
(You can <strong>kill the king</strong>)
</div>
<div className="text-muted">(You can <strong>kill the king</strong>)</div>
</li>
</ul>
) : (
<ul>
: <ul>
<li>
<div>
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>
<div>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>
<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}
/>
<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)}
>
<button type="button" className="btn btn-primary clb" onClick={() => setShowing(false)}>
Close
</button>
</Modal.Footer>
</Modal>
</>
);
</>;
}
export default VPExplorerExplanationModal;

View File

@ -5,14 +5,14 @@ import { baParticle } from "../../../lib/src/grammar-units";
import { randomSubjObj } from "../../../lib/src/np-tools";
import { standardizePashto } from "../../../lib/src/standardize-pashto";
import shuffleArray from "../../../lib/src/shuffle-array";
import InlinePs from "../text-display/InlinePs";
import InlinePs from "../InlinePs";
import { psStringEquals } from "../../../lib/src/p-text-helpers";
import { renderVP } from "../../../lib/src/phrase-building/render-vp";
import {
compileVP,
flattenLengths,
} from "../../../lib/src/phrase-building/compile";
import { getRandomTense } from "./vp-explorer-util";
import { getRandomTense } from "./TensePicker";
import {
getTenseFromVerbSelection,
removeBa,
@ -20,7 +20,7 @@ import {
} from "../../../lib/src/phrase-building/vp-tools";
import playAudio from "../play-audio";
import TensePicker from "./TensePicker";
import Keyframes from "./Keyframes";
import Keyframes from "../Keyframes";
import { isImperativeTense } from "../../../lib/src/type-predicates";
import {
adjustObjectSelection,
@ -114,7 +114,7 @@ function VPExplorerQuiz(props: {
const correct =
"p" in a
? isInAnswer(a, quizState.answer)
: // @ts-expect-error // TODO: CLEANUP
: // @ts-ignore // TODO: CLEANUP
blanksAnswerCorrect(a, quizState.answer);
setAnswerBlank("");
setWithBa(false);
@ -190,7 +190,9 @@ function VPExplorerQuiz(props: {
<div className="text-center">
<div
style={
showCheck ? answerFeedback : { ...answerFeedback, opacity: 0 }
showCheck
? answerFeedback
: ({ ...answerFeedback, opacity: 0 } as any)
}
>
{currentCorrectEmoji}
@ -222,7 +224,7 @@ function VPExplorerQuiz(props: {
className="btn btn-answer btn-outline-secondary"
onClick={() => checkAnswer(o)}
>
<InlinePs opts={props.opts} ps={o} />
<InlinePs opts={props.opts}>{o}</InlinePs>
</button>
</div>
))}
@ -270,7 +272,7 @@ function VPExplorerQuiz(props: {
className="form-check-label text-muted"
htmlFor="addBa"
>
add <InlinePs opts={props.opts} ps={baParticle} /> in
add <InlinePs opts={props.opts}>{baParticle}</InlinePs> in
kids' section
</label>
</div>
@ -286,14 +288,13 @@ function VPExplorerQuiz(props: {
{quizState.stage === "multiple choice" ? (
<div>
<div className="my-4 lead">The correct answer was:</div>
<InlinePs
opts={props.opts}
ps={
<InlinePs opts={props.opts}>
{
quizState.options.find((x) =>
isInAnswer(x, quizState.answer)
) as T.PsString
}
/>
</InlinePs>
</div>
) : (
<div>
@ -302,7 +303,7 @@ function VPExplorerQuiz(props: {
</div>
{quizState.answer.ps.map((p, i) => (
<div key={i}>
<InlinePs opts={props.opts} ps={p} />
<InlinePs opts={props.opts}>{p}</InlinePs>
</div>
))}
<div className="mt-2">
@ -311,8 +312,8 @@ function VPExplorerQuiz(props: {
? "With"
: "without"}
</strong>
{` `}a <InlinePs opts={props.opts} ps={baParticle} /> in the
phrase
{` `}a <InlinePs opts={props.opts}>{baParticle}</InlinePs>{" "}
in the phrase
</div>
</div>
)}
@ -398,10 +399,9 @@ function QuizNPDisplay({
<div className="text-centered" style={{ fontSize: "large" }}>
{stage === "blanks" && (
<div>
<InlinePs
opts={opts}
ps={flattenLengths(children.selection.ps)[0]}
/>
<InlinePs opts={opts}>
{flattenLengths(children.selection.ps)[0]}
</InlinePs>
</div>
)}
<div>{children.selection.e}</div>
@ -441,6 +441,7 @@ function tickQuizState(
v = getRandomVPSelection("tenses")(
SOSwitch ? switchSubjObj(newVps) : newVps
);
// eslint-disable-next-line
} while (wrongVpsS.find((x) => x.verb.tense === v.verb.tense));
wrongVpsS.push(v);
});
@ -454,7 +455,7 @@ function tickQuizState(
: "stage" in startingWith
? startingWith.stage
: "multiple choice";
const blanksAnswer = getBlanksAnswer(/* newVps */);
const blanksAnswer = getBlanksAnswer(newVps);
if (stage === "blanks") {
return {
stage,
@ -479,7 +480,7 @@ function tickQuizState(
return out;
}
function getBlanksAnswer(/* vps: T.VPSelectionComplete */): {
function getBlanksAnswer(vps: T.VPSelectionComplete): {
ps: T.PsString[];
withBa: boolean;
} {

View File

@ -1,4 +1,4 @@
import NPPicker, { shrunkenBackground } from "../block-pickers/NPPicker";
import NPPicker, { shrunkenBackground } from "../../src/np-picker/NPPicker";
import TensePicker from "./TensePicker";
import * as T from "../../../types";
import {
@ -14,9 +14,10 @@ import {
completeVPSelection,
isPastTense,
} from "../../../lib/src/phrase-building/vp-tools";
import { roleIcon } from "../role-icons";
import VPExplorerExplanationModal from "./VPExplorerExplanationModal";
import APPicker from "../block-pickers/APPicker";
import VPExplorerExplanationModal, {
roleIcon,
} from "./VPExplorerExplanationModal";
import APPicker from "../../src/ap-picker/APPicker";
// import autoAnimate from "@formkit/auto-animate";
import {
getObjectSelection,
@ -24,7 +25,7 @@ import {
includesShrunkenServant,
isNoObject,
} from "../../../lib/src/phrase-building/blocks-utils";
import ComplementPicker from "../block-pickers/ComplementPicker";
import ComplementPicker from "../ComplementPicker";
import {
vpsReducer,
VpsReducerAction,
@ -45,9 +46,10 @@ function VPPicker({
// useEffect(() => {
// parent.current && autoAnimate(parent.current);
// }, [parent]);
const [showingExplanation, setShowingExplanation] = useState<
{ role: "servant" | "king"; item: "subject" | "object" } | false
>(false);
const [showingExplanation, setShowingExplanation] =
useState<{ role: "servant" | "king"; item: "subject" | "object" } | false>(
false
);
function adjustVps(action: VpsReducerAction) {
onChange(vpsReducer(vps, action));
}

Some files were not shown because too many files have changed in this diff Show More