Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
## 2025-03-01

- 优化了移动端界面 [@guansss](https://github.com/guansss)
- 加入广告 [@guansss](https://github.com/guansss)

## 2025-02-22

- 添加了关卡筛选器的联想输入功能 [@guansss](https://github.com/guansss)
Expand Down
Binary file added public/ads_mumu.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 0 additions & 1 deletion src/apis/announcement.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ export function useAnnouncement() {
.then((res) => res.text())
.catch((e) => {
if ((e as Error).message === 'Failed to fetch') {
console.warn(e)
throw new Error('网络错误')
}

Expand Down
13 changes: 6 additions & 7 deletions src/components/NavExpandButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,13 @@ import { navAtom, toggleExpandNavAtom } from 'store/nav'

export const NavExpandButton = () => {
const expanded = useAtomValue(navAtom)
const toggleExpaned = useSetAtom(toggleExpandNavAtom)
const toggleExpand = useSetAtom(toggleExpandNavAtom)

return (
<div className="sm:hidden">
<Button
onClick={toggleExpaned}
icon={expanded.expanded ? 'cross' : 'menu'}
/>
</div>
<Button
className="md:!hidden"
onClick={() => toggleExpand()}
icon={expanded.expanded ? 'cross' : 'menu'}
/>
)
}
59 changes: 31 additions & 28 deletions src/components/announcement/AnnPanel.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Card, Icon } from '@blueprintjs/core'

import clsx from 'clsx'
import { FC, useEffect, useMemo, useState } from 'react'
import { FC, ReactNode, useEffect, useMemo, useState } from 'react'

import { useAnnouncement } from '../../apis/announcement'
import {
Expand All @@ -15,9 +15,10 @@ import { AnnDialog } from './AnnDialog'

interface AnnPanelProps {
className?: string
trigger?: (params: { handleClick: () => void }) => ReactNode
}

export const AnnPanel: FC<AnnPanelProps> = ({ className }) => {
export const AnnPanel: FC<AnnPanelProps> = ({ className, trigger }) => {
const { data, error } = useAnnouncement()
const announcement = useMemo(
() => (data ? parseAnnouncement(data) : undefined),
Expand Down Expand Up @@ -45,34 +46,36 @@ export const AnnPanel: FC<AnnPanelProps> = ({ className }) => {
}
}, [announcement, lastNoticed, setLastNoticed])

const handleClick = () => {
setIsOpen({ yes: true, manually: true })
setDisplaySections(announcement?.sections)
}

trigger ??= ({ handleClick }) => (
<Card interactive className={clsx(className)} onClick={handleClick}>
<CardTitle icon="info-sign">公告</CardTitle>

<div className="flex">
{announcement && (
<ul className="grow list-disc pl-4">
{announcement?.sections
.slice(0, 3)
.map(({ title }) => <li key={title}>{title}</li>)}
</ul>
)}
{!announcement && error && (
<div className="grow text-red-500">
公告加载失败:{formatError(error)}
</div>
)}
<Icon className="self-end" icon="more" size={14} />
</div>
</Card>
)

return (
<>
<Card
interactive
className={clsx(className)}
onClick={() => {
setIsOpen({ yes: true, manually: true })
setDisplaySections(announcement?.sections)
}}
>
<CardTitle icon="info-sign">公告</CardTitle>

<div className="flex">
{announcement && (
<ul className="grow list-disc pl-4">
{announcement?.sections
.slice(0, 3)
.map(({ title }) => <li key={title}>{title}</li>)}
</ul>
)}
{!announcement && error && (
<div className="grow text-red-500">
公告加载失败:{formatError(error)}
</div>
)}
<Icon className="self-end" icon="more" size={14} />
</div>
</Card>
{trigger({ handleClick })}
<AnnDialog
sections={displaySections}
isOpen={!!isOpen?.yes}
Expand Down
2 changes: 1 addition & 1 deletion src/components/drawer/DrawerLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ export const DrawerLayout: FCC<{
}> = ({ title, children }) => {
return (
<section className="flex flex-col relative h-full">
<div className="px-8 py-2 text-lg font-medium flex items-center bg-slate-100 shadow w-full h-12 dark:bg-slate-900 dark:text-white">
<div className="px-4 md:px-8 py-2 text-lg font-medium flex flex-wrap items-center bg-slate-100 shadow w-full min-h-12 dark:bg-slate-900 dark:text-white">
{title}
</div>

Expand Down
116 changes: 84 additions & 32 deletions src/components/drawer/NavAside.tsx
Original file line number Diff line number Diff line change
@@ -1,48 +1,100 @@
import { Button, Portal } from '@blueprintjs/core'
import { Drawer, Menu, MenuDivider } from '@blueprintjs/core'
import { MenuItem2 } from '@blueprintjs/popover2'

import { useAtomValue, useSetAtom } from 'jotai'
import { LINKS } from 'layouts/AppLayout'
import { useState } from 'react'
import { NavLink } from 'react-router-dom'

import { navAtom, toggleExpandNavAtom } from 'store/nav'

import { NAV_LINKS, SOCIAL_LINKS } from '../../links'
import { useCurrentSize } from '../../utils/useCurrenSize'
import { AnnPanel } from '../announcement/AnnPanel'
import { OperationSetEditorDialog } from '../operation-set/OperationSetEditor'

export const NavAside = () => {
const { isMD } = useCurrentSize()
const nav = useAtomValue(navAtom)
const toggleNav = useSetAtom(toggleExpandNavAtom)
const navLinks = [...LINKS]

if (!nav.expanded) return null
const [showOperationSetDialog, setShowOperationSetDialog] = useState(false)

if (!isMD) return null

return (
<Portal>
{/* todo: 这里可以重写移动端header 需要去掉top */}
<div className="fixed inset-0 top-14 z-50 overflow-y-auto backdrop-blur-lg sm:hidden">
<div className="px-4 sm:px-6 pt-3 pb-6">
<nav className="flex flex-col gap-3">
{navLinks.map((link) => (
<div className="flex-col w-full" key={link.to}>
<NavLink
to={link.to}
className="text-sm text-zinc-600 dark:text-slate-100 !no-underline"
<>
<Drawer
isOpen={isMD && !!nav.expanded}
onClose={() => toggleNav()}
position="left"
size="100%"
portalClassName="[&>.bp4-overlay-container]:z-10"
backdropClassName="bg-transparent"
className="bg-transparent backdrop-blur-lg overflow-y-auto mt-14 p-2"
>
<Menu className="p-0 space-y-2 bg-transparent font-bold">
{NAV_LINKS.map((link) => (
<NavLink key={link.to} to={link.to} className="block !no-underline">
{({ isActive }) => (
<MenuItem2
key={link.to}
icon={link.icon}
active={isActive}
text={link.label}
className="p-2 rounded-md"
tagName="div"
onClick={() => toggleNav()}
>
{({ isActive }) => (
<Button
minimal
icon={link.icon}
active={isActive}
className="w-full flex items-center"
style={{ justifyContent: 'flex-start' }}
>
{link.label}
</Button>
)}
</NavLink>
</div>
))}
</nav>
/>
)}
</NavLink>
))}
<MenuDivider />
<MenuItem2
icon="folder-new"
text="创建作业集..."
className="p-2 rounded-md"
onClick={() => {
setShowOperationSetDialog(true)
toggleNav()
}}
/>
<AnnPanel
trigger={({ handleClick }) => (
<MenuItem2
icon="info-sign"
text="公告"
className="p-2 rounded-md"
onClick={handleClick}
/>
)}
/>
<MenuDivider />
</Menu>
<div className="flex flex-wrap leading-relaxed mt-2 p-2 section-social-links">
{SOCIAL_LINKS.map((link) => (
<a
href={link.href}
target="_blank"
rel="noopener noreferrer"
className="flex items-center text-zinc-600 dark:text-slate-100 no-underline"
>
{link.icon}
<span>{link.label}</span>
</a>
)).reduce((prev, curr) => (
<>
{prev}
<div className="mx-2 opacity-50">·</div>
{curr}
</>
))}
</div>
</div>
</Portal>
</Drawer>

<OperationSetEditorDialog
isOpen={showOperationSetDialog}
onClose={() => setShowOperationSetDialog(false)}
/>
</>
)
}
73 changes: 34 additions & 39 deletions src/components/viewer/OperationViewer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -189,38 +189,33 @@ export const OperationViewer: ComponentType<{

<div className="flex-1" />

{operation.uploaderId === auth.userId && (
<Popover2
content={
<ManageMenu
operation={operation}
onUpdate={() => onCloseDrawer()}
/>
}
>
<Button
className="ml-4"
icon="wrench"
text="管理"
rightIcon="caret-down"
/>
</Popover2>
)}

<Button
className="ml-4"
icon="download"
text="下载原 JSON"
onClick={() => handleDownloadJSON(operation.parsedContent)}
/>
<div className="flex flex-wrap items-center gap-2 md:gap-4">
{operation.uploaderId === auth.userId && (
<Popover2
content={
<ManageMenu
operation={operation}
onUpdate={() => onCloseDrawer()}
/>
}
>
<Button icon="wrench" text="管理" rightIcon="caret-down" />
</Popover2>
)}

<Button
icon="download"
text="下载原 JSON"
onClick={() => handleDownloadJSON(operation.parsedContent)}
/>

<Button
className="ml-4"
icon="clipboard"
text="复制神秘代码"
intent="primary"
onClick={() => copyShortCode(operation)}
/>
<Button
icon="clipboard"
text="复制神秘代码"
intent="primary"
onClick={() => copyShortCode(operation)}
/>
</div>
</>
}
>
Expand Down Expand Up @@ -276,10 +271,10 @@ function OperationViewerInner({
handleRating: (decision: OpRatingType) => Promise<void>
}) {
return (
<div className="h-full overflow-auto py-4 px-8 pt-8">
<div className="h-full overflow-auto p-4 md:p-8">
<H3>{operation.parsedContent.doc.title}</H3>

<div className="grid grid-rows-1 grid-cols-3 gap-8">
<div className="flex flex-col-reverse md:grid grid-rows-1 grid-cols-3 gap-2 md:gap-8">
<div className="flex flex-col">
<Paragraphs content={operation.parsedContent.doc.details} linkify />
</div>
Expand Down Expand Up @@ -330,20 +325,20 @@ function OperationViewerInner({
</FactItem>
</div>

<div className="flex flex-col items-start select-none tabular-nums">
<FactItem title="浏览量" icon="eye-open">
<div className="flex flex-wrap md:flex-col items-start select-none tabular-nums gap-4">
<FactItem dense title="浏览量" icon="eye-open">
<span className="text-gray-800 dark:text-slate-100 font-bold">
{operation.views}
</span>
</FactItem>

<FactItem title="发布于" icon="time">
<FactItem dense title="发布于" icon="time">
<span className="text-gray-800 dark:text-slate-100 font-bold">
<RelativeTime moment={operation.uploadTime} />
</span>
</FactItem>

<FactItem title="作者" icon="user">
<FactItem dense title="作者" icon="user">
<UserName
className="text-gray-800 dark:text-slate-100 font-bold"
userId={operation.uploaderId}
Expand Down Expand Up @@ -409,7 +404,7 @@ function OperationViewerInnerDetails({ operation }: { operation: Operation }) {
/>
</H4>
<Collapse isOpen={showOperators}>
<div className="mt-2 flex flex-wrap -ml-4">
<div className="mt-2 flex flex-wrap -ml-4 gap-y-2">
{!operation.parsedContent.opers?.length &&
!operation.parsedContent.groups?.length && (
<NonIdealState
Expand All @@ -432,7 +427,7 @@ function OperationViewerInnerDetails({ operation }: { operation: Operation }) {
key={group.name}
>
<H6 className="text-gray-800">{group.name}</H6>
<div className="flex gap-1">
<div className="flex flex-wrap gap-y-2">
{group.opers
?.filter(Boolean)
.map((operator) => (
Expand Down
Loading
Loading