From cc96cd176c58ec24194154f69ea0619c37b02463 Mon Sep 17 00:00:00 2001 From: uxiun Date: Sun, 10 Sep 2023 16:56:55 +0900 Subject: [PATCH] debounce; css --- pages/TextRewriter/entries.tsx | 139 ++++++++++++++++------------- pages/TextRewriter/entryForm.tsx | 63 +++++++------ pages/TextRewriter/recycleForm.tsx | 7 +- pages/TextRewriter/searchForm.tsx | 47 ++++++++++ ts/atom.ts | 1 + ts/css.ts | 5 ++ ts/hook.ts | 12 +++ ts/lang.ts | 11 +++ ts/type.ts | 7 ++ 9 files changed, 199 insertions(+), 93 deletions(-) create mode 100644 pages/TextRewriter/searchForm.tsx create mode 100644 ts/css.ts create mode 100644 ts/hook.ts diff --git a/pages/TextRewriter/entries.tsx b/pages/TextRewriter/entries.tsx index 8254325..828ef9b 100644 --- a/pages/TextRewriter/entries.tsx +++ b/pages/TextRewriter/entries.tsx @@ -10,18 +10,33 @@ import { inputtingEntryAtom, newEntryAtom, ngramIndexesForEntryAtom, + searchDelayAtom, uilanguageAtom, } from "@/ts/atom" -import { Box, Button, FormControl, IconButton, Input, TextField, Typography, Checkbox, FormControlLabel } from "@mui/material" -import EditIcon from '@mui/icons-material/Edit'; +import { + Box, + Button, + FormControl, + IconButton, + Input, + TextField, + Typography, + Checkbox, + FormControlLabel, +} from "@mui/material" +import EditIcon from "@mui/icons-material/Edit" import { useAtom } from "jotai" -import { FC, useMemo, useState } from "react" +import React, { FC, ReactEventHandler, useEffect, useMemo, useState } from "react" import RecycleForm from "./recycleForm" import { concernLength, ngramScoreMap } from "@/ts/ts/text" import { caseUndefined, mapUnions, mapUnionsToAverage } from "@/ts/ts/map" -import { LimitN } from "@/ts/type" +import { LimitN, SimilarForm, defaultSimilarForm } from "@/ts/type" import { translationTree } from "@/ts/lang" -import { ketaDirty, ketaDirtyDisplay } from "@/ts/ts/number"; +import { ketaDirty, ketaDirtyDisplay } from "@/ts/ts/number" +import { Controller, useForm, useWatch } from "react-hook-form" +import { useDebounce } from "@/ts/hook" +import SearchForm from "./searchForm" +import { spacecss } from "@/ts/css" const Entries: FC = () => { return ( @@ -69,21 +84,19 @@ const SimilarEntries: FC = prop => { const [atomUIlanguage] = useAtom(uilanguageAtom) const [showPoint, setShowPoint] = useState(false) + const lengthmaps = getLengthMap(atomEntryIdDedupedMap, atomDedupedMap) - const ngramScoreMap_concernLength = - (fromto: keyof FromToObj) => + const ngramScoreMap_concernLength = (fromto: keyof FromToObj) => concernLength( mapUnionsToAverage( atomIndex[fromto].map(index => { const ngramScored = ngramScoreMap(index)(inputting[fromto]) // console.log(`ngramScored${index.n}`, ngramScored) return ngramScored - }) ) - ) - (inputting[fromto].length, lengthmaps[fromto]) + )(inputting[fromto].length, lengthmaps[fromto]) const similarfrom = useMemo(() => { // console.log("atom index", atomIndex) @@ -98,65 +111,66 @@ const SimilarEntries: FC = prop => { const similarCalc = { from: similarfrom, - to: similarto + to: similarto, } const cssflexCenter = { - display: "flex" - ,alignItems:"center" + display: "flex", + alignItems: "center", } - const similarList = (fromto: keyof FromToObj) => function listfunc() { - return - {fromto} - - {similarCalc[fromto].map(([id, score]) => { - // console.log("atomEntryIdDedupedMap", atomEntryIdDedupedMap) - const fkey = getDedupKeyById(id, atomEntryIdDedupedMap) - return fkey === undefined ? ( - cannot get id - ) : ( - - {showPoint? `${ketaDirtyDisplay("round", 5)(score)}`: ``} - {caseUndefined(atomDedupedMap.get(fkey ?? ""))(v => { - const e: Entry = { ...JSON.parse(fkey), to: v } - return - }, stringified key not found)} - - ) - })} - - - } + const similarList = (fromto: keyof FromToObj) => + function listfunc() { + return ( + + {fromto} + + {similarCalc[fromto].map(([id, score]) => { + // console.log("atomEntryIdDedupedMap", atomEntryIdDedupedMap) + const fkey = getDedupKeyById(id, atomEntryIdDedupedMap) + return fkey === undefined ? ( + cannot get id + ) : ( + + {showPoint ? `${ketaDirtyDisplay("round", 5)(score)}` : ``} + {caseUndefined(atomDedupedMap.get(fkey ?? ""))(v => { + const e: Entry = { ...JSON.parse(fkey), to: v } + return + }, stringified key not found)} + + ) + })} + + + ) + } + return ( - - {translationTree.headerTitle.list.similar[atomUIlanguage]} - - setShowPoint(d => !d)} - value="showPoint" + sx={{ + display: "flex", + ...spacecss.similarForm, + }}> + {translationTree.headerTitle.list.similar[atomUIlanguage]} + + setShowPoint(d => !d)} + value="showPoint" /> - } - label={translationTree.showPoint[atomUIlanguage]} - /> - - - {[ - similarList("from")() - ,similarList("to")() - ]} + label={translationTree.showPoint[atomUIlanguage]} + /> + + + {[similarList("from")(), similarList("to")()]} ) } @@ -208,16 +222,13 @@ const EachEntry: FC = ({ entry }) => { const [formVisible, setFormVisible] = useState(false) return ( - + { setFormVisible(s => !s) }}> - + {formVisible ? ( setFormVisible(false)} defaultValues={entry} /> diff --git a/pages/TextRewriter/entryForm.tsx b/pages/TextRewriter/entryForm.tsx index 1430200..774bb11 100644 --- a/pages/TextRewriter/entryForm.tsx +++ b/pages/TextRewriter/entryForm.tsx @@ -1,5 +1,5 @@ import validator from "validator"; -import { Entry, TooltipOperationBools, allentriesAtom, dedupEntries, dedupedEntryMapAtom, defaultEntry, defaultTooltipOperationBools, entryAttributeReadable, entryIdDedupedMapAtom, get_by_entry, inputtingEntryAtom, keyvalueToEntry, matchingEntryAtom, newEntryAtom, nextEntryIdAtom, ngramIndexesForEntryAtom, ngramIndexingEntry, uilanguageAtom } from "@/ts/atom"; +import { Entry, TooltipOperationBools, allentriesAtom, dedupEntries, dedupedEntryMapAtom, defaultEntry, defaultTooltipOperationBools, entryAttributeReadable, entryIdDedupedMapAtom, get_by_entry, inputtingEntryAtom, keyvalueToEntry, matchingEntryAtom, newEntryAtom, nextEntryIdAtom, ngramIndexesForEntryAtom, ngramIndexingEntry, searchDelayAtom, uilanguageAtom } from "@/ts/atom"; import { translationTree } from "@/ts/lang"; import { Box, Button, Checkbox, FormControlLabel, TextField, Tooltip } from "@mui/material"; import { useAtom } from "jotai"; @@ -8,6 +8,7 @@ import { Controller, useForm, useWatch } from "react-hook-form"; import Entries from "./entries"; import JSONoutput from "./output"; import { ngramIndexing, ngramSegmentify } from "@/ts/ts/text"; +import { useDebounce } from "@/ts/hook"; const EntryForm: FC = () => { const [message, setMessage] = useState({ @@ -23,40 +24,46 @@ const EntryForm: FC = () => { const [atomIndex, setatomIndex] = useAtom(ngramIndexesForEntryAtom) const [atomNextEntryId, setatomNextEntryId] = useAtom(nextEntryIdAtom) const [atomEntryIdDedupedMap, setatomEntryIdDedupedMap] = useAtom(entryIdDedupedMapAtom) - + const [atomSearchDelay] = useAtom(searchDelayAtom) + const [entriesForJSON, setEntriesForJSON] = useState(allentries) type Form = Entry const {control, handleSubmit, formState} = useForm
({ defaultValues: defaultEntry }) - const useWatchValue = useWatch({control}) + const realtimeUseWatchValue = useWatch({control}) + const useWatchValue = useDebounce(atomSearchDelay, realtimeUseWatchValue) const [dedupedEntryMap, setDedupedEntryMap] = useAtom(dedupedEntryMapAtom) const [isCheckboxEnable, setIsCheckboxEnable] = useState(false); type AddButtonContent = "add"|"update" const [addButtonContent, setAddButtonContent] = useState("add"); const [openTip, setOpenTip] = useState(defaultTooltipOperationBools) - + useEffect(()=>{ - const newe: Entry = { - from: useWatchValue.from?? newForm.from, - to: useWatchValue.to?? newForm.to, - ic: useWatchValue.ic?? newForm.ic, - mw: useWatchValue.mw?? newForm.mw, - sc: useWatchValue.sc?? newForm.sc - } - setatomInputtingEntry(newe); - setIsCheckboxEnable(!!newe.from.match(/.*[a-zA-Z].*/)) - const to = get_by_entry(dedupedEntryMap)(newe) - if (typeof to=="string") { - setMessage(s => ({...s, alreadyExist: translationTree.confirm.alreadyExist[lang]})) - setAddButtonContent("update") - setatomMatchingEntry({...newe, to}) - } else { - setMessage(s => ({...s, alreadyExist: ""})) - setAddButtonContent("add") - setatomMatchingEntry(newe) - } + const debounce = setTimeout(()=>{ + + const newe: Entry = { + from: useWatchValue.from?? newForm.from, + to: useWatchValue.to?? newForm.to, + ic: useWatchValue.ic?? newForm.ic, + mw: useWatchValue.mw?? newForm.mw, + sc: useWatchValue.sc?? newForm.sc + } + setatomInputtingEntry(newe); + setIsCheckboxEnable(!!newe.from.match(/.*[a-zA-Z].*/)) + const to = get_by_entry(dedupedEntryMap)(newe) + if (typeof to=="string") { + setMessage(s => ({...s, alreadyExist: translationTree.confirm.alreadyExist[lang]})) + setAddButtonContent("update") + setatomMatchingEntry({...newe, to}) + } else { + setMessage(s => ({...s, alreadyExist: ""})) + setAddButtonContent("add") + setatomMatchingEntry(newe) + } + }, 100); + return ()=> clearTimeout(debounce) }, [useWatchValue, allentries]) const addEntry = (f: Form) => { const {to, ...fkey} = f @@ -107,7 +114,7 @@ const EntryForm: FC = () => { console.log("failed to delete") setMessage(s => ({...s, operation: translationTree.tooltip.fail("delete")[lang]})) } - + const array = Array.from(dedupedEntryMap) const map = new Map(array) setDedupedEntryMap(map) @@ -120,7 +127,7 @@ const EntryForm: FC = () => { ( { )} const handleClick = - // : MouseEventHandler = e => + // : MouseEventHandler = e => (name: keyof TooltipOperationBools) => { const entry: Entry = { from: useWatchValue.from?? newForm.from, @@ -204,14 +211,14 @@ const EntryForm: FC = () => { )}/> ( = prop => { const [atomIndex, setatomIndex] = useAtom(ngramIndexesForEntryAtom) const [atomNextEntryId, setatomNextEntryId] = useAtom(nextEntryIdAtom) const [atomEntryIdDedupedMap, setatomEntryIdDedupedMap] = useAtom(entryIdDedupedMapAtom) + const [atomSearchDelay] = useAtom(searchDelayAtom) const [entriesForJSON, setEntriesForJSON] = useState(allentries) type Form = Entry const { control, handleSubmit, formState } = useForm({ defaultValues: prop.defaultValues, }) - const useWatchValue = useWatch({ control }) + const realtimeUseWatchValue = useWatch({ control }) const [dedupedEntryMap, setDedupedEntryMap] = useAtom(dedupedEntryMapAtom) const [isCheckboxEnable, setIsCheckboxEnable] = useState(false) type AddButtonContent = "add" | "update" const [addButtonContent, setAddButtonContent] = useState("add") const [openTip, setOpenTip] = useState(defaultTooltipOperationBools) + const useWatchValue = useDebounce(atomSearchDelay, realtimeUseWatchValue) useEffect(() => { const newe: Entry = { @@ -80,6 +84,7 @@ const RecycleForm: FC = prop => { setatomMatchingEntry(newe) } }, [useWatchValue, allentries]) + const addEntry = (f: Form) => { // console.log("addEntry called") // console.log("atomNextEntryId", atomNextEntryId) diff --git a/pages/TextRewriter/searchForm.tsx b/pages/TextRewriter/searchForm.tsx new file mode 100644 index 0000000..9474ce8 --- /dev/null +++ b/pages/TextRewriter/searchForm.tsx @@ -0,0 +1,47 @@ +import { searchDelayAtom, uilanguageAtom } from "@/ts/atom" +import { useDebounce } from "@/ts/hook" +import { translationTree } from "@/ts/lang" +import { SimilarForm, defaultSimilarForm } from "@/ts/type" +import { Box, TextField } from "@mui/material" +import { useAtom } from "jotai" +import { FC, useEffect, useState } from "react" +import { Controller, useForm, useWatch } from "react-hook-form" + +const SearchForm: FC = () => { + const [atomSearchDelay, setAtomSearchDelay] = useAtom(searchDelayAtom) + const [atomUIlanguage] = useAtom(uilanguageAtom) + + const { control, register } = useForm({ + defaultValues: defaultSimilarForm, + }) + const useWatchValue = useWatch({ control }) + const useWatchValueCopy = useDebounce(300, useWatchValue) + const [focusing, setFocusing] = useState(false) + + useEffect(() => { + setAtomSearchDelay(useWatchValue.delay ?? defaultSimilarForm.delay) + }, [useWatchValueCopy]) + + return ( + + ( + setFocusing(f => !f)} + onBlur={() => setFocusing(f => !f)} + helperText={focusing && translationTree.searchDelay.help[atomUIlanguage]} + /> + )} + /> + + ) +} + +export default SearchForm diff --git a/ts/atom.ts b/ts/atom.ts index 5884fa4..24fb45b 100644 --- a/ts/atom.ts +++ b/ts/atom.ts @@ -27,6 +27,7 @@ export type Entry = { } const langs = ["ja", "en"] as const type Lang = (typeof langs)[number] +export const searchDelayAtom = atom(300) const entryAttributeTranslations: Record> = { from: { en: "from", diff --git a/ts/css.ts b/ts/css.ts new file mode 100644 index 0000000..c74e06a --- /dev/null +++ b/ts/css.ts @@ -0,0 +1,5 @@ +export const spacecss = { + similarForm: { + padding: [1, 2], + }, +} diff --git a/ts/hook.ts b/ts/hook.ts new file mode 100644 index 0000000..39b85e9 --- /dev/null +++ b/ts/hook.ts @@ -0,0 +1,12 @@ +import { useEffect, useState } from "react" + +export const useDebounce = (delay: number, value: K): K => { + const [debouncedValue, setDebouncedValue] = useState(value); + useEffect(()=>{ + const timer = setTimeout(()=> setDebouncedValue(value), delay) + + return ()=> clearTimeout(timer) + }, [value, delay]) + + return debouncedValue +} \ No newline at end of file diff --git a/ts/lang.ts b/ts/lang.ts index d033e41..e3798ae 100644 --- a/ts/lang.ts +++ b/ts/lang.ts @@ -78,5 +78,16 @@ export const translationTree = { en: "show match point", ja: "一致度表示" }, + searchDelay: { + label: + { + en: "search delay", + ja: "検索遅延" + }, + help: { + en: "increase this if input lagged", + ja: "入力がもたつくなら数値を大きく" + } + } } diff --git a/ts/type.ts b/ts/type.ts index 3de7f49..93c0cf9 100644 --- a/ts/type.ts +++ b/ts/type.ts @@ -1,3 +1,10 @@ export type LimitN = { limit: number +} + +export type SimilarForm = { + delay: number +} +export const defaultSimilarForm: SimilarForm = { + delay: 300 } \ No newline at end of file