diff --git a/web/chat/chat-thread-ancestors.css b/web/chat/chat-thread-ancestors.css
new file mode 100644
index 000000000..22e718617
--- /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
index 000000000..da9299520
--- /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
index 8a096896d..07052c663 100644
--- a/web/chat/thread-top-bar.react.js
+++ b/web/chat/thread-top-bar.react.js
@@ -1,38 +1,40 @@
// @flow
import * as React from 'react';
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 = {
+threadInfo: ThreadInfo,
};
function ThreadTopBar(props: threadTopBarProps): React.Node {
const { threadInfo } = props;
const threadBackgroundColorStyle = React.useMemo(
() => ({
background: `#${threadInfo.color}`,
}),
[threadInfo.color],
);
return (
);
}
export default ThreadTopBar;
diff --git a/web/theme.css b/web/theme.css
index c835f9a20..1f808d44c 100644
--- a/web/theme.css
+++ b/web/theme.css
@@ -1,101 +1,104 @@
:root {
/* Never use color values defined here directly in CSS. Add color variables to "Color Theme" below
The reason we never use color values defined here directly in CSS is
1. It makes changing themes from light / dark / user generated impossible.
2. Gives the programmer context into the color being used.
3. If our color system changes it's much easier to change color values in one place.
Add a color value to the theme below, and then use it in your CSS.
naming convention:
- bg: background.
- fg: foreground.
- color: text-color
*/
--shades-white-100: #ffffff;
--shades-white-90: #f5f5f5;
--shades-white-80: #ebebeb;
--shades-white-70: #e0e0e0;
--shades-white-60: #cccccc;
--shades-black-100: #0a0a0a;
--shades-black-90: #1f1f1f;
--shades-black-80: #404040;
--shades-black-70: #666666;
--shades-black-60: #808080;
--violet-dark-100: #7e57c2;
--violet-dark-80: #6d49ab;
--violet-dark-60: #563894;
--violet-dark-40: #44297a;
--violet-dark-20: #331f5c;
--violet-light-100: #ae94db;
--violet-light-80: #b9a4df;
--violet-light-60: #d3c6ec;
--violet-light-40: #e8e0f5;
--violet-light-20: #f3f0fa;
--success-light-10: #d5f6e3;
--success-light-50: #6cdf9c;
--success-primary: #00c853;
--success-dark-50: #029841;
--success-dark-90: #034920;
--error-light-10: #feebe6;
--error-light-50: #f9947b;
--error-primary: #f53100;
--error-dark-50: #b62602;
--error-dark-90: #4f1203;
--bg: var(--shades-black-100);
--fg: var(--shades-white-100);
--color-disabled: var(--shades-black-60);
--text-input-bg: var(--shades-black-80);
--text-input-color: var(--shades-white-60);
--text-input-placeholder: var(--shades-white-60);
--border: var(--shades-black-80);
--error: var(--error-primary);
--success: var(--success-dark-50);
/* Color Theme */
--btn-bg-primary: var(--violet-dark-100);
--btn-bg-danger: var(--error-primary);
--chat-bg: var(--violet-dark-80);
--chat-confirmation-icon: var(--violet-dark-100);
--keyserver-selection: var(--violet-dark-60);
--thread-selection: var(--violet-light-80);
--selected-thread-bg: var(--shades-black-90);
--chat-timestamp-color: var(--shades-black-60);
--tool-tip-bg: var(--shades-black-80);
--tool-tip-color: var(--shades-black-60);
--border-color: var(--shades-black-60);
--calendar-chevron: var(--shades-black-60);
--calendar-day-bg: var(--shades-black-60);
--calendar-day-selected-color: var(--violet-dark-80);
--community-bg: var(--shades-black-90);
--unread-bg: var(--error-primary);
--settings-btn-bg: var(--violet-dark-100);
--modal-bg: var(--shades-black-90);
--join-bg: var(--shades-black-90);
--help-color: var(--shades-black-60);
--modal-bg: var(--shades-black-90);
--breadcrumb-color: var(--shades-black-60);
--breadcrumb-color-unread: var(--shades-white-60);
--join-bg: var(--shades-black-90);
--btn-secondary-border: var(--shades-black-60);
--thread-color-read: var(--shades-black-60);
--thread-from-color-read: var(--shades-black-80);
--thread-last-message-color-read: var(--shades-black-60);
--relationship-button-green: var(--success-dark-50);
--relationship-button-red: var(--error-primary);
--relationship-button-text: var(--fg);
--disconnected-bar-alert-bg: var(--error-dark-50);
--disconnected-bar-alert-color: var(--shades-white-100);
--disconnected-bar-connecting-bg: var(--shades-white-70);
--disconnected-bar-connecting-color: var(--shades-black-100);
--permission-color: var(--shades-white-60);
--thread-top-bar-color: var(--shades-white-100);
--thread-top-bar-menu-color: var(--shades-white-70);
+ --thread-ancestor-keyserver-border: var(--shades-black-70);
+ --thread-ancestor-color-light: var(--shades-white-70);
+ --thread-ancestor-color-dark: var(--shades-black-100);
}