mirror of
https://github.com/RealKai42/qwerty-learner.git
synced 2026-04-05 06:19:08 +08:00
Master export error chapters (#515)
Co-authored-by: peakhgrhill <peakhgrhill@porton.me> Co-authored-by: eriksyang <eriksyang@gmail.com> Co-authored-by: 邓亮 <787615673@qq.com> Co-authored-by: Anthony H <anthonyhuanggr@gmail.com> Co-authored-by: jasonw <111627243+Jasonw372@users.noreply.github.com> Co-authored-by: EriksYang <35193151+EriksYang@users.noreply.github.com> Co-authored-by: KaiyiWing <Zhang.kaiyi42@gmail.com>
This commit is contained in:
@@ -37,7 +37,8 @@
|
||||
"swr": "^2.0.4",
|
||||
"typescript": "^4.0.3",
|
||||
"use-immer": "^0.9.0",
|
||||
"use-sound": "^4.0.1"
|
||||
"use-sound": "^4.0.1",
|
||||
"xlsx": "^0.18.5"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -7,12 +7,12 @@ import styles from './index.module.css'
|
||||
import Tooltip from '@/components/Tooltip'
|
||||
import { currentChapterAtom, currentDictInfoAtom, infoPanelStateAtom, randomConfigAtom, wordDictationConfigAtom } from '@/store'
|
||||
import type { InfoPanelType } from '@/typings'
|
||||
import type { WordWithIndex } from '@/typings'
|
||||
import { recordOpenInfoPanelAction } from '@/utils'
|
||||
import { Transition } from '@headlessui/react'
|
||||
import { useAtom, useAtomValue, useSetAtom } from 'jotai'
|
||||
import { useCallback, useContext, useEffect, useMemo } from 'react'
|
||||
import { useHotkeys } from 'react-hotkeys-hook'
|
||||
import IexportWords from '~icons/icon-park-outline/excel'
|
||||
import IconCoffee from '~icons/mdi/coffee'
|
||||
import IconXiaoHongShu from '~icons/my-icons/xiaohongshu'
|
||||
import IconGithub from '~icons/simple-icons/github'
|
||||
@@ -34,11 +34,39 @@ const ResultScreen = () => {
|
||||
dispatch({ type: TypingStateActionType.TICK_TIMER, addTime: 0 })
|
||||
}, [dispatch])
|
||||
|
||||
const wrongWords = useMemo(() => {
|
||||
const wordList = state.chapterData.wrongWordIndexes.map((index) => state.chapterData.words.find((word) => word.index === index))
|
||||
const exportWords = useCallback(() => {
|
||||
const { words, userInputLogs } = state.chapterData
|
||||
const exportData = userInputLogs.map((log) => {
|
||||
const word = words[log.index]
|
||||
const wordName = word.name
|
||||
return {
|
||||
...word,
|
||||
correctCount: log.correctCount,
|
||||
wrongCount: log.wrongCount,
|
||||
wrongLetters: Object.entries(log.LetterMistakes)
|
||||
.map(([key, mistakes]) => `${wordName[Number(key)]}:${mistakes.length}`)
|
||||
.join(';'),
|
||||
}
|
||||
})
|
||||
|
||||
return wordList.filter((word) => word !== undefined) as WordWithIndex[]
|
||||
}, [state.chapterData.wrongWordIndexes, state.chapterData.words])
|
||||
import('xlsx')
|
||||
.then(({ utils, writeFileXLSX }) => {
|
||||
const ws = utils.json_to_sheet(exportData)
|
||||
const wb = utils.book_new()
|
||||
utils.book_append_sheet(wb, ws, 'Data')
|
||||
writeFileXLSX(wb, `${currentDictInfo.name}第${currentChapter + 1}章.xlsx`)
|
||||
})
|
||||
.catch(() => {
|
||||
console.log('写入 xlsx 模块导入失败')
|
||||
})
|
||||
}, [currentChapter, currentDictInfo.name, state.chapterData])
|
||||
|
||||
const wrongWords = useMemo(() => {
|
||||
return state.chapterData.userInputLogs
|
||||
.filter((log) => log.wrongCount > 0)
|
||||
.map((log) => state.chapterData.words[log.index])
|
||||
.filter((word) => word !== undefined)
|
||||
}, [state.chapterData.userInputLogs, state.chapterData.words])
|
||||
|
||||
const isLastChapter = useMemo(() => {
|
||||
return currentChapter >= currentDictInfo.chapterCount - 1
|
||||
@@ -46,9 +74,9 @@ const ResultScreen = () => {
|
||||
|
||||
const correctRate = useMemo(() => {
|
||||
const chapterLength = state.chapterData.words.length
|
||||
const correctCount = chapterLength - state.chapterData.wrongWordIndexes.length
|
||||
const correctCount = chapterLength - wrongWords.length
|
||||
return Math.floor((correctCount / chapterLength) * 100)
|
||||
}, [state.chapterData.words.length, state.chapterData.wrongWordIndexes])
|
||||
}, [state.chapterData.words.length, wrongWords.length])
|
||||
|
||||
const mistakeLevel = useMemo(() => {
|
||||
if (correctRate >= 85) {
|
||||
@@ -172,12 +200,12 @@ const ResultScreen = () => {
|
||||
))}
|
||||
</div>
|
||||
<div className="align-center flex w-full flex-row justify-start rounded-b-xl bg-indigo-200 px-4 dark:bg-indigo-400">
|
||||
<ConclusionBar mistakeLevel={mistakeLevel} mistakeCount={state.chapterData.wrongWordIndexes.length} />
|
||||
<ConclusionBar mistakeLevel={mistakeLevel} mistakeCount={wrongWords.length} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="ml-2 flex flex-col items-center justify-end gap-3.5 text-xl">
|
||||
<ShareButton />
|
||||
|
||||
<IexportWords fontSize={18} className="cursor-pointer text-gray-500" onClick={exportWords}></IexportWords>
|
||||
<IconXiaoHongShu
|
||||
fontSize={15}
|
||||
className="cursor-pointer text-gray-500 hover:text-red-500 focus:outline-none"
|
||||
|
||||
@@ -2,9 +2,10 @@ import type { WordUpdateAction } from '../InputHandler'
|
||||
import InputHandler from '../InputHandler'
|
||||
import WordSound from '../WordSound'
|
||||
import Letter from './Letter'
|
||||
import type { LetterState } from './Letter'
|
||||
import Notation from './Notation'
|
||||
import style from './index.module.css'
|
||||
import { initialWordState } from './type'
|
||||
import type { WordState } from './type'
|
||||
import { EXPLICIT_SPACE } from '@/constants'
|
||||
import useKeySounds from '@/hooks/useKeySounds'
|
||||
import { TypingContext, TypingStateActionType } from '@/pages/Typing/store'
|
||||
@@ -19,49 +20,10 @@ import {
|
||||
import type { Word } from '@/typings'
|
||||
import { getUtcStringForMixpanel, useMixPanelWordLogUploader } from '@/utils'
|
||||
import { useSaveWordRecord } from '@/utils/db'
|
||||
import type { LetterMistakes } from '@/utils/db/record'
|
||||
import { useAtomValue } from 'jotai'
|
||||
import { useCallback, useContext, useEffect, useState } from 'react'
|
||||
import { useImmer } from 'use-immer'
|
||||
|
||||
type WordState = {
|
||||
displayWord: string
|
||||
inputWord: string
|
||||
letterStates: LetterState[]
|
||||
isFinished: boolean
|
||||
// 是否出现输入错误
|
||||
hasWrong: boolean
|
||||
// 记录是否已经出现过输入错误
|
||||
hasMadeInputWrong: boolean
|
||||
// 用户输入错误的次数
|
||||
wrongCount: number
|
||||
startTime: string
|
||||
endTime: string
|
||||
inputCount: number
|
||||
correctCount: number
|
||||
letterTimeArray: number[]
|
||||
letterMistake: LetterMistakes
|
||||
// 用于随机隐藏字母功能
|
||||
randomLetterVisible: boolean[]
|
||||
}
|
||||
|
||||
const initialWordState: WordState = {
|
||||
displayWord: '',
|
||||
inputWord: '',
|
||||
letterStates: [],
|
||||
isFinished: false,
|
||||
hasWrong: false,
|
||||
hasMadeInputWrong: false,
|
||||
wrongCount: 0,
|
||||
startTime: '',
|
||||
endTime: '',
|
||||
inputCount: 0,
|
||||
correctCount: 0,
|
||||
letterTimeArray: [],
|
||||
letterMistake: {},
|
||||
randomLetterVisible: [],
|
||||
}
|
||||
|
||||
const vowelLetters = ['A', 'E', 'I', 'O', 'U']
|
||||
|
||||
export default function WordComponent({ word, onFinish }: { word: Word; onFinish: () => void }) {
|
||||
@@ -161,7 +123,6 @@ export default function WordComponent({ word, onFinish }: { word: Word; onFinish
|
||||
|
||||
const inputChar = wordState.inputWord[inputLength - 1]
|
||||
const correctChar = wordState.displayWord[inputLength - 1]
|
||||
|
||||
let isEqual = false
|
||||
if (inputChar != undefined && correctChar != undefined) {
|
||||
isEqual = isIgnoreCase ? inputChar.toLowerCase() === correctChar.toLowerCase() : inputChar === correctChar
|
||||
@@ -189,26 +150,26 @@ export default function WordComponent({ word, onFinish }: { word: Word; onFinish
|
||||
playKeySound()
|
||||
}
|
||||
|
||||
dispatch({ type: TypingStateActionType.INCREASE_CORRECT_COUNT })
|
||||
dispatch({ type: TypingStateActionType.REPORT_CORRECT_WORD })
|
||||
} else {
|
||||
// 出错时
|
||||
playBeepSound()
|
||||
|
||||
setWordState((state) => {
|
||||
state.letterStates[inputLength - 1] = 'wrong'
|
||||
state.hasWrong = true
|
||||
state.hasMadeInputWrong = true
|
||||
state.wrongCount += 1
|
||||
state.letterTimeArray = []
|
||||
|
||||
if (state.letterMistake[inputLength - 1]) {
|
||||
state.letterMistake[inputLength - 1].push(inputChar)
|
||||
} else {
|
||||
state.letterMistake[inputLength - 1] = [inputChar]
|
||||
}
|
||||
})
|
||||
|
||||
dispatch({ type: TypingStateActionType.INCREASE_WRONG_COUNT })
|
||||
dispatch({ type: TypingStateActionType.REPORT_WRONG_WORD })
|
||||
const currentState = JSON.parse(JSON.stringify(state))
|
||||
dispatch({ type: TypingStateActionType.REPORT_WRONG_WORD, payload: { letterMistake: currentState.letterMistake } })
|
||||
})
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [wordState.inputWord])
|
||||
@@ -231,10 +192,6 @@ export default function WordComponent({ word, onFinish }: { word: Word; onFinish
|
||||
|
||||
useEffect(() => {
|
||||
if (wordState.isFinished) {
|
||||
if (!wordState.hasMadeInputWrong) {
|
||||
dispatch({ type: TypingStateActionType.REPORT_CORRECT_WORD })
|
||||
}
|
||||
|
||||
dispatch({ type: TypingStateActionType.SET_IS_SAVING_RECORD, payload: true })
|
||||
|
||||
wordLogUploader({
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
import type { LetterState } from './Letter'
|
||||
import type { LetterMistakes } from '@/utils/db/record'
|
||||
|
||||
export type WordState = {
|
||||
displayWord: string
|
||||
inputWord: string
|
||||
letterStates: LetterState[]
|
||||
isFinished: boolean
|
||||
// 是否出现输入错误
|
||||
hasWrong: boolean
|
||||
// 记录是否已经出现过输入错误
|
||||
hasMadeInputWrong: boolean
|
||||
// 用户输入错误的次数
|
||||
wrongCount: number
|
||||
startTime: string
|
||||
endTime: string
|
||||
inputCount: number
|
||||
correctCount: number
|
||||
letterTimeArray: number[]
|
||||
letterMistake: LetterMistakes
|
||||
// 用于随机隐藏字母功能
|
||||
randomLetterVisible: boolean[]
|
||||
}
|
||||
|
||||
export const initialWordState: WordState = {
|
||||
displayWord: '',
|
||||
inputWord: '',
|
||||
letterStates: [],
|
||||
isFinished: false,
|
||||
hasWrong: false,
|
||||
hasMadeInputWrong: false,
|
||||
wrongCount: 0,
|
||||
startTime: '',
|
||||
endTime: '',
|
||||
inputCount: 0,
|
||||
correctCount: 0,
|
||||
letterTimeArray: [],
|
||||
letterMistake: {},
|
||||
randomLetterVisible: [],
|
||||
}
|
||||
@@ -1,38 +1,10 @@
|
||||
import type { TypingState, UserInputLog } from './type'
|
||||
import type { WordWithIndex } from '@/typings'
|
||||
import type { LetterMistakes } from '@/utils/db/record'
|
||||
import { mergeLetterMistake } from '@/utils/db/utils'
|
||||
import shuffle from '@/utils/shuffle'
|
||||
import { createContext } from 'react'
|
||||
|
||||
export type ChapterData = {
|
||||
words: WordWithIndex[]
|
||||
index: number
|
||||
// 用户输入的单词数
|
||||
wordCount: number
|
||||
correctCount: number
|
||||
wrongCount: number
|
||||
wrongWordIndexes: number[]
|
||||
// 一次打对未犯错的单词索引
|
||||
correctWordIndexes: number[]
|
||||
// 本章节用户输入的单词的 record id 列表
|
||||
wordRecordIds: number[]
|
||||
}
|
||||
export type TimerData = {
|
||||
time: number
|
||||
accuracy: number
|
||||
wpm: number
|
||||
}
|
||||
|
||||
export type TypingState = {
|
||||
chapterData: ChapterData
|
||||
timerData: TimerData
|
||||
isTyping: boolean
|
||||
isFinished: boolean
|
||||
isShowSkip: boolean
|
||||
isTransVisible: boolean
|
||||
isLoopSingleWord: boolean
|
||||
// 是否正在保存数据
|
||||
isSavingRecord: boolean
|
||||
}
|
||||
|
||||
export const initialState: TypingState = {
|
||||
chapterData: {
|
||||
words: [],
|
||||
@@ -40,9 +12,8 @@ export const initialState: TypingState = {
|
||||
wordCount: 0,
|
||||
correctCount: 0,
|
||||
wrongCount: 0,
|
||||
wrongWordIndexes: [],
|
||||
correctWordIndexes: [],
|
||||
wordRecordIds: [],
|
||||
userInputLogs: [],
|
||||
},
|
||||
timerData: {
|
||||
time: 0,
|
||||
@@ -57,6 +28,13 @@ export const initialState: TypingState = {
|
||||
isSavingRecord: false,
|
||||
}
|
||||
|
||||
export const initialUserInputLog: UserInputLog = {
|
||||
index: 0,
|
||||
correctCount: 0,
|
||||
wrongCount: 0,
|
||||
LetterMistakes: {},
|
||||
}
|
||||
|
||||
export enum TypingStateActionType {
|
||||
SETUP_CHAPTER = 'SETUP_CHAPTER',
|
||||
SET_IS_SKIP = 'SET_IS_SKIP',
|
||||
@@ -67,8 +45,7 @@ export enum TypingStateActionType {
|
||||
NEXT_WORD = 'NEXT_WORD',
|
||||
LOOP_CURRENT_WORD = 'LOOP_CURRENT_WORD',
|
||||
FINISH_CHAPTER = 'FINISH_CHAPTER',
|
||||
INCREASE_CORRECT_COUNT = 'INCREASE_CORRECT_COUNT',
|
||||
INCREASE_WRONG_COUNT = 'INCREASE_WRONG_COUNT',
|
||||
INCREASE_WRONG_WORD = 'INCREASE_WRONG_WORD',
|
||||
SKIP_WORD = 'SKIP_WORD',
|
||||
SKIP_2_WORD_INDEX = 'SKIP_2_WORD_INDEX',
|
||||
REPEAT_CHAPTER = 'REPEAT_CHAPTER',
|
||||
@@ -87,13 +64,11 @@ export type TypingStateAction =
|
||||
| { type: TypingStateActionType.SET_IS_SKIP; payload: boolean }
|
||||
| { type: TypingStateActionType.SET_IS_TYPING; payload: boolean }
|
||||
| { type: TypingStateActionType.TOGGLE_IS_TYPING }
|
||||
| { type: TypingStateActionType.REPORT_WRONG_WORD }
|
||||
| { type: TypingStateActionType.REPORT_WRONG_WORD; payload: { letterMistake: LetterMistakes } }
|
||||
| { type: TypingStateActionType.REPORT_CORRECT_WORD }
|
||||
| { type: TypingStateActionType.NEXT_WORD }
|
||||
| { type: TypingStateActionType.LOOP_CURRENT_WORD }
|
||||
| { type: TypingStateActionType.FINISH_CHAPTER }
|
||||
| { type: TypingStateActionType.INCREASE_CORRECT_COUNT }
|
||||
| { type: TypingStateActionType.INCREASE_WRONG_COUNT }
|
||||
| { type: TypingStateActionType.SKIP_WORD }
|
||||
| { type: TypingStateActionType.SKIP_2_WORD_INDEX; newIndex: number }
|
||||
| { type: TypingStateActionType.REPEAT_CHAPTER; shouldShuffle: boolean }
|
||||
@@ -111,6 +86,7 @@ export const typingReducer = (state: TypingState, action: TypingStateAction) =>
|
||||
switch (action.type) {
|
||||
case TypingStateActionType.SETUP_CHAPTER:
|
||||
state.chapterData.words = action.payload.shouldShuffle ? shuffle(action.payload.words) : action.payload.words
|
||||
state.chapterData.userInputLogs = state.chapterData.words.map((_, index) => ({ ...structuredClone(initialUserInputLog), index }))
|
||||
break
|
||||
case TypingStateActionType.SET_IS_SKIP:
|
||||
state.isShowSkip = action.payload
|
||||
@@ -122,25 +98,20 @@ export const typingReducer = (state: TypingState, action: TypingStateAction) =>
|
||||
case TypingStateActionType.TOGGLE_IS_TYPING:
|
||||
state.isTyping = !state.isTyping
|
||||
break
|
||||
case TypingStateActionType.REPORT_WRONG_WORD: {
|
||||
const wordIndex = state.chapterData.words[state.chapterData.index].index
|
||||
case TypingStateActionType.REPORT_CORRECT_WORD: {
|
||||
state.chapterData.correctCount += 1
|
||||
|
||||
const prevIndex = state.chapterData.wrongWordIndexes.indexOf(wordIndex)
|
||||
if (prevIndex === -1) {
|
||||
state.chapterData.wrongWordIndexes.push(wordIndex)
|
||||
}
|
||||
const wordLog = state.chapterData.userInputLogs[state.chapterData.index]
|
||||
wordLog.correctCount += 1
|
||||
break
|
||||
}
|
||||
case TypingStateActionType.REPORT_CORRECT_WORD: {
|
||||
const wordIndex = state.chapterData.words[state.chapterData.index].index
|
||||
case TypingStateActionType.REPORT_WRONG_WORD: {
|
||||
state.chapterData.wrongCount += 1
|
||||
|
||||
const prevWrongIndex = state.chapterData.wrongWordIndexes.indexOf(wordIndex)
|
||||
const prevCorrectIndex = state.chapterData.correctWordIndexes.indexOf(wordIndex)
|
||||
|
||||
// 如果之前没有被记录过 出现错误或者正确
|
||||
if (prevCorrectIndex === -1 && prevWrongIndex === -1) {
|
||||
state.chapterData.correctWordIndexes.push(wordIndex)
|
||||
}
|
||||
const letterMistake = action.payload.letterMistake
|
||||
const wordLog = state.chapterData.userInputLogs[state.chapterData.index]
|
||||
wordLog.wrongCount += 1
|
||||
wordLog.LetterMistakes = mergeLetterMistake(wordLog.LetterMistakes, letterMistake)
|
||||
break
|
||||
}
|
||||
case TypingStateActionType.NEXT_WORD:
|
||||
@@ -158,12 +129,6 @@ export const typingReducer = (state: TypingState, action: TypingStateAction) =>
|
||||
state.isFinished = true
|
||||
state.isShowSkip = false
|
||||
break
|
||||
case TypingStateActionType.INCREASE_CORRECT_COUNT:
|
||||
state.chapterData.correctCount += 1
|
||||
break
|
||||
case TypingStateActionType.INCREASE_WRONG_COUNT:
|
||||
state.chapterData.wrongCount += 1
|
||||
break
|
||||
case TypingStateActionType.SKIP_WORD: {
|
||||
const newIndex = state.chapterData.index + 1
|
||||
if (newIndex >= state.chapterData.words.length) {
|
||||
@@ -186,14 +151,15 @@ export const typingReducer = (state: TypingState, action: TypingStateAction) =>
|
||||
}
|
||||
case TypingStateActionType.REPEAT_CHAPTER: {
|
||||
const newState = structuredClone(initialState)
|
||||
newState.chapterData.userInputLogs = state.chapterData.words.map((_, index) => ({ ...structuredClone(initialUserInputLog), index }))
|
||||
newState.isTyping = true
|
||||
newState.chapterData.words = action.shouldShuffle ? shuffle(state.chapterData.words) : state.chapterData.words
|
||||
newState.isTransVisible = state.isTransVisible
|
||||
return newState
|
||||
}
|
||||
|
||||
case TypingStateActionType.NEXT_CHAPTER: {
|
||||
const newState = structuredClone(initialState)
|
||||
newState.chapterData.userInputLogs = state.chapterData.words.map((_, index) => ({ ...structuredClone(initialUserInputLog), index }))
|
||||
newState.isTyping = true
|
||||
newState.isTransVisible = state.isTransVisible
|
||||
return newState
|
||||
|
||||
54
src/pages/Typing/store/type.ts
Normal file
54
src/pages/Typing/store/type.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
import type { WordWithIndex } from '@/typings'
|
||||
import type { LetterMistakes } from '@/utils/db/record'
|
||||
|
||||
export type ChapterData = {
|
||||
// warning: 因为有章节内随机的存在,所有记录 index 的场景都应该使用 WordWithIndex.index
|
||||
words: WordWithIndex[]
|
||||
// chapter index
|
||||
index: number
|
||||
// 输入的单词数
|
||||
wordCount: number
|
||||
// 输入正确的单词数
|
||||
correctCount: number
|
||||
// 输入错误的单词数
|
||||
wrongCount: number
|
||||
// 每个单词的输入记录
|
||||
userInputLogs: UserInputLog[]
|
||||
// 本章节用户输入的单词的 record id 列表
|
||||
wordRecordIds: number[]
|
||||
}
|
||||
|
||||
export type UserInputLog = {
|
||||
// the index in ChapterData.words, not the index in WordWithIndex
|
||||
index: number
|
||||
correctCount: number
|
||||
wrongCount: number
|
||||
LetterMistakes: LetterMistakes
|
||||
}
|
||||
|
||||
export type TimerData = {
|
||||
time: number
|
||||
accuracy: number
|
||||
wpm: number
|
||||
}
|
||||
|
||||
export type WrongWordData = {
|
||||
name: string
|
||||
wrongCount: number
|
||||
wrongLetters: Array<{
|
||||
letter: string
|
||||
count: number
|
||||
}>
|
||||
}
|
||||
|
||||
export type TypingState = {
|
||||
chapterData: ChapterData
|
||||
timerData: TimerData
|
||||
isTyping: boolean
|
||||
isFinished: boolean
|
||||
isShowSkip: boolean
|
||||
isTransVisible: boolean
|
||||
isLoopSingleWord: boolean
|
||||
// 是否正在保存数据
|
||||
isSavingRecord: boolean
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { IChapterRecord, IWordRecord, LetterMistakes } from './record'
|
||||
import { ChapterRecord, WordRecord } from './record'
|
||||
import type { TypingState } from '@/pages/Typing/store'
|
||||
import { TypingContext, TypingStateActionType } from '@/pages/Typing/store'
|
||||
import type { TypingState } from '@/pages/Typing/store/type'
|
||||
import { currentChapterAtom, currentDictIdAtom } from '@/store'
|
||||
import type { Table } from 'dexie'
|
||||
import Dexie from 'dexie'
|
||||
@@ -33,9 +33,10 @@ export function useSaveChapterRecord() {
|
||||
const saveChapterRecord = useCallback(
|
||||
(typingState: TypingState) => {
|
||||
const {
|
||||
chapterData: { correctCount, wrongCount, wordCount, correctWordIndexes, words, wordRecordIds },
|
||||
chapterData: { correctCount, wrongCount, userInputLogs, wordCount, words, wordRecordIds },
|
||||
timerData: { time },
|
||||
} = typingState
|
||||
const correctWordIndexes = userInputLogs.filter((log) => log.correctCount > 0 && log.wrongCount === 0).map((log) => log.index)
|
||||
|
||||
const chapterRecord = new ChapterRecord(
|
||||
dictID,
|
||||
|
||||
17
src/utils/db/utils.ts
Normal file
17
src/utils/db/utils.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import type { LetterMistakes } from './record'
|
||||
|
||||
export function mergeLetterMistake(letterMistake1: LetterMistakes, letterMistake2: LetterMistakes): LetterMistakes {
|
||||
const result: LetterMistakes = {}
|
||||
|
||||
for (const mistakes of [letterMistake1, letterMistake2]) {
|
||||
for (const key in mistakes) {
|
||||
if (result[key]) {
|
||||
result[key].push(...mistakes[key])
|
||||
} else {
|
||||
result[key] = [...mistakes[key]]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
100
yarn.lock
100
yarn.lock
@@ -1125,17 +1125,17 @@
|
||||
|
||||
"@esbuild/android-arm64@0.17.19":
|
||||
version "0.17.19"
|
||||
resolved "https://registry.npmmirror.com/@esbuild/android-arm64/-/android-arm64-0.17.19.tgz#bafb75234a5d3d1b690e7c2956a599345e84a2fd"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.17.19.tgz#bafb75234a5d3d1b690e7c2956a599345e84a2fd"
|
||||
integrity sha512-KBMWvEZooR7+kzY0BtbTQn0OAYY7CsiydT63pVEaPtVYF0hXbUaOyZog37DKxK7NF3XacBJOpYT4adIJh+avxA==
|
||||
|
||||
"@esbuild/android-arm@0.17.19":
|
||||
version "0.17.19"
|
||||
resolved "https://registry.npmmirror.com/@esbuild/android-arm/-/android-arm-0.17.19.tgz#5898f7832c2298bc7d0ab53701c57beb74d78b4d"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.17.19.tgz#5898f7832c2298bc7d0ab53701c57beb74d78b4d"
|
||||
integrity sha512-rIKddzqhmav7MSmoFCmDIb6e2W57geRsM94gV2l38fzhXMwq7hZoClug9USI2pFRGL06f4IOPHHpFNOkWieR8A==
|
||||
|
||||
"@esbuild/android-x64@0.17.19":
|
||||
version "0.17.19"
|
||||
resolved "https://registry.npmmirror.com/@esbuild/android-x64/-/android-x64-0.17.19.tgz#658368ef92067866d95fb268719f98f363d13ae1"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.17.19.tgz#658368ef92067866d95fb268719f98f363d13ae1"
|
||||
integrity sha512-uUTTc4xGNDT7YSArp/zbtmbhO0uEEK9/ETW29Wk1thYUJBz3IVnvgEiEwEa9IeLyvnpKrWK64Utw2bgUmDveww==
|
||||
|
||||
"@esbuild/darwin-arm64@0.17.19":
|
||||
@@ -1145,92 +1145,92 @@
|
||||
|
||||
"@esbuild/darwin-x64@0.17.19":
|
||||
version "0.17.19"
|
||||
resolved "https://registry.npmmirror.com/@esbuild/darwin-x64/-/darwin-x64-0.17.19.tgz#7751d236dfe6ce136cce343dce69f52d76b7f6cb"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.17.19.tgz#7751d236dfe6ce136cce343dce69f52d76b7f6cb"
|
||||
integrity sha512-IJM4JJsLhRYr9xdtLytPLSH9k/oxR3boaUIYiHkAawtwNOXKE8KoU8tMvryogdcT8AU+Bflmh81Xn6Q0vTZbQw==
|
||||
|
||||
"@esbuild/freebsd-arm64@0.17.19":
|
||||
version "0.17.19"
|
||||
resolved "https://registry.npmmirror.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.17.19.tgz#cacd171665dd1d500f45c167d50c6b7e539d5fd2"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.17.19.tgz#cacd171665dd1d500f45c167d50c6b7e539d5fd2"
|
||||
integrity sha512-pBwbc7DufluUeGdjSU5Si+P3SoMF5DQ/F/UmTSb8HXO80ZEAJmrykPyzo1IfNbAoaqw48YRpv8shwd1NoI0jcQ==
|
||||
|
||||
"@esbuild/freebsd-x64@0.17.19":
|
||||
version "0.17.19"
|
||||
resolved "https://registry.npmmirror.com/@esbuild/freebsd-x64/-/freebsd-x64-0.17.19.tgz#0769456eee2a08b8d925d7c00b79e861cb3162e4"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.17.19.tgz#0769456eee2a08b8d925d7c00b79e861cb3162e4"
|
||||
integrity sha512-4lu+n8Wk0XlajEhbEffdy2xy53dpR06SlzvhGByyg36qJw6Kpfk7cp45DR/62aPH9mtJRmIyrXAS5UWBrJT6TQ==
|
||||
|
||||
"@esbuild/linux-arm64@0.17.19":
|
||||
version "0.17.19"
|
||||
resolved "https://registry.npmmirror.com/@esbuild/linux-arm64/-/linux-arm64-0.17.19.tgz#38e162ecb723862c6be1c27d6389f48960b68edb"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.17.19.tgz#38e162ecb723862c6be1c27d6389f48960b68edb"
|
||||
integrity sha512-ct1Tg3WGwd3P+oZYqic+YZF4snNl2bsnMKRkb3ozHmnM0dGWuxcPTTntAF6bOP0Sp4x0PjSF+4uHQ1xvxfRKqg==
|
||||
|
||||
"@esbuild/linux-arm@0.17.19":
|
||||
version "0.17.19"
|
||||
resolved "https://registry.npmmirror.com/@esbuild/linux-arm/-/linux-arm-0.17.19.tgz#1a2cd399c50040184a805174a6d89097d9d1559a"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.17.19.tgz#1a2cd399c50040184a805174a6d89097d9d1559a"
|
||||
integrity sha512-cdmT3KxjlOQ/gZ2cjfrQOtmhG4HJs6hhvm3mWSRDPtZ/lP5oe8FWceS10JaSJC13GBd4eH/haHnqf7hhGNLerA==
|
||||
|
||||
"@esbuild/linux-ia32@0.17.19":
|
||||
version "0.17.19"
|
||||
resolved "https://registry.npmmirror.com/@esbuild/linux-ia32/-/linux-ia32-0.17.19.tgz#e28c25266b036ce1cabca3c30155222841dc035a"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.17.19.tgz#e28c25266b036ce1cabca3c30155222841dc035a"
|
||||
integrity sha512-w4IRhSy1VbsNxHRQpeGCHEmibqdTUx61Vc38APcsRbuVgK0OPEnQ0YD39Brymn96mOx48Y2laBQGqgZ0j9w6SQ==
|
||||
|
||||
"@esbuild/linux-loong64@0.17.19":
|
||||
version "0.17.19"
|
||||
resolved "https://registry.npmmirror.com/@esbuild/linux-loong64/-/linux-loong64-0.17.19.tgz#0f887b8bb3f90658d1a0117283e55dbd4c9dcf72"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.17.19.tgz#0f887b8bb3f90658d1a0117283e55dbd4c9dcf72"
|
||||
integrity sha512-2iAngUbBPMq439a+z//gE+9WBldoMp1s5GWsUSgqHLzLJ9WoZLZhpwWuym0u0u/4XmZ3gpHmzV84PonE+9IIdQ==
|
||||
|
||||
"@esbuild/linux-mips64el@0.17.19":
|
||||
version "0.17.19"
|
||||
resolved "https://registry.npmmirror.com/@esbuild/linux-mips64el/-/linux-mips64el-0.17.19.tgz#f5d2a0b8047ea9a5d9f592a178ea054053a70289"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.17.19.tgz#f5d2a0b8047ea9a5d9f592a178ea054053a70289"
|
||||
integrity sha512-LKJltc4LVdMKHsrFe4MGNPp0hqDFA1Wpt3jE1gEyM3nKUvOiO//9PheZZHfYRfYl6AwdTH4aTcXSqBerX0ml4A==
|
||||
|
||||
"@esbuild/linux-ppc64@0.17.19":
|
||||
version "0.17.19"
|
||||
resolved "https://registry.npmmirror.com/@esbuild/linux-ppc64/-/linux-ppc64-0.17.19.tgz#876590e3acbd9fa7f57a2c7d86f83717dbbac8c7"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.17.19.tgz#876590e3acbd9fa7f57a2c7d86f83717dbbac8c7"
|
||||
integrity sha512-/c/DGybs95WXNS8y3Ti/ytqETiW7EU44MEKuCAcpPto3YjQbyK3IQVKfF6nbghD7EcLUGl0NbiL5Rt5DMhn5tg==
|
||||
|
||||
"@esbuild/linux-riscv64@0.17.19":
|
||||
version "0.17.19"
|
||||
resolved "https://registry.npmmirror.com/@esbuild/linux-riscv64/-/linux-riscv64-0.17.19.tgz#7f49373df463cd9f41dc34f9b2262d771688bf09"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.17.19.tgz#7f49373df463cd9f41dc34f9b2262d771688bf09"
|
||||
integrity sha512-FC3nUAWhvFoutlhAkgHf8f5HwFWUL6bYdvLc/TTuxKlvLi3+pPzdZiFKSWz/PF30TB1K19SuCxDTI5KcqASJqA==
|
||||
|
||||
"@esbuild/linux-s390x@0.17.19":
|
||||
version "0.17.19"
|
||||
resolved "https://registry.npmmirror.com/@esbuild/linux-s390x/-/linux-s390x-0.17.19.tgz#e2afd1afcaf63afe2c7d9ceacd28ec57c77f8829"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.17.19.tgz#e2afd1afcaf63afe2c7d9ceacd28ec57c77f8829"
|
||||
integrity sha512-IbFsFbxMWLuKEbH+7sTkKzL6NJmG2vRyy6K7JJo55w+8xDk7RElYn6xvXtDW8HCfoKBFK69f3pgBJSUSQPr+4Q==
|
||||
|
||||
"@esbuild/linux-x64@0.17.19":
|
||||
version "0.17.19"
|
||||
resolved "https://registry.npmmirror.com/@esbuild/linux-x64/-/linux-x64-0.17.19.tgz#8a0e9738b1635f0c53389e515ae83826dec22aa4"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.17.19.tgz#8a0e9738b1635f0c53389e515ae83826dec22aa4"
|
||||
integrity sha512-68ngA9lg2H6zkZcyp22tsVt38mlhWde8l3eJLWkyLrp4HwMUr3c1s/M2t7+kHIhvMjglIBrFpncX1SzMckomGw==
|
||||
|
||||
"@esbuild/netbsd-x64@0.17.19":
|
||||
version "0.17.19"
|
||||
resolved "https://registry.npmmirror.com/@esbuild/netbsd-x64/-/netbsd-x64-0.17.19.tgz#c29fb2453c6b7ddef9a35e2c18b37bda1ae5c462"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.17.19.tgz#c29fb2453c6b7ddef9a35e2c18b37bda1ae5c462"
|
||||
integrity sha512-CwFq42rXCR8TYIjIfpXCbRX0rp1jo6cPIUPSaWwzbVI4aOfX96OXY8M6KNmtPcg7QjYeDmN+DD0Wp3LaBOLf4Q==
|
||||
|
||||
"@esbuild/openbsd-x64@0.17.19":
|
||||
version "0.17.19"
|
||||
resolved "https://registry.npmmirror.com/@esbuild/openbsd-x64/-/openbsd-x64-0.17.19.tgz#95e75a391403cb10297280d524d66ce04c920691"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.17.19.tgz#95e75a391403cb10297280d524d66ce04c920691"
|
||||
integrity sha512-cnq5brJYrSZ2CF6c35eCmviIN3k3RczmHz8eYaVlNasVqsNY+JKohZU5MKmaOI+KkllCdzOKKdPs762VCPC20g==
|
||||
|
||||
"@esbuild/sunos-x64@0.17.19":
|
||||
version "0.17.19"
|
||||
resolved "https://registry.npmmirror.com/@esbuild/sunos-x64/-/sunos-x64-0.17.19.tgz#722eaf057b83c2575937d3ffe5aeb16540da7273"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.17.19.tgz#722eaf057b83c2575937d3ffe5aeb16540da7273"
|
||||
integrity sha512-vCRT7yP3zX+bKWFeP/zdS6SqdWB8OIpaRq/mbXQxTGHnIxspRtigpkUcDMlSCOejlHowLqII7K2JKevwyRP2rg==
|
||||
|
||||
"@esbuild/win32-arm64@0.17.19":
|
||||
version "0.17.19"
|
||||
resolved "https://registry.npmmirror.com/@esbuild/win32-arm64/-/win32-arm64-0.17.19.tgz#9aa9dc074399288bdcdd283443e9aeb6b9552b6f"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.17.19.tgz#9aa9dc074399288bdcdd283443e9aeb6b9552b6f"
|
||||
integrity sha512-yYx+8jwowUstVdorcMdNlzklLYhPxjniHWFKgRqH7IFlUEa0Umu3KuYplf1HUZZ422e3NU9F4LGb+4O0Kdcaag==
|
||||
|
||||
"@esbuild/win32-ia32@0.17.19":
|
||||
version "0.17.19"
|
||||
resolved "https://registry.npmmirror.com/@esbuild/win32-ia32/-/win32-ia32-0.17.19.tgz#95ad43c62ad62485e210f6299c7b2571e48d2b03"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.17.19.tgz#95ad43c62ad62485e210f6299c7b2571e48d2b03"
|
||||
integrity sha512-eggDKanJszUtCdlVs0RB+h35wNlb5v4TWEkq4vZcmVt5u/HiDZrTXe2bWFQUez3RgNHwx/x4sk5++4NSSicKkw==
|
||||
|
||||
"@esbuild/win32-x64@0.17.19":
|
||||
version "0.17.19"
|
||||
resolved "https://registry.npmmirror.com/@esbuild/win32-x64/-/win32-x64-0.17.19.tgz#8cfaf2ff603e9aabb910e9c0558c26cf32744061"
|
||||
resolved "https://registry.npmmirror.com/@esbuild/win32-x64/-/win32-x64-0.17.19.tgz"
|
||||
integrity sha512-lAhycmKnVOuRYNtRtatQR1LPQf2oYCkRGkSFnseDAKPl8lu5SOsK/e1sXe5a0Pc5kHIHe6P2I/ilntNv2xf3cA==
|
||||
|
||||
"@eslint-community/eslint-utils@^4.2.0":
|
||||
@@ -2030,6 +2030,11 @@ acorn@^8.8.0, acorn@^8.8.2:
|
||||
resolved "https://registry.npmmirror.com/acorn/-/acorn-8.8.2.tgz"
|
||||
integrity sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==
|
||||
|
||||
adler-32@~1.3.0:
|
||||
version "1.3.1"
|
||||
resolved "https://registry.npmjs.org/adler-32/-/adler-32-1.3.1.tgz"
|
||||
integrity sha512-ynZ4w/nUUv5rrsR8UUGoe1VC9hZj6V5hU9Qw1HlMDJGEJw5S7TfTErWTjMys6M7vr0YWcPqs3qAr4ss0nDfP+A==
|
||||
|
||||
aggregate-error@^3.0.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.npmmirror.com/aggregate-error/-/aggregate-error-3.1.0.tgz"
|
||||
@@ -2383,6 +2388,14 @@ canvas-confetti@^1.6.0:
|
||||
resolved "https://registry.npmmirror.com/canvas-confetti/-/canvas-confetti-1.6.0.tgz"
|
||||
integrity sha512-ej+w/m8Jzpv9Z7W7uJZer14Ke8P2ogsjg4ZMGIuq4iqUOqY2Jq8BNW42iGmNfRwREaaEfFIczLuZZiEVSYNHAA==
|
||||
|
||||
cfb@~1.2.1:
|
||||
version "1.2.2"
|
||||
resolved "https://registry.npmmirror.com/cfb/-/cfb-1.2.2.tgz#94e687628c700e5155436dac05f74e08df23bc44"
|
||||
integrity sha512-KfdUZsSOw19/ObEWasvBP/Ac4reZvAGauZhs6S/gqNhXhI7cKwvlH7ulj+dOEYnca4bm4SGo8C1bTAQvnTjgQA==
|
||||
dependencies:
|
||||
adler-32 "~1.3.0"
|
||||
crc-32 "~1.2.0"
|
||||
|
||||
chalk@5.2.0:
|
||||
version "5.2.0"
|
||||
resolved "https://registry.npmmirror.com/chalk/-/chalk-5.2.0.tgz"
|
||||
@@ -2481,6 +2494,11 @@ cliui@^8.0.1:
|
||||
strip-ansi "^6.0.1"
|
||||
wrap-ansi "^7.0.0"
|
||||
|
||||
codepage@~1.15.0:
|
||||
version "1.15.0"
|
||||
resolved "https://registry.npmjs.org/codepage/-/codepage-1.15.0.tgz"
|
||||
integrity sha512-3g6NUTPd/YtuuGrhMnOMRjFc+LJw/bnMp3+0r/Wcz3IXUuCosKRJvMphm5+Q+bvTVGcJJuRvVLuYba+WojaFaA==
|
||||
|
||||
color-convert@^1.9.0:
|
||||
version "1.9.3"
|
||||
resolved "https://registry.npmmirror.com/color-convert/-/color-convert-1.9.3.tgz"
|
||||
@@ -2596,6 +2614,11 @@ cosmiconfig@^8.1.3:
|
||||
parse-json "^5.0.0"
|
||||
path-type "^4.0.0"
|
||||
|
||||
crc-32@~1.2.0, crc-32@~1.2.1:
|
||||
version "1.2.2"
|
||||
resolved "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz"
|
||||
integrity sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==
|
||||
|
||||
cross-env@^7.0.3:
|
||||
version "7.0.3"
|
||||
resolved "https://registry.npmmirror.com/cross-env/-/cross-env-7.0.3.tgz"
|
||||
@@ -3327,6 +3350,11 @@ for-each@^0.3.3:
|
||||
dependencies:
|
||||
is-callable "^1.1.3"
|
||||
|
||||
frac@~1.1.2:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.npmjs.org/frac/-/frac-1.1.2.tgz"
|
||||
integrity sha512-w/XBfkibaTl3YDqASwfDUqkna4Z2p9cFSr1aHDt0WoMTECnRfBOv2WArlZILlqgWlmdIlALXGpM2AOhEk5W3IA==
|
||||
|
||||
fraction.js@^4.2.0:
|
||||
version "4.2.0"
|
||||
resolved "https://registry.npmmirror.com/fraction.js/-/fraction.js-4.2.0.tgz"
|
||||
@@ -5192,6 +5220,13 @@ source-map@^0.7.3, source-map@^0.7.4:
|
||||
resolved "https://registry.npmmirror.com/source-map/-/source-map-0.7.4.tgz"
|
||||
integrity sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==
|
||||
|
||||
ssf@~0.11.2:
|
||||
version "0.11.2"
|
||||
resolved "https://registry.npmjs.org/ssf/-/ssf-0.11.2.tgz"
|
||||
integrity sha512-+idbmIXoYET47hH+d7dfm2epdOMUDjqcB4648sTZ+t2JwoyBFL/insLfB/racrDmsKB3diwsDA696pZMieAC5g==
|
||||
dependencies:
|
||||
frac "~1.1.2"
|
||||
|
||||
stop-iteration-iterator@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.npmmirror.com/stop-iteration-iterator/-/stop-iteration-iterator-1.0.0.tgz"
|
||||
@@ -5705,11 +5740,21 @@ which@^2.0.1:
|
||||
dependencies:
|
||||
isexe "^2.0.0"
|
||||
|
||||
wmf@~1.0.1:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.npmjs.org/wmf/-/wmf-1.0.2.tgz"
|
||||
integrity sha512-/p9K7bEh0Dj6WbXg4JG0xvLQmIadrner1bi45VMJTfnbVHsc7yIajZyoSoK60/dtVBs12Fm6WkUI5/3WAVsNMw==
|
||||
|
||||
word-wrap@^1.2.3:
|
||||
version "1.2.3"
|
||||
resolved "https://registry.npmmirror.com/word-wrap/-/word-wrap-1.2.3.tgz"
|
||||
integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==
|
||||
|
||||
word@~0.3.0:
|
||||
version "0.3.0"
|
||||
resolved "https://registry.npmjs.org/word/-/word-0.3.0.tgz"
|
||||
integrity sha512-OELeY0Q61OXpdUfTp+oweA/vtLVg5VDOXh+3he3PNzLGG/y0oylSOC1xRVj0+l4vQ3tj/bB1HVHv1ocXkQceFA==
|
||||
|
||||
wrap-ansi@^6.2.0:
|
||||
version "6.2.0"
|
||||
resolved "https://registry.npmmirror.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz"
|
||||
@@ -5733,6 +5778,19 @@ wrappy@1:
|
||||
resolved "https://registry.npmmirror.com/wrappy/-/wrappy-1.0.2.tgz"
|
||||
integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==
|
||||
|
||||
xlsx@^0.18.5:
|
||||
version "0.18.5"
|
||||
resolved "https://registry.npmjs.org/xlsx/-/xlsx-0.18.5.tgz"
|
||||
integrity sha512-dmg3LCjBPHZnQp5/F/+nnTa+miPJxUXB6vtk42YjBBKayDNagxGEeIdWApkYPOf3Z3pm3k62Knjzp7lMeTEtFQ==
|
||||
dependencies:
|
||||
adler-32 "~1.3.0"
|
||||
cfb "~1.2.1"
|
||||
codepage "~1.15.0"
|
||||
crc-32 "~1.2.1"
|
||||
ssf "~0.11.2"
|
||||
wmf "~1.0.1"
|
||||
word "~0.3.0"
|
||||
|
||||
xtend@^4.0.2:
|
||||
version "4.0.2"
|
||||
resolved "https://registry.npmmirror.com/xtend/-/xtend-4.0.2.tgz"
|
||||
|
||||
Reference in New Issue
Block a user