diff --git a/apps/web/package.json b/apps/web/package.json index 8ef0ec4..a304f9e 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -17,7 +17,8 @@ "@remake/vitex": "workspace:*", "jotai": "^2.20.2", "react": "^19.2.8", - "react-dom": "^19.2.8" + "react-dom": "^19.2.8", + "react-hot-toast": "^2.6.0" }, "devDependencies": { "@types/react": "^19.2.18", diff --git a/apps/web/src/Game.tsx b/apps/web/src/Game.tsx index 0968c30..a443d25 100644 --- a/apps/web/src/Game.tsx +++ b/apps/web/src/Game.tsx @@ -1,6 +1,7 @@ import { useEffect } from 'react' import { useInit, useWatcher } from '@/hooks/storage' import { useStep, Step } from '@remake/hooks' +import ToastContainer from '@/toast' import Home from '@/containers/Home' import Mode from '@/containers/Mode' import Chara from '@/containers/Chara' @@ -40,6 +41,6 @@ export function Game() { const saving = useWatcher() useEffect(() => { if (!inited) init() }, [inited, init]) if (!inited) return - return <> + return <> } export default Game diff --git a/apps/web/src/components/Achievement.css b/apps/web/src/components/Achievement.css new file mode 100644 index 0000000..cca5d20 --- /dev/null +++ b/apps/web/src/components/Achievement.css @@ -0,0 +1,23 @@ +.achievement-item { + display: flex; + justify-content: space-between; + align-items: center; + padding: 0 1rem; + height: 2rem; + gap: 1rem; + color: var(--grade-color); + background: var(--background, var(--base-background-color)); + cursor: pointer; + user-select: none; + border: 0.0625rem solid var(--grade-color); + box-sizing: border-box; + transition: all 0.2s ease-in-out; + .name { font-weight: bold; } + .description { + font-size: 0.85rem; + user-select: none; + &::before { content: '['; margin-right: 0.125rem; } + &::after { content: ']'; margin-left: 0.125rem; } + &::before, &::after { color: var(--grade-color); } + } +} \ No newline at end of file diff --git a/apps/web/src/components/Achievement.tsx b/apps/web/src/components/Achievement.tsx new file mode 100644 index 0000000..ff0fbc3 --- /dev/null +++ b/apps/web/src/components/Achievement.tsx @@ -0,0 +1,19 @@ +import { type Achievement as A, achievement } from '@remake/data' +import './Achievement.css' + +export interface AchievementProps { + id: A['id'] +} +export function Achievement({ id }: AchievementProps) { + const ach = achievement.get(id) + if (!ach) return null + const grade = ach?.grade ?? 0 + return ( +
+ {ach.name} + {ach.description} +
+ ) +} + +export default Achievement diff --git a/apps/web/src/components/Message.css b/apps/web/src/components/Message.css new file mode 100644 index 0000000..a4ebc95 --- /dev/null +++ b/apps/web/src/components/Message.css @@ -0,0 +1,10 @@ +.message-item { + display: flex; + justify-content: center; + align-items: center; + height: 2rem; + border: 0.0625rem solid var(--base-text-color); + color: var(--base-text-color); + background: var(--base-background-color); + padding: 0 1rem; +} \ No newline at end of file diff --git a/apps/web/src/components/Message.tsx b/apps/web/src/components/Message.tsx new file mode 100644 index 0000000..4db8b05 --- /dev/null +++ b/apps/web/src/components/Message.tsx @@ -0,0 +1,10 @@ +import './Message.css' + +export interface MsgToastProps { + message: React.ReactNode +} +export function MsgToast({ message }: MsgToastProps) { + return
{message}
+} + +export default MsgToast diff --git a/apps/web/src/containers/Achv.tsx b/apps/web/src/containers/Achv.tsx index 304fb83..6759051 100644 --- a/apps/web/src/containers/Achv.tsx +++ b/apps/web/src/containers/Achv.tsx @@ -2,6 +2,7 @@ import { useAchv, useGoHome } from '@remake/hooks' import { judge, judgeGrade, judgeGradeByValue } from '@/config' import { rates } from '@/display' import { achievements, type Achievement } from '@remake/data' +import { toastMsg } from '@/toast' import './Achv.css' interface AchievementProps { @@ -22,6 +23,7 @@ export function AchievementItem({ id, colled }: AchievementProps) { export function Achv() { const goHome = useGoHome() + const showRank = () => toastMsg('别卷了,没有排行榜', 'achv-rank') const [stats, sorted] = useAchv() const jtimes = judge('times', stats.times) const gtimes = judgeGrade('times', jtimes) @@ -34,7 +36,9 @@ export function Achv() { return (
- + diff --git a/apps/web/src/containers/Alloc.tsx b/apps/web/src/containers/Alloc.tsx index e8649d9..75b3872 100644 --- a/apps/web/src/containers/Alloc.tsx +++ b/apps/web/src/containers/Alloc.tsx @@ -6,6 +6,7 @@ import { properties } from '@/display' import { keys } from '@remake/vitex' import { judgeGradeByValue } from '@/config' import { talents } from '@remake/data' +import { toastAchvs, toastMsg } from '@/toast' import Replaced from '@/components/Replaced' import './Alloc.css' @@ -65,12 +66,9 @@ export function Alloc() { const start = useStart() const [showDetail, setShowDetail] = useState(false) const handleNext = () => { - if (left != 0) { - // TODO: Show a warning that there are still points left - return - } + if (left != 0) return toastMsg('还有剩余点数未分配', 'alloc-toast') const achievements = start() - // TODO: Show achievements + toastAchvs(achievements) console.log('Achievements', achievements) } return ( diff --git a/apps/web/src/containers/Chara.tsx b/apps/web/src/containers/Chara.tsx index 64dde0e..0a10230 100644 --- a/apps/web/src/containers/Chara.tsx +++ b/apps/web/src/containers/Chara.tsx @@ -5,6 +5,7 @@ import { judgeGradeByValue } from '@/config' import { characters } from '@remake/data' import { properties } from '@/display' import { keys } from '@remake/vitex' +import { toastMsg } from '@/toast' import Talent from '@/components/Talent' import './Chara.css' @@ -89,6 +90,11 @@ export function Chara() { const [{ unique, characters }, puller] = useCharaPuller() const [picked, picker] = useCharaPicker() const submit = useCharaSubmit() + const handleSubmit = () => { + if (ready) return submit(picked) + if (!picked) toastMsg('请先选择角色', 'chara-toast-pick') + else toastMsg('请先生成唯一角色', 'chara-toast-unique') + } const ready = picked && (picked.type === 'unique' ? !!u : true) return (
@@ -114,7 +120,7 @@ export function Chara() { diff --git a/apps/web/src/containers/Home.css b/apps/web/src/containers/Home.css index d6c2e4a..eb133b4 100644 --- a/apps/web/src/containers/Home.css +++ b/apps/web/src/containers/Home.css @@ -14,8 +14,7 @@ width: auto; display: flex; gap: 1rem; - >.github-btn, - >.theme-toggle-btn { + >button { width: 2.5rem; height: 2.5rem; padding: 0.25rem; diff --git a/apps/web/src/containers/Pick.tsx b/apps/web/src/containers/Pick.tsx index 51e4fee..0f38702 100644 --- a/apps/web/src/containers/Pick.tsx +++ b/apps/web/src/containers/Pick.tsx @@ -1,6 +1,7 @@ import { useTalentPuller, useTalentPicker } from '@remake/hooks' import { useTalentSubmit, useSubmitIsEnable } from '@remake/hooks' import { PullCount, dev } from '@/config' +import { toastMsg } from '@/toast' import Talent from '@/components/Talent' import './Pick.css' @@ -9,7 +10,13 @@ export function Pick() { const [talents, picker] = useTalentPicker() const { enabled, min, max } = useSubmitIsEnable() const submit = useTalentSubmit() - // useEffect(puller, []) + const msg = enabled + ? '下一步' + : `请选取 ${min == max ? min : `${min}~${max}`} 个天赋` + const handleSubmit = () => { + if (enabled) return submit() + toastMsg(msg, 'pick-toast') + } if (!pulled) return (
@@ -33,15 +40,12 @@ export function Pick() { ))} - {enabled ? ( - - ) : ( - - )} +
) } diff --git a/apps/web/src/containers/Play.tsx b/apps/web/src/containers/Play.tsx index 2b4d3bd..d037e64 100644 --- a/apps/web/src/containers/Play.tsx +++ b/apps/web/src/containers/Play.tsx @@ -6,6 +6,7 @@ import { achievements, events, talents } from '@remake/data' import { properties } from '@/display' import { AutoInterval } from '@/config' import { format } from '@remake/vitex' +import { toastAchvs } from '@/toast/Achv' import './Play.css' function LogTalent({ id }: { id: number }) { @@ -153,13 +154,13 @@ export function Play() { const handleNext = useCallback(() => { if (ended) return const achievements = next() - // TODO: Show achievements + toastAchvs(achievements) console.debug('Achievements:', achievements) }, [ended, next]) const handleGotoSummary = useCallback(() => { if (!ended) return const achievements = gotoSummary() - // TODO: Show achievements + toastAchvs(achievements) console.debug('Achievements:', achievements) }, [ended, gotoSummary]) useLayoutEffect(() => { diff --git a/apps/web/src/containers/Summary.tsx b/apps/web/src/containers/Summary.tsx index d2f402d..9bdab04 100644 --- a/apps/web/src/containers/Summary.tsx +++ b/apps/web/src/containers/Summary.tsx @@ -2,6 +2,7 @@ import { useEffect, useRef } from 'react' import { usePicked, useEnd, useProfile, useIsClassic } from '@remake/hooks' import { useEndJudge } from '@/hooks/judge' import { properties, judgeDisplay } from '@/display' +import { toastAchvs, toastMsg } from '@/toast' import Talent from '@/components/Talent' import './Summary.css' @@ -54,9 +55,13 @@ function TalentList({ picked, picker }: TalentListProps) { export default function Summary() { const isClassic = useIsClassic() const [locked, picker, end] = useEnd() + const handlePicker = (id: number) => { + if (isClassic) picker(id) + else toastMsg('名人天赋不可锁定', 'summary-toast') + } const handleEnd = () => { const achievements = end() - // TODO: Show achievements + toastAchvs(achievements) console.log('Achievements', achievements) } return ( @@ -68,7 +73,7 @@ export default function Summary() { ? '你可以锁定一个天赋,下辈子还能抽到' : '名人天赋不可锁定'}
- +