Revert "feat: wrong words revision (#717)"

This reverts commit 61d80e39db.
This commit is contained in:
Kai
2023-12-05 23:54:25 +08:00
committed by GitHub
parent 61d80e39db
commit 4043369b04
19 changed files with 123 additions and 493 deletions

View File

@@ -25,7 +25,6 @@
"immer": "^9.0.21",
"jotai": "^2.0.3",
"mixpanel-browser": "^2.45.0",
"moment": "^2.29.4",
"pako": "^2.1.0",
"react": "^18.2.0",
"react-activity-calendar": "^2.0.2",

View File

@@ -21,7 +21,13 @@
"depends": []
},
"externalBin": [],
"icon": ["icons/32x32.png", "icons/128x128.png", "icons/128x128@2x.png", "icons/icon.icns", "icons/icon.ico"],
"icon": [
"icons/32x32.png",
"icons/128x128.png",
"icons/128x128@2x.png",
"icons/icon.icns",
"icons/icon.ico"
],
"identifier": "com.litongjava.qwerty.learner",
"longDescription": "",
"macOS": {

View File

@@ -20,9 +20,7 @@ const Tooltip = ({ children, content, className, placement = 'top' }: TooltipPro
visible ? 'opacity-100' : 'opacity-0'
} ${placementClasses} pointer-events-none absolute left-1/2 flex -translate-x-1/2 transform items-center justify-center transition-opacity`}
>
<span className="tooltip" style={{ whiteSpace: 'pre' }}>
{content}
</span>
<span className="tooltip">{content}</span>
</div>
</div>
)

View File

@@ -1,9 +1,6 @@
import useIntersectionObserver from '@/hooks/useIntersectionObserver'
import { useChapterStats } from '@/pages/Gallery-N/hooks/useChapterStats'
import { isInRevisionModeAtom } from '@/store'
import noop from '@/utils/noop'
import classNames from 'classnames'
import { useAtomValue } from 'jotai'
import { useEffect, useRef } from 'react'
type ChapterRowProps = {
@@ -18,7 +15,6 @@ export default function ChapterRow({ index, dictID, checked, onChange }: Chapter
const entry = useIntersectionObserver(rowRef, {})
const isVisible = !!entry?.isIntersecting
const chapterStatus = useChapterStats(index, dictID, isVisible)
const isRevisionMode = useAtomValue(isInRevisionModeAtom)
useEffect(() => {
if (checked && rowRef.current !== null) {
@@ -47,28 +43,13 @@ export default function ChapterRow({ index, dictID, checked, onChange }: Chapter
/>
</td>
<td className="flex-1 px-6 py-4 text-center text-sm text-gray-700 dark:text-gray-200">{index + 1}</td>
<td
className={classNames(
isRevisionMode ? 'invisible' : 'visible',
'flex-1 px-6 py-4 text-center text-sm text-gray-700 dark:text-gray-200',
)}
>
<td className="flex-1 px-6 py-4 text-center text-sm text-gray-700 dark:text-gray-200">
{chapterStatus ? chapterStatus.exerciseCount : 0}
</td>
<td
className={classNames(
isRevisionMode ? 'invisible' : 'visible',
'flex-1 px-6 py-4 text-center text-sm text-gray-700 dark:text-gray-200',
)}
>
<td className="flex-1 px-6 py-4 text-center text-sm text-gray-700 dark:text-gray-200">
{chapterStatus ? chapterStatus.avgWrongWordCount : 0}
</td>
<td
className={classNames(
isRevisionMode ? 'invisible' : 'visible',
'flex-1 px-6 py-4 text-center text-sm text-gray-700 dark:text-gray-200',
)}
>
<td className="flex-1 px-6 py-4 text-center text-sm text-gray-700 dark:text-gray-200">
{chapterStatus ? chapterStatus.avgWrongInputCount : 0}
</td>
</tr>

View File

@@ -1,142 +0,0 @@
import { useRevisionWordCount } from '../hooks/useRevisionWordCount'
import { isInRevisionModeAtom, isRestartRevisionProgressAtom } from '@/store'
import { db } from '@/utils/db'
import { Popover, Transition } from '@headlessui/react'
import * as Progress from '@radix-ui/react-progress'
import { useSetAtom } from 'jotai'
import moment from 'moment'
import { Fragment, useState } from 'react'
type RevisionSwitcherProps = {
dictId: string
onConfirm: () => void
}
const RevisionSwitcher: React.FC<RevisionSwitcherProps> = ({ dictId, onConfirm }: RevisionSwitcherProps) => {
const [revisionIndex, setRevisionIndex] = useState<number | undefined>(0)
const [createdTime, setCreatedTime] = useState<number | undefined>(0)
const setRevisionMode = useSetAtom(isInRevisionModeAtom)
const revisionWordCount = useRevisionWordCount(dictId)
const setRestartRevision = useSetAtom(isRestartRevisionProgressAtom)
const onContinueProgress = () => {
onConfirm()
setRevisionMode(true)
}
const onCreateNewProgress = () => {
onConfirm()
setRevisionMode(true)
setRestartRevision(true)
}
const fetchRevisionIndex = async () => {
let index, timeStamp
await db.revisionDictRecords
.where('dict')
.equals(dictId)
.first((record) => {
index = record?.revisionIndex
timeStamp = record?.createdTime
console.log('index', index)
console.log('timeStamp', timeStamp)
})
setRevisionIndex(index)
setCreatedTime(timeStamp)
}
// useEffect(() => {
// fetchRevisionIndex()
// }, [dictId])
return (
<Popover className="relative">
{({ open }) => (
<>
<Popover.Button
className={`flex h-8 min-w-max cursor-pointer items-center justify-center rounded-md border-2 px-2 text-gray-700 transition-colors duration-300 ease-in-out hover:bg-indigo-400 hover:text-white focus:outline-none dark:text-white dark:text-opacity-60 dark:hover:text-opacity-100 ${
open ? 'bg-indigo-400 text-white' : 'bg-transparent'
}`}
onFocus={(e) => {
e.target.blur()
}}
onClick={fetchRevisionIndex}
>
</Popover.Button>
<Transition
as={Fragment}
enter="transition ease-out duration-200"
enterFrom="opacity-0 translate-y-1"
enterTo="opacity-100 translate-y-0"
leave="transition ease-in duration-150"
leaveFrom="opacity-100 translate-y-0"
leaveTo="opacity-0 translate-y-1"
>
<Popover.Panel className="absolute left-0 z-20 mt-2 flex max-w-max -translate-x-1/2 px-4 ">
<div className="shadow-upper box-border flex w-60 select-none flex-col items-center justify-center gap-3 rounded-xl bg-white p-4 text-gray-700 drop-shadow transition duration-1000 ease-in-out dark:bg-gray-800 dark:text-gray-200">
{revisionWordCount === 0 ? (
<span className="text-sm"></span>
) : (
<>
<div className="flex w-full flex-col items-start py-0">
<span></span>
</div>
{revisionIndex === undefined || createdTime === undefined ? (
<span></span>
) : (
<>
<div className="flex w-full flex-col items-start gap-0 py-0">
<div className=" flex w-full items-center py-0">
<Progress.Root
value={revisionIndex}
max={revisionWordCount}
className="mr-4 h-2 w-full rounded-full border border-indigo-400 bg-white"
>
<Progress.Indicator
className="h-full rounded-full bg-indigo-400 pl-0"
style={{ width: `calc(${(revisionIndex / revisionWordCount) * 100}% )` }}
/>
</Progress.Root>
<span className="p-0 text-xs">
{revisionIndex}/{revisionWordCount}
</span>
</div>
<span className="text-xs">({moment(createdTime).format('YYYY-MM-DD HH:mm:ss')})</span>
</div>
</>
)}
</>
)}
{revisionWordCount !== 0 && (
<div className="flex w-full items-start justify-center gap-3">
{revisionIndex !== undefined && createdTime !== undefined && (
<button
className="my-btn-primary flex-1 px-1 text-sm disabled:bg-gray-300"
type="button"
title="继续当前进度"
onClick={onContinueProgress}
>
</button>
)}
<button
className="my-btn-primary max-w-[98px] flex-1 px-1 text-sm disabled:bg-gray-300"
type="button"
title="创建新进度"
onClick={onCreateNewProgress}
>
</button>
</div>
)}
</div>
</Popover.Panel>
</Transition>
</>
)}
</Popover>
)
}
export default RevisionSwitcher

View File

@@ -1,16 +1,12 @@
import { GalleryContext } from '..'
import { useRevisionWordCount } from '../hooks/useRevisionWordCount'
import ChapterRow from './ChapterRow'
import RevisionSwitcher from './RevisionSwitcher'
import Tooltip from '@/components/Tooltip'
import { currentChapterAtom, currentDictIdAtom, isInRevisionModeAtom } from '@/store'
import { currentChapterAtom, currentDictIdAtom } from '@/store'
import { calcChapterCount } from '@/utils'
import range from '@/utils/range'
import { Dialog, Switch, Transition } from '@headlessui/react'
import { Dialog, Transition } from '@headlessui/react'
import { useAtom } from 'jotai'
import { Fragment, useCallback, useContext, useEffect, useState } from 'react'
import { useNavigate } from 'react-router-dom'
import IconInfo from '~icons/ic/outline-info'
import IconX from '~icons/tabler/x'
export default function ChapterList() {
@@ -22,13 +18,11 @@ export default function ChapterList() {
const [currentChapter, setCurrentChapter] = useAtom(currentChapterAtom)
const [currentDictId, setCurrentDictId] = useAtom(currentDictIdAtom)
const [isRevisionMode, setRevisionMode] = useAtom(isInRevisionModeAtom)
const [checkedChapter, setCheckedChapter] = useState(dict?.id === currentDictId ? currentChapter : 0)
const navigate = useNavigate()
const chapterCount = calcChapterCount(dict?.length ?? 0)
const showChapterList = dict !== null
const revisionWordCount = useRevisionWordCount(currentDictId)
useEffect(() => {
if (dict) {
@@ -55,12 +49,6 @@ export default function ChapterList() {
setState((state) => {
state.chapterListDict = null
})
setRevisionMode(false)
}
const onToggleRevisionMode = () => {
setRevisionMode((old) => !old)
setCheckedChapter(0)
}
return (
@@ -94,67 +82,54 @@ export default function ChapterList() {
<>
<div className="flex w-full items-end justify-between py-4 pl-5">
<span className="text-lg text-gray-700 dark:text-gray-200">{dict.name}</span>
<div className="flex items-center gap-3">
<RevisionSwitcher dictId={dict?.id} onConfirm={onConfirm} />
<IconX className="mr-2 cursor-pointer text-gray-400" onClick={onCloseDialog} />
</div>
<IconX className="mr-2 cursor-pointer text-gray-400" onClick={onCloseDialog} />
</div>
<div className="w-full flex-1 overflow-y-auto">
{isRevisionMode && revisionWordCount === 0 ? (
<div className="flex min-h-full items-center justify-center">
<span className="text-sm font-normal leading-5 text-gray-900 dark:text-white dark:text-opacity-60">
</span>
</div>
) : (
<table className="block min-w-full divide-y divide-gray-100 dark:divide-gray-800">
<thead className="sticky top-0 block h-10 w-full bg-gray-50 dark:bg-gray-700">
<tr className="flex">
<th
scope="col"
className="w-15 px-2 py-3 text-center text-sm font-bold tracking-wider text-gray-600 dark:text-gray-200"
></th>
<th
scope="col"
className="flex-1 px-2 py-3 text-center text-sm font-bold tracking-wider text-gray-600 dark:text-gray-200"
>
Chapter
</th>
<th
scope="col"
className="flex-1 px-2 py-3 text-center text-sm font-bold tracking-wider text-gray-600 dark:text-gray-200"
>
</th>
<th
scope="col"
className="flex-1 px-2 py-3 text-center text-sm font-bold tracking-wider text-gray-600 dark:text-gray-200"
>
</th>
<th
scope="col"
className="flex-1 px-2 py-3 text-center text-sm font-bold tracking-wider text-gray-600 dark:text-gray-200"
>
</th>
</tr>
</thead>
<tbody className="block h-full w-full divide-y divide-gray-100 overflow-y-scroll bg-white dark:divide-gray-800">
{range(0, chapterCount, 1).map((index) => (
<ChapterRow
key={`${dict.id}-${index}`}
index={index}
dictID={dict.id}
checked={checkedChapter === index}
onChange={onChangeChapter}
/>
))}
</tbody>
</table>
)}
<div className="w-full flex-1 overflow-y-auto ">
<table className="block min-w-full divide-y divide-gray-100 dark:divide-gray-800">
<thead className="sticky top-0 block h-10 w-full bg-gray-50 dark:bg-gray-700">
<tr className="flex">
<th
scope="col"
className="w-15 px-2 py-3 text-center text-sm font-bold tracking-wider text-gray-600 dark:text-gray-200"
></th>
<th
scope="col"
className="flex-1 px-2 py-3 text-center text-sm font-bold tracking-wider text-gray-600 dark:text-gray-200"
>
Chapter
</th>
<th
scope="col"
className="flex-1 px-2 py-3 text-center text-sm font-bold tracking-wider text-gray-600 dark:text-gray-200"
>
</th>
<th
scope="col"
className="flex-1 px-2 py-3 text-center text-sm font-bold tracking-wider text-gray-600 dark:text-gray-200"
>
</th>
<th
scope="col"
className="flex-1 px-2 py-3 text-center text-sm font-bold tracking-wider text-gray-600 dark:text-gray-200"
>
</th>
</tr>
</thead>
<tbody className="block h-full w-full divide-y divide-gray-100 overflow-y-scroll bg-white dark:divide-gray-800">
{range(0, chapterCount, 1).map((index) => (
<ChapterRow
key={`${dict.id}-${index}`}
index={index}
dictID={dict.id}
checked={checkedChapter === index}
onChange={onChangeChapter}
/>
))}
</tbody>
</table>
</div>
<button className="text-bold h-15 w-full bg-indigo-400 text-white" onClick={onConfirm}>

View File

@@ -30,6 +30,7 @@ async function getDictStats(dict: string): Promise<IDictStats> {
const uniqueChapter = allChapter.filter((value, index, self) => {
return self.indexOf(value) === index
})
const exercisedChapterCount = uniqueChapter.length
return { exercisedChapterCount }

View File

@@ -1,34 +0,0 @@
import { db } from '@/utils/db'
import { useEffect, useState } from 'react'
export function useRevisionWordCount(dictID: string) {
const [wordCount, setWordCount] = useState<number>(0)
useEffect(() => {
const fetchWordCount = async () => {
const count = await getRevisionWordCount(dictID)
setWordCount(count)
}
if (dictID) {
fetchWordCount()
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [dictID])
return wordCount
}
async function getRevisionWordCount(dict: string): Promise<number> {
const wordCount = await db.wordRecords
.where('dict')
.equals(dict)
.and((wordRecord) => wordRecord.wrongCount > 0)
.toArray()
.then((wordRecords) => {
const res = new Map()
const reducedRecords = wordRecords.filter((item) => !res.has(item['word'] + item['dict']) && res.set(item['word'] + item['dict'], 1))
return reducedRecords.length
})
return wordCount
}

View File

@@ -1,34 +1,16 @@
import Tooltip from '@/components/Tooltip'
import { currentChapterAtom, currentDictInfoAtom, isInRevisionModeAtom } from '@/store'
import { db } from '@/utils/db'
import { currentChapterAtom, currentDictInfoAtom } from '@/store'
import range from '@/utils/range'
import { Listbox, Transition } from '@headlessui/react'
import { useAtom, useAtomValue } from 'jotai'
import { Fragment, useCallback } from 'react'
import { Fragment } from 'react'
import { NavLink } from 'react-router-dom'
import IconCheck from '~icons/tabler/check'
type DictChapterButtonProps = {
index: number
}
export const DictChapterButton = ({ index }: DictChapterButtonProps) => {
export const DictChapterButton = () => {
const currentDictInfo = useAtomValue(currentDictInfoAtom)
const [currentChapter, setCurrentChapter] = useAtom(currentChapterAtom)
const chapterCount = currentDictInfo.chapterCount
const [isRevision, setRevisionMode] = useAtom(isInRevisionModeAtom)
const saveDictRevisionIndex = useCallback(
async (dictId: string, index: number) => {
// 保存错题进度(点击章节切换时)
setRevisionMode(false)
// if ((await db.revisionDictRecords.where('dict').equals(dictId).count()) > 1) {
// console.log('delete')
// await db.revisionDictRecords.where('dict').equals(dictId).delete()
// }
await db.revisionDictRecords.where('dict').equals(dictId).modify({ revisionIndex: index })
},
[setRevisionMode],
)
return (
<>
@@ -36,38 +18,35 @@ export const DictChapterButton = ({ index }: DictChapterButtonProps) => {
<NavLink
className="block rounded-lg px-3 py-1 text-lg transition-colors duration-300 ease-in-out hover:bg-indigo-400 hover:text-white focus:outline-none dark:text-white dark:text-opacity-60 dark:hover:text-opacity-100"
to="/gallery"
onClick={() => isRevision && saveDictRevisionIndex(currentDictInfo.id, index)}
>
{currentDictInfo.name} {isRevision && '错题练习'}
{currentDictInfo.name}
</NavLink>
</Tooltip>
{!isRevision && (
<Tooltip content="章节切换">
<Listbox value={currentChapter} onChange={setCurrentChapter}>
<Listbox.Button className="rounded-lg px-3 py-1 text-lg transition-colors duration-300 ease-in-out hover:bg-indigo-400 hover:text-white focus:outline-none dark:text-white dark:text-opacity-60 dark:hover:text-opacity-100">
{currentChapter + 1}
</Listbox.Button>
<Transition as={Fragment} leave="transition ease-in duration-100" leaveFrom="opacity-100" leaveTo="opacity-0">
<Listbox.Options className="listbox-options z-10 w-32">
{range(0, chapterCount, 1).map((index) => (
<Listbox.Option key={index} value={index}>
{({ selected }) => (
<div className="group flex cursor-pointer items-center justify-between">
{selected ? (
<span className="listbox-options-icon">
<IconCheck className="focus:outline-none" />
</span>
) : null}
<span> {index + 1} </span>
</div>
)}
</Listbox.Option>
))}
</Listbox.Options>
</Transition>
</Listbox>
</Tooltip>
)}
<Tooltip content="章节切换">
<Listbox value={currentChapter} onChange={setCurrentChapter}>
<Listbox.Button className="rounded-lg px-3 py-1 text-lg transition-colors duration-300 ease-in-out hover:bg-indigo-400 hover:text-white focus:outline-none dark:text-white dark:text-opacity-60 dark:hover:text-opacity-100">
{currentChapter + 1}
</Listbox.Button>
<Transition as={Fragment} leave="transition ease-in duration-100" leaveFrom="opacity-100" leaveTo="opacity-0">
<Listbox.Options className="listbox-options z-10 w-32">
{range(0, chapterCount, 1).map((index) => (
<Listbox.Option key={index} value={index}>
{({ selected }) => (
<div className="group flex cursor-pointer items-center justify-between">
{selected ? (
<span className="listbox-options-icon">
<IconCheck className="focus:outline-none" />
</span>
) : null}
<span> {index + 1} </span>
</div>
)}
</Listbox.Option>
))}
</Listbox.Options>
</Transition>
</Listbox>
</Tooltip>
</>
)
}

View File

@@ -5,17 +5,9 @@ import RemarkRing from './RemarkRing'
import WordChip from './WordChip'
import styles from './index.module.css'
import Tooltip from '@/components/Tooltip'
import {
currentChapterAtom,
currentDictInfoAtom,
infoPanelStateAtom,
isInRevisionModeAtom,
randomConfigAtom,
wordDictationConfigAtom,
} from '@/store'
import { currentChapterAtom, currentDictInfoAtom, infoPanelStateAtom, randomConfigAtom, wordDictationConfigAtom } from '@/store'
import type { InfoPanelType } from '@/typings'
import { recordOpenInfoPanelAction } from '@/utils'
import { db } from '@/utils/db'
import { Transition } from '@headlessui/react'
import { useAtom, useAtomValue, useSetAtom } from 'jotai'
import { useCallback, useContext, useEffect, useMemo } from 'react'
@@ -36,7 +28,6 @@ const ResultScreen = () => {
const [currentChapter, setCurrentChapter] = useAtom(currentChapterAtom)
const setInfoPanelState = useSetAtom(infoPanelStateAtom)
const randomConfig = useAtomValue(randomConfigAtom)
const isRevisionMode = useAtomValue(isInRevisionModeAtom)
useEffect(() => {
// tick a zero timer to calc the stats
@@ -79,8 +70,8 @@ const ResultScreen = () => {
}, [state.chapterData.userInputLogs, state.chapterData.words])
const isLastChapter = useMemo(() => {
return isRevisionMode ? true : currentChapter >= currentDictInfo.chapterCount - 1
}, [currentChapter, currentDictInfo, isRevisionMode])
return currentChapter >= currentDictInfo.chapterCount - 1
}, [currentChapter, currentDictInfo])
const correctRate = useMemo(() => {
const chapterLength = state.chapterData.words.length
@@ -107,7 +98,7 @@ const ResultScreen = () => {
return `${minuteString}:${secondString}`
}, [state.timerData.time])
const repeatButtonHandler = useCallback(async () => {
const repeatButtonHandler = useCallback(() => {
setWordDictationConfig((old) => {
if (old.isOpen) {
if (old.openBy === 'auto') {
@@ -116,15 +107,14 @@ const ResultScreen = () => {
}
return old
})
await db.revisionDictRecords.where('dict').equals(currentDictInfo.id).modify({ revisionIndex: 0 })
dispatch({ type: TypingStateActionType.REPEAT_CHAPTER, shouldShuffle: randomConfig.isOpen })
}, [dispatch, randomConfig.isOpen, setWordDictationConfig, currentDictInfo.id])
}, [dispatch, randomConfig.isOpen, setWordDictationConfig])
const dictationButtonHandler = useCallback(async () => {
const dictationButtonHandler = useCallback(() => {
setWordDictationConfig((old) => ({ ...old, isOpen: true, openBy: 'auto' }))
await db.revisionDictRecords.where('dict').equals(currentDictInfo.id).modify({ revisionIndex: 0 })
dispatch({ type: TypingStateActionType.REPEAT_CHAPTER, shouldShuffle: randomConfig.isOpen })
}, [dispatch, randomConfig.isOpen, setWordDictationConfig, currentDictInfo.id])
}, [dispatch, randomConfig.isOpen, setWordDictationConfig])
const nextButtonHandler = useCallback(() => {
setWordDictationConfig((old) => {

View File

@@ -2,7 +2,7 @@ import { TypingContext, TypingStateActionType } from '../../store'
import WordCard from './WordCard'
import Drawer from '@/components/Drawer'
import Tooltip from '@/components/Tooltip'
import { currentChapterAtom, currentDictInfoAtom, isInRevisionModeAtom } from '@/store'
import { currentChapterAtom, currentDictInfoAtom } from '@/store'
import { Dialog } from '@headlessui/react'
import * as ScrollArea from '@radix-ui/react-scroll-area'
import { atom, useAtomValue } from 'jotai'
@@ -20,7 +20,6 @@ export default function WordList() {
const [isOpen, setIsOpen] = useState(false)
const currentDictTitleValue = useAtomValue(currentDictTitle)
const isRevision = useAtomValue(isInRevisionModeAtom)
function closeModal() {
setIsOpen(false)
@@ -45,7 +44,7 @@ export default function WordList() {
<Drawer open={isOpen} onClose={closeModal} classNames="bg-stone-50 dark:bg-gray-900">
<Dialog.Title as="h3" className="flex items-center justify-between p-4 text-lg font-medium leading-6 dark:text-gray-50">
{isRevision && '错题练习 ' + currentDictTitleValue}
{currentDictTitleValue}
<IconX onClick={closeModal} className="cursor-pointer" />
</Dialog.Title>
<ScrollArea.Root className="flex-1 select-none overflow-y-auto ">

View File

@@ -5,11 +5,10 @@ import Phonetic from './components/Phonetic'
import Translation from './components/Translation'
import WordComponent from './components/Word'
import { usePrefetchPronunciationSound } from '@/hooks/usePronunciation'
import { currentDictIdAtom, isInRevisionModeAtom, isShowPrevAndNextWordAtom, loopWordConfigAtom, phoneticConfigAtom } from '@/store'
import { isShowPrevAndNextWordAtom, loopWordConfigAtom, phoneticConfigAtom } from '@/store'
import type { Word } from '@/typings'
import { db } from '@/utils/db'
import { useAtomValue } from 'jotai'
import { useCallback, useContext, useEffect, useMemo, useState } from 'react'
import { useCallback, useContext, useMemo, useState } from 'react'
import { useHotkeys } from 'react-hotkeys-hook'
export default function WordPanel() {
@@ -22,8 +21,6 @@ export default function WordPanel() {
const { times: loopWordTimes } = useAtomValue(loopWordConfigAtom)
const currentWord = state.chapterData.words[state.chapterData.index]
const nextWord = state.chapterData.words[state.chapterData.index + 1] as Word | undefined
const currentDictId = useAtomValue(currentDictIdAtom)
const isRevision = useAtomValue(isInRevisionModeAtom)
const prevIndex = useMemo(() => {
const newIndex = state.chapterData.index - 1
@@ -40,22 +37,6 @@ export default function WordPanel() {
setWordComponentKey((old) => old + 1)
}, [])
useEffect(() => {
// 复习错题模式下,当组件挂载时,检查是否存在错题进度,如果存在,则跳转到该进度
const fetchRevisionIndex = async () => {
let index
await db.revisionDictRecords
.where('dict')
.equals(currentDictId)
.first((record) => (index = record?.revisionIndex))
index && dispatch({ type: TypingStateActionType.SKIP_2_WORD_INDEX, newIndex: index })
reloadCurrentWordComponent()
}
if (isRevision) {
fetchRevisionIndex()
}
}, [currentDictId, dispatch, isRevision, reloadCurrentWordComponent])
const onFinish = useCallback(() => {
if (state.chapterData.index < state.chapterData.words.length - 1 || currentWordExerciseCount < loopWordTimes - 1) {
// 用户完成当前单词

View File

@@ -1,13 +1,9 @@
import { isRestartRevisionProgressAtom } from './../../../store/index'
import { WordRecord } from './../../../utils/db/record'
import { CHAPTER_LENGTH } from '@/constants'
import { currentChapterAtom, currentDictInfoAtom, isInRevisionModeAtom } from '@/store'
import { currentChapterAtom, currentDictInfoAtom } from '@/store'
import type { WordWithIndex } from '@/typings/index'
import { db } from '@/utils/db'
import type { IWordRecord } from '@/utils/db/record'
import { wordListFetcher } from '@/utils/wordListFetcher'
import { useAtom, useAtomValue } from 'jotai'
import { useEffect, useMemo, useState } from 'react'
import { useMemo } from 'react'
import useSWR from 'swr'
export type UseWordListResult = {
@@ -22,72 +18,26 @@ export type UseWordListResult = {
export function useWordList(): UseWordListResult {
const currentDictInfo = useAtomValue(currentDictInfoAtom)
const [currentChapter, setCurrentChapter] = useAtom(currentChapterAtom)
const [isInRevisionMode] = useAtom(isInRevisionModeAtom)
const [isRestartRevisionProgress, setRestartRevisionProgress] = useAtom(isRestartRevisionProgressAtom)
const [wrongListDesc, setWrongListDesc] = useState<IWordRecord[]>()
const isFirstChapter = currentDictInfo.id === 'cet4' && currentChapter === 0 && !isInRevisionMode
const isFirstChapter = currentDictInfo.id === 'cet4' && currentChapter === 0
// Reset current chapter to 0, when currentChapter is greater than chapterCount.
if (currentChapter >= currentDictInfo.chapterCount) {
setCurrentChapter(0)
}
useEffect(() => {
const fetchWrongList = async () => {
let lastErrorList: IWordRecord[] = []
lastErrorList = await db.revisionWordRecords.where('dict').equals(currentDictInfo.id).toArray()
console.log(lastErrorList)
if (lastErrorList.length === 0 || isRestartRevisionProgress) {
//获取最新的wrongRecords
if (lastErrorList.length === 0)
await db.revisionDictRecords.add({ dict: currentDictInfo.id, revisionIndex: 0, createdTime: Date.now() })
if (isRestartRevisionProgress)
await db.revisionDictRecords.where('dict').equals(currentDictInfo.id).modify({ revisionIndex: 0, createdTime: Date.now() })
setRestartRevisionProgress(false)
const wrongList = await db.wordRecords
.where('wrongCount')
.above(0)
.and((record) => record.dict === currentDictInfo.id)
.reverse()
.toArray()
const processedWrongList = wrongList.map((record) => {
return new WordRecord(record.word, record.dict, record.chapter, record.timing, record.wrongCount, record.mistakes)
})
wrongList.length !== 0 && (await db.revisionWordRecords.bulkAdd(processedWrongList))
lastErrorList = await db.revisionWordRecords.where('dict').equals(currentDictInfo.id).toArray()
}
setWrongListDesc(lastErrorList)
}
if (isInRevisionMode) {
fetchWrongList()
}
}, [isInRevisionMode, currentDictInfo.id, isRestartRevisionProgress, setRestartRevisionProgress])
const { data: wordList, error, isLoading } = useSWR(currentDictInfo.url, wordListFetcher)
const words: WordWithIndex[] = useMemo(() => {
const newWords = isFirstChapter
? firstChapter
: wordList
? isInRevisionMode
? wordList
.filter(
(word, index, self) =>
self.findIndex((w) => w.name === word.name) === index &&
wrongListDesc?.find((wrong) => wrong.dict === currentDictInfo.id && wrong.word === word.name),
)
.sort((a, b) => {
const aWrong = wrongListDesc?.find((wrong) => wrong.dict === currentDictInfo.id && wrong.word === a.name)
const bWrong = wrongListDesc?.find((wrong) => wrong.dict === currentDictInfo.id && wrong.word === b.name)
if ((bWrong?.wrongCount ?? 0) - (aWrong?.wrongCount ?? 0) === 0) return a.name.localeCompare(b.name)
else return (bWrong?.wrongCount ?? 0) - (aWrong?.wrongCount ?? 0)
})
: wordList.slice(currentChapter * CHAPTER_LENGTH, (currentChapter + 1) * CHAPTER_LENGTH)
? wordList.slice(currentChapter * CHAPTER_LENGTH, (currentChapter + 1) * CHAPTER_LENGTH)
: []
// 记录原始 index
return newWords.map((word, index) => ({ ...word, index }))
}, [isFirstChapter, wordList, currentChapter, currentDictInfo, isInRevisionMode, wrongListDesc])
}, [isFirstChapter, wordList, currentChapter])
return { words: wordList === undefined ? undefined : words, isLoading, error }
}

View File

@@ -31,8 +31,10 @@ const App: React.FC = () => {
const [currentDictId, setCurrentDictId] = useAtom(currentDictIdAtom)
const randomConfig = useAtomValue(randomConfigAtom)
const chapterLogUploader = useMixPanelChapterLogUploader(state)
const saveChapterRecord = useSaveChapterRecord()
useEffect(() => {
// 检测用户设备
if (!IsDesktop()) {
@@ -125,7 +127,7 @@ const App: React.FC = () => {
{state.isFinished && <ResultScreen />}
<Layout>
<Header>
<DictChapterButton index={state.chapterData.index} />
<DictChapterButton />
<PronunciationSwitcher />
<Switcher />
<StartButton isLoading={isLoading} />

View File

@@ -57,7 +57,6 @@ export enum TypingStateActionType {
SET_IS_SAVING_RECORD = 'SET_IS_SAVING_RECORD',
SET_IS_LOOP_SINGLE_WORD = 'SET_IS_LOOP_SINGLE_WORD',
TOGGLE_IS_LOOP_SINGLE_WORD = 'TOGGLE_IS_LOOP_SINGLE_WORD',
SET_REVISION_INDEX = 'SET_REVISION_INDEX',
}
export type TypingStateAction =
@@ -148,8 +147,6 @@ export const typingReducer = (state: TypingState, action: TypingStateAction) =>
case TypingStateActionType.SKIP_2_WORD_INDEX: {
const newIndex = action.newIndex
if (newIndex >= state.chapterData.words.length) {
console.log('newIndex', newIndex)
console.log('state.chapterData.words', state.chapterData.words)
state.isTyping = false
state.isFinished = true
}

View File

@@ -76,10 +76,6 @@ export const isShowAnswerOnHoverAtom = atomWithStorage('isShowAnswerOnHover', tr
export const isTextSelectableAtom = atomWithStorage('isTextSelectable', false)
export const isInRevisionModeAtom = atomWithStorage('isInRevisionMode', false)
export const isRestartRevisionProgressAtom = atomWithStorage('isRestartRevisionProgress', false)
export const phoneticConfigAtom = atomForConfig('phoneticConfig', {
isOpen: true,
type: 'us' as PhoneticType,

View File

@@ -1,8 +1,8 @@
import type { IChapterRecord, IRevisionDictRecord, IWordRecord, LetterMistakes } from './record'
import { ChapterRecord, RevisionDictRecord, WordRecord } from './record'
import type { IChapterRecord, IWordRecord, LetterMistakes } from './record'
import { ChapterRecord, WordRecord } from './record'
import { TypingContext, TypingStateActionType } from '@/pages/Typing/store'
import type { TypingState } from '@/pages/Typing/store/type'
import { currentChapterAtom, currentDictIdAtom, isInRevisionModeAtom } from '@/store'
import { currentChapterAtom, currentDictIdAtom } from '@/store'
import type { Table } from 'dexie'
import Dexie from 'dexie'
import { useAtomValue } from 'jotai'
@@ -11,16 +11,16 @@ import { useCallback, useContext } from 'react'
class RecordDB extends Dexie {
wordRecords!: Table<IWordRecord, number>
chapterRecords!: Table<IChapterRecord, number>
revisionDictRecords!: Table<IRevisionDictRecord, number>
revisionWordRecords!: Table<IWordRecord, number>
constructor() {
super('RecordDB')
this.version(3).stores({
this.version(1).stores({
wordRecords: '++id,word,timeStamp,dict,chapter,errorCount,[dict+chapter]',
chapterRecords: '++id,timeStamp,dict,chapter,time,[dict+chapter]',
})
this.version(2).stores({
wordRecords: '++id,word,timeStamp,dict,chapter,wrongCount,[dict+chapter]',
chapterRecords: '++id,timeStamp,dict,chapter,time,[dict+chapter]',
revisionDictRecords: '++id,dict,revisionIndex,createdTime',
revisionWordRecords: '++id,word,timeStamp,dict,chapter,errorCount,[dict+chapter]',
})
}
}
@@ -29,12 +29,9 @@ export const db = new RecordDB()
db.wordRecords.mapToClass(WordRecord)
db.chapterRecords.mapToClass(ChapterRecord)
db.revisionDictRecords.mapToClass(RevisionDictRecord)
db.revisionWordRecords.mapToClass(WordRecord)
export function useSaveChapterRecord() {
const currentChapter = useAtomValue(currentChapterAtom)
const isRevision = useAtomValue(isInRevisionModeAtom)
const dictID = useAtomValue(currentDictIdAtom)
const saveChapterRecord = useCallback(
@@ -47,7 +44,7 @@ export function useSaveChapterRecord() {
const chapterRecord = new ChapterRecord(
dictID,
isRevision ? -1 : currentChapter,
currentChapter,
time,
correctCount,
wrongCount,
@@ -58,7 +55,7 @@ export function useSaveChapterRecord() {
)
db.chapterRecords.add(chapterRecord)
},
[currentChapter, dictID, isRevision],
[currentChapter, dictID],
)
return saveChapterRecord
@@ -70,7 +67,6 @@ export type WordKeyLogger = {
}
export function useSaveWordRecord() {
const isRevision = useAtomValue(isInRevisionModeAtom)
const currentChapter = useAtomValue(currentChapterAtom)
const dictID = useAtomValue(currentDictIdAtom)
@@ -94,7 +90,7 @@ export function useSaveWordRecord() {
timing.push(diff)
}
const wordRecord = new WordRecord(word, dictID, isRevision ? -1 : currentChapter, timing, wrongCount, letterMistake)
const wordRecord = new WordRecord(word, dictID, currentChapter, timing, wrongCount, letterMistake)
let dbID = -1
try {
@@ -107,7 +103,7 @@ export function useSaveWordRecord() {
dispatch({ type: TypingStateActionType.SET_IS_SAVING_RECORD, payload: false })
}
},
[currentChapter, dictID, dispatch, isRevision],
[currentChapter, dictID, dispatch],
)
return saveWordRecord

View File

@@ -113,42 +113,3 @@ export class ChapterRecord implements IChapterRecord {
return Math.round((this.correctWordIndexes.length / this.wordNumber) * 100)
}
}
export interface IRevisionDictRecord {
dict: string
revisionIndex: number
createdTime: number
}
export class RevisionDictRecord implements IRevisionDictRecord {
dict: string
revisionIndex: number
createdTime: number
constructor(dict: string, revisionIndex: number, createdTime: number) {
this.dict = dict
this.revisionIndex = revisionIndex
this.createdTime = createdTime
}
}
export interface IRevisionWordRecord {
word: string
timeStamp: number
dict: string
errorCount: number
}
export class RevisionWordRecord implements IRevisionWordRecord {
word: string
timeStamp: number
dict: string
errorCount: number
constructor(word: string, dict: string, errorCount: number) {
this.word = word
this.timeStamp = getUTCUnixTimestamp()
this.dict = dict
this.errorCount = errorCount
}
}

View File

@@ -4291,11 +4291,6 @@ modern-normalize@^1.1.0:
resolved "https://registry.npmmirror.com/modern-normalize/-/modern-normalize-1.1.0.tgz"
integrity sha512-2lMlY1Yc1+CUy0gw4H95uNN7vjbpoED7NNRSBHE25nWfLBdmMzFCsPshlzbxHz+gYMcBEUN8V4pU16prcdPSgA==
moment@^2.29.4:
version "2.29.4"
resolved "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz#3dbe052889fe7c1b2ed966fcb3a77328964ef108"
integrity sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==
ms@2.1.2:
version "2.1.2"
resolved "https://registry.npmmirror.com/ms/-/ms-2.1.2.tgz"