mirror of
https://github.com/RealKai42/qwerty-learner.git
synced 2026-04-05 06:19:08 +08:00
feat: update info panel (#367)
This commit is contained in:
1
.vscode/settings.json
vendored
1
.vscode/settings.json
vendored
@@ -21,6 +21,7 @@
|
||||
"ukphone",
|
||||
"usphone",
|
||||
"vercel",
|
||||
"Weixin",
|
||||
"wordlist"
|
||||
]
|
||||
}
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 109 KiB After Width: | Height: | Size: 57 KiB |
BIN
src/assets/weChat-group.png
Normal file
BIN
src/assets/weChat-group.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 81 KiB |
BIN
src/assets/weChat.png
Normal file
BIN
src/assets/weChat.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 52 KiB |
@@ -1,68 +1,125 @@
|
||||
import React, { useState } from 'react'
|
||||
import React, { useCallback } from 'react'
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||
import InfoPanel from '@/components/InfoPanel'
|
||||
import alipay from '@/assets/alipay.png'
|
||||
import weChat from '@/assets/weChat.png'
|
||||
import weChatGroup from '@/assets/weChat-group.png'
|
||||
import vscLogo from '@/assets/vsc-logo.svg'
|
||||
import { recordOpenInfoPanelAction } from '@/utils'
|
||||
import { InfoPanelType } from '@/typings'
|
||||
import { useAtom } from 'jotai'
|
||||
import { infoPanelStateAtom } from '@/store'
|
||||
|
||||
const Footer: React.FC = () => {
|
||||
const [showModal, setShowModal] = useState<boolean>(false)
|
||||
const [content, setShowContent] = useState('donate')
|
||||
const [infoPanelState, setInfoPanelState] = useAtom(infoPanelStateAtom)
|
||||
|
||||
const icon = content === 'donate' ? 'coffee' : 'terminal'
|
||||
const color = content === 'donate' ? 'bg-yellow-100' : 'bg-blue-300'
|
||||
const btnColor = content === 'donate' ? 'bg-yellow-300' : 'bg-blue-400'
|
||||
const iconColor = content === 'donate' ? 'text-yellow-500' : 'text-blue-600'
|
||||
const handleOpenInfoPanel = useCallback(
|
||||
(modalType: InfoPanelType) => {
|
||||
recordOpenInfoPanelAction(modalType, 'footer')
|
||||
setInfoPanelState((state) => {
|
||||
return {
|
||||
...state,
|
||||
[modalType]: true,
|
||||
}
|
||||
})
|
||||
},
|
||||
[setInfoPanelState],
|
||||
)
|
||||
|
||||
const handleCloseInfoPanel = useCallback(
|
||||
(modalType: InfoPanelType) => {
|
||||
setInfoPanelState((state) => {
|
||||
return {
|
||||
...state,
|
||||
[modalType]: false,
|
||||
}
|
||||
})
|
||||
},
|
||||
[setInfoPanelState],
|
||||
)
|
||||
|
||||
return (
|
||||
<>
|
||||
{showModal && (
|
||||
{infoPanelState.donate && (
|
||||
<InfoPanel
|
||||
state={showModal}
|
||||
icon={icon}
|
||||
color={color}
|
||||
btnColor={btnColor}
|
||||
iconColor={iconColor}
|
||||
buttonOnclick={() => setShowModal(false)}
|
||||
openState={infoPanelState.donate}
|
||||
title="Buy us a coffee"
|
||||
icon="coffee"
|
||||
btnColor="bg-yellow-300"
|
||||
iconColor="text-yellow-500"
|
||||
iconBackgroundColor="bg-yellow-100"
|
||||
onClose={() => handleCloseInfoPanel('donate')}
|
||||
>
|
||||
{content === 'donate' ? (
|
||||
<>
|
||||
<h3 className="text-lg font-medium leading-6 text-gray-900 dark:text-white dark:text-opacity-70" id="modal-headline">
|
||||
Buy me a coffee
|
||||
</h3>
|
||||
<div className="mt-2 ">
|
||||
<p className="text-sm text-gray-500 dark:text-gray-400">
|
||||
非常感谢大家使用 Qwerty Learner,
|
||||
目前该网站使用业余时间在维护,我们希望在未来购买独立的域名(目前使用vercel部署),并购买服务器以方便国内用户访问与云同步存储数据。
|
||||
</p>
|
||||
<br />
|
||||
<p className="text-sm text-gray-700 dark:text-gray-200">如果您喜欢我们软件,非常感谢您对我们未来的支持!</p>
|
||||
<br />
|
||||
<img className="ml-1 w-2/6 " src={alipay} alt="alipay" />
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<h3 className="text-lg font-medium leading-6 text-gray-900 dark:text-white dark:text-opacity-70" id="modal-headline">
|
||||
VSCode 摸🐟插件
|
||||
</h3>
|
||||
<div className="mt-2 ">
|
||||
<p className="text-sm text-gray-500 dark:text-gray-400">
|
||||
我们根据大家的建议开发了 VSCode 插件,支持一键启动,随时开始背单词。
|
||||
可以在任意文件中一键开启,开启后单词显示在状态栏中,且插件会拦截用户对文档的输入,不会影响到原始文档。
|
||||
</p>
|
||||
<br /> <br />
|
||||
<a className="mr-5 underline dark:text-gray-300" href="https://github.com/Kaiyiwing/qwerty-learner-vscode">
|
||||
GitHub 项目
|
||||
</a>
|
||||
<a className="underline dark:text-gray-300" href="https://marketplace.visualstudio.com/items?itemName=Kaiyi.qwerty-learner">
|
||||
VSCode 插件链接
|
||||
</a>
|
||||
<br />
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
<p className="text-sm text-gray-500 dark:text-gray-300">
|
||||
非常感谢大家使用 Qwerty Learner, 目前该网站使用业余时间在维护, 为了保证网站能够持续地提供给大家高质量的服务,我们需要您的帮助!
|
||||
<br />
|
||||
您的捐款将有助于我们支付网站的运营成本,改进网站的功能和设计,并提高用户体验。
|
||||
<br />
|
||||
</p>
|
||||
<br />
|
||||
<p className="text-sm text-gray-700 dark:text-gray-200">
|
||||
我们相信,共同的努力可以让 Qwerty Learner 成为更好的学习平台,也相信您的支持将给予我们持续前进的动力。 感谢您的支持!
|
||||
</p>
|
||||
<br />
|
||||
<div className="flex w-full justify-start">
|
||||
<img src={alipay} alt="alipay" className="mx-4 w-1/3" />
|
||||
<img src={weChat} alt="weChat" className="mx-4 w-1/3" />
|
||||
</div>
|
||||
</InfoPanel>
|
||||
)}
|
||||
{infoPanelState.vsc && (
|
||||
<InfoPanel
|
||||
openState={infoPanelState.vsc}
|
||||
title="VSCode 摸🐟插件"
|
||||
icon="terminal"
|
||||
btnColor="bg-blue-400"
|
||||
iconColor="text-blue-600"
|
||||
iconBackgroundColor="bg-blue-300"
|
||||
onClose={() => handleCloseInfoPanel('vsc')}
|
||||
>
|
||||
<p className="text-sm text-gray-500 dark:text-gray-400">
|
||||
我们根据大家的建议开发了 VSCode 插件,支持一键启动,随时开始背单词。
|
||||
可以在任意文件中一键开启,开启后单词显示在状态栏中,且插件会拦截用户对文档的输入,不会影响到原始文档。
|
||||
</p>
|
||||
<br /> <br />
|
||||
<a className="mr-5 underline dark:text-gray-300" href="https://github.com/Kaiyiwing/qwerty-learner-vscode">
|
||||
GitHub 项目
|
||||
</a>
|
||||
<a className="underline dark:text-gray-300" href="https://marketplace.visualstudio.com/items?itemName=Kaiyi.qwerty-learner">
|
||||
VSCode 插件链接
|
||||
</a>
|
||||
<br />
|
||||
</InfoPanel>
|
||||
)}
|
||||
{infoPanelState.community && (
|
||||
<InfoPanel
|
||||
openState={infoPanelState.community}
|
||||
title="用户反馈社群"
|
||||
icon={['fab', 'weixin']}
|
||||
btnColor="bg-cyan-400"
|
||||
iconColor="text-cyan-600"
|
||||
iconBackgroundColor="bg-cyan-300"
|
||||
onClose={() => handleCloseInfoPanel('community')}
|
||||
>
|
||||
<p className="text-sm text-gray-500 dark:text-gray-400">
|
||||
Qwerty Learner 是一个开源项目,旨在为用户提供高质量、可靠的打字练习工具。
|
||||
<br />
|
||||
加入我们的用户社群后,您可以与我们的开发团队进行沟通,分享您的使用体验和建议,帮助我们改进产品,同时也能够及时了解我们的最新动态和更新内容。
|
||||
<br />
|
||||
<br />
|
||||
</p>
|
||||
<p className="text-sm text-gray-700 dark:text-gray-200">
|
||||
我们深信,与用户的良好互动和反馈是推动我们不断前进和提高的重要因素。因此,我们诚挚邀请您加入我们的社群,与我们一起打造更好的
|
||||
「Qwerty Learner」!
|
||||
</p>
|
||||
<br />
|
||||
<p className="text-sm text-gray-500 dark:text-gray-400">再次感谢您的支持和关注!</p>
|
||||
<br />
|
||||
<img className="ml-1 w-2/6 " src={weChatGroup} alt="alipay" />
|
||||
<br />
|
||||
</InfoPanel>
|
||||
)}
|
||||
|
||||
<div className="mt-4 w-full pb-1 text-center text-sm ease-in" onClick={(e) => e.currentTarget.blur()}>
|
||||
<a href="https://github.com/Kaiyiwing/qwerty-learner" target="_blank" rel="noreferrer">
|
||||
<FontAwesomeIcon icon={['fab', 'github']} className="mr-3 text-gray-500 dark:text-gray-400" />
|
||||
@@ -71,8 +128,17 @@ const Footer: React.FC = () => {
|
||||
<span
|
||||
className="cursor-pointer"
|
||||
onClick={(e) => {
|
||||
setShowContent('donate')
|
||||
setShowModal(true)
|
||||
handleOpenInfoPanel('community')
|
||||
e.currentTarget.blur()
|
||||
}}
|
||||
>
|
||||
<FontAwesomeIcon icon={['fab', 'weixin']} className="mr-3 text-gray-500 dark:text-gray-400" />
|
||||
</span>
|
||||
|
||||
<span
|
||||
className="cursor-pointer"
|
||||
onClick={(e) => {
|
||||
handleOpenInfoPanel('donate')
|
||||
e.currentTarget.blur()
|
||||
}}
|
||||
>
|
||||
@@ -82,15 +148,14 @@ const Footer: React.FC = () => {
|
||||
<span
|
||||
className="mr-3 cursor-pointer"
|
||||
onClick={(e) => {
|
||||
setShowContent('vscode')
|
||||
setShowModal(true)
|
||||
handleOpenInfoPanel('vsc')
|
||||
e.currentTarget.blur()
|
||||
}}
|
||||
>
|
||||
<img src={vscLogo} className="svg-inline--fa fill-current text-gray-500" alt="visual studio code" />
|
||||
</span>
|
||||
|
||||
<a href="mailto:ZHANG.Kaiyi42@gmail.com" target="_blank" rel="noreferrer" onClick={(e) => e.currentTarget.blur()}>
|
||||
<a href="mailto:me@kaiyi.cool" target="_blank" rel="noreferrer" onClick={(e) => e.currentTarget.blur()}>
|
||||
<FontAwesomeIcon icon={['fas', 'envelope']} className="mr-3 text-gray-500 dark:text-gray-400" />
|
||||
</a>
|
||||
|
||||
@@ -105,8 +170,7 @@ const Footer: React.FC = () => {
|
||||
<span
|
||||
className="cursor-pointer text-gray-500 no-underline hover:no-underline dark:text-gray-400 "
|
||||
onClick={(e) => {
|
||||
setShowContent('donate')
|
||||
setShowModal(true)
|
||||
handleOpenInfoPanel('donate')
|
||||
e.currentTarget.blur()
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -1,68 +1,77 @@
|
||||
import React, { MouseEvent } from 'react'
|
||||
import { Transition } from '@headlessui/react'
|
||||
import React, { Fragment } from 'react'
|
||||
import { Dialog, Transition } from '@headlessui/react'
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||
import { IconProp } from '@fortawesome/fontawesome-svg-core'
|
||||
|
||||
type InfoPanelProps = {
|
||||
state: boolean
|
||||
buttonOnclick: (e: MouseEvent) => void
|
||||
openState: boolean
|
||||
onClose: () => void
|
||||
title: string
|
||||
icon: IconProp
|
||||
color: string
|
||||
btnColor: string
|
||||
iconColor: string
|
||||
iconBackgroundColor: string
|
||||
btnColor: string
|
||||
children: React.ReactNode
|
||||
}
|
||||
|
||||
const InfoPanel: React.FC<InfoPanelProps> = ({ state, buttonOnclick, icon, color, iconColor, btnColor, children }) => {
|
||||
const InfoPanel: React.FC<InfoPanelProps> = ({ openState, title, onClose, icon, iconBackgroundColor, iconColor, btnColor, children }) => {
|
||||
return (
|
||||
<div className="fixed inset-0 z-10 overflow-y-auto">
|
||||
<div className="flex min-h-screen items-end justify-center px-4 pt-4 pb-20 text-center sm:block sm:p-0">
|
||||
<Transition
|
||||
show={state}
|
||||
enter="ease-out duration-30"
|
||||
enterFrom="opacity-0 "
|
||||
enterTo="opacity-100 "
|
||||
<Transition.Root show={openState} as={Fragment}>
|
||||
<Dialog as="div" className="relative z-10" onClose={() => onClose()}>
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter="ease-out duration-300"
|
||||
enterFrom="opacity-0"
|
||||
enterTo="opacity-100"
|
||||
leave="ease-in duration-200"
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
>
|
||||
<div className="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity" aria-hidden="true"></div>
|
||||
</Transition>
|
||||
<span className="hidden sm:inline-block sm:h-screen sm:align-middle" aria-hidden="true">
|
||||
​
|
||||
</span>
|
||||
<div className="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity" />
|
||||
</Transition.Child>
|
||||
|
||||
<div
|
||||
className="inline-block transform overflow-hidden rounded-lg bg-white text-left align-bottom shadow-xl transition-all dark:bg-gray-800 sm:my-8 sm:w-full sm:max-w-lg sm:align-middle "
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-labelledby="modal-headline"
|
||||
ref={(el) => el && el.classList.add('group')}
|
||||
>
|
||||
<div className="px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
|
||||
<div className="sm:flex sm:items-start">
|
||||
<div
|
||||
className={`mx-auto flex h-12 w-12 flex-shrink-0 items-center justify-center rounded-md dark:bg-opacity-70 ${color} sm:mx-0 sm:h-10 sm:w-10 `}
|
||||
>
|
||||
<FontAwesomeIcon icon={icon} className={`h-5 w-5 stroke-current ${iconColor}`} />
|
||||
</div>
|
||||
|
||||
<div className="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">{children}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="bg-gray-50 px-4 py-3 dark:bg-gray-700 dark:bg-opacity-10 sm:flex sm:flex-row-reverse sm:px-6">
|
||||
<button
|
||||
type="button"
|
||||
className={`inline-flex w-full justify-center rounded-md border border-transparent px-4 py-2 shadow-sm ${btnColor} text-base font-medium text-white focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 dark:bg-opacity-70 dark:text-opacity-80 sm:ml-3 sm:w-auto sm:text-sm `}
|
||||
onClick={buttonOnclick}
|
||||
<div className="fixed inset-0 z-10 overflow-y-auto">
|
||||
<div className="flex min-h-full items-end justify-center p-4 text-center sm:items-center sm:p-0">
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter="ease-out duration-300"
|
||||
enterFrom="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
||||
enterTo="opacity-100 translate-y-0 sm:scale-100"
|
||||
leave="ease-in duration-200"
|
||||
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
|
||||
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
||||
>
|
||||
{'关闭'}
|
||||
</button>
|
||||
<Dialog.Panel className="relative transform overflow-hidden rounded-lg bg-white text-left shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-lg ">
|
||||
<div className="bg-white px-4 pt-5 pb-4 dark:bg-gray-800 sm:p-6 sm:pb-4">
|
||||
<div className="sm:flex sm:items-start">
|
||||
<div
|
||||
className={`${iconBackgroundColor} mx-auto flex h-12 w-12 flex-shrink-0 items-center justify-center rounded-full dark:bg-opacity-50 sm:mx-0 sm:h-10 sm:w-10`}
|
||||
>
|
||||
<FontAwesomeIcon icon={icon} className={`h-5 w-5 stroke-current dark:bg-opacity-100 ${iconColor}`} />
|
||||
</div>
|
||||
<div className="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
|
||||
<Dialog.Title as="h3" className="text-base font-semibold leading-6 text-gray-900 dark:text-white">
|
||||
{title}
|
||||
</Dialog.Title>
|
||||
<div className="mt-2">{children}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="bg-gray-50 px-4 py-3 dark:bg-gray-700 sm:flex sm:flex-row-reverse sm:px-6">
|
||||
<button
|
||||
type="button"
|
||||
className={`${btnColor} mt-3 inline-flex w-full justify-center rounded-md px-3 py-2 text-sm font-semibold text-white shadow-sm focus:outline-none dark:bg-opacity-70 dark:text-opacity-80 sm:ml-3 sm:mt-0 sm:w-auto sm:w-auto sm:text-sm`}
|
||||
onClick={() => onClose()}
|
||||
>
|
||||
关闭
|
||||
</button>
|
||||
</div>
|
||||
</Dialog.Panel>
|
||||
</Transition.Child>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
</Transition.Root>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -27,12 +27,13 @@ import {
|
||||
faTimes,
|
||||
faTimesCircle,
|
||||
} from '@fortawesome/free-solid-svg-icons'
|
||||
import { faGithub } from '@fortawesome/free-brands-svg-icons'
|
||||
import { faGithub, faWeixin } from '@fortawesome/free-brands-svg-icons'
|
||||
|
||||
library.add(
|
||||
faKeyboard,
|
||||
faBookReader,
|
||||
faGithub,
|
||||
faWeixin,
|
||||
faEnvelope,
|
||||
faVolumeUp,
|
||||
faVolumeMute,
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
import { Transition } from '@headlessui/react'
|
||||
import Tooltip from '@/components/Tooltip'
|
||||
import { useWordList } from '@/pages/Typing/hooks/useWordList'
|
||||
import { useMemo } from 'react'
|
||||
import { useCallback, useMemo } from 'react'
|
||||
import { useHotkeys } from 'react-hotkeys-hook'
|
||||
import ConclusionBar from './ConclusionBar'
|
||||
import RemarkRing from './RemarkRing'
|
||||
import WordChip from './WordChip'
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||
import { useAtomValue } from 'jotai'
|
||||
import { currentChapterAtom, currentDictInfoAtom } from '@/store'
|
||||
import { useAtomValue, useSetAtom } from 'jotai'
|
||||
import { currentChapterAtom, currentDictInfoAtom, infoPanelStateAtom } from '@/store'
|
||||
import { recordOpenInfoPanelAction } from '@/utils'
|
||||
import { InfoPanelType } from '@/typings'
|
||||
|
||||
export type IncorrectInfo = {
|
||||
word: string
|
||||
@@ -41,6 +43,7 @@ const ResultScreen = ({
|
||||
const wordList = useWordList()
|
||||
const currentDictInfo = useAtomValue(currentDictInfoAtom)
|
||||
const currentChapter = useAtomValue(currentChapterAtom)
|
||||
const setInfoPanelState = useSetAtom(infoPanelStateAtom)
|
||||
|
||||
const isLastChapter = useMemo(() => {
|
||||
return currentChapter >= currentDictInfo.length - 1
|
||||
@@ -86,6 +89,19 @@ const ResultScreen = ({
|
||||
invisibleButtonHandler()
|
||||
})
|
||||
|
||||
const handleOpenInfoPanel = useCallback(
|
||||
(modalType: InfoPanelType) => {
|
||||
recordOpenInfoPanelAction(modalType, 'resultScreen')
|
||||
setInfoPanelState((state) => {
|
||||
return {
|
||||
...state,
|
||||
[modalType]: true,
|
||||
}
|
||||
})
|
||||
},
|
||||
[setInfoPanelState],
|
||||
)
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 z-10 overflow-y-auto">
|
||||
<div className="absolute inset-0 bg-gray-300 opacity-80 dark:bg-gray-600"></div>
|
||||
@@ -112,7 +128,7 @@ const ResultScreen = ({
|
||||
<RemarkRing remark={timeString} caption="章节耗时" />
|
||||
<RemarkRing remark={`${speedInfo.speed}个/s`} caption="输入字符" />
|
||||
</div>
|
||||
<div className="z-10 mx-6 flex-1 overflow-visible rounded-xl bg-indigo-50 dark:bg-gray-700">
|
||||
<div className="z-10 ml-6 flex-1 overflow-visible rounded-xl bg-indigo-50 dark:bg-gray-700">
|
||||
<div className="customized-scrollbar z-20 ml-8 mr-1 flex h-80 flex-row flex-wrap content-start gap-4 overflow-y-auto overflow-x-hidden pr-7 pt-9">
|
||||
{incorrectInfo.map((info) => (
|
||||
<WordChip key={info.word} mistake={info} />
|
||||
@@ -122,6 +138,26 @@ const ResultScreen = ({
|
||||
<ConclusionBar mistakeLevel={mistakeLevel} mistakeCount={incorrectInfo.length} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="ml-2 flex flex-col items-center justify-end text-xl">
|
||||
<span
|
||||
className="cursor-pointer"
|
||||
onClick={(e) => {
|
||||
handleOpenInfoPanel('donate')
|
||||
e.currentTarget.blur()
|
||||
}}
|
||||
>
|
||||
<FontAwesomeIcon icon={['fas', 'coffee']} className=" w-10 text-gray-500 dark:text-gray-400" />
|
||||
</span>
|
||||
<span
|
||||
className="cursor-pointer"
|
||||
onClick={(e) => {
|
||||
handleOpenInfoPanel('community')
|
||||
e.currentTarget.blur()
|
||||
}}
|
||||
>
|
||||
<FontAwesomeIcon icon={['fab', 'weixin']} className=" w-10 text-gray-500 dark:text-gray-400" />
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-10 flex w-full justify-center gap-5 px-5 text-xl">
|
||||
<Tooltip content="快捷键:shift + enter">
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { atomWithStorage } from 'jotai/utils'
|
||||
import { atom, SetStateAction, WritableAtom } from 'jotai'
|
||||
import { keySoundResources, wrongSoundResources, correctSoundResources } from '@/resources/soundResource'
|
||||
import { PronunciationType, PhoneticType, Dictionary } from '@/typings'
|
||||
import { PronunciationType, PhoneticType, Dictionary, InfoPanelState } from '@/typings'
|
||||
import { idDictionaryMap } from '@/resources/dictionary'
|
||||
import { CHAPTER_LENGTH } from '@/constants'
|
||||
|
||||
@@ -56,11 +56,17 @@ export const isChapterEndAtom = atom(false)
|
||||
|
||||
export const isInDevModeAtom = atom(false)
|
||||
|
||||
export const infoPanelStateAtom = atom<InfoPanelState>({
|
||||
donate: false,
|
||||
vsc: false,
|
||||
community: false,
|
||||
})
|
||||
|
||||
let dismissStartCardDateAtom: WritableAtom<Date | null, [SetStateAction<Date | null>], void>
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
dismissStartCardDateAtom = atomWithStorage<Date | null>('dismissStartCardDate', null)
|
||||
} else {
|
||||
// for dev test
|
||||
dismissStartCardDateAtom = atom<Date | null>(null)
|
||||
dismissStartCardDateAtom = atom<Date | null>(new Date())
|
||||
}
|
||||
export { dismissStartCardDateAtom }
|
||||
|
||||
@@ -11,3 +11,9 @@ export type Word = {
|
||||
usphone: string
|
||||
ukphone: string
|
||||
}
|
||||
|
||||
export type InfoPanelType = 'donate' | 'vsc' | 'community'
|
||||
|
||||
export type InfoPanelState = {
|
||||
[key in InfoPanelType]: boolean
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { InfoPanelType } from '@/typings'
|
||||
import mixpanel from 'mixpanel-browser'
|
||||
|
||||
export type starAction = 'star' | 'dismiss'
|
||||
@@ -8,3 +9,12 @@ export function recordStarAction(action: starAction) {
|
||||
}
|
||||
mixpanel.track('star', props)
|
||||
}
|
||||
|
||||
export type openInfoPanelLocation = 'footer' | 'resultScreen'
|
||||
export function recordOpenInfoPanelAction(type: InfoPanelType, location: openInfoPanelLocation) {
|
||||
const props = {
|
||||
type,
|
||||
location,
|
||||
}
|
||||
mixpanel.track('openInfoPanel', props)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user