增加toast提示

main
落雨楓 3 months ago
parent 4b203902d4
commit f7f9425117

3
.gitignore vendored

@ -38,4 +38,5 @@ next-env.d.ts
*__debug*
movie-sync*
dist
dist
/main

@ -28,6 +28,7 @@
"react": "^18",
"react-dom": "^18",
"react-icons": "^5.0.1",
"react-toastify": "^11.0.5",
"socket.io-client": "^4.7.4",
"tailwind-merge": "^2.2.0",
"tailwindcss-animate": "^1.0.7"

@ -65,6 +65,9 @@ importers:
react-icons:
specifier: ^5.0.1
version: 5.5.0(react@18.3.1)
react-toastify:
specifier: ^11.0.5
version: 11.0.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
socket.io-client:
specifier: ^4.7.4
version: 4.8.1
@ -2449,6 +2452,12 @@ packages:
'@types/react':
optional: true
react-toastify@11.0.5:
resolution: {integrity: sha512-EpqHBGvnSTtHYhCPLxML05NLY2ZX0JURbAdNYa6BUkk+amz4wbKBQvoKQAB0ardvSarUBuY4Q4s1sluAzZwkmA==}
peerDependencies:
react: ^18 || ^19
react-dom: ^18 || ^19
react@18.3.1:
resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==}
engines: {node: '>=0.10.0'}
@ -5489,6 +5498,12 @@ snapshots:
optionalDependencies:
'@types/react': 18.3.23
react-toastify@11.0.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
dependencies:
clsx: 2.1.1
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
react@18.3.1:
dependencies:
loose-envify: 1.4.0

@ -32,16 +32,13 @@ func EmbedFolder(fsEmbed embed.FS, targetPath string) static.ServeFileSystem {
func FindNextIndexFile(currentPath string, fsys static.ServeFileSystem) string {
findPath := strings.TrimSuffix(currentPath, "/")
logrus.Infoln("Finding index file for path:", findPath)
testFile := findPath + ".html"
logrus.Infoln("Checking for HTML file:", testFile)
if fsys.Exists("", testFile) {
return testFile
}
testFile = findPath + "/index.html"
logrus.Infoln("Checking for index file:", testFile)
if fsys.Exists("", testFile) {
return testFile
}
@ -60,7 +57,6 @@ func FindNextIndexFile(currentPath string, fsys static.ServeFileSystem) string {
endfix := findPath[lastSlash+1:]
testFile = findPath + "/[" + endfix + "].html"
logrus.Infoln("Checking for dynamic route file:", testFile)
if fsys.Exists("", testFile) {
return testFile
}

@ -0,0 +1,31 @@
import { ToastContentProps } from "react-toastify";
import { cn } from "@/lib/utils"
type CustomNotificationProps = ToastContentProps<{
title: string;
content: string;
}>;
export function CustomNotification({
closeToast,
data,
toastProps,
}: CustomNotificationProps) {
const isColored = toastProps.theme === 'colored';
return (
<div className="flex flex-col w-full">
<h3
className={cn(
'text-sm font-semibold',
isColored ? 'text-white' : 'text-zinc-800'
)}
>
{data.title}
</h3>
<div className="flex items-center justify-between">
<p className="text-sm">{data.content}</p>
</div>
</div>
);
}

@ -4,6 +4,8 @@ import { ClientMessage, ClientUserStatus, RoomState, RoomStateChangedMessage, Se
import { $playerState, $userInfo, $userStatus } from '@/store/player'
import { useStore as useNanoStore } from '@nanostores/react'
import { getUserPlayTimeSeconds, unixTimestampWithOffset } from '@/lib/utils'
import { toast } from 'react-toastify'
import { CustomNotification } from './custom-notification'
const TIME_SYNC_ALLOWANCE = 5 // 如果播放时间与服务器时间差小于此值,则认为是同步的
@ -94,6 +96,18 @@ export const Player = ({ roomName }: { roomName: string }) => {
room: roomName,
username: $userInfo.value.username,
} as ClientMessage)
} else {
// 如果是普通用户,检测是否可以开始播放
if (!prevRoomState.current.playing) {
player.current.pause()
toast.info(CustomNotification, {
data: {
title: '提示',
content: '已准备好播放,正在等待房管开始播放...'
}
});
}
}
// 标记用户开始播放

@ -0,0 +1,21 @@
import * as React from "react"
import { cn } from "@/lib/utils"
export interface TextAreaProps
extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {}
export const TextArea = React.forwardRef<HTMLTextAreaElement, TextAreaProps>(
({ className, ...props }, ref) => {
return (
<textarea
className={cn(
"flex w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50",
className
)}
ref={ref}
{...props}
/>
)
}
)

@ -2,9 +2,11 @@ import { Player } from '@/components/player'
import { socket } from '@/components/socket'
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
import { TextArea } from '@/components/ui/textarea'
import { UserList } from '@/components/user-list'
import { toast, ToastContainer } from 'react-toastify'
import { Badge, Tabs } from '@radix-ui/themes'
import { ClientMessage, RoomStateChangedMessage, ServerMessage, UserJoinLeaveMessage } from '@/lib/types/message'
import { ClientMessage, RoomStateChangedMessage, ServerMessage, ServerNotificationMessage, UserJoinLeaveMessage } from '@/lib/types/message'
import { $playerState, $userInfo, $userStatus } from '@/store/player'
import { useStore } from '@nanostores/react'
import { useRouter } from 'next/router'
@ -13,6 +15,7 @@ import "@radix-ui/themes/components/tabs"
import { Label } from '@/components/ui/label'
import { useSignal } from '@/lib/signal'
import Head from 'next/head'
import { CustomNotification } from '@/components/custom-notification'
export default function Page() {
const router = useRouter()
@ -85,9 +88,40 @@ export default function Page() {
})
}
function onServerMessage(d: any) {
const msg = d as ServerNotificationMessage;
switch (msg.severity) {
case 'info':
toast.info(CustomNotification, {
data: {
title: msg.title || '提示',
content: msg.message
}
});
break;
case 'warning':
toast.warning(CustomNotification, {
data: {
title: msg.title || '提示',
content: msg.message
}
});
break;
case 'error':
toast.error(CustomNotification, {
data: {
title: msg.title || '提示',
content: msg.message
}
});
break;
}
}
socket.on('connect', onConnect)
socket.on('roomInfo', onRoomInfo)
socket.on('joined', onJoined)
socket.on('message', onServerMessage)
if (socket.connected) {
onConnect()
@ -97,6 +131,7 @@ export default function Page() {
socket.off('connect', onConnect)
socket.off('roomInfo', onRoomInfo)
socket.off('joined', onJoined)
socket.off('message', onServerMessage)
}
}, [roomName])
@ -116,7 +151,7 @@ export default function Page() {
username: $userInfo.value?.username,
url: urlInput.value,
} as ClientMessage)
}, [])
}, [roomName])
const isAllowedLogin = useCallback(() => {
if (isJoined.value) {
@ -134,6 +169,9 @@ export default function Page() {
<Head>
{roomName ? <title>{roomName} | </title> : <title></title>}
</Head>
<ToastContainer theme="colored">
</ToastContainer>
{isJoined.value && <div className='w-full lg:mr-2'>
<Player roomName={roomName} />
</div>}
@ -191,16 +229,16 @@ export default function Page() {
disabled={!isAllowedLogin()}
>{isRoomEmpty.value ? "创建房间" : "加入房间"}</Button>
</div>}
{isJoined.value && userInfo?.isAdmin && <div className='m-2 flex flex-row gap-2'>
<Input
{isJoined.value && userInfo?.isAdmin && <div className='m-2 flex flex-col gap-2'>
<TextArea
name='videoUrl'
type='url'
value={urlInput.value}
onChange={(e) => {
urlInput.value = e.target.value
}}
autoComplete='url'
placeholder='视频直链'
rows={3}
placeholder='视频链接,一行一个,填写多个时会从上到下尝试读取'
/>
<Button onClick={handleSetUrlClick}></Button>
</div>}

Loading…
Cancel
Save