diff --git a/pkg/interface/src/logic/lib/omnibox.js b/pkg/interface/src/logic/lib/omnibox.js index eb1d547b0..d79ae08e8 100644 --- a/pkg/interface/src/logic/lib/omnibox.js +++ b/pkg/interface/src/logic/lib/omnibox.js @@ -110,8 +110,12 @@ export default function index(contacts, associations, apps, currentGroup, groups landscape.push(obj); } else { const app = each.metadata.module || each['app-name']; - const group = (groups[each.group]?.hidden) - ? '/home' : each.group; + let group = each.group; + if (groups[each.group]?.hidden && app === 'chat') { + group = '/messages'; + } else if (groups[each.group]?.hidden) { + group = '/home'; + } const obj = result( title, `/~landscape${group}/join/${app}${each.resource}`, diff --git a/pkg/interface/src/logic/lib/util.ts b/pkg/interface/src/logic/lib/util.ts index 679511522..c39f9607e 100644 --- a/pkg/interface/src/logic/lib/util.ts +++ b/pkg/interface/src/logic/lib/util.ts @@ -392,3 +392,16 @@ export const useHovering = (): useHoveringInterface => { }; return { hovering, bind }; }; + +const DM_REGEX = /ship\/~([a-z]|-)*\/dm--/; +export function getItemTitle(association: Association) { + if(DM_REGEX.test(association.resource)) { + const [,,ship,name] = association.resource.split('/'); + if(ship.slice(1) === window.ship) { + return cite(`~${name.slice(4)}`); + } + return cite(ship); + + } + return association.metadata.title || association.resource +}; diff --git a/pkg/interface/src/logic/lib/workspace.ts b/pkg/interface/src/logic/lib/workspace.ts index 7f63b447a..e17b81e7b 100644 --- a/pkg/interface/src/logic/lib/workspace.ts +++ b/pkg/interface/src/logic/lib/workspace.ts @@ -7,6 +7,8 @@ export function getTitleFromWorkspace( switch (workspace.type) { case "home": return "My Channels"; + case "messages": + return "Messages"; case "group": const association = associations.groups[workspace.group]; return association?.metadata?.title || ""; diff --git a/pkg/interface/src/types/workspace.ts b/pkg/interface/src/types/workspace.ts index 5b4e68877..69da82ed3 100644 --- a/pkg/interface/src/types/workspace.ts +++ b/pkg/interface/src/types/workspace.ts @@ -9,4 +9,8 @@ interface HomeWorkspace { type: 'home' } -export type Workspace = HomeWorkspace | GroupWorkspace; +interface Messages { + type: 'messages' +} + +export type Workspace = HomeWorkspace | GroupWorkspace | Messages; diff --git a/pkg/interface/src/views/components/StatusBar.js b/pkg/interface/src/views/components/StatusBar.js index ca6cd9da6..94fa9f949 100644 --- a/pkg/interface/src/views/components/StatusBar.js +++ b/pkg/interface/src/views/components/StatusBar.js @@ -27,7 +27,7 @@ const StatusBar = (props) => { const invites = [].concat(...Object.values(props.invites).map(obj => Object.values(obj))); const metaKey = (window.navigator.platform.includes('Mac')) ? '⌘' : 'Ctrl+'; const { toggleOmnibox, hideAvatars } = - useLocalState(({ toggleOmnibox, hideAvatars }) => + useLocalState(({ toggleOmnibox, hideAvatars }) => ({ toggleOmnibox, hideAvatars }) ); @@ -91,6 +91,9 @@ const StatusBar = (props) => { > Submit an issue + props.history.push('/~landscape/messages')}> + + `${props.baseUrl}${to}`; return ( diff --git a/pkg/interface/src/views/landscape/components/InvitePopover.tsx b/pkg/interface/src/views/landscape/components/InvitePopover.tsx index 2f16a42a5..bcc851b15 100644 --- a/pkg/interface/src/views/landscape/components/InvitePopover.tsx +++ b/pkg/interface/src/views/landscape/components/InvitePopover.tsx @@ -47,10 +47,6 @@ export function InvitePopover(props: InvitePopoverProps) { useOutsideClick(innerRef, onOutsideClick); const onSubmit = async ({ ships, emails }: { ships: string[] }, actions) => { - if(props.workspace.type === 'home') { - history.push(`/~landscape/dm/${deSig(ships[0])}`); - return; - } // TODO: how to invite via email? try { const resource = resourceFromPath(association.group); @@ -105,14 +101,13 @@ export function InvitePopover(props: InvitePopoverProps) { Invite to - {title || "DM"} + {title} diff --git a/pkg/interface/src/views/landscape/components/NewChannel.tsx b/pkg/interface/src/views/landscape/components/NewChannel.tsx index 99f2e6af3..95fb4cf15 100644 --- a/pkg/interface/src/views/landscape/components/NewChannel.tsx +++ b/pkg/interface/src/views/landscape/components/NewChannel.tsx @@ -3,10 +3,7 @@ import { Box, ManagedTextInputField as Input, Col, - ManagedRadioButtonField as Radio, - Text, - Icon, - Row + Text } from '@tlon/indigo-react'; import { Formik, Form } from 'formik'; import * as Yup from 'yup'; @@ -21,8 +18,8 @@ import { useWaitForProps } from '~/logic/lib/useWaitForProps'; import { Groups } from '~/types/group-update'; import { ShipSearch, shipSearchSchemaInGroup, shipSearchSchema } from '~/views/components/ShipSearch'; import { Rolodex, Workspace } from '~/types'; -import {IconRadio} from '~/views/components/IconRadio'; -import {ChannelWriteFieldSchema, ChannelWritePerms} from './ChannelWritePerms'; +import { IconRadio } from '~/views/components/IconRadio'; +import { ChannelWriteFieldSchema, ChannelWritePerms } from './ChannelWritePerms'; type FormSchema = { name: string; @@ -32,7 +29,7 @@ type FormSchema = { } & ChannelWriteFieldSchema; const formSchema = (members?: string[]) => Yup.object({ - name: Yup.string().required('Channel must have a name'), + name: Yup.string(), description: Yup.string(), ships: Yup.array(Yup.string()), moduleType: Yup.string().required('Must choose channel type'), @@ -55,11 +52,16 @@ export function NewChannel(props: NewChannelProps & RouteComponentProps) { const waiter = useWaitForProps(props, 5000); const onSubmit = async (values: FormSchema, actions) => { + const name = (values.name) ? values.name : values.moduleType; const resId: string = stringToSymbol(values.name) - + ((workspace?.type !== 'home') ? `-${Math.floor(Math.random() * 10000)}` + + ((workspace?.type !== 'messages') ? `-${Math.floor(Math.random() * 10000)}` : ''); try { - let { name, description, moduleType, ships, writers } = values; + let { description, moduleType, ships, writers } = values; + ships = ships.filter(e => e !== ""); + if (workspace?.type === 'messages' && ships.length === 1) { + return history.push(`/~landscape/dm/${deSig(ships[0])}`); + } if (group) { await api.graph.createManagedGraph( resId, @@ -83,7 +85,6 @@ export function NewChannel(props: NewChannelProps & RouteComponentProps) { writers.push(us); await api.groups.addTag(resource, tag, writers); } - } else { await api.graph.createUnmanagedGraph( resId, @@ -115,13 +116,13 @@ export function NewChannel(props: NewChannelProps & RouteComponentProps) { history.push(props.baseUrl)}> {'<- Back'} - - New Channel + + {workspace?.type === 'messages' ? 'Direct Message' : 'New Channel'} - + Channel Type - - - + + + - {(workspace?.type === 'home') ? ( + {(workspace?.type === 'home' || workspace?.type === 'messages') ? ( ) : ( + />) : ( )} - - Create Channel + Create diff --git a/pkg/interface/src/views/landscape/components/Resource.tsx b/pkg/interface/src/views/landscape/components/Resource.tsx index e13136060..9cafcf5e7 100644 --- a/pkg/interface/src/views/landscape/components/Resource.tsx +++ b/pkg/interface/src/views/landscape/components/Resource.tsx @@ -28,14 +28,14 @@ type ResourceProps = StoreState & { } & RouteComponentProps; export function Resource(props: ResourceProps) { - const { association, api, notificationsGraphConfig, groups } = props; + const { association, api, notificationsGraphConfig, groups, contacts } = props; const app = association.metadata.module || association["app-name"]; const rid = association.resource; const selectedGroup = association.group; const relativePath = (p: string) => `${props.baseUrl}/resource/${app}${rid}${p}`; - const skelProps = { api, association, groups }; + const skelProps = { api, association, groups, contacts }; let title = props.association.metadata.title; if ('workspace' in props) { if ('group' in props.workspace && props.workspace.group in props.associations.groups) { @@ -65,12 +65,12 @@ export function Resource(props: ResourceProps) { render={(routeProps) => { return ( ); diff --git a/pkg/interface/src/views/landscape/components/ResourceSkeleton.tsx b/pkg/interface/src/views/landscape/components/ResourceSkeleton.tsx index f4caf0bdc..63bc35f86 100644 --- a/pkg/interface/src/views/landscape/components/ResourceSkeleton.tsx +++ b/pkg/interface/src/views/landscape/components/ResourceSkeleton.tsx @@ -15,6 +15,8 @@ import { ChannelSettings } from "./ChannelSettings"; import { ChannelMenu } from "./ChannelMenu"; import { NotificationGraphConfig, Groups } from "~/types"; import {isWriter} from "~/logic/lib/group"; +import urbitOb from 'urbit-ob'; +import { getItemTitle } from '~/logic/lib/util'; const TruncatedBox = styled(Box)` white-space: pre; @@ -24,6 +26,7 @@ const TruncatedBox = styled(Box)` type ResourceSkeletonProps = { groups: Groups; + contacts: any; association: Association; api: GlobalApi; baseUrl: string; @@ -35,16 +38,30 @@ type ResourceSkeletonProps = { export function ResourceSkeleton(props: ResourceSkeletonProps) { const { association, api, baseUrl, children, atRoot, groups } = props; const app = association?.metadata?.module || association["app-name"]; - const rid = association.resource; + const rid = association.resource; const group = groups[association.group]; - const workspace = - group?.hidden ? "/home" : association.group; + let workspace = association.group; - const title = props.title || association?.metadata?.title; + if (group?.hidden && app === "chat") { + workspace = "/messages"; + } else if (group?.hidden) { + workspace = "/home"; + } + + let title = (workspace === "/messages") + ? getItemTitle(association) + : association?.metadata?.title; + + let recipient = false; + + if (urbitOb.isValidPatp(title)) { + recipient = title; + title = (props.contacts?.[title]?.nickname) ? props.contacts[title].nickname : title; + } const [, , ship, resource] = rid.split("/"); - const resourcePath = (p: string) => baseUrl + `/resource/${app}/ship/${ship}/${resource}` + p; + const resourcePath = (p: string) => baseUrl + p; const isOwn = `~${window.ship}` === ship; let canWrite = (app === 'publish') ? true : false; @@ -78,7 +95,16 @@ export function ResourceSkeleton(props: ResourceSkeletonProps) { {"<- Back"} - + {title} @@ -91,12 +117,13 @@ export function ResourceSkeleton(props: ResourceSkeletonProps) { color="gray" > - {association?.metadata?.description} + {(workspace === "/messages") ? recipient : association?.metadata?.description} diff --git a/pkg/interface/src/views/landscape/components/Sidebar/Sidebar.tsx b/pkg/interface/src/views/landscape/components/Sidebar/Sidebar.tsx index 9dfe49a96..8998d4b92 100644 --- a/pkg/interface/src/views/landscape/components/Sidebar/Sidebar.tsx +++ b/pkg/interface/src/views/landscape/components/Sidebar/Sidebar.tsx @@ -93,6 +93,8 @@ export function Sidebar(props: SidebarProps) { handleSubmit={setConfig} selected={selected || ''} workspace={workspace} + api={props.api} + history={props.history} /> ); diff --git a/pkg/interface/src/views/landscape/components/Sidebar/SidebarItem.tsx b/pkg/interface/src/views/landscape/components/Sidebar/SidebarItem.tsx index 0e53ecc27..40cf6ff81 100644 --- a/pkg/interface/src/views/landscape/components/Sidebar/SidebarItem.tsx +++ b/pkg/interface/src/views/landscape/components/Sidebar/SidebarItem.tsx @@ -1,13 +1,14 @@ import React from "react"; import _ from 'lodash'; -import { Icon, Row, Box, Text } from "@tlon/indigo-react"; +import { Icon, Row, Box, Text, BaseImage } from "@tlon/indigo-react"; import { SidebarAppConfigs, SidebarItemStatus } from "./Sidebar"; import { HoverBoxLink } from "~/views/components/HoverBox"; import { Groups, Association } from "~/types"; - -import { cite, getModuleIcon } from "~/logic/lib/util"; +import { Sigil } from '~/logic/lib/sigil'; +import urbitOb from 'urbit-ob'; +import { getModuleIcon, getItemTitle, uxToHex } from "~/logic/lib/util"; function SidebarItemIndicator(props: { status?: SidebarItemStatus }) { switch (props.status) { @@ -24,31 +25,18 @@ function SidebarItemIndicator(props: { status?: SidebarItemStatus }) { } } -; - -const DM_REGEX = /ship\/~([a-z]|-)*\/dm--/; -function getItemTitle(association: Association) { - if(DM_REGEX.test(association.resource)) { - const [,,ship,name] = association.resource.split('/'); - if(ship.slice(1) === window.ship) { - return cite(`~${name.slice(4)}`); - } - return cite(ship); - - } - return association.metadata.title || association.resource -} - export function SidebarItem(props: { hideUnjoined: boolean; association: Association; + contacts: any; groups: Groups; path: string; selected: boolean; apps: SidebarAppConfigs; + workspace: Workspace; }) { const { association, path, selected, apps, groups } = props; - const title = getItemTitle(association); + let title = getItemTitle(association); const appName = association?.["app-name"]; const mod = association?.metadata?.module || appName; const rid = association?.resource @@ -58,12 +46,19 @@ export function SidebarItem(props: { if (!app) { return null; } + const DM = (isUnmanaged && props.workspace?.type === "messages"); const itemStatus = app.getStatus(path); const hasUnread = itemStatus === "unread" || itemStatus === "mention"; const isSynced = itemStatus !== "unsubscribed"; - const baseUrl = isUnmanaged ? `/~landscape/home` : `/~landscape${groupPath}`; + let baseUrl = `/~landscape${groupPath}`; + + if (DM) { + baseUrl = '/~landscape/messages'; + } else if (isUnmanaged) { + baseUrl = '/~landscape/home'; + } const to = isSynced ? `${baseUrl}/resource/${mod}${rid}` @@ -75,6 +70,21 @@ export function SidebarItem(props: { return null; } + let img = null; + + if (urbitOb.isValidPatp(title)) { + if (props.contacts?.[title] && props.contacts[title].avatar) { + img = ; + } else { + img = + } + if (props.contacts?.[title] && props.contacts[title].nickname) { + title = props.contacts[title].nickname; + } + } else { + img = + } + return ( - + {DM ? img : ( + + ) + } { const assoc = associations[a]; - return group - ? assoc.group === group - : !(assoc.group in props.associations.groups); + if (workspace?.type === 'messages') { + return (!(assoc.group in props.associations.groups) && assoc.metadata.module === "chat"); + } else { + return group + ? assoc.group === group + : (!(assoc.group in props.associations.groups) && assoc.metadata.module !== "chat"); + } }) .sort(sidebarSort(associations, props.apps)[config.sortBy]); @@ -69,6 +75,8 @@ export function SidebarList(props: { apps={props.apps} hideUnjoined={config.hideUnjoined} groups={props.groups} + contacts={props.contacts} + workspace={workspace} /> ); })} diff --git a/pkg/interface/src/views/landscape/components/Sidebar/SidebarListHeader.tsx b/pkg/interface/src/views/landscape/components/Sidebar/SidebarListHeader.tsx index 6d49d10f9..66364ac66 100644 --- a/pkg/interface/src/views/landscape/components/Sidebar/SidebarListHeader.tsx +++ b/pkg/interface/src/views/landscape/components/Sidebar/SidebarListHeader.tsx @@ -17,8 +17,11 @@ import { Link, useHistory } from 'react-router-dom'; import { getGroupFromWorkspace } from "~/logic/lib/workspace"; import { roleForShip } from "~/logic/lib/group"; import {Groups, Rolodex, Associations} from "~/types"; +import { NewChannel } from "~/views/landscape/components/NewChannel"; +import GlobalApi from "~/logic/api/global"; export function SidebarListHeader(props: { + api: GlobalApi; initialValues: SidebarListConfig; associations: Associations; groups: Groups; @@ -40,10 +43,12 @@ export function SidebarListHeader(props: { const groupPath = getGroupFromWorkspace(props.workspace); const role = props.groups?.[groupPath] ? roleForShip(props.groups[groupPath], window.ship) : undefined; - const memberMetadata = + const memberMetadata = groupPath ? props.associations.contacts?.[groupPath].metadata.vip === 'member-metadata' : false; - const isAdmin = memberMetadata || (role === "admin") || (props.workspace?.type === 'home'); + const isAdmin = memberMetadata || (role === "admin") || (props.workspace?.type === 'home') || (props.workspace?.type === "messages"); + + const noun = (props.workspace?.type === "messages") ? "Messages" : "Channels"; return ( - {props.initialValues.hideUnjoined ? "Joined Channels" : "All Channels"} + {props.initialValues.hideUnjoined ? `Joined ${noun}` : `All ${noun}`} - + + + } + > + + + ) + : ( + + to={!!groupPath + ? `/~landscape${groupPath}/new` + : `/~landscape/${props.workspace?.type}/new`}> - - - + DM - - + ) + } {props.children} diff --git a/pkg/interface/src/views/landscape/index.tsx b/pkg/interface/src/views/landscape/index.tsx index fe00da563..b04e3892d 100644 --- a/pkg/interface/src/views/landscape/index.tsx +++ b/pkg/interface/src/views/landscape/index.tsx @@ -27,7 +27,7 @@ type LandscapeProps = StoreState & { export function DMRedirect(props: LandscapeProps & RouteComponentProps & { ship: string; }) { const { ship, api, history, graphKeys } = props; const goToGraph = useCallback((graph: string) => { - history.push(`/~landscape/home/resource/chat/ship/~${graph}`); + history.push(`/~landscape/messages/resource/chat/ship/~${graph}`); }, [history]); useEffect(() => {