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

refactor(flat-components): update room list item styles #2109

Merged
merged 4 commits into from
Jan 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
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
Original file line number Diff line number Diff line change
@@ -1,12 +1,20 @@
import "./style.less";

import React, { PropsWithChildren, ReactElement, useState } from "react";
import { format } from "date-fns";
import React, {
PropsWithChildren,
ReactElement,
ReactNode,
useCallback,
useEffect,
useState,
} from "react";
import { format, isToday, isTomorrow, differenceInMinutes } from "date-fns";
import classNames from "classnames";
import { useTranslate } from "@netless/flat-i18n";
import { Button } from "antd";
import { Button, Tooltip, message } from "antd";
import { RoomListItemMenus } from "./RoomListItemMenus";
import { RoomListItemAction, RoomListItemPrimaryAction, RoomStatusType } from "./types";
import { formatInviteCode } from "../../../../utils/room";

export * from "./types";

Expand All @@ -19,6 +27,8 @@ export interface RoomListItemProps<T extends string> {
ownerAvatar?: string;
generateAvatar?: (uid: string) => string;
status: RoomStatusType;
inviteCode?: string;
joinEarly?: number;
isPmi?: boolean;
isPeriodic?: boolean;
menuActions?: Array<RoomListItemAction<T>> | null;
Expand All @@ -36,6 +46,8 @@ export function RoomListItem<T extends string = string>({
ownerAvatar,
generateAvatar,
status,
inviteCode,
joinEarly = 5,
isPmi,
isPeriodic,
menuActions,
Expand All @@ -45,12 +57,88 @@ export function RoomListItem<T extends string = string>({
}: PropsWithChildren<RoomListItemProps<T>>): ReactElement {
const t = useTranslate();
const [isAvatarLoadFailed, setAvatarLoadFailed] = useState(false);
const [, forceUpdate] = useState(0);

const copyInviteCode = useCallback(() => {
const text = formatInviteCode("", inviteCode);
navigator.clipboard.writeText(text);
void message.success(t("copy-success"));
}, [t]);

const oneHour = 60;
// Positive number means future: 5 = will start after 5 minutes
const diffMinutes = beginTime ? differenceInMinutes(beginTime, new Date()) : null;

// Force update after 1 minute to update the "will start after x minutes" text
useEffect(() => {
if (diffMinutes !== null && joinEarly < diffMinutes && diffMinutes < oneHour) {
const timer = setTimeout(
() => forceUpdate(a => (a + 1) | 0),
// Random delay to avoid performance issue
60_000 + ((Math.random() * 3000) | 0),
);
return () => clearTimeout(timer);
} else if (diffMinutes !== null && oneHour <= diffMinutes && diffMinutes < 24 * oneHour) {
const timer = setTimeout(
() => forceUpdate(a => (a + 1) | 0),
(diffMinutes - oneHour + 1) * 60_000 + ((Math.random() * 3000) | 0),
);
return () => clearTimeout(timer);
}
return;
}, [diffMinutes, forceUpdate]);

const avatar =
generateAvatar && ownerUUID && (isAvatarLoadFailed || !ownerAvatar)
? generateAvatar(ownerUUID)
: ownerAvatar;

let date = "";
if (beginTime) {
if (isToday(beginTime)) {
date = t("today");
} else if (isTomorrow(beginTime)) {
date = t("tomorrow");
} else {
date = format(beginTime, "yyyy/MM/dd");
}
}

const statusView = (
<span
className={`room-list-item-status-${
status === "upcoming" ? "warning" : status === "running" ? "success" : "default"
}`}
>
{t(`room-status.${status}`)}
</span>
);

const primaryView = primaryAction && (
<Button
key={primaryAction.key}
className="room-list-item-primary-action"
disabled={primaryAction.disabled}
type={primaryAction.type}
onClick={() => onAction(primaryAction.key)}
>
{primaryAction.text}
</Button>
);

let actionView: ReactNode = null;
if (diffMinutes === null) {
actionView = primaryView || statusView;
} else if (joinEarly < diffMinutes && diffMinutes < oneHour) {
actionView = (
<span className="room-list-item-status-success">
{t("will-start-after-minutes", { minutes: diffMinutes })}
</span>
);
} else {
actionView = diffMinutes <= joinEarly && primaryView ? primaryView : statusView;
}

return (
<div className={classNames("room-list-item", { pointer: !!onClick })}>
<div className="room-list-item-content">
Expand All @@ -72,39 +160,32 @@ export function RoomListItem<T extends string = string>({
{beginTime && format(beginTime, "HH:mm")}~
{endTime && format(endTime, "HH:mm")}
</span>
<span className="room-list-item-date">
{beginTime && format(beginTime, "yyyy/MM/dd")}
</span>
<span className="room-list-item-date">{date}</span>
<span>{isPeriodic && `(${t("periodic")})`}</span>
<span>{isPmi && `(${t("pmi")})`}</span>
</div>
<div>
<span
className={`room-list-item-status-${
status === "upcoming"
? "warning"
: status === "running"
? "success"
: "default"
}`}
>
{t(`room-status.${status}`)}
</span>
{status !== "stopped" && inviteCode && inviteCode.length < 32 && (
<Tooltip
className="room-list-item-uuid-help"
placement="right"
title={t("click-to-copy")}
>
<button
className={classNames("room-list-item-uuid", {
active: beginTime && isToday(beginTime),
})}
onClick={copyInviteCode}
>
{t("invite-suffix", { uuid: formatInviteCode("", inviteCode) })}
</button>
</Tooltip>
)}
</div>
</div>
<div className="room-list-item-right">
{menuActions && <RoomListItemMenus actions={menuActions} onAction={onAction} />}
{primaryAction && (
<Button
key={primaryAction.key}
className="room-list-item-primary-action"
disabled={primaryAction.disabled}
type={primaryAction.type}
onClick={() => onAction(primaryAction.key)}
>
{primaryAction.text}
</Button>
)}
{actionView}
</div>
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
border-radius: 50%;
font-size: 0;

> img {
>img {
width: 100%;
height: 100%;
object-fit: cover;
Expand All @@ -46,11 +46,15 @@
.room-list-item-middle {
flex: 1;
padding: 4px 8px;

>div {
min-height: 22px;
}
}

.room-list-item-title {
display: inline;
margin: 0 8px 4px 0;
display: inline-block;
margin: 0 8px 6px 0;
font-size: 16px;
font-weight: 600;
word-break: break-all;
Expand All @@ -62,27 +66,46 @@
}

.room-list-item-info {
& > * {
&>* {
margin-right: 8px;
}
}

.room-list-item-time-date {
margin-bottom: 4px;
margin-bottom: 6px;
white-space: wrap;
word-break: break-all;

> * {
>* {
white-space: nowrap;
word-break: keep-all;
}
}

.room-list-item-uuid {
margin: -6px auto -8px -8px;
padding: 4px 8px;
border: 0;
border-radius: 4px;
background-color: transparent;
transition: background-color 0.2s;
cursor: pointer;

&.active,
&:hover {
background-color: var(--grey-0);
}
}

.room-list-item-right {
display: flex;
flex-direction: column;
justify-content: space-between;
align-items: flex-end;

>span {
margin: 0 4px 2px;
}
}

.room-list-item-more {
Expand Down Expand Up @@ -130,4 +153,9 @@
}
}
}

.room-list-item-uuid.active,
.room-list-item-uuid:hover {
background-color: var(--grey-10);
}
}
6 changes: 6 additions & 0 deletions packages/flat-i18n/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@
"begin-time": "Start time",
"join-and-book-by-room-uuid": "Can join and book by room ID",
"copy": "Copy",
"click-to-copy": "Click to copy",
"share-record": "Share",
"login-github": "GitHub",
"login-google": "Google",
Expand Down Expand Up @@ -645,8 +646,13 @@
"rooms-has-reached-the-limit": "You have reached the limit of creating rooms this month, upgrade your account to create more rooms",
"the-room-is-full": "The room is full, please try again later or contact the owner",
"the-room-is-expired": "The room has ended",
"room-not-begin-title-pre": "The room will open",
"minutes": "{{minutes}} minutes",
"room-not-begin-title-post": "before it starts",
"room-has-been-added": "has been added to rooms list",
"the-room-is-not-started-yet": "The room has not started yet, please try again later",
"your-room-is-not-started-yet": "Your room has not started yet, please try again later",
"will-start-after-minutes": "will start after {{minutes}} minutes",
"time-limit-tip": "This {{roomType}} room has a limit of {{minutes}} minutes",
"vip-level": {
"0": "free",
Expand Down
6 changes: 6 additions & 0 deletions packages/flat-i18n/locales/zh-CN.json
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@
"room-type": "房间类型",
"begin-time": "开始时间",
"copy": "复制",
"click-to-copy": "点击复制",
"share-record": "分享回放",
"login-wechat": "微信",
"login-github": "GitHub",
Expand Down Expand Up @@ -645,8 +646,13 @@
"rooms-has-reached-the-limit": "本月创建免费房间个数已达上限,升级版本享受更多权益",
"the-room-is-full": "免费版房间人数已达上限,暂时无法加入,请联系老师解决",
"the-room-is-expired": "房间已结束",
"room-not-begin-title-pre": "房间未开始,开课前",
"minutes": "{{minutes}}分钟",
"room-not-begin-title-post": "可进入",
"room-has-been-added": "已加入房间列表",
"the-room-is-not-started-yet": "房间未开始,开课前 5 分钟可进入,已将该房间添加到房间列表",
"your-room-is-not-started-yet": "房间未开始,开课前 5 分钟可进入",
"will-start-after-minutes": "{{minutes}} 分钟后开始",
"time-limit-tip": "你已加入{{roomType}} {{minutes}} 分钟限时房间",
"vip-level": {
"0": "免费版",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -136,8 +136,10 @@ export const MainRoomList = observer<MainRoomListProps>(function MainRoomList({
beginTime={beginTime}
endTime={endTime}
generateAvatar={generateAvatar}
inviteCode={room.inviteCode}
isPeriodic={!!room.periodicUUID}
isPmi={room.inviteCode === globalStore.pmi}
joinEarly={globalStore.serverRegionConfig?.server.joinEarly}
menuActions={getSubActions(room)}
ownerAvatar={room.ownerAvatarURL}
ownerName={room.ownerName}
Expand Down
26 changes: 14 additions & 12 deletions packages/flat-pages/src/HomePage/MainRoomMenu/JoinRoomBox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -109,19 +109,21 @@ export const JoinRoomBox = observer<JoinRoomBoxProps>(function JoinRoomBox({ onJ
overlay={
<Menu
className="join-room-box-dropdown-menu"
items={globalStore.roomHistory.map(room => ({
key: room.uuid,
label: (
<>
<span className="room-title">
{room.title}
</span>
<span className="invite-code">
{formatInviteCode("", room.uuid)}
</span>
</>
),
}))}
onClick={e => selectRoomFromHistory(e.key)}
>
{globalStore.roomHistory.map(room => (
<Menu.Item key={room.uuid}>
<span className="room-title">
{room.title}
</span>
<span className="invite-code">
{formatInviteCode("", room.uuid)}
</span>
</Menu.Item>
))}
</Menu>
/>
}
overlayClassName="join-room-box-dropdown"
>
Expand Down
2 changes: 2 additions & 0 deletions packages/flat-pages/src/HomePage/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
RoomStoreContext,
} from "../components/StoreProvider";
import { AppUpgradeModal } from "../components/AppUpgradeModal";
import { RoomNotBeginModal } from "../components/RoomNotBeginModal";

export const HomePage = observer(function HomePage() {
const sp = useSafePromise();
Expand Down Expand Up @@ -84,6 +85,7 @@ export const HomePage = observer(function HomePage() {
/>
<MainRoomHistoryPanel refreshRooms={refreshRooms} roomStore={roomStore} />
</div>
<RoomNotBeginModal />
<AppUpgradeModal />
</div>
);
Expand Down
Loading