From 8e24d1490c10eed3cd5f5f72cf3a402afa867dc8 Mon Sep 17 00:00:00 2001 From: Paul Date: Sun, 8 Aug 2021 16:17:16 +0100 Subject: [PATCH] Virtualised sidebar test. --- external/lang | 2 +- package.json | 4 + .../common/messaging/MessageBase.tsx | 7 ++ .../common/messaging/SystemMessage.tsx | 9 +- src/components/navigation/SidebarBase.tsx | 8 +- .../navigation/items/ButtonItem.tsx | 14 ++- .../navigation/items/Item.module.scss | 27 ++++-- .../navigation/left/HomeSidebar.tsx | 2 +- .../navigation/right/MemberList.tsx | 92 +++++++++++++++++++ .../navigation/right/MemberSidebar.tsx | 66 ++++++++++++- yarn.lock | 46 ++++++++-- 11 files changed, 241 insertions(+), 36 deletions(-) create mode 100644 src/components/navigation/right/MemberList.tsx diff --git a/external/lang b/external/lang index 30558b79..46a2dcdd 160000 --- a/external/lang +++ b/external/lang @@ -1 +1 @@ -Subproject commit 30558b7989de3b6b39c360d85db4fc58e146ad5e +Subproject commit 46a2dcdd88c42d4e6e504ca5c60a4beebc8d0280 diff --git a/package.json b/package.json index de932dc9..c36abdc1 100644 --- a/package.json +++ b/package.json @@ -78,6 +78,8 @@ "@types/react-helmet": "^6.1.1", "@types/react-router-dom": "^5.1.7", "@types/react-scroll": "^1.8.2", + "@types/react-virtualized-auto-sizer": "^1.0.1", + "@types/react-window": "^1.8.5", "@types/styled-components": "^5.1.10", "@types/twemoji": "^12.1.1", "@typescript-eslint/eslint-plugin": "^4.27.0", @@ -111,6 +113,8 @@ "react-redux": "^7.2.4", "react-router-dom": "^5.2.0", "react-scroll": "^1.8.2", + "react-virtualized-auto-sizer": "^1.0.5", + "react-window": "^1.8.6", "redux": "^4.1.0", "revolt-api": "0.5.1-alpha.10-patch.0", "revolt.js": "5.0.0-alpha.20", diff --git a/src/components/common/messaging/MessageBase.tsx b/src/components/common/messaging/MessageBase.tsx index 4aa7050d..166f2918 100644 --- a/src/components/common/messaging/MessageBase.tsx +++ b/src/components/common/messaging/MessageBase.tsx @@ -182,6 +182,13 @@ export const MessageInfo = styled.div` .header { cursor: pointer; } + + .systemIcon { + height: 1.33em; + width: 1.33em; + margin-right: 0.5em; + color: var(--tertiary-foreground); + } `; export const MessageContent = styled.div` diff --git a/src/components/common/messaging/SystemMessage.tsx b/src/components/common/messaging/SystemMessage.tsx index d027b055..f44be0cb 100644 --- a/src/components/common/messaging/SystemMessage.tsx +++ b/src/components/common/messaging/SystemMessage.tsx @@ -57,13 +57,6 @@ export const SystemMessage = observer( const SystemMessageIcon = iconDictionary[data.type as SystemMessageI["type"]] ?? InfoCircle; - const SystemIcon = styled(SystemMessageIcon)` - height: 1.33em; - width: 1.33em; - margin-right: 0.5em; - color: var(--tertiary-foreground); - `; - let children; switch (data.type) { case "text": @@ -136,7 +129,7 @@ export const SystemMessage = observer( {!hideInfo && ( - + )} {children} diff --git a/src/components/navigation/SidebarBase.tsx b/src/components/navigation/SidebarBase.tsx index 064c7b3e..2ed36084 100644 --- a/src/components/navigation/SidebarBase.tsx +++ b/src/components/navigation/SidebarBase.tsx @@ -10,17 +10,19 @@ export default styled.div` align-items: stretch; `; -export const GenericSidebarBase = styled.div<{ padding?: boolean }>` +export const GenericSidebarBase = styled.div<{ + mobilePadding?: boolean; +}>` height: 100%; width: 240px; display: flex; flex-shrink: 0; flex-direction: column; - background: var(--secondary-background); border-end-start-radius: 8px; + background: var(--secondary-background); ${(props) => - props.padding && + props.mobilePadding && isTouchscreenDevice && css` padding-bottom: 50px; diff --git a/src/components/navigation/items/ButtonItem.tsx b/src/components/navigation/items/ButtonItem.tsx index 751a65c5..ae38ba54 100644 --- a/src/components/navigation/items/ButtonItem.tsx +++ b/src/components/navigation/items/ButtonItem.tsx @@ -30,6 +30,7 @@ type CommonProps = Omit< active?: boolean; alert?: "unread" | "mention"; alertCount?: number; + margin?: boolean; }; type UserProps = CommonProps & { @@ -39,8 +40,16 @@ type UserProps = CommonProps & { }; export const UserButton = observer((props: UserProps) => { - const { active, alert, alertCount, user, context, channel, ...divProps } = - props; + const { + active, + alert, + margin, + alertCount, + user, + context, + channel, + ...divProps + } = props; const { openScreen } = useIntermediate(); return ( @@ -48,6 +57,7 @@ export const UserButton = observer((props: UserProps) => { {...divProps} className={classNames(styles.item, styles.user)} data-active={active} + data-margin={margin} data-alert={typeof alert === "string"} data-online={ typeof channel !== "undefined" || diff --git a/src/components/navigation/items/Item.module.scss b/src/components/navigation/items/Item.module.scss index 123aa743..3b7a20f5 100644 --- a/src/components/navigation/items/Item.module.scss +++ b/src/components/navigation/items/Item.module.scss @@ -9,10 +9,10 @@ gap: 8px; align-items: center; flex-direction: row; - + cursor: pointer; font-size: 16px; - transition: .1s ease-in-out background-color; + transition: 0.1s ease-in-out background-color; color: var(--tertiary-foreground); @@ -20,14 +20,15 @@ height: 42px; } - &.compact { /* TOFIX: Introduce two separate compact items, one for settings, other for channels. */ + &.compact { + /* TOFIX: Introduce two separate compact items, one for settings, other for channels. */ height: 32px; } &.user { opacity: 0.4; cursor: pointer; - transition: .1s ease-in-out opacity; + transition: 0.1s ease-in-out opacity; &[data-online="true"], &:hover { @@ -43,7 +44,7 @@ overflow: hidden; white-space: nowrap; text-overflow: ellipsis; - transition: color .1s ease-in-out; + transition: color 0.1s ease-in-out; &.content { gap: 10px; @@ -73,13 +74,13 @@ flex-grow: 1; display: flex; font-weight: 600; - font-size: .90625rem; + font-size: 0.90625rem; flex-direction: column; .subText { margin-top: -1px; font-weight: 500; - font-size: .6875rem; + font-size: 0.6875rem; color: var(--tertiary-foreground); } } @@ -90,7 +91,7 @@ svg { opacity: 0; display: none; - transition: .1s ease-in-out opacity; + transition: 0.1s ease-in-out opacity; } } } @@ -115,13 +116,19 @@ } } - &[data-alert="true"], &[data-active="true"], &:hover { + &[data-alert="true"], + &[data-active="true"], + &:hover { color: var(--foreground); .subText { color: var(--secondary-foreground) !important; } } + + &[data-margin="true"] { + margin: 0 6px; + } } .alert { @@ -162,4 +169,4 @@ } } } - } \ No newline at end of file +} diff --git a/src/components/navigation/left/HomeSidebar.tsx b/src/components/navigation/left/HomeSidebar.tsx index 291dc9d3..8c66fc7c 100644 --- a/src/components/navigation/left/HomeSidebar.tsx +++ b/src/components/navigation/left/HomeSidebar.tsx @@ -65,7 +65,7 @@ const HomeSidebar = observer((props: Props) => { channels.sort((b, a) => a.timestamp.localeCompare(b.timestamp)); return ( - + diff --git a/src/components/navigation/right/MemberList.tsx b/src/components/navigation/right/MemberList.tsx new file mode 100644 index 00000000..e167c89f --- /dev/null +++ b/src/components/navigation/right/MemberList.tsx @@ -0,0 +1,92 @@ +import AutoSizer from "react-virtualized-auto-sizer"; +import { FixedSizeList as List } from "react-window"; +import { User } from "revolt.js/dist/maps/Users"; + +import { forwardRef } from "preact/compat"; + +import { UserButton } from "../items/ButtonItem"; + +export type MemberListEntry = string | User; +interface ItemData { + entries: MemberListEntry[]; +} + +const PADDING_SIZE = 6; + +const Row = ({ + data, + style, + index, +}: { + data: ItemData; + index: number; + style: JSX.CSSProperties; +}) => { + const item = data.entries[index]; + + return ( +
+ {typeof item === "string" ? ( + `cat ${item}` + ) : ( + + openScreen({ + id: "profile", + user_id: user._id, + }) + } */ + /> + )} +
+ ); +}; + +// @ts-expect-error Copied directly from example code. +const innerElementType = forwardRef(({ style, ...rest }, ref) => ( +
+)); + +export default function MemberList({ + entries, +}: { + entries: MemberListEntry[]; +}) { + return ( + + {({ width, height }) => ( + + { + // eslint-disable-next-line + Row as any + } + + )} + + ); +} diff --git a/src/components/navigation/right/MemberSidebar.tsx b/src/components/navigation/right/MemberSidebar.tsx index b710b22a..aea913e1 100644 --- a/src/components/navigation/right/MemberSidebar.tsx +++ b/src/components/navigation/right/MemberSidebar.tsx @@ -4,9 +4,10 @@ import { Link, useParams } from "react-router-dom"; import { Presence } from "revolt-api/types/Users"; import { Channel } from "revolt.js/dist/maps/Channels"; import { Message } from "revolt.js/dist/maps/Messages"; +import { User } from "revolt.js/dist/maps/Users"; import { Text } from "preact-i18n"; -import { useContext, useEffect, useState } from "preact/hooks"; +import { useContext, useEffect, useMemo, useState } from "preact/hooks"; import { getState } from "../../../redux"; @@ -27,6 +28,7 @@ import placeholderSVG from "../items/placeholder.svg"; import { GenericSidebarBase, GenericSidebarList } from "../SidebarBase"; import { UserButton } from "../items/ButtonItem"; import { ChannelDebugInfo } from "./ChannelDebugInfo"; +import MemberList, { MemberListEntry } from "./MemberList"; export default function MemberSidebar({ channel: obj }: { channel?: Channel }) { const { channel: channel_id } = useParams<{ channel: string }>(); @@ -90,7 +92,6 @@ export const GroupMemberSidebar = observer( return ( - {/*voiceActive && voiceParticipants.length !== 0 && ( @@ -161,6 +162,64 @@ export const GroupMemberSidebar = observer( export const ServerMemberSidebar = observer( ({ channel }: { channel: Channel }) => { + const client = useClient(); + const status = useContext(StatusContext); + + useEffect(() => { + if (status === ClientStatus.ONLINE) { + channel.server!.fetchMembers(); + } + }, [status, channel.server]); + + const keys = [...client.members.keys()]; + const entries = useMemo(() => { + const categories: { [key: string]: [User, string][] } = { + online: [], + offline: [], + }; + + keys.forEach((key) => { + const { server, user } = JSON.parse(key); + if (server !== channel.server_id) return; + + const u = client.users.get(user); + if (!u) return; + + const member = client.members.get(key); + const sort = member?.nickname ?? u.username; + const entry = [u, sort] as [User, string]; + + if (!u.online || u.status?.presence === Presence.Invisible) { + categories.offline.push(entry); + } else { + categories.online.push(entry); + } + }); + + Object.keys(categories).forEach((key) => + categories[key].sort((a, b) => a[1].localeCompare(b[1])), + ); + + const entries = []; + + entries.push( + "online", + ...categories.online.map((x) => x[0]), + "offline", + ...categories.offline.map((x) => x[0]), + ); + + return entries; + // eslint-disable-next-line + }, [keys]); + + return ( + + + + ); + + /* const client = useClient(); const { openScreen } = useIntermediate(); const status = useContext(StatusContext); @@ -202,7 +261,6 @@ export const ServerMemberSidebar = observer( return ( -
{users.length === 0 && }
{users.length > 0 && ( @@ -236,7 +294,7 @@ export const ServerMemberSidebar = observer( )}
- ); + );*/ }, ); diff --git a/yarn.lock b/yarn.lock index 553497ad..59823df0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -872,6 +872,13 @@ "@babel/types" "^7.4.4" esutils "^2.0.2" +"@babel/runtime@^7.0.0", "@babel/runtime@^7.14.8": + version "7.14.8" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.14.8.tgz#7119a56f421018852694290b9f9148097391b446" + integrity sha512-twj3L8Og5SaCRCErB4x4ajbvBIVV77CGeFglHpeg5WC5FF8TZzBWXtTJ4MqaD9QszLYTtr+IsaAL2rEUevb+eg== + dependencies: + regenerator-runtime "^0.13.4" + "@babel/runtime@^7.1.2", "@babel/runtime@^7.10.5", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.1", "@babel/runtime@^7.14.0", "@babel/runtime@^7.14.6", "@babel/runtime@^7.8.4", "@babel/runtime@^7.9.2": version "7.14.6" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.14.6.tgz#535203bc0892efc7dec60bdc27b2ecf6e409062d" @@ -879,13 +886,6 @@ dependencies: regenerator-runtime "^0.13.4" -"@babel/runtime@^7.14.8": - version "7.14.8" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.14.8.tgz#7119a56f421018852694290b9f9148097391b446" - integrity sha512-twj3L8Og5SaCRCErB4x4ajbvBIVV77CGeFglHpeg5WC5FF8TZzBWXtTJ4MqaD9QszLYTtr+IsaAL2rEUevb+eg== - dependencies: - regenerator-runtime "^0.13.4" - "@babel/template@^7.12.13", "@babel/template@^7.14.5": version "7.14.5" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.14.5.tgz#a9bc9d8b33354ff6e55a9c60d1109200a68974f4" @@ -1466,6 +1466,20 @@ dependencies: "@types/react" "*" +"@types/react-virtualized-auto-sizer@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@types/react-virtualized-auto-sizer/-/react-virtualized-auto-sizer-1.0.1.tgz#b3187dae1dfc4c15880c9cfc5b45f2719ea6ebd4" + integrity sha512-GH8sAnBEM5GV9LTeiz56r4ZhMOUSrP43tAQNSRVxNexDjcNKLCEtnxusAItg1owFUFE6k0NslV26gqVClVvong== + dependencies: + "@types/react" "*" + +"@types/react-window@^1.8.5": + version "1.8.5" + resolved "https://registry.yarnpkg.com/@types/react-window/-/react-window-1.8.5.tgz#285fcc5cea703eef78d90f499e1457e9b5c02fc1" + integrity sha512-V9q3CvhC9Jk9bWBOysPGaWy/Z0lxYcTXLtLipkt2cnRj1JOSFNF7wqGpkScSXMgBwC+fnVRg/7shwgddBG5ICw== + dependencies: + "@types/react" "*" + "@types/react@*": version "17.0.13" resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.13.tgz#6b7c9a8f2868586ad87d941c02337c6888fb874f" @@ -3033,6 +3047,11 @@ mdurl@^1.0.1: sdp-transform "^2.14.1" supports-color "^8.1.1" +"memoize-one@>=3.1.1 <6": + version "5.2.1" + resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-5.2.1.tgz#8337aa3c4335581839ec01c3d594090cebe8f00e" + integrity sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q== + merge-stream@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" @@ -3451,6 +3470,19 @@ react-side-effect@^2.1.0: resolved "https://registry.yarnpkg.com/react-side-effect/-/react-side-effect-2.1.1.tgz#66c5701c3e7560ab4822a4ee2742dee215d72eb3" integrity sha512-2FoTQzRNTncBVtnzxFOk2mCpcfxQpenBMbk5kSVBg5UcPqV9fRbgY2zhb7GTWWOlpFmAxhClBDlIq8Rsubz1yQ== +react-virtualized-auto-sizer@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/react-virtualized-auto-sizer/-/react-virtualized-auto-sizer-1.0.5.tgz#9eeeb8302022de56fbd7a860b08513120ce36509" + integrity sha512-kivjYVWX15TX2IUrm8F1jaCEX8EXrpy3DD+u41WGqJ1ZqbljWpiwscV+VxOM1l7sSIM1jwi2LADjhhAJkJ9dxA== + +react-window@^1.8.6: + version "1.8.6" + resolved "https://registry.yarnpkg.com/react-window/-/react-window-1.8.6.tgz#d011950ac643a994118632665aad0c6382e2a112" + integrity sha512-8VwEEYyjz6DCnGBsd+MgkD0KJ2/OXFULyDtorIiTz+QzwoP94tBoA7CnbtyXMm+cCeAUER5KJcPtWl9cpKbOBg== + dependencies: + "@babel/runtime" "^7.0.0" + memoize-one ">=3.1.1 <6" + readdirp@~3.6.0: version "3.6.0" resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7"