Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F32168898
D15347.1765055349.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Size
18 KB
Referenced Files
None
Subscribers
None
D15347.1765055349.diff
View Options
diff --git a/lib/shared/comm-icon-config.json b/lib/shared/comm-icon-config.json
--- a/lib/shared/comm-icon-config.json
+++ b/lib/shared/comm-icon-config.json
@@ -447,6 +447,31 @@
"setIdx": 0,
"setId": 2,
"iconIdx": 17
+ },
+ {
+ "icon": {
+ "paths": [
+ "M263.889 159.369H759.593V864.569H687.296V541.091H686.569C678.312 448.462 600.253 375.963 505.741 375.963C411.229 375.963 333.17 448.462 324.913 541.091H324.185V864.569H251.889V159.369H263.889Z",
+ "M131.852 259.481L162.074 359.737H187.593V763.815C174.791 763.815 164.444 774.483 164.444 787.63V815.408H159.741C146.938 815.408 136.593 826.074 136.593 839.222V866.998H391.111V839.222C391.111 826.074 380.766 815.408 367.963 815.408H363.259V787.63C363.259 774.483 352.914 763.815 340.111 763.815H312.593V259.481H131.852Z",
+ "M691.111 763.815C678.309 763.815 667.963 774.483 667.963 787.63V815.408H663.259C650.457 815.408 640.111 826.074 640.111 839.222V866.998H894.63V839.222C894.63 826.074 884.284 815.408 871.481 815.408H866.778V787.63C866.778 774.483 856.432 763.815 843.63 763.815V359.737H869.148L899.37 259.481H718.63V763.815H691.111Z"
+ ],
+ "attrs": [{}, {}, {}],
+ "isMulticolor": false,
+ "isMulticolor2": false,
+ "tags": ["farcaster"],
+ "grid": 0
+ },
+ "attrs": [{}, {}, {}],
+ "properties": {
+ "order": 408,
+ "id": 19,
+ "name": "farcaster",
+ "prevSize": 32,
+ "code": 59667
+ },
+ "setIdx": 0,
+ "setId": 2,
+ "iconIdx": 19
}
],
"height": 1024,
diff --git a/web/assets/comm-logo.react.js b/web/assets/comm-logo.react.js
new file mode 100644
--- /dev/null
+++ b/web/assets/comm-logo.react.js
@@ -0,0 +1,31 @@
+// @flow
+
+import * as React from 'react';
+
+function CommLogo(): React.Node {
+ return (
+ <svg
+ width="14"
+ height="14"
+ viewBox="0 0 1177 1177"
+ fill="none"
+ xmlns="http://www.w3.org/2000/svg"
+ >
+ <path
+ d="M272.256 0.078125C222.269 0.078125 181.782 40.5653 181.782 90.5523H950.813C1025.77 90.5523 1086.52 151.306 1086.52 226.264V814.346C1136.51 814.346 1177 773.858 1177 723.871V90.5523C1177 40.5653 1136.51 0.078125 1086.52 0.078125H272.256ZM91.3081 181.026C41.3212 181.026 0.833984 221.514 0.833984 271.501V904.82C0.833984 954.807 41.3212 995.294 91.3081 995.294H227.019V1131.01C227.019 1143 231.785 1154.51 240.269 1162.99C248.753 1171.48 260.259 1176.24 272.256 1176.24C279.797 1176.23 287.214 1174.33 293.834 1170.72C300.454 1167.11 306.066 1161.9 310.16 1155.57L430.498 995.294H905.576C955.563 995.294 996.05 954.807 996.05 904.82V271.501C996.05 221.514 955.563 181.026 905.576 181.026H91.3081Z"
+ fill="white"
+ />
+ <circle cx="499.496" cy="503.222" r="114.408" fill="#121826" />
+ <rect
+ x="448.211"
+ y="479.549"
+ width="98.6275"
+ height="276.157"
+ rx="49.3137"
+ fill="#121826"
+ />
+ </svg>
+ );
+}
+
+export default CommLogo;
diff --git a/web/chat/chat-thread-composer.css b/web/chat/chat-thread-composer.css
--- a/web/chat/chat-thread-composer.css
+++ b/web/chat/chat-thread-composer.css
@@ -32,7 +32,13 @@
div.searchField {
flex-grow: 1;
- padding: 1rem;
+ padding: 0.5rem 1rem 1rem 1rem;
+}
+
+div.protocolRow {
+ padding: 1rem 1rem 0.5rem 1rem;
+ display: flex;
+ align-items: center;
}
.closeSearch {
@@ -77,3 +83,17 @@
flex-direction: row;
align-items: center;
}
+
+div.rightContainer {
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ gap: 8px;
+}
+
+div.protocolIcons {
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ gap: 4px;
+}
diff --git a/web/chat/chat-thread-composer.react.js b/web/chat/chat-thread-composer.react.js
--- a/web/chat/chat-thread-composer.react.js
+++ b/web/chat/chat-thread-composer.react.js
@@ -12,6 +12,7 @@
import { useResolvableNames } from 'lib/hooks/names-cache.js';
import { userInfoSelectorForPotentialMembers } from 'lib/selectors/user-selectors.js';
import { extractFIDFromUserID } from 'lib/shared/id-utils.js';
+import { protocolNames } from 'lib/shared/protocol-names.js';
import {
usePotentialMemberItems,
useSearchUsers,
@@ -29,10 +30,13 @@
import { supportsFarcasterDCs } from 'lib/utils/services-utils.js';
import css from './chat-thread-composer.css';
+import CommLogo from '../assets/comm-logo.react.js';
import UserAvatar from '../avatars/user-avatar.react.js';
import Button from '../components/button.react.js';
import Label from '../components/label.react.js';
+import ProtocolIcon from '../components/protocol-icon.react.js';
import Search from '../components/search.react.js';
+import SelectProtocolDropdown from '../components/select-protocol-dropdown.react.js';
import type { InputState } from '../input/input-state.js';
import Alert from '../modals/alert.react.js';
import { updateNavInfoActionType } from '../redux/action-types.js';
@@ -50,10 +54,10 @@
function ChatThreadComposer(props: Props): React.Node {
const { userInfoInputArray, threadID, inputState } = props;
+ const { selectedProtocol } = useProtocolSelection();
const [usernameInputText, setUsernameInputText] = React.useState('');
- const { selectedProtocol } = useProtocolSelection();
const dispatch = useDispatch();
const loggedInUserInfo = useLoggedInUserInfo();
@@ -178,6 +182,18 @@
const userItems = userListItemsWithENSNames.map(
(userSearchResult: UserListItem) => {
+ let icon = null;
+ if (
+ userSearchResult.supportedProtocols.includes(protocolNames.COMM_DM)
+ ) {
+ icon = <ProtocolIcon icon={<CommLogo />} size={23} />;
+ } else if (
+ userSearchResult.supportedProtocols.includes(
+ protocolNames.FARCASTER_DC,
+ )
+ ) {
+ icon = <ProtocolIcon protocol="Farcaster DC" size={23} />;
+ }
return (
<li key={userSearchResult.id} className={css.searchResultsItem}>
<Button
@@ -189,7 +205,10 @@
<UserAvatar size="S" userID={userSearchResult.id} />
<div className={css.userName}>{userSearchResult.username}</div>
</div>
- <div className={css.userInfo}>{userSearchResult.notice}</div>
+ <div className={css.rightContainer}>
+ <div className={css.userInfo}>{userSearchResult.notice}</div>
+ <div className={css.protocolIcons}>{icon}</div>
+ </div>
</Button>
</li>
);
@@ -198,10 +217,10 @@
return <ul className={css.searchResultsContainer}>{userItems}</ul>;
}, [
- onSelectUserFromSearch,
- userInfoInputArray.length,
userListItemsWithENSNames,
usernameInputText,
+ userInfoInputArray.length,
+ onSelectUserFromSearch,
]);
const hideSearch = React.useCallback(
@@ -254,6 +273,9 @@
return (
<div className={threadSearchContainerStyles}>
+ <div className={css.protocolRow}>
+ <SelectProtocolDropdown />
+ </div>
<div className={css.searchRow}>
<div className={css.searchField}>
<Search
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
@@ -4,7 +4,10 @@
import { useModalContext } from 'lib/components/modal-provider.react.js';
import SWMansionIcon from 'lib/components/swmansion-icon.react.js';
+import { useProtocolSelection } from 'lib/contexts/protocol-selection-context.js';
import { threadIsPending } from 'lib/shared/thread-utils.js';
+import { getProtocolByName } from 'lib/shared/threads/protocols/thread-protocols.js';
+import { threadSpecs } from 'lib/shared/threads/thread-specs.js';
import type { ThreadInfo } from 'lib/types/minimally-encoded-thread-permissions-types.js';
import { useResolvedThreadInfo } from 'lib/utils/entity-helpers.js';
@@ -13,15 +16,22 @@
import css from './thread-top-bar.css';
import ThreadAvatar from '../avatars/thread-avatar.react.js';
import Button from '../components/button.react.js';
+import ProtocolIcon from '../components/protocol-icon.react.js';
import { InputStateContext } from '../input/input-state.js';
+import Modal from '../modals/modal.react.js';
import MessageSearchModal from '../modals/search/message-search-modal.react.js';
+import { useSelector } from '../redux/redux-utils.js';
type ThreadTopBarProps = {
+threadInfo: ThreadInfo,
};
function ThreadTopBar(props: ThreadTopBarProps): React.Node {
const { threadInfo } = props;
- const { pushModal } = useModalContext();
+ const { pushModal, popModal } = useModalContext();
+
+ const isThreadCreation = useSelector(
+ state => state.navInfo.chatMode === 'create',
+ );
let threadMenu = null;
if (!threadIsPending(threadInfo.id)) {
@@ -42,6 +52,43 @@
const { uiName } = useResolvedThreadInfo(threadInfo);
+ const { selectedProtocol } = useProtocolSelection();
+
+ const protocolIcon = React.useMemo(() => {
+ if (!isThreadCreation) {
+ return null;
+ }
+
+ const protocol = selectedProtocol
+ ? getProtocolByName(selectedProtocol)
+ : threadSpecs[threadInfo.type].protocol();
+ if (!protocol) {
+ return null;
+ }
+
+ const handleProtocolClick = () => {
+ pushModal(
+ <Modal name={protocol.protocolName} onClose={popModal} size="large">
+ <div style={{ color: 'white' }}>
+ {protocol.presentationDetails.description}
+ </div>
+ </Modal>,
+ );
+ };
+
+ return (
+ <Button onClick={handleProtocolClick} variant="plain">
+ <ProtocolIcon protocol={protocol.protocolName} size={30} />
+ </Button>
+ );
+ }, [
+ isThreadCreation,
+ selectedProtocol,
+ threadInfo.type,
+ pushModal,
+ popModal,
+ ]);
+
return (
<>
<div className={css.topBarContainer}>
@@ -58,6 +105,7 @@
/>
</Button>
{threadMenu}
+ {protocolIcon}
</div>
</div>
<PinnedMessagesBanner threadInfo={threadInfo} />
diff --git a/web/comm-icon.react.js b/web/comm-icon.react.js
--- a/web/comm-icon.react.js
+++ b/web/comm-icon.react.js
@@ -26,7 +26,8 @@
| 'link_plus-outline'
| 'user-filled'
| 'user-edit'
- | 'farcaster-outline';
+ | 'farcaster-outline'
+ | 'farcaster';
type CommIconProps = {
+icon: CommIcons,
diff --git a/web/components/protocol-icon.react.js b/web/components/protocol-icon.react.js
new file mode 100644
--- /dev/null
+++ b/web/components/protocol-icon.react.js
@@ -0,0 +1,67 @@
+// @flow
+
+import {
+ faServer as server,
+ faLock as lock,
+} from '@fortawesome/free-solid-svg-icons';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import * as React from 'react';
+
+import { getProtocolByName } from 'lib/shared/threads/protocols/thread-protocols.js';
+import type { ProtocolName } from 'lib/shared/threads/thread-spec.js';
+
+import CommIcon from '../comm-icon.react.js';
+
+type Props = {
+ +icon?: React.Node,
+ +protocol?: ProtocolName,
+ +size: number,
+};
+
+function ProtocolIcon(props: Props): React.Node {
+ let iconComponent = null;
+ const protocolIcon = getProtocolByName(props.protocol)?.presentationDetails
+ ?.protocolIcon;
+ const iconSize = props.size * 0.5;
+ let iconBackground = 'var(--bg)';
+ if (props.icon) {
+ iconComponent = props.icon;
+ } else if (protocolIcon === 'lock') {
+ iconComponent = (
+ <FontAwesomeIcon
+ icon={lock}
+ style={{ color: 'var(--fg)', fontSize: `${iconSize}px` }}
+ />
+ );
+ } else if (protocolIcon === 'server') {
+ iconComponent = (
+ <FontAwesomeIcon
+ icon={server}
+ style={{ color: 'var(--fg)', fontSize: `${iconSize}px` }}
+ />
+ );
+ } else if (protocolIcon === 'farcaster') {
+ iconComponent = <CommIcon icon="farcaster" size={iconSize} color="#fff" />;
+ iconBackground = '#855DCD';
+ }
+
+ return (
+ <div
+ style={{
+ width: props.size,
+ height: props.size,
+ borderRadius: props.size,
+ backgroundColor: iconBackground,
+ display: 'flex',
+ justifyContent: 'center',
+ alignItems: 'center',
+ marginLeft: 3,
+ marginRight: 3,
+ }}
+ >
+ {iconComponent}
+ </div>
+ );
+}
+
+export default ProtocolIcon;
diff --git a/web/components/select-protocol-dropdown.css b/web/components/select-protocol-dropdown.css
new file mode 100644
--- /dev/null
+++ b/web/components/select-protocol-dropdown.css
@@ -0,0 +1,86 @@
+.container {
+ background-color: var(--inputField-background-secondary-default);
+ position: relative;
+ width: 100%;
+}
+
+.bordersWithOptions {
+ border-top-left-radius: 10px;
+ border-top-right-radius: 10px;
+}
+
+.bordersWithoutOptions {
+ border-radius: 10px;
+}
+
+.dropdownHeader {
+ padding: 8px 12px;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ cursor: pointer;
+}
+
+.dropdownOption {
+ padding: 8px 12px;
+ display: flex;
+ align-items: center;
+ cursor: pointer;
+ list-style: none;
+}
+
+.dropdownOption:hover {
+ background-color: var(--dropdown-option-hover-bg);
+ border-radius: 8px;
+}
+
+.optionContent {
+ display: flex;
+ align-items: center;
+ height: 30px;
+}
+
+.selectedOption {
+ display: flex;
+ align-items: center;
+ height: 30px;
+}
+
+.optionsContainer {
+ background-color: var(--inputField-background-secondary-default);
+ border-bottom-left-radius: 10px;
+ border-bottom-right-radius: 10px;
+ position: absolute;
+ top: 47px;
+ width: 100%;
+ margin: 0;
+ padding: 0;
+ list-style: none;
+ box-shadow: 0px 2px 8px rgba(0, 0, 0, 0.15);
+}
+
+.text {
+ font-size: 16px;
+ color: var(--fg);
+ display: flex;
+ align-items: center;
+ height: 30px;
+}
+
+.protocolName {
+ margin-left: 8px;
+ font-size: 16px;
+ color: var(--fg);
+}
+
+.chevron {
+ color: var(--fg);
+}
+
+.disabled {
+ cursor: not-allowed;
+}
+
+.disabled .dropdownHeader {
+ cursor: not-allowed;
+}
diff --git a/web/components/select-protocol-dropdown.react.js b/web/components/select-protocol-dropdown.react.js
new file mode 100644
--- /dev/null
+++ b/web/components/select-protocol-dropdown.react.js
@@ -0,0 +1,127 @@
+// @flow
+
+import { faChevronDown } from '@fortawesome/free-solid-svg-icons';
+import { faInfoCircle } from '@fortawesome/free-solid-svg-icons/faInfoCircle.js';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import classNames from 'classnames';
+import * as React from 'react';
+
+import { useModalContext } from 'lib/components/modal-provider.react.js';
+import { useProtocolSelection } from 'lib/contexts/protocol-selection-context.js';
+import { protocols } from 'lib/shared/threads/protocols/thread-protocols.js';
+import type { ProtocolName } from 'lib/shared/threads/thread-spec.js';
+import { protocolInfoAlert } from 'lib/utils/alert-utils.js';
+
+import ProtocolIcon from './protocol-icon.react.js';
+import css from './select-protocol-dropdown.css';
+import Modal from '../modals/modal.react.js';
+
+function SelectProtocolDropdown(): React.Node {
+ const { selectedProtocol, setSelectedProtocol, availableProtocols } =
+ useProtocolSelection();
+ const { pushModal, popModal } = useModalContext();
+ const [showOptions, setShowOptions] = React.useState(false);
+
+ const onDropdownPress = React.useCallback(() => {
+ if (availableProtocols.length < 1) {
+ return;
+ }
+ setShowOptions(!showOptions);
+ }, [availableProtocols.length, showOptions]);
+
+ const onInfoPress = React.useCallback(() => {
+ pushModal(
+ <Modal
+ size="fit-content"
+ name={protocolInfoAlert.title}
+ onClose={popModal}
+ >
+ <div style={{ color: 'var(--fg)' }}>
+ <p>{protocolInfoAlert.message}</p>
+ </div>
+ </Modal>,
+ );
+ }, [pushModal, popModal]);
+
+ const onOptionSelection = React.useCallback(
+ (protocolName: ProtocolName) => {
+ setSelectedProtocol(protocolName);
+ setShowOptions(false);
+ },
+ [setSelectedProtocol],
+ );
+
+ const options = React.useMemo(
+ () =>
+ protocols()
+ .filter(protocol => availableProtocols.includes(protocol.protocolName))
+ .map(protocol => (
+ <li
+ className={css.dropdownOption}
+ key={protocol.protocolName}
+ onClick={() => onOptionSelection(protocol.protocolName)}
+ >
+ <div className={css.optionContent}>
+ <ProtocolIcon protocol={protocol.protocolName} size={30} />
+ <span className={css.protocolName}>{protocol.protocolName}</span>
+ </div>
+ </li>
+ )),
+ [availableProtocols, onOptionSelection],
+ );
+
+ const containerClassNames = classNames(css.container, {
+ [css.bordersWithOptions]: showOptions,
+ [css.bordersWithoutOptions]: !showOptions,
+ [css.disabled]: availableProtocols.length < 1,
+ });
+
+ const dropdownHeader = React.useMemo(() => {
+ if (!selectedProtocol) {
+ return <span className={css.text}>Select chat type</span>;
+ }
+ return (
+ <div className={css.selectedOption}>
+ <ProtocolIcon protocol={selectedProtocol} size={30} />
+ <span className={css.protocolName}>{selectedProtocol}</span>
+ </div>
+ );
+ }, [selectedProtocol]);
+
+ const optionsComponent = React.useMemo(() => {
+ if (!showOptions) {
+ return null;
+ }
+ return <ul className={css.optionsContainer}>{options}</ul>;
+ }, [options, showOptions]);
+
+ const icon = React.useMemo(
+ () => (availableProtocols.length > 0 ? faChevronDown : faInfoCircle),
+ [availableProtocols.length],
+ );
+
+ return (
+ <div className={containerClassNames}>
+ <div className={css.dropdownHeader}>
+ <div
+ onClick={onDropdownPress}
+ style={{ display: 'flex', alignItems: 'center', flex: 1 }}
+ >
+ {dropdownHeader}
+ </div>
+ <FontAwesomeIcon
+ icon={icon}
+ size={14}
+ className={css.chevron}
+ onClick={
+ availableProtocols.length > 0 ? onDropdownPress : onInfoPress
+ }
+ style={{ cursor: 'pointer' }}
+ />
+ </div>
+ {optionsComponent}
+ </div>
+ );
+}
+
+export default SelectProtocolDropdown;
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Sat, Dec 6, 9:09 PM (21 h, 56 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
5840875
Default Alt Text
D15347.1765055349.diff (18 KB)
Attached To
Mode
D15347: [web] choosing protocol type while creating a thread
Attached
Detach File
Event Timeline
Log In to Comment