fix: 终端字符宽度&微调样式&路由切换动画

This commit is contained in:
bietiaop
2025-01-27 15:58:27 +08:00
parent 823faa2790
commit dcef3f3c3b
13 changed files with 133 additions and 96 deletions

View File

@@ -187,7 +187,7 @@ export default function AudioPlayer(props: AudioPlayerProps) {
return ( return (
<div <div
className={clsx( className={clsx(
'fixed right-0 bottom-0 z-[9999] w-full md:w-96', 'fixed right-0 bottom-0 z-[52] w-full md:w-96',
!translateX && !translateY && 'transition-transform', !translateX && !translateY && 'transition-transform',
isCollapsed && 'md:hover:!translate-x-80' isCollapsed && 'md:hover:!translate-x-80'
)} )}

View File

@@ -19,12 +19,6 @@ loader.config({
} }
}) })
loader.config({
'vs/nls': {
availableLanguages: { '*': 'zh-cn' }
}
})
export interface CodeEditorProps extends React.ComponentProps<typeof Editor> { export interface CodeEditorProps extends React.ComponentProps<typeof Editor> {
test?: string test?: string
} }

View File

@@ -1125,7 +1125,7 @@ export const WebUIIcon = (props: IconSvgProps) => (
> >
<g <g
id="svgGroup" id="svgGroup"
stroke-linecap="round" strokeLinecap="round"
stroke="#000" stroke="#000"
fill="transparent" fill="transparent"
style={{ style={{
@@ -1173,7 +1173,7 @@ export const WebUIIcon = (props: IconSvgProps) => (
> >
<g <g
id="svgGroup" id="svgGroup"
stroke-linecap="round" strokeLinecap="round"
stroke="#000" stroke="#000"
fill="transparent" fill="transparent"
style={{ style={{
@@ -1220,7 +1220,7 @@ export const WebUIIcon = (props: IconSvgProps) => (
> >
<g <g
id="svgGroup" id="svgGroup"
stroke-linecap="round" strokeLinecap="round"
stroke="#000" stroke="#000"
fill="transparent" fill="transparent"
style={{ style={{
@@ -1270,7 +1270,7 @@ export const WebUIIcon = (props: IconSvgProps) => (
> >
<g <g
id="svgGroup" id="svgGroup"
stroke-linecap="round" strokeLinecap="round"
stroke="#000" stroke="#000"
fill="transparent" fill="transparent"
style={{ style={{
@@ -1320,7 +1320,7 @@ export const WebUIIcon = (props: IconSvgProps) => (
> >
<g <g
id="svgGroup" id="svgGroup"
stroke-linecap="round" strokeLinecap="round"
stroke="#000" stroke="#000"
fill="transparent" fill="transparent"
style={{ style={{
@@ -1372,7 +1372,7 @@ export const BietiaopIcon = (props: IconSvgProps) => (
> >
<g <g
id="svgGroup" id="svgGroup"
stroke-linecap="round" strokeLinecap="round"
stroke="#000" stroke="#000"
fill="transparent" fill="transparent"
style={{ style={{
@@ -1422,7 +1422,7 @@ export const BietiaopIcon = (props: IconSvgProps) => (
> >
<g <g
id="svgGroup" id="svgGroup"
stroke-linecap="round" strokeLinecap="round"
stroke="#000" stroke="#000"
fill="transparent" fill="transparent"
style={{ style={{
@@ -1469,7 +1469,7 @@ export const BietiaopIcon = (props: IconSvgProps) => (
> >
<g <g
id="svgGroup" id="svgGroup"
stroke-linecap="round" strokeLinecap="round"
stroke="#000" stroke="#000"
fill="transparent" fill="transparent"
style={{ style={{
@@ -1519,7 +1519,7 @@ export const BietiaopIcon = (props: IconSvgProps) => (
> >
<g <g
id="svgGroup" id="svgGroup"
stroke-linecap="round" strokeLinecap="round"
stroke="#000" stroke="#000"
fill="transparent" fill="transparent"
style={{ style={{
@@ -1566,7 +1566,7 @@ export const BietiaopIcon = (props: IconSvgProps) => (
> >
<g <g
id="svgGroup" id="svgGroup"
stroke-linecap="round" strokeLinecap="round"
stroke="#000" stroke="#000"
fill="transparent" fill="transparent"
style={{ style={{
@@ -1613,7 +1613,7 @@ export const BietiaopIcon = (props: IconSvgProps) => (
> >
<g <g
id="svgGroup" id="svgGroup"
stroke-linecap="round" strokeLinecap="round"
stroke="#000" stroke="#000"
fill="transparent" fill="transparent"
style={{ style={{
@@ -1660,7 +1660,7 @@ export const BietiaopIcon = (props: IconSvgProps) => (
> >
<g <g
id="svgGroup" id="svgGroup"
stroke-linecap="round" strokeLinecap="round"
stroke="#000" stroke="#000"
fill="transparent" fill="transparent"
style={{ style={{
@@ -1707,7 +1707,7 @@ export const BietiaopIcon = (props: IconSvgProps) => (
> >
<g <g
id="svgGroup" id="svgGroup"
stroke-linecap="round" strokeLinecap="round"
stroke="#000" stroke="#000"
fill="transparent" fill="transparent"
style={{ style={{

View File

@@ -50,8 +50,8 @@ const Modal: React.FC<ModalProps> = React.memo((props) => {
onNativeClose() onNativeClose()
}} }}
classNames={{ classNames={{
backdrop: 'z-[99999999]', backdrop: 'z-[99]',
wrapper: 'z-[999999999]' wrapper: 'z-[99]'
}} }}
{...rest} {...rest}
> >

View File

@@ -4,7 +4,7 @@ import { Input } from '@heroui/input'
import { Snippet } from '@heroui/snippet' import { Snippet } from '@heroui/snippet'
import { useLocalStorage } from '@uidotdev/usehooks' import { useLocalStorage } from '@uidotdev/usehooks'
import { motion } from 'motion/react' import { motion } from 'motion/react'
import { useEffect, useState } from 'react' import { useEffect, useRef, useState } from 'react'
import toast from 'react-hot-toast' import toast from 'react-hot-toast'
import { IoLink, IoSend } from 'react-icons/io5' import { IoLink, IoSend } from 'react-icons/io5'
import { PiCatDuotone } from 'react-icons/pi' import { PiCatDuotone } from 'react-icons/pi'
@@ -41,6 +41,7 @@ const OneBotApiDebug: React.FC<OneBotApiDebugProps> = (props) => {
const [isCodeEditorOpen, setIsCodeEditorOpen] = useState(false) const [isCodeEditorOpen, setIsCodeEditorOpen] = useState(false)
const [isResponseOpen, setIsResponseOpen] = useState(false) const [isResponseOpen, setIsResponseOpen] = useState(false)
const [isFetching, setIsFetching] = useState(false) const [isFetching, setIsFetching] = useState(false)
const responseRef = useRef<HTMLDivElement>(null)
const parsedRequest = parse(data.request) const parsedRequest = parse(data.request)
const parsedResponse = parse(data.response) const parsedResponse = parse(data.response)
@@ -69,6 +70,11 @@ const OneBotApiDebug: React.FC<OneBotApiDebugProps> = (props) => {
}) })
.finally(() => { .finally(() => {
setIsFetching(false) setIsFetching(false)
setIsResponseOpen(true)
responseRef.current?.scrollIntoView({
behavior: 'smooth',
block: 'start'
})
toast.dismiss(r) toast.dismiss(r)
}) })
} catch (error) { } catch (error) {
@@ -84,8 +90,8 @@ const OneBotApiDebug: React.FC<OneBotApiDebugProps> = (props) => {
}, [path]) }, [path])
return ( return (
<div className="flex-1 overflow-y-auto p-4 rounded-lg shadow-md"> <section className="p-4 pt-14 rounded-lg shadow-md">
<h1 className="text-2xl font-bold mb-4 flex items-center gap-1 text-danger-400 "> <h1 className="text-2xl font-bold mb-4 flex items-center gap-1 text-danger-400">
<PiCatDuotone /> <PiCatDuotone />
{data.description} {data.description}
</h1> </h1>
@@ -93,6 +99,9 @@ const OneBotApiDebug: React.FC<OneBotApiDebugProps> = (props) => {
<Snippet <Snippet
className="bg-default-50 bg-opacity-50 backdrop-blur-md" className="bg-default-50 bg-opacity-50 backdrop-blur-md"
symbol={<IoLink size={18} className="inline-block mr-1" />} symbol={<IoLink size={18} className="inline-block mr-1" />}
tooltipProps={{
content: '点击复制地址'
}}
> >
{path} {path}
</Snippet> </Snippet>
@@ -125,7 +134,10 @@ const OneBotApiDebug: React.FC<OneBotApiDebugProps> = (props) => {
<IoSend /> <IoSend />
</Button> </Button>
</div> </div>
<Card shadow="sm" className="my-4 bg-opacity-50 backdrop-blur-md"> <Card
shadow="sm"
className="my-4 bg-opacity-50 backdrop-blur-md overflow-visible z-20"
>
<CardHeader className="font-noto-serif font-bold text-lg gap-1 pb-0"> <CardHeader className="font-noto-serif font-bold text-lg gap-1 pb-0">
<span className="mr-2"></span> <span className="mr-2"></span>
<Button <Button
@@ -140,7 +152,7 @@ const OneBotApiDebug: React.FC<OneBotApiDebugProps> = (props) => {
</CardHeader> </CardHeader>
<CardBody> <CardBody>
<motion.div <motion.div
className="overflow-hidden" ref={responseRef}
initial={{ opacity: 0, height: 0 }} initial={{ opacity: 0, height: 0 }}
animate={{ animate={{
opacity: isCodeEditorOpen ? 1 : 0, opacity: isCodeEditorOpen ? 1 : 0,
@@ -223,7 +235,7 @@ const OneBotApiDebug: React.FC<OneBotApiDebugProps> = (props) => {
<h2 className="text-xl font-semibold mt-4 mb-2"></h2> <h2 className="text-xl font-semibold mt-4 mb-2"></h2>
<DisplayStruct schema={parsedResponse} /> <DisplayStruct schema={parsedResponse} />
</div> </div>
</div> </section>
) )
} }

View File

@@ -19,9 +19,8 @@ const OneBotApiNavList: React.FC<OneBotApiNavListProps> = (props) => {
return ( return (
<motion.div <motion.div
className={clsx( className={clsx(
'flex-shrink-0 absolute md:!top-0 md:bottom-0 left-0 !overflow-hidden md:relative md:w-auto z-20', 'h-[calc(100vh-3.5rem)] left-0 !overflow-hidden md:w-auto z-20 top-[3.3rem] md:top-[3rem] absolute md:sticky md:float-start',
openSideBar && openSideBar && 'bg-background bg-opacity-20 backdrop-blur-md'
'bottom-8 z-10 bg-background bg-opacity-20 backdrop-blur-md top-14'
)} )}
initial={{ width: 0 }} initial={{ width: 0 }}
transition={{ transition={{
@@ -32,7 +31,7 @@ const OneBotApiNavList: React.FC<OneBotApiNavListProps> = (props) => {
animate={{ width: openSideBar ? '16rem' : '0rem' }} animate={{ width: openSideBar ? '16rem' : '0rem' }}
style={{ overflowY: openSideBar ? 'auto' : 'hidden' }} style={{ overflowY: openSideBar ? 'auto' : 'hidden' }}
> >
<div className="w-64 h-full overflow-y-auto px-2 float-right"> <div className="w-64 h-full overflow-y-auto px-2 pt-2 pb-10 md:pb-0">
<Input <Input
className="sticky top-0 z-10 text-danger-600" className="sticky top-0 z-10 text-danger-600"
classNames={{ classNames={{

View File

@@ -10,6 +10,7 @@ import {
import { useCallback, useRef } from 'react' import { useCallback, useRef } from 'react'
import toast from 'react-hot-toast' import toast from 'react-hot-toast'
import ChatInputModal from '@/components/chat_input/modal'
import CodeEditor from '@/components/code_editor' import CodeEditor from '@/components/code_editor'
import type { CodeEditorRef } from '@/components/code_editor' import type { CodeEditorRef } from '@/components/code_editor'
@@ -72,6 +73,8 @@ const OneBotSendModal: React.FC<OneBotSendModalProps> = (props) => {
</div> </div>
</ModalBody> </ModalBody>
<ModalFooter> <ModalFooter>
<ChatInputModal />
<Button color="danger" variant="flat" onPress={onClose}> <Button color="danger" variant="flat" onPress={onClose}>
</Button> </Button>

View File

@@ -36,7 +36,9 @@ const XTerm = forwardRef<XTermRef, React.HTMLAttributes<HTMLDivElement>>(
allowTransparency: true, allowTransparency: true,
fontFamily: '"Fira Code", "Harmony", "Noto Serif SC", monospace', fontFamily: '"Fira Code", "Harmony", "Noto Serif SC", monospace',
cursorInactiveStyle: 'outline', cursorInactiveStyle: 'outline',
drawBoldTextInBrightColors: false drawBoldTextInBrightColors: false,
letterSpacing: 0,
lineHeight: 1.0
}) })
terminalRef.current = terminal terminalRef.current = terminal
const fitAddon = new FitAddon() const fitAddon = new FitAddon()
@@ -51,10 +53,6 @@ const XTerm = forwardRef<XTermRef, React.HTMLAttributes<HTMLDivElement>>(
terminal.loadAddon(new WebglAddon()) terminal.loadAddon(new WebglAddon())
terminal.open(domRef.current) terminal.open(domRef.current)
setTimeout(() => {
fitAddon.fit()
}, 0)
terminal.writeln( terminal.writeln(
gradientText( gradientText(
'Welcome to NapCat WebUI', 'Welcome to NapCat WebUI',
@@ -69,16 +67,16 @@ const XTerm = forwardRef<XTermRef, React.HTMLAttributes<HTMLDivElement>>(
const resizeObserver = new ResizeObserver(() => { const resizeObserver = new ResizeObserver(() => {
fitAddon.fit() fitAddon.fit()
}) })
resizeObserver.observe(domRef.current)
const handleFontLoad = () => { // 字体加载完成后重新调整终端大小
terminal.refresh(0, terminal.rows - 1) document.fonts.ready.then(() => {
} fitAddon.fit()
document.fonts.addEventListener('loadingdone', handleFontLoad)
resizeObserver.observe(domRef.current!)
})
return () => { return () => {
resizeObserver.disconnect() resizeObserver.disconnect()
document.fonts.removeEventListener('loadingdone', handleFontLoad)
setTimeout(() => { setTimeout(() => {
terminal.dispose() terminal.dispose()
}, 0) }, 0)

View File

@@ -2,7 +2,7 @@ import { BreadcrumbItem, Breadcrumbs } from '@heroui/breadcrumbs'
import { Button } from '@heroui/button' import { Button } from '@heroui/button'
import { useLocalStorage } from '@uidotdev/usehooks' import { useLocalStorage } from '@uidotdev/usehooks'
import clsx from 'clsx' import clsx from 'clsx'
import { motion } from 'motion/react' import { AnimatePresence, motion } from 'motion/react'
import { useEffect, useMemo, useRef } from 'react' import { useEffect, useMemo, useRef } from 'react'
import { ErrorBoundary } from 'react-error-boundary' import { ErrorBoundary } from 'react-error-boundary'
import { MdMenu, MdMenuOpen } from 'react-icons/md' import { MdMenu, MdMenuOpen } from 'react-icons/md'
@@ -79,7 +79,7 @@ const Layout: React.FC<{ children: React.ReactNode }> = ({ children }) => {
}, [location.pathname]) }, [location.pathname])
return ( return (
<div <div
className="h-screen relative flex bg-danger-50 dark:bg-black" className="h-screen relative flex bg-danger-50 dark:bg-black items-stretch"
style={{ style={{
backgroundImage: `url(${b64img})`, backgroundImage: `url(${b64img})`,
backgroundSize: 'cover' backgroundSize: 'cover'
@@ -89,13 +89,22 @@ const Layout: React.FC<{ children: React.ReactNode }> = ({ children }) => {
<div <div
ref={contentRef} ref={contentRef}
className={clsx( className={clsx(
'overflow-y-auto relative flex-1 rounded-md m-1 bg-content1 pb-10 md:pb-0', 'overflow-y-auto flex-1 rounded-md m-1 bg-content1 pb-10 md:pb-0',
openSideBar ? 'ml-0' : 'ml-1', openSideBar ? 'ml-0' : 'ml-1',
!b64img && 'shadow-inner', !b64img && 'shadow-inner',
b64img && '!bg-opacity-50 backdrop-blur-none dark:bg-background' b64img && '!bg-opacity-50 backdrop-blur-none dark:bg-background',
'overflow-x-hidden'
)}
>
<div
className={clsx(
'h-10 flex items-center hm-medium text-xl backdrop-blur-lg rounded-full',
'dark:bg-background dark:shadow-danger-100',
'bg-background !bg-opacity-50',
'shadow-sm shadow-danger-50',
'z-30 m-2 mb-0 sticky top-2 left-0'
)} )}
> >
<div className="h-10 flex items-center hm-medium text-xl sticky top-2 left-0 backdrop-blur-lg z-20 shadow-sm bg-background dark:bg-background shadow-danger-50 dark:shadow-danger-100 m-2 rounded-full !bg-opacity-50">
<motion.div <motion.div
className={clsx( className={clsx(
'mr-1 ease-in-out ml-0 md:relative', 'mr-1 ease-in-out ml-0 md:relative',
@@ -117,7 +126,19 @@ const Layout: React.FC<{ children: React.ReactNode }> = ({ children }) => {
</motion.div> </motion.div>
<Breadcrumbs isDisabled size="lg"> <Breadcrumbs isDisabled size="lg">
{title?.map((item, index) => ( {title?.map((item, index) => (
<BreadcrumbItem key={index}>{item}</BreadcrumbItem> <BreadcrumbItem key={index}>
<AnimatePresence mode="wait">
<motion.div
key={item}
initial={{ opacity: 0, y: -10 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: 10 }}
transition={{ duration: 0.3 }}
>
{item}
</motion.div>
</AnimatePresence>
</BreadcrumbItem>
))} ))}
</Breadcrumbs> </Breadcrumbs>
</div> </div>

View File

@@ -27,19 +27,15 @@ export default function HttpDebug() {
return ( return (
<> <>
<title>HTTP调试 - NapCat WebUI</title> <title>HTTP调试 - NapCat WebUI</title>
<div className="w-full h-[calc(100%-3.6rem)] flex items-stretch">
<OneBotApiNavList <OneBotApiNavList
data={oneBotHttpApi} data={oneBotHttpApi}
selectedApi={selectedApi} selectedApi={selectedApi}
onSelect={setSelectedApi} onSelect={setSelectedApi}
openSideBar={openSideBar} openSideBar={openSideBar}
/> />
<div <div ref={contentRef} className="flex-1 h-full overflow-x-hidden">
ref={contentRef}
className="flex-1 h-full overflow-x-hidden relative"
>
<motion.div <motion.div
className="sticky top-0 z-20 md:!ml-4" className="absolute top-16 z-30 md:!ml-4"
animate={{ marginLeft: openSideBar ? '16rem' : '1rem' }} animate={{ marginLeft: openSideBar ? '16rem' : '1rem' }}
transition={{ type: 'spring', stiffness: 150, damping: 15 }} transition={{ type: 'spring', stiffness: 150, damping: 15 }}
> >
@@ -62,7 +58,6 @@ export default function HttpDebug() {
</motion.div> </motion.div>
<OneBotApiDebug path={selectedApi} data={data} /> <OneBotApiDebug path={selectedApi} data={data} />
</div> </div>
</div>
</> </>
) )
} }

View File

@@ -7,7 +7,6 @@ import toast from 'react-hot-toast'
import key from '@/const/key' import key from '@/const/key'
import ChatInputModal from '@/components/chat_input/modal'
import OneBotMessageList from '@/components/onebot/message_list' import OneBotMessageList from '@/components/onebot/message_list'
import OneBotSendModal from '@/components/onebot/send_modal' import OneBotSendModal from '@/components/onebot/send_modal'
import WSStatus from '@/components/onebot/ws_status' import WSStatus from '@/components/onebot/ws_status'
@@ -82,7 +81,6 @@ export default function WSDebug() {
{FilterMessagesType} {FilterMessagesType}
</div> </div>
<OneBotSendModal sendMessage={sendMessage} /> <OneBotSendModal sendMessage={sendMessage} />
<ChatInputModal />
</div> </div>
</div> </div>
</CardBody> </CardBody>

View File

@@ -1,4 +1,5 @@
import { Route, Routes } from 'react-router-dom' import { AnimatePresence, motion } from 'motion/react'
import { Route, Routes, useLocation } from 'react-router-dom'
import DefaultLayout from '@/layouts/default' import DefaultLayout from '@/layouts/default'
@@ -12,9 +13,18 @@ import LogsPage from './dashboard/logs'
import NetworkPage from './dashboard/network' import NetworkPage from './dashboard/network'
export default function IndexPage() { export default function IndexPage() {
const location = useLocation()
return ( return (
<DefaultLayout> <DefaultLayout>
<Routes> <AnimatePresence mode="wait">
<motion.div
key={location.pathname}
initial={{ opacity: 0, y: 50 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -50 }}
transition={{ duration: 0.3 }}
>
<Routes location={location} key={location.pathname}>
<Route element={<DashboardIndexPage />} path="/" /> <Route element={<DashboardIndexPage />} path="/" />
<Route element={<NetworkPage />} path="/network" /> <Route element={<NetworkPage />} path="/network" />
<Route element={<ConfigPage />} path="/config" /> <Route element={<ConfigPage />} path="/config" />
@@ -25,6 +35,8 @@ export default function IndexPage() {
</Route> </Route>
<Route element={<AboutPage />} path="/about" /> <Route element={<AboutPage />} path="/about" />
</Routes> </Routes>
</motion.div>
</AnimatePresence>
</DefaultLayout> </DefaultLayout>
) )
} }

View File

@@ -44,6 +44,7 @@ body {
-moz-border-radius: 2em; -moz-border-radius: 2em;
border-radius: 2em; border-radius: 2em;
} }
.monaco-editor { .monaco-editor {
outline: none !important; outline: none !important;
border-radius: 5px !important; border-radius: 5px !important;
@@ -75,6 +76,10 @@ body {
border-radius: 5px !important; border-radius: 5px !important;
} }
.context-view.monaco-menu-container * {
font-family: PingFang SC,"Harmony",Helvetica Neue,Microsoft YaHei,sans-serif !important;
}
.ql-hidden { .ql-hidden {
@apply hidden; @apply hidden;
} }