diff --git a/web/chat/chat-thread-ancestors.css b/web/chat/chat-thread-ancestors.css new file mode 100644 --- /dev/null +++ b/web/chat/chat-thread-ancestors.css @@ -0,0 +1,47 @@ +div.ancestorThreadsContainer { + font-size: var(--xs-font-12); + display: flex; + align-items: center; + column-gap: 5px; +} + +div.ancestorName { + display: flex; + align-items: center; + padding: 2px 6px; + border-radius: 2px; + box-sizing: border-box; + line-height: 1.5; +} + +button.seeFullStructure { + background-color: transparent; + border: none; + cursor: pointer; +} + +div.ancestorKeyserver { + display: flex; + align-items: center; +} + +div.ancestorKeyserverName { + border-radius: 0px 2px 2px 0px; +} + +div.ancestorKeyserverOperator { + display: flex; + column-gap: 5px; + padding: 0px 5px; + align-items: center; + border: 1px solid var(--thread-ancestor-keyserver-border); + border-radius: 2px 0px 0px 2px; + height: 22px; + box-sizing: border-box; +} + +button.seeFullAncestor { + font-size: var(--xs-font-12); + font-weight: var(--semi-bold); + cursor: pointer; +} diff --git a/web/chat/chat-thread-ancestors.react.js b/web/chat/chat-thread-ancestors.react.js new file mode 100644 --- /dev/null +++ b/web/chat/chat-thread-ancestors.react.js @@ -0,0 +1,117 @@ +// @flow + +import classNames from 'classnames'; +import * as React from 'react'; + +import { useAncestorThreads } from 'lib/shared/ancestor-threads'; +import { memberHasAdminPowers, colorIsDark } from 'lib/shared/thread-utils'; +import type { ThreadInfo } from 'lib/types/thread-types'; + +import { useSelector } from '../redux/redux-utils'; +import SWMansionIcon from '../SWMansionIcon.react'; +import css from './chat-thread-ancestors.css'; + +type ThreadAncestorsProps = { + +threadInfo: ThreadInfo, +}; +function ThreadAncestors(props: ThreadAncestorsProps): React.Node { + const { threadInfo } = props; + const { color: threadColor } = threadInfo; + const darkColor = colorIsDark(threadColor); + const threadColorStyle = React.useMemo( + () => ({ + backgroundColor: `#${threadColor}`, + color: darkColor + ? 'var(--thread-ancestor-color-light)' + : 'var(--thread-ancestor-color-dark)', + }), + [darkColor, threadColor], + ); + const fullStructureButtonColorStyle = React.useMemo( + () => ({ color: `#${threadColor}` }), + [threadColor], + ); + + const ancestorThreads = useAncestorThreads(threadInfo); + + const userInfos = useSelector(state => state.userStore.userInfos); + const community = ancestorThreads[0] ?? threadInfo; + const keyserverOwnerUsername: ?string = React.useMemo(() => { + for (const member of community.members) { + if (memberHasAdminPowers(member)) { + return userInfos[member.id].username; + } + } + return undefined; + }, [community.members, userInfos]); + + const keyserverInfo = React.useMemo( + () => ( +
+
+ + {keyserverOwnerUsername} +
+
+ {community.uiName} +
+
+ ), + [community.uiName, keyserverOwnerUsername, threadColorStyle], + ); + + const middlePath = React.useMemo(() => { + if (ancestorThreads.length < 2) { + return null; + } + return ( + <> +
+ +
+
+ … +
+ + ); + }, [ancestorThreads.length, threadColorStyle]); + + const threadHasNoAncestors = community === threadInfo; + + const currentThread = React.useMemo(() => { + if (threadHasNoAncestors) { + return null; + } + return ( + <> +
+ +
+
+ {threadInfo.uiName} +
+ + ); + }, [threadHasNoAncestors, threadColorStyle, threadInfo.uiName]); + + return ( + <> +
+ {keyserverInfo} + {middlePath} + {currentThread} +
+ + + ); +} + +export default ThreadAncestors; diff --git a/web/chat/thread-top-bar.react.js b/web/chat/thread-top-bar.react.js --- a/web/chat/thread-top-bar.react.js +++ b/web/chat/thread-top-bar.react.js @@ -5,6 +5,7 @@ import type { ThreadInfo } from 'lib/types/thread-types'; import SWMansionIcon from '../SWMansionIcon.react'; +import ThreadAncestors from './chat-thread-ancestors.react'; import css from './thread-top-bar.css'; type threadTopBarProps = { @@ -27,6 +28,7 @@ style={threadBackgroundColorStyle} />

{threadInfo.uiName}

+