mirror of
https://github.com/NapNeko/NapCatQQ.git
synced 2025-07-19 12:03:37 +00:00
feat: 新版webui
This commit is contained in:
15
napcat.webui/src/hooks/auth.ts
Normal file
15
napcat.webui/src/hooks/auth.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { useLocalStorage } from '@uidotdev/usehooks'
|
||||
|
||||
import key from '@/const/key'
|
||||
|
||||
const useAuth = () => {
|
||||
const [token, setToken] = useLocalStorage<string>(key.token, '')
|
||||
|
||||
return {
|
||||
token,
|
||||
isAuth: !!token,
|
||||
revokeAuth: () => setToken('')
|
||||
}
|
||||
}
|
||||
|
||||
export default useAuth
|
187
napcat.webui/src/hooks/use-config.ts
Normal file
187
napcat.webui/src/hooks/use-config.ts
Normal file
@@ -0,0 +1,187 @@
|
||||
import { updateConfig as storeUpdateConfig } from '@/store/modules/config'
|
||||
|
||||
import { deepClone } from '@/utils/object'
|
||||
|
||||
import QQManager from '@/controllers/qq_manager'
|
||||
|
||||
import { useAppDispatch, useAppSelector } from './use-store'
|
||||
|
||||
const useConfig = () => {
|
||||
const config = useAppSelector((state) => state.config.value)
|
||||
const dispatch = useAppDispatch()
|
||||
|
||||
const createNetworkConfig = async <T extends keyof OneBotConfig['network']>(
|
||||
key: T,
|
||||
value: OneBotConfig['network'][T][0]
|
||||
) => {
|
||||
if (
|
||||
value.name &&
|
||||
config.network[key].some((item) => item.name === value.name)
|
||||
) {
|
||||
throw new Error('已经存在相同的配置项名')
|
||||
}
|
||||
|
||||
const newConfig = deepClone(config)
|
||||
|
||||
;(newConfig.network[key] as (typeof value)[]).push(value)
|
||||
|
||||
await QQManager.setOB11Config(newConfig)
|
||||
|
||||
dispatch(storeUpdateConfig(newConfig))
|
||||
|
||||
return newConfig
|
||||
}
|
||||
|
||||
const updateNetworkConfig = async <T extends keyof OneBotConfig['network']>(
|
||||
key: T,
|
||||
value: OneBotConfig['network'][T][0]
|
||||
) => {
|
||||
const newConfig = deepClone(config)
|
||||
const name = value.name
|
||||
const index = newConfig.network[key].findIndex((item) => item.name === name)
|
||||
|
||||
if (index === -1) {
|
||||
throw new Error('找不到对应的配置项')
|
||||
}
|
||||
|
||||
newConfig.network[key][index] = value
|
||||
|
||||
await QQManager.setOB11Config(newConfig)
|
||||
|
||||
dispatch(storeUpdateConfig(newConfig))
|
||||
|
||||
return newConfig
|
||||
}
|
||||
|
||||
const deleteNetworkConfig = async <T extends keyof OneBotConfig['network']>(
|
||||
key: T,
|
||||
name: string
|
||||
) => {
|
||||
const newConfig = deepClone(config)
|
||||
const index = newConfig.network[key].findIndex((item) => item.name === name)
|
||||
|
||||
if (index === -1) {
|
||||
throw new Error('找不到对应的配置项')
|
||||
}
|
||||
|
||||
newConfig.network[key].splice(index, 1)
|
||||
|
||||
await QQManager.setOB11Config(newConfig)
|
||||
|
||||
dispatch(storeUpdateConfig(newConfig))
|
||||
|
||||
return newConfig
|
||||
}
|
||||
|
||||
const enableNetworkConfig = async <T extends keyof OneBotConfig['network']>(
|
||||
key: T,
|
||||
name: string
|
||||
) => {
|
||||
const newConfig = deepClone(config)
|
||||
const index = newConfig.network[key].findIndex((item) => item.name === name)
|
||||
|
||||
if (index === -1) {
|
||||
throw new Error('找不到对应的配置项')
|
||||
}
|
||||
|
||||
newConfig.network[key][index].enable = !newConfig.network[key][index].enable
|
||||
|
||||
await QQManager.setOB11Config(newConfig)
|
||||
|
||||
dispatch(storeUpdateConfig(newConfig))
|
||||
|
||||
return newConfig
|
||||
}
|
||||
|
||||
const enableDebugNetworkConfig = async <
|
||||
T extends keyof OneBotConfig['network']
|
||||
>(
|
||||
key: T,
|
||||
name: string
|
||||
) => {
|
||||
const newConfig = deepClone(config)
|
||||
const index = newConfig.network[key].findIndex((item) => item.name === name)
|
||||
|
||||
if (index === -1) {
|
||||
throw new Error('找不到对应的配置项')
|
||||
}
|
||||
|
||||
newConfig.network[key][index].debug = !newConfig.network[key][index].debug
|
||||
|
||||
await QQManager.setOB11Config(newConfig)
|
||||
|
||||
dispatch(storeUpdateConfig(newConfig))
|
||||
|
||||
return newConfig
|
||||
}
|
||||
|
||||
const updateSingleConfig = async <T extends keyof OneBotConfig>(
|
||||
key: T,
|
||||
value: OneBotConfig[T]
|
||||
) => {
|
||||
const newConfig = deepClone(config)
|
||||
|
||||
newConfig[key] = value
|
||||
|
||||
await QQManager.setOB11Config(newConfig)
|
||||
|
||||
dispatch(storeUpdateConfig(newConfig))
|
||||
|
||||
return newConfig
|
||||
}
|
||||
|
||||
const updateConfig = async (newConfig: OneBotConfig) => {
|
||||
await QQManager.setOB11Config(newConfig)
|
||||
|
||||
dispatch(storeUpdateConfig(newConfig))
|
||||
|
||||
return newConfig
|
||||
}
|
||||
|
||||
const refreshConfig = async () => {
|
||||
const newConfig = await QQManager.getOB11Config()
|
||||
|
||||
if (JSON.stringify(newConfig) === JSON.stringify(config)) {
|
||||
return config
|
||||
}
|
||||
|
||||
dispatch(storeUpdateConfig(newConfig))
|
||||
|
||||
return newConfig
|
||||
}
|
||||
|
||||
const mergeConfig = async (newConfig: OneBotConfig) => {
|
||||
const mergedConfig = deepClone(config)
|
||||
|
||||
Object.assign(mergedConfig, newConfig)
|
||||
|
||||
await QQManager.setOB11Config(mergedConfig)
|
||||
|
||||
dispatch(storeUpdateConfig(mergedConfig))
|
||||
|
||||
return mergedConfig
|
||||
}
|
||||
|
||||
const saveConfigWithoutNetwork = async (newConfig: OneBotConfig) => {
|
||||
newConfig.network = config.network
|
||||
await QQManager.setOB11Config(newConfig)
|
||||
dispatch(storeUpdateConfig(newConfig))
|
||||
return newConfig
|
||||
}
|
||||
|
||||
return {
|
||||
config,
|
||||
createNetworkConfig,
|
||||
refreshConfig,
|
||||
updateConfig,
|
||||
updateSingleConfig,
|
||||
updateNetworkConfig,
|
||||
deleteNetworkConfig,
|
||||
enableNetworkConfig,
|
||||
enableDebugNetworkConfig,
|
||||
mergeConfig,
|
||||
saveConfigWithoutNetwork
|
||||
}
|
||||
}
|
||||
|
||||
export default useConfig
|
11
napcat.webui/src/hooks/use-dialog.ts
Normal file
11
napcat.webui/src/hooks/use-dialog.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import React from 'react'
|
||||
|
||||
import { DialogContext } from '@/contexts/dialog'
|
||||
|
||||
const useDialog = () => {
|
||||
const dialog = React.useContext(DialogContext)
|
||||
|
||||
return dialog
|
||||
}
|
||||
|
||||
export default useDialog
|
11
napcat.webui/src/hooks/use-music.ts
Normal file
11
napcat.webui/src/hooks/use-music.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import React from 'react'
|
||||
|
||||
import { AudioContext } from '@/contexts/songs'
|
||||
|
||||
const useMusic = () => {
|
||||
const music = React.useContext(AudioContext)
|
||||
|
||||
return music
|
||||
}
|
||||
|
||||
export default useMusic
|
6
napcat.webui/src/hooks/use-store.ts
Normal file
6
napcat.webui/src/hooks/use-store.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { useDispatch, useSelector } from 'react-redux'
|
||||
|
||||
import type { AppDispatch, RootState } from '@/store'
|
||||
|
||||
export const useAppDispatch = useDispatch.withTypes<AppDispatch>()
|
||||
export const useAppSelector = useSelector.withTypes<RootState>()
|
42
napcat.webui/src/hooks/use-theme.ts
Normal file
42
napcat.webui/src/hooks/use-theme.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
// originally written by @imoaazahmed
|
||||
import { useLocalStorage } from '@uidotdev/usehooks'
|
||||
import { useEffect, useMemo } from 'react'
|
||||
|
||||
const ThemeProps = {
|
||||
key: 'theme',
|
||||
light: 'light',
|
||||
dark: 'dark'
|
||||
} as const
|
||||
|
||||
type Theme = typeof ThemeProps.light | typeof ThemeProps.dark
|
||||
|
||||
export const useTheme = (defaultTheme?: Theme) => {
|
||||
const [theme, setTheme] = useLocalStorage<Theme>(ThemeProps.key, defaultTheme)
|
||||
|
||||
const isDark = useMemo(() => {
|
||||
return theme === ThemeProps.dark
|
||||
}, [theme])
|
||||
|
||||
const isLight = useMemo(() => {
|
||||
return theme === ThemeProps.light
|
||||
}, [theme])
|
||||
|
||||
const _setTheme = (theme: Theme) => {
|
||||
setTheme(theme)
|
||||
document.documentElement.classList.remove(ThemeProps.light, ThemeProps.dark)
|
||||
document.documentElement.classList.add(theme)
|
||||
}
|
||||
|
||||
const setLightTheme = () => _setTheme(ThemeProps.light)
|
||||
|
||||
const setDarkTheme = () => _setTheme(ThemeProps.dark)
|
||||
|
||||
const toggleTheme = () =>
|
||||
theme === ThemeProps.dark ? setLightTheme() : setDarkTheme()
|
||||
|
||||
useEffect(() => {
|
||||
_setTheme(theme)
|
||||
})
|
||||
|
||||
return { theme, isDark, isLight, setLightTheme, setDarkTheme, toggleTheme }
|
||||
}
|
67
napcat.webui/src/hooks/use-websocket-debug.ts
Normal file
67
napcat.webui/src/hooks/use-websocket-debug.ts
Normal file
@@ -0,0 +1,67 @@
|
||||
import type { Selection } from '@react-types/shared'
|
||||
import { useReactive } from 'ahooks'
|
||||
import { useCallback, useState } from 'react'
|
||||
import toast from 'react-hot-toast'
|
||||
import useWebSocket from 'react-use-websocket'
|
||||
import { ReadyState } from 'react-use-websocket'
|
||||
|
||||
import { renderFilterMessageType } from '@/components/onebot/filter_message_type'
|
||||
|
||||
import { isOB11Event, isOB11RequestResponse } from '@/utils/onebot'
|
||||
|
||||
import type { AllOB11WsResponse } from '@/types/onebot'
|
||||
|
||||
export { ReadyState } from 'react-use-websocket'
|
||||
export function useWebSocketDebug(url: string, token: string) {
|
||||
const messageHistory = useReactive<AllOB11WsResponse[]>([])
|
||||
const [filterTypes, setFilterTypes] = useState<Selection>('all')
|
||||
|
||||
const filteredMessages = messageHistory.filter((msg) => {
|
||||
if (filterTypes === 'all' || filterTypes.size === 0) return true
|
||||
if (isOB11Event(msg)) return filterTypes.has(msg.post_type)
|
||||
if (isOB11RequestResponse(msg)) return filterTypes.has('request')
|
||||
})
|
||||
|
||||
const { sendMessage, readyState } = useWebSocket(url, {
|
||||
onMessage: useCallback((event: WebSocketEventMap['message']) => {
|
||||
try {
|
||||
const data = JSON.parse(event.data)
|
||||
messageHistory.unshift(data)
|
||||
} catch (error) {
|
||||
toast.error('WebSocket 消息解析失败')
|
||||
}
|
||||
}, []),
|
||||
queryParams: {
|
||||
access_token: token
|
||||
},
|
||||
onError: (event) => {
|
||||
toast.error('WebSocket 连接失败')
|
||||
console.error('WebSocket error:', event)
|
||||
},
|
||||
onOpen: () => {
|
||||
messageHistory.splice(0, messageHistory.length)
|
||||
}
|
||||
})
|
||||
|
||||
const _sendMessage = (msg: string) => {
|
||||
if (readyState !== ReadyState.OPEN) {
|
||||
throw new Error('WebSocket 连接未建立')
|
||||
}
|
||||
sendMessage(msg)
|
||||
}
|
||||
|
||||
const FilterMessagesType = renderFilterMessageType(
|
||||
filterTypes,
|
||||
setFilterTypes
|
||||
)
|
||||
|
||||
return {
|
||||
sendMessage: _sendMessage,
|
||||
readyState,
|
||||
messageHistory,
|
||||
filteredMessages,
|
||||
filterTypes,
|
||||
setFilterTypes,
|
||||
FilterMessagesType
|
||||
}
|
||||
}
|
31
napcat.webui/src/hooks/use_custom_quill.ts
Normal file
31
napcat.webui/src/hooks/use_custom_quill.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import Quill from 'quill'
|
||||
import 'quill/dist/quill.core.css'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
|
||||
interface UseCustomQuillProps {
|
||||
modules: Record<string, unknown>
|
||||
formats: string[]
|
||||
placeholder: string
|
||||
}
|
||||
|
||||
export const useCustomQuill = ({
|
||||
modules,
|
||||
formats,
|
||||
placeholder
|
||||
}: UseCustomQuillProps) => {
|
||||
const quillRef = useRef<HTMLDivElement | null>(null)
|
||||
const [quill, setQuill] = useState<Quill | null>(null)
|
||||
|
||||
useEffect(() => {
|
||||
if (quillRef.current) {
|
||||
const quillInstance = new Quill(quillRef.current, {
|
||||
modules,
|
||||
formats,
|
||||
placeholder
|
||||
})
|
||||
setQuill(quillInstance)
|
||||
}
|
||||
}, [])
|
||||
|
||||
return { quillRef, quill, Quill }
|
||||
}
|
25
napcat.webui/src/hooks/use_show_strcuted_message.tsx
Normal file
25
napcat.webui/src/hooks/use_show_strcuted_message.tsx
Normal file
@@ -0,0 +1,25 @@
|
||||
import { createElement } from 'react'
|
||||
|
||||
import ShowStructedMessage from '@/components/chat_input/components/show_structed_message'
|
||||
|
||||
import { OB11Segment } from '@/types/onebot'
|
||||
|
||||
import useDialog from './use-dialog'
|
||||
|
||||
const useShowStructuredMessage = () => {
|
||||
const dialog = useDialog()
|
||||
|
||||
const showStructuredMessage = (messages: OB11Segment[]) => {
|
||||
dialog.alert({
|
||||
title: '消息内容',
|
||||
size: '3xl',
|
||||
content: createElement(ShowStructedMessage, {
|
||||
messages: messages
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
return showStructuredMessage
|
||||
}
|
||||
|
||||
export default useShowStructuredMessage
|
Reference in New Issue
Block a user