mirror of
https://github.com/johndoe6345789/metabuilder.git
synced 2026-04-24 13:54:57 +00:00
refactor: migrate IRC Webchat components to Lua package structure and update ChatTabContent
This commit is contained in:
@@ -1,19 +1,19 @@
|
||||
import type { User } from '@/lib/level-types'
|
||||
import { Box, Typography } from '@mui/material'
|
||||
|
||||
import { IRCWebchatDeclarative } from '../../misc/demos/IRCWebchatDeclarative'
|
||||
import { ResultsPane } from '../sections/ResultsPane'
|
||||
|
||||
interface ChatTabContentProps {
|
||||
user: User
|
||||
}
|
||||
|
||||
export function ChatTabContent({ user }: ChatTabContentProps) {
|
||||
export function ChatTabContent() {
|
||||
return (
|
||||
<ResultsPane
|
||||
title="Webchat"
|
||||
description="Collaborate with other builders in real-time via the IRC channel."
|
||||
>
|
||||
<IRCWebchatDeclarative user={user} channelName="general" />
|
||||
<Box sx={{ p: 4, border: '2px dashed', borderColor: 'divider', borderRadius: 1 }}>
|
||||
<Typography variant="h6">IRC Webchat</Typography>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
This component is now a Lua package. See packages/irc_webchat/
|
||||
</Typography>
|
||||
</Box>
|
||||
</ResultsPane>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -94,7 +94,7 @@ export function Level2({ user, onLogout, onNavigate }: Level2Props) {
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="chat" className="space-y-6">
|
||||
<ChatTabContent user={currentUser} />
|
||||
<ChatTabContent />
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</div>
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
/**
|
||||
* This file has been refactored into modular lambda-per-file structure.
|
||||
*
|
||||
* Import individual functions or use the class wrapper:
|
||||
* @example
|
||||
* import { IRCWebchatDeclarative } from './IRCWebchatDeclarative'
|
||||
*
|
||||
* @example
|
||||
* import { IRCWebchatDeclarativeUtils } from './IRCWebchatDeclarative'
|
||||
* IRCWebchatDeclarativeUtils.IRCWebchatDeclarative(...)
|
||||
*/
|
||||
|
||||
export * from './IRCWebchatDeclarative'
|
||||
@@ -1,172 +0,0 @@
|
||||
import { Gear, PaperPlaneTilt, SignOut, Users } from '@phosphor-icons/react'
|
||||
|
||||
import {
|
||||
Badge,
|
||||
Button,
|
||||
Card,
|
||||
CardContent,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
Input,
|
||||
ScrollArea,
|
||||
} from '@/components/ui'
|
||||
|
||||
import type { ChatMessage } from './types'
|
||||
import { UserList } from './UserList'
|
||||
|
||||
interface ChatWindowProps {
|
||||
channelName: string
|
||||
messages: ChatMessage[]
|
||||
formattedTimes: Record<string, string>
|
||||
onlineUsers: string[]
|
||||
inputMessage: string
|
||||
onInputChange: (value: string) => void
|
||||
onSendMessage: () => void
|
||||
onToggleSettings: () => void
|
||||
showSettings: boolean
|
||||
onClose?: () => void
|
||||
onInputKeyPress?: (event: React.KeyboardEvent) => void
|
||||
}
|
||||
|
||||
export function ChatWindow({
|
||||
channelName,
|
||||
messages,
|
||||
formattedTimes,
|
||||
onlineUsers,
|
||||
inputMessage,
|
||||
onInputChange,
|
||||
onSendMessage,
|
||||
onToggleSettings,
|
||||
showSettings,
|
||||
onClose,
|
||||
onInputKeyPress,
|
||||
}: ChatWindowProps) {
|
||||
const getMessageStyle = (message: ChatMessage) => {
|
||||
if (
|
||||
message.type === 'system' ||
|
||||
message.type === 'join' ||
|
||||
message.type === 'leave' ||
|
||||
message.type === 'command'
|
||||
) {
|
||||
return 'text-muted-foreground italic text-sm'
|
||||
}
|
||||
return ''
|
||||
}
|
||||
|
||||
return (
|
||||
<Card className="h-[600px] flex flex-col">
|
||||
<CardHeader className="border-b border-border pb-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<CardTitle className="flex items-center gap-2 text-lg">
|
||||
<span className="font-mono">#</span>
|
||||
{channelName}
|
||||
</CardTitle>
|
||||
<div className="flex items-center gap-2">
|
||||
<Badge variant="secondary" className="gap-1.5">
|
||||
<Users size={14} />
|
||||
{onlineUsers.length}
|
||||
</Badge>
|
||||
<Button size="sm" variant="ghost" onClick={onToggleSettings}>
|
||||
<Gear size={16} />
|
||||
</Button>
|
||||
{onClose && (
|
||||
<Button size="sm" variant="ghost" onClick={onClose}>
|
||||
<SignOut size={16} />
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent className="flex-1 flex flex-col p-0 overflow-hidden">
|
||||
<div className="flex flex-1 overflow-hidden">
|
||||
<ScrollArea className="flex-1 p-4">
|
||||
<div className="space-y-2 font-mono text-sm">
|
||||
{messages.map(message => (
|
||||
<div key={message.id} className={getMessageStyle(message)}>
|
||||
{message.type === 'message' && (
|
||||
<div className="flex gap-2">
|
||||
<span className="text-muted-foreground shrink-0">
|
||||
{formattedTimes[message.id] || ''}
|
||||
</span>
|
||||
<span className="font-semibold shrink-0 text-primary">
|
||||
<{message.username}>
|
||||
</span>
|
||||
<span className="break-words">{message.message}</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{message.type === 'system' && message.username === 'System' && (
|
||||
<div className="flex gap-2">
|
||||
<span className="text-muted-foreground shrink-0">
|
||||
{formattedTimes[message.id] || ''}
|
||||
</span>
|
||||
<span>*** {message.message}</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{message.type === 'system' && message.username !== 'System' && (
|
||||
<div className="flex gap-2">
|
||||
<span className="text-muted-foreground shrink-0">
|
||||
{formattedTimes[message.id] || ''}
|
||||
</span>
|
||||
<span className="text-accent">
|
||||
* {message.username} {message.message}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{(message.type === 'join' || message.type === 'leave') && (
|
||||
<div className="flex gap-2">
|
||||
<span className="text-muted-foreground shrink-0">
|
||||
{formattedTimes[message.id] || ''}
|
||||
</span>
|
||||
<span
|
||||
className={message.type === 'join' ? 'text-green-500' : 'text-orange-500'}
|
||||
>
|
||||
--> {message.message}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{message.type === 'command' && (
|
||||
<div className="flex gap-2">
|
||||
<span className="text-muted-foreground shrink-0">
|
||||
{formattedTimes[message.id] || ''}
|
||||
</span>
|
||||
<span className="text-muted-foreground">{message.message}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</ScrollArea>
|
||||
|
||||
{showSettings && (
|
||||
<div className="w-48 border-l border-border p-4 bg-muted/20">
|
||||
<h4 className="font-semibold text-sm mb-3">Online Users</h4>
|
||||
<UserList users={onlineUsers} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="border-t border-border p-4">
|
||||
<div className="flex gap-2">
|
||||
<Input
|
||||
value={inputMessage}
|
||||
onChange={event => onInputChange(event.target.value)}
|
||||
onKeyPress={onInputKeyPress}
|
||||
placeholder="Type a message... (/help for commands)"
|
||||
className="flex-1 font-mono"
|
||||
/>
|
||||
<Button onClick={onSendMessage} size="icon">
|
||||
<PaperPlaneTilt size={18} />
|
||||
</Button>
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground mt-2">
|
||||
Press Enter to send. Type /help for commands.
|
||||
</p>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
interface UserListProps {
|
||||
users: string[]
|
||||
}
|
||||
|
||||
export function UserList({ users }: UserListProps) {
|
||||
if (users.length === 0) {
|
||||
return <p className="text-sm text-muted-foreground">No users online</p>
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-1.5 text-sm">
|
||||
{users.map(username => (
|
||||
<div key={username} className="flex items-center gap-2">
|
||||
<div className="w-2 h-2 rounded-full bg-green-500" />
|
||||
<span>{username}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
import { useEffect, useState } from 'react'
|
||||
|
||||
import type { ChatMessage } from './types'
|
||||
|
||||
type TimestampFormatter = (timestamp: number) => Promise<string> | string
|
||||
|
||||
export function useChatInput(onSubmit: () => void) {
|
||||
const [inputMessage, setInputMessage] = useState('')
|
||||
|
||||
const handleKeyPress = (event: React.KeyboardEvent) => {
|
||||
if (event.key === 'Enter' && !event.shiftKey) {
|
||||
event.preventDefault()
|
||||
onSubmit()
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
inputMessage,
|
||||
setInputMessage,
|
||||
handleKeyPress,
|
||||
}
|
||||
}
|
||||
|
||||
export function useFormattedTimes(
|
||||
messages: ChatMessage[] | undefined,
|
||||
formatTime: TimestampFormatter
|
||||
) {
|
||||
const [formattedTimes, setFormattedTimes] = useState<Record<string, string>>({})
|
||||
|
||||
useEffect(() => {
|
||||
let isMounted = true
|
||||
|
||||
const formatAllTimes = async () => {
|
||||
if (!messages) {
|
||||
setFormattedTimes({})
|
||||
return
|
||||
}
|
||||
|
||||
const entries = await Promise.all(
|
||||
messages.map(async message => {
|
||||
const formatted = await formatTime(message.timestamp)
|
||||
return [message.id, formatted] as const
|
||||
})
|
||||
)
|
||||
|
||||
if (isMounted) {
|
||||
setFormattedTimes(Object.fromEntries(entries))
|
||||
}
|
||||
}
|
||||
|
||||
formatAllTimes()
|
||||
|
||||
return () => {
|
||||
isMounted = false
|
||||
}
|
||||
}, [messages, formatTime])
|
||||
|
||||
return formattedTimes
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
export type ChatMessageType = 'message' | 'system' | 'join' | 'leave' | 'command'
|
||||
|
||||
export interface ChatMessage {
|
||||
id: string
|
||||
username: string
|
||||
userId: string
|
||||
message: string
|
||||
timestamp: number
|
||||
type: ChatMessageType
|
||||
}
|
||||
@@ -105,7 +105,6 @@ export { FieldRenderer } from '../FieldRenderer'
|
||||
export { GenericPage } from '../GenericPage'
|
||||
export { GitHubActionsFetcher } from '../GitHubActionsFetcher'
|
||||
export { GodCredentialsSettings } from '../GodCredentialsSettings'
|
||||
export { IRCWebchatDeclarative } from '../misc/demos/IRCWebchatDeclarative'
|
||||
export { JsonEditor } from '../JsonEditor'
|
||||
export { ContactSection } from '../level1/ContactSection'
|
||||
export { FeaturesSection } from '../level1/FeaturesSection'
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import type React from 'react'
|
||||
|
||||
import { IRCWebchatDeclarative } from '@/components/misc/demos/IRCWebchatDeclarative'
|
||||
import { NotificationSummaryCard } from '@/components/NotificationSummaryCard'
|
||||
import {
|
||||
Alert,
|
||||
@@ -39,21 +38,11 @@ export function RenderNode({ component, renderChildren, user }: RenderNodeProps)
|
||||
const renderer = getDeclarativeRenderer()
|
||||
|
||||
if (renderer.hasComponentConfig(type)) {
|
||||
if (type === 'IRCWebchat' && user) {
|
||||
return (
|
||||
<IRCWebchatDeclarative
|
||||
user={user}
|
||||
channelName={props.channelName || 'general'}
|
||||
onClose={props.onClose}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="p-4 border-2 border-dashed border-accent">
|
||||
Declarative Component: {type}
|
||||
Lua Package Component: {type}
|
||||
<div className="text-xs text-muted-foreground mt-2">
|
||||
This is a package-defined component
|
||||
This component is rendered from packages/irc_webchat
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
136
packages/irc_webchat/seed/layout.json
Normal file
136
packages/irc_webchat/seed/layout.json
Normal file
@@ -0,0 +1,136 @@
|
||||
{
|
||||
"id": "irc_webchat_layout",
|
||||
"type": "Card",
|
||||
"props": {
|
||||
"className": "h-[500px] flex flex-col"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"id": "header",
|
||||
"type": "CardHeader",
|
||||
"props": {
|
||||
"className": "border-b pb-3"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"id": "title_row",
|
||||
"type": "Flex",
|
||||
"props": {
|
||||
"className": "items-center justify-between"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"id": "channel_title",
|
||||
"type": "Typography",
|
||||
"props": {
|
||||
"variant": "h6",
|
||||
"text": "#{channelName}"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "user_count",
|
||||
"type": "Badge",
|
||||
"props": {
|
||||
"variant": "secondary",
|
||||
"text": "{onlineUsersCount} online"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "messages_area",
|
||||
"type": "ScrollArea",
|
||||
"props": {
|
||||
"className": "flex-1 p-4"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"id": "message_list",
|
||||
"type": "Stack",
|
||||
"props": {
|
||||
"spacing": 1,
|
||||
"dataSource": "messages"
|
||||
},
|
||||
"itemTemplate": {
|
||||
"id": "message_item",
|
||||
"type": "Box",
|
||||
"props": {
|
||||
"className": "font-mono text-sm"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"id": "timestamp",
|
||||
"type": "Typography",
|
||||
"props": {
|
||||
"variant": "caption",
|
||||
"color": "text.secondary",
|
||||
"text": "[{formattedTime}]"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "username",
|
||||
"type": "Typography",
|
||||
"props": {
|
||||
"variant": "body2",
|
||||
"component": "span",
|
||||
"fontWeight": "bold",
|
||||
"text": " <{username}> "
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "message_text",
|
||||
"type": "Typography",
|
||||
"props": {
|
||||
"variant": "body2",
|
||||
"component": "span",
|
||||
"text": "{message}"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "input_area",
|
||||
"type": "Box",
|
||||
"props": {
|
||||
"className": "border-t p-4"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"id": "input_row",
|
||||
"type": "Flex",
|
||||
"props": {
|
||||
"gap": 2
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"id": "message_input",
|
||||
"type": "TextField",
|
||||
"props": {
|
||||
"fullWidth": true,
|
||||
"size": "small",
|
||||
"placeholder": "Type a message... (/help for commands)",
|
||||
"value": "{inputMessage}",
|
||||
"onKeyPress": "handleKeyPress",
|
||||
"onChange": "updateInputMessage"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "send_button",
|
||||
"type": "Button",
|
||||
"props": {
|
||||
"variant": "contained",
|
||||
"onClick": "handleSendMessage",
|
||||
"text": "Send"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -9,6 +9,7 @@
|
||||
"dependencies": [],
|
||||
"exports": {
|
||||
"components": ["IRCWebchat"],
|
||||
"layouts": ["layout.json"],
|
||||
"luaScripts": [
|
||||
"send_message",
|
||||
"handle_command",
|
||||
|
||||
Reference in New Issue
Block a user