From ca91b58293551cf1964e814ad01d526e5539e69b Mon Sep 17 00:00:00 2001 From: crupest Date: Sat, 30 Jan 2021 23:35:38 +0800 Subject: ... --- FrontEnd/src/app/locales/en/translation.json | 2 +- FrontEnd/src/app/locales/zh/translation.json | 2 +- .../src/app/views/timeline-common/TimelineItem.tsx | 19 +- .../app/views/timeline-common/TimelineMember.tsx | 257 +++++++++------------ 4 files changed, 121 insertions(+), 159 deletions(-) (limited to 'FrontEnd/src') diff --git a/FrontEnd/src/app/locales/en/translation.json b/FrontEnd/src/app/locales/en/translation.json index ebf9552a..0aafb2ae 100644 --- a/FrontEnd/src/app/locales/en/translation.json +++ b/FrontEnd/src/app/locales/en/translation.json @@ -80,7 +80,7 @@ "description": "Description" }, "member": { - "alreadyMember": "The user is already a member.", + "noUserAvailableToAdd": "Sorry, no user available to be a member in search result.", "add": "Add", "remove": "Remove" }, diff --git a/FrontEnd/src/app/locales/zh/translation.json b/FrontEnd/src/app/locales/zh/translation.json index 0d063b4e..a12a6296 100644 --- a/FrontEnd/src/app/locales/zh/translation.json +++ b/FrontEnd/src/app/locales/zh/translation.json @@ -80,7 +80,7 @@ "description": "描述" }, "member": { - "alreadyMember": "该用户已经是一个成员。", + "noUserAvailableToAdd": "搜索结果显示没有可以添加为成员的用户。", "add": "添加", "remove": "移除" }, diff --git a/FrontEnd/src/app/views/timeline-common/TimelineItem.tsx b/FrontEnd/src/app/views/timeline-common/TimelineItem.tsx index c096f890..a5b6d04a 100644 --- a/FrontEnd/src/app/views/timeline-common/TimelineItem.tsx +++ b/FrontEnd/src/app/views/timeline-common/TimelineItem.tsx @@ -2,10 +2,10 @@ import React from "react"; import clsx from "clsx"; import { Link } from "react-router-dom"; -import { useAvatar } from "@/services/user"; import { TimelinePostInfo } from "@/services/timeline"; import BlobImage from "../common/BlobImage"; +import UserAvatar from "../common/user/UserAvatar"; import TimelineLine from "./TimelineLine"; import TimelinePostDeleteConfirmDialog from "./TimelinePostDeleteConfirmDialog"; @@ -25,9 +25,7 @@ export interface TimelineItemProps { const TimelineItem: React.FC = (props) => { const current = props.current === true; - const { more } = props; - - const avatar = useAvatar(props.post.author.username); + const { post, more } = props; const [deleteDialog, setDeleteDialog] = React.useState(false); @@ -52,20 +50,21 @@ const TimelineItem: React.FC = (props) => { - + - - {props.post.author.nickname} - + {post.author.nickname} - {props.post.time.toLocaleTimeString()} + {post.time.toLocaleTimeString()}
{(() => { - const { content } = props.post; + const { content } = post; if (content.type === "text") { return content.text; } else { diff --git a/FrontEnd/src/app/views/timeline-common/TimelineMember.tsx b/FrontEnd/src/app/views/timeline-common/TimelineMember.tsx index efa7e971..7bc81e7f 100644 --- a/FrontEnd/src/app/views/timeline-common/TimelineMember.tsx +++ b/FrontEnd/src/app/views/timeline-common/TimelineMember.tsx @@ -2,27 +2,27 @@ import React, { useState } from "react"; import { useTranslation } from "react-i18next"; import { Container, ListGroup, Modal, Row, Col, Button } from "react-bootstrap"; -import { User, useAvatar } from "@/services/user"; +import { getHttpSearchClient } from "@/http/search"; + +import { User } from "@/services/user"; import { TimelineInfo, timelineService } from "@/services/timeline"; -import { getHttpUserClient, HttpUserNotExistError } from "@/http/user"; import SearchInput from "../common/SearchInput"; -import BlobImage from "../common/BlobImage"; +import UserAvatar from "../common/user/UserAvatar"; +import { convertI18nText, I18nText } from "@/common"; const TimelineMemberItem: React.FC<{ user: User; - owner: boolean; - onRemove?: (username: string) => void; -}> = ({ user, owner, onRemove }) => { + add?: boolean; + onAction?: (username: string) => void; +}> = ({ user, add, onAction }) => { const { t } = useTranslation(); - const avatar = useAvatar(user.username); - return ( - + {user.nickname} @@ -30,55 +30,122 @@ const TimelineMemberItem: React.FC<{ {"@" + user.username} - {(() => { - if (owner) { - return null; - } - if (onRemove == null) { - return null; - } - return ( - - ); - })()} + {onAction ? ( + + ) : null} ); }; -export interface TimelineMemberProps { - timeline: TimelineInfo; - editable: boolean; -} - -const TimelineMember: React.FC = (props) => { +const TimelineMemberUserSearch: React.FC<{ timeline: TimelineInfo }> = ({ + timeline, +}) => { const { t } = useTranslation(); const [userSearchText, setUserSearchText] = useState(""); const [userSearchState, setUserSearchState] = useState< | { - type: "user"; - data: User; + type: "users"; + data: User[]; } - | { type: "error"; data: string } + | { type: "error"; data: I18nText } | { type: "loading" } | { type: "init" } >({ type: "init" }); - const userSearchAvatar = useAvatar( - userSearchState.type === "user" ? userSearchState.data.username : undefined + return ( + <> + { + setUserSearchText(v); + }} + loading={userSearchState.type === "loading"} + onButtonClick={() => { + if (userSearchText === "") { + setUserSearchState({ + type: "error", + data: "login.emptyUsername", + }); + return; + } + setUserSearchState({ type: "loading" }); + getHttpSearchClient() + .searchUsers(userSearchText) + .then( + (users) => { + users = users.filter( + (user) => + timeline.members.findIndex( + (m) => m.username === user.username + ) === -1 + ); + setUserSearchState({ type: "users", data: users }); + }, + (e) => { + setUserSearchState({ + type: "error", + data: { type: "custom", value: String(e) }, + }); + } + ); + }} + /> + {(() => { + if (userSearchState.type === "users") { + const users = userSearchState.data; + if (users.length === 0) { + return
{t("timeline.member.noUserAvailableToAdd")}
; + } else { + return ( + + {users.map((user) => { + { + void timelineService + .addMember(timeline.name, user.username) + .then(() => { + setUserSearchText(""); + setUserSearchState({ type: "init" }); + }); + }} + />; + })} + + ); + } + } else if (userSearchState.type === "error") { + return ( +
+ {convertI18nText(userSearchState.data, t)} +
+ ); + } + })()} + ); +}; - const { timeline } = props; +export interface TimelineMemberProps { + timeline: TimelineInfo; + editable: boolean; +} +const TimelineMember: React.FC = (props) => { + const { timeline, editable } = props; const members = [timeline.owner, ...timeline.members]; return ( @@ -88,9 +155,8 @@ const TimelineMember: React.FC = (props) => { { void timelineService.removeMember( timeline.name, @@ -102,110 +168,7 @@ const TimelineMember: React.FC = (props) => { /> ))} - {(() => { - if (props.editable) { - return ( - <> - { - setUserSearchText(v); - }} - loading={userSearchState.type === "loading"} - onButtonClick={() => { - if (userSearchText === "") { - setUserSearchState({ - type: "error", - data: "login.emptyUsername", - }); - return; - } - setUserSearchState({ type: "loading" }); - getHttpUserClient() - .get(userSearchText) - .catch((e) => { - if (e instanceof HttpUserNotExistError) { - return null; - } else { - throw e; - } - }) - .then( - (u) => { - if (u == null) { - setUserSearchState({ - type: "error", - data: "timeline.userNotExist", - }); - } else { - setUserSearchState({ type: "user", data: u }); - } - }, - (e) => { - setUserSearchState({ - type: "error", - data: `${e as string}`, - }); - } - ); - }} - /> - {(() => { - if (userSearchState.type === "user") { - const u = userSearchState.data; - const addable = - members.findIndex((m) => m.username === u.username) === -1; - return ( - <> - {!addable ? ( -

{t("timeline.member.alreadyMember")}

- ) : null} - - - - - - - {u.nickname} - - {"@" + u.username} - - - - - - - ); - } else if (userSearchState.type === "error") { - return ( -

{t(userSearchState.data)}

- ); - } - })()} - - ); - } else { - return null; - } - })()} + {editable ? : null} ); }; -- cgit v1.2.3