Browse Source

fix: improve styling of messsges

pull/390/head
Dan Ditomaso 1 year ago
parent
commit
d9ad044ecd
  1. 96
      src/components/PageComponents/Messages/ChannelChat.tsx
  2. 89
      src/components/PageComponents/Messages/Message.tsx
  3. 2
      src/components/PageComponents/Messages/MessageInput.tsx
  4. 2
      src/components/UI/Footer.tsx
  5. 5
      src/components/UI/Sidebar/sidebarButton.tsx

96
src/components/PageComponents/Messages/ChannelChat.tsx

@ -8,6 +8,7 @@ import { MessageInput } from "@components/PageComponents/Messages/MessageInput.t
import { TraceRoute } from "@components/PageComponents/Messages/TraceRoute.tsx"; import { TraceRoute } from "@components/PageComponents/Messages/TraceRoute.tsx";
import type { Protobuf, Types } from "@meshtastic/js"; import type { Protobuf, Types } from "@meshtastic/js";
import { InboxIcon } from "lucide-react"; import { InboxIcon } from "lucide-react";
import type { JSX } from "react";
export interface ChannelChatProps { export interface ChannelChatProps {
messages?: MessageWithState[]; messages?: MessageWithState[];
@ -16,6 +17,13 @@ export interface ChannelChatProps {
traceroutes?: Types.PacketMetadata<Protobuf.Mesh.RouteDiscovery>[]; traceroutes?: Types.PacketMetadata<Protobuf.Mesh.RouteDiscovery>[];
} }
const EmptyState = () => (
<div className="flex flex-col place-content-center place-items-center p-8 text-white">
<InboxIcon className="h-8 w-8 mb-2" />
<span className="text-sm">No Messages</span>
</div>
);
export const ChannelChat = ({ export const ChannelChat = ({
messages, messages,
channel, channel,
@ -24,53 +32,53 @@ export const ChannelChat = ({
}: ChannelChatProps): JSX.Element => { }: ChannelChatProps): JSX.Element => {
const { nodes } = useDevice(); const { nodes } = useDevice();
if (!messages?.length) {
return (
<>
<div className="flex place-content-center place-items-center h-full">
<EmptyState />
</div>
<div className="mt-auto pb-4 w-full">
<MessageInput to={to} channel={channel} />
</div>
</>
);
}
return ( return (
<div className="flex flex-grow flex-col"> <>
<div className="flex flex-grow"> <div className="flex flex-col h-full">
<div className="flex flex-grow flex-col"> <div className="w-full">
{messages ? ( {messages.map((message, index) => (
messages.map((message, index) => ( <Message
<Message key={message.id}
key={message.id} message={message}
message={message} lastMsgSameUser={
lastMsgSameUser={ index > 0 && messages[index - 1].from === message.from
index === 0 }
? false sender={nodes.get(message.from)}
: messages[index - 1].from === message.from />
} ))}
sender={nodes.get(message.from)}
/>
))
) : (
<div className="m-auto">
<InboxIcon className="m-auto" />
<Subtle>No Messages</Subtle>
</div>
)}
</div> </div>
<div <div className="mt-auto pb-4 w-full">
className={`flex flex-grow flex-col border-slate-400 border-l ${traceroutes === undefined ? "hidden" : ""}`} <MessageInput to={to} channel={channel} />
>
{to === "broadcast" ? null : traceroutes ? (
traceroutes.map((traceroute, index) => (
<TraceRoute
key={traceroute.id}
from={nodes.get(traceroute.from)}
to={nodes.get(traceroute.to)}
route={traceroute.data.route}
/>
))
) : (
<div className="m-auto">
<InboxIcon className="m-auto" />
<Subtle>No Traceroutes</Subtle>
</div>
)}
</div> </div>
</div> </div>
<div className="pl-3 pr-3 pt-3 pb-1"> {/* {to === "broadcast" ? null : traceroutes ? (
<MessageInput to={to} channel={channel} /> traceroutes.map((traceroute, index) => (
</div> <TraceRoute
</div> key={traceroute.id}
from={nodes.get(traceroute.from)}
to={nodes.get(traceroute.to)}
route={traceroute.data.route}
/>
))
) : (
<div className="m-auto">
<InboxIcon className="m-auto" />
<Subtle>No Traceroutes</Subtle>
</div>
)} */}
</>
); );
}; };

89
src/components/PageComponents/Messages/Message.tsx

@ -1,11 +1,7 @@
import type { MessageWithState } from "@app/core/stores/deviceStore.ts"; import type { MessageWithState } from "@app/core/stores/deviceStore.ts";
import { Avatar } from "@components/UI/Avatar"; import { Avatar } from "@components/UI/Avatar";
import type { Protobuf } from "@meshtastic/js"; import type { Protobuf } from "@meshtastic/js";
import { import { AlertCircle, CheckCircle2, CircleEllipsis } from "lucide-react";
AlertCircleIcon,
CheckCircle2Icon,
CircleEllipsisIcon,
} from "lucide-react";
export interface MessageProps { export interface MessageProps {
lastMsgSameUser: boolean; lastMsgSameUser: boolean;
@ -13,61 +9,62 @@ export interface MessageProps {
sender?: Protobuf.Mesh.NodeInfo; sender?: Protobuf.Mesh.NodeInfo;
} }
const StatusIcon = ({ state }: { state: MessageWithState["state"] }) => {
const iconClass = "text-gray-500 dark:text-gray-400 w-4 h-4";
switch (state) {
case "ack":
return <CheckCircle2 className={iconClass} />;
case "waiting":
return <CircleEllipsis className={iconClass} />;
default:
return <AlertCircle className={iconClass} />;
}
};
export const Message = ({ lastMsgSameUser, message, sender }: MessageProps) => { export const Message = ({ lastMsgSameUser, message, sender }: MessageProps) => {
return lastMsgSameUser ? ( const messageTextClass =
<div className="ml-5 flex"> message.state === "ack"
{message.state === "ack" ? ( ? "text-gray-900 dark:text-white"
<CheckCircle2Icon size={16} className="my-auto text-textSecondary" /> : "text-gray-500 dark:text-gray-400";
) : message.state === "waiting" ? ( if (lastMsgSameUser) {
<CircleEllipsisIcon size={16} className="my-auto text-textSecondary" /> return (
) : ( <div className="mx-4 mt-2">
<AlertCircleIcon size={16} className="my-auto text-textSecondary" /> <div className="ml-12 flex items-start gap-2">
)} <div
<span className={`${messageTextClass} border-l-2 border-gray-200 dark:border-gray-700 pl-4 flex-grow`}
className={`ml-4 border-l-2 border-l-backgroundPrimary pl-2 ${ >
message.state === "ack" ? "text-textPrimary" : "text-textSecondary" {message.data}
}`} </div>
> <StatusIcon state={message.state} />
{message.data}
</span>
</div>
) : (
<div className="mx-4 mt-2 gap-2">
<div className="flex gap-2">
<div className="w-6 cursor-pointer">
<Avatar text={sender?.user?.shortName ?? "UNK"} />
</div> </div>
<span className="cursor-pointer font-medium text-textPrimary"> </div>
);
}
return (
<div className="mx-4 mt-2 space-y-2">
<div className="flex items-center gap-2">
<Avatar text={sender?.user?.shortName ?? "UNK"} />
<span className="font-medium text-gray-900 dark:text-white">
{sender?.user?.longName ?? "UNK"} {sender?.user?.longName ?? "UNK"}
</span> </span>
<span className="mt-1 font-mono text-xs text-textSecondary"> <span className="text-xs text-gray-500 dark:text-gray-400 font-mono">
{message.rxTime.toLocaleDateString()} {message.rxTime.toLocaleDateString()}
</span> </span>
<span className="mt-1 font-mono text-xs text-textSecondary"> <span className="text-xs text-gray-500 dark:text-gray-400 font-mono">
{message.rxTime.toLocaleTimeString(undefined, { {message.rxTime.toLocaleTimeString(undefined, {
hour: "2-digit", hour: "2-digit",
minute: "2-digit", minute: "2-digit",
})} })}
</span> </span>
</div> </div>
<div className="ml-1 flex"> <div className="ml-12 flex items-start gap-2">
{message.state === "ack" ? ( <div
<CheckCircle2Icon size={16} className="my-auto text-textSecondary" /> className={`${messageTextClass} border-l-2 border-gray-200 dark:border-gray-700 pl-4 flex-grow`}
) : message.state === "waiting" ? (
<CircleEllipsisIcon
size={16}
className="my-auto text-textSecondary"
/>
) : (
<AlertCircleIcon size={16} className="my-auto text-textSecondary" />
)}
<span
className={`ml-4 border-l-2 border-l-backgroundPrimary pl-2 ${
message.state === "ack" ? "text-textPrimary" : "text-textSecondary"
}`}
> >
{message.data} {message.data}
</span> </div>
<StatusIcon state={message.state} />
</div> </div>
</div> </div>
); );

2
src/components/PageComponents/Messages/MessageInput.tsx

@ -4,7 +4,7 @@ import { Input } from "@components/UI/Input.tsx";
import { useDevice } from "@core/stores/deviceStore.ts"; import { useDevice } from "@core/stores/deviceStore.ts";
import type { Types } from "@meshtastic/js"; import type { Types } from "@meshtastic/js";
import { SendIcon } from "lucide-react"; import { SendIcon } from "lucide-react";
import { useCallback, useMemo, useState } from "react"; import { type JSX, useCallback, useMemo, useState } from "react";
export interface MessageInputProps { export interface MessageInputProps {
to: Types.Destination; to: Types.Destination;

2
src/components/UI/Footer.tsx

@ -6,7 +6,7 @@ const Footer = React.forwardRef<HTMLElement, FooterProps>(
({ className, ...props }, ref) => { ({ className, ...props }, ref) => {
return ( return (
<footer <footer
className={`flex flex- justify-center p-2 ${className}`} className={`flex mt-auto justify-center p-2 ${className}`}
style={{ style={{
backgroundColor: "var(--backgroundPrimary)", backgroundColor: "var(--backgroundPrimary)",
color: "var(--textPrimary)", color: "var(--textPrimary)",

5
src/components/UI/Sidebar/sidebarButton.tsx

@ -1,5 +1,6 @@
import { Button } from "@components/UI/Button.tsx"; import { Button } from "@components/UI/Button.tsx";
import type { LucideIcon } from "lucide-react"; import type { LucideIcon } from "lucide-react";
import type { JSX } from "react";
export interface SidebarButtonProps { export interface SidebarButtonProps {
label: string; label: string;
@ -20,10 +21,10 @@ export const SidebarButton = ({
onClick={onClick} onClick={onClick}
variant={active ? "subtle" : "ghost"} variant={active ? "subtle" : "ghost"}
size="sm" size="sm"
className="w-full justify-start gap-2" className="flex gap-2"
> >
{Icon && <Icon size={16} />} {Icon && <Icon size={16} />}
{element && element} {element && element}
{label} <span className="flex flex-1 justify-start flex-shrink-0">{label}</span>
</Button> </Button>
); );

Loading…
Cancel
Save