Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: improve styling of messages #390

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 52 additions & 44 deletions src/components/PageComponents/Messages/ChannelChat.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { MessageInput } from "@components/PageComponents/Messages/MessageInput.t
import { TraceRoute } from "@components/PageComponents/Messages/TraceRoute.tsx";
import type { Protobuf, Types } from "@meshtastic/js";
import { InboxIcon } from "lucide-react";
import type { JSX } from "react";

export interface ChannelChatProps {
messages?: MessageWithState[];
Expand All @@ -16,6 +17,13 @@ export interface ChannelChatProps {
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 = ({
messages,
channel,
Expand All @@ -24,53 +32,53 @@ export const ChannelChat = ({
}: ChannelChatProps): JSX.Element => {
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 (
<div className="flex flex-grow flex-col">
<div className="flex flex-grow">
<div className="flex flex-grow flex-col">
{messages ? (
messages.map((message, index) => (
<Message
key={message.id}
message={message}
lastMsgSameUser={
index === 0
? false
: 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 className="flex flex-col h-full">
<div className="w-full">
{messages.map((message, index) => (
<Message
key={message.id}
message={message}
lastMsgSameUser={
index > 0 && messages[index - 1].from === message.from
}
sender={nodes.get(message.from)}
/>
))}
</div>
<div
className={`flex flex-grow flex-col border-slate-400 border-l ${traceroutes === undefined ? "hidden" : ""}`}
>
{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 className="mt-auto pb-4 w-full">
<MessageInput to={to} channel={channel} />
</div>
</div>
<div className="pl-3 pr-3 pt-3 pb-1">
<MessageInput to={to} channel={channel} />
</div>
</div>
{/* {to === "broadcast" ? null : traceroutes ? (
Hunter275 marked this conversation as resolved.
Show resolved Hide resolved
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>
)} */}
</>
);
};
89 changes: 43 additions & 46 deletions src/components/PageComponents/Messages/Message.tsx
Original file line number Diff line number Diff line change
@@ -1,73 +1,70 @@
import type { MessageWithState } from "@app/core/stores/deviceStore.ts";
import { Avatar } from "@components/UI/Avatar";
import type { Protobuf } from "@meshtastic/js";
import {
AlertCircleIcon,
CheckCircle2Icon,
CircleEllipsisIcon,
} from "lucide-react";
import { AlertCircle, CheckCircle2, CircleEllipsis } from "lucide-react";

export interface MessageProps {
lastMsgSameUser: boolean;
message: MessageWithState;
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) => {
return lastMsgSameUser ? (
<div className="ml-5 flex">
{message.state === "ack" ? (
<CheckCircle2Icon size={16} className="my-auto text-textSecondary" />
) : 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}
</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"} />
const messageTextClass =
message.state === "ack"
? "text-gray-900 dark:text-white"
: "text-gray-500 dark:text-gray-400";
if (lastMsgSameUser) {
return (
<div className="mx-4 mt-2">
<div className="ml-12 flex items-start gap-2">
<div
className={`${messageTextClass} border-l-2 border-gray-200 dark:border-gray-700 pl-4 flex-grow`}
>
{message.data}
</div>
<StatusIcon state={message.state} />
</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"}
</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()}
</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, {
hour: "2-digit",
minute: "2-digit",
})}
</span>
</div>
<div className="ml-1 flex">
{message.state === "ack" ? (
<CheckCircle2Icon size={16} className="my-auto text-textSecondary" />
) : 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"
}`}
<div className="ml-12 flex items-start gap-2">
<div
className={`${messageTextClass} border-l-2 border-gray-200 dark:border-gray-700 pl-4 flex-grow`}
>
{message.data}
</span>
</div>
<StatusIcon state={message.state} />
</div>
</div>
);
Expand Down
2 changes: 1 addition & 1 deletion src/components/PageComponents/Messages/MessageInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { Input } from "@components/UI/Input.tsx";
import { useDevice } from "@core/stores/deviceStore.ts";
import type { Types } from "@meshtastic/js";
import { SendIcon } from "lucide-react";
import { useCallback, useMemo, useState } from "react";
import { type JSX, useCallback, useMemo, useState } from "react";

export interface MessageInputProps {
to: Types.Destination;
Expand Down
2 changes: 1 addition & 1 deletion src/components/UI/Footer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ const Footer = React.forwardRef<HTMLElement, FooterProps>(
({ className, ...props }, ref) => {
return (
<footer
className={`flex flex- justify-center p-2 ${className}`}
className={`flex mt-auto justify-center p-2 ${className}`}
style={{
backgroundColor: "var(--backgroundPrimary)",
color: "var(--textPrimary)",
Expand Down
5 changes: 3 additions & 2 deletions src/components/UI/Sidebar/sidebarButton.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Button } from "@components/UI/Button.tsx";
import type { LucideIcon } from "lucide-react";
import type { JSX } from "react";

export interface SidebarButtonProps {
label: string;
Expand All @@ -20,10 +21,10 @@ export const SidebarButton = ({
onClick={onClick}
variant={active ? "subtle" : "ghost"}
size="sm"
className="w-full justify-start gap-2"
className="flex gap-2"
>
{Icon && <Icon size={16} />}
{element && element}
{label}
<span className="flex flex-1 justify-start flex-shrink-0">{label}</span>
</Button>
);