mirror of
https://github.com/ilyakooo0/urbit.git
synced 2024-12-12 15:01:38 +03:00
Merge pull request #4345 from urbit/mp/profile/stubs
profile: more stubs
This commit is contained in:
commit
6927327c7f
@ -1,6 +1,6 @@
|
||||
/- spider,
|
||||
graph=graph-store,
|
||||
*metadata-store,
|
||||
met=metadata-store,
|
||||
*group,
|
||||
group-store,
|
||||
inv=invite-store
|
||||
@ -57,18 +57,18 @@
|
||||
::
|
||||
:: Setup metadata
|
||||
::
|
||||
=/ =metadata
|
||||
%* . *metadata
|
||||
=/ =metadatum:met
|
||||
%* . *metadatum:met
|
||||
title title.action
|
||||
description description.action
|
||||
date-created now.bowl
|
||||
creator our.bowl
|
||||
module module.action
|
||||
==
|
||||
=/ =metadata-action
|
||||
[%add group graph+rid.action metadata]
|
||||
=/ met-action=action:met
|
||||
[%add group graph+rid.action metadatum]
|
||||
;< ~ bind:m
|
||||
(poke-our %metadata-store %metadata-action !>(metadata-action))
|
||||
(poke-our %metadata-store %metadata-action !>(met-action))
|
||||
;< ~ bind:m
|
||||
(poke-our %metadata-push-hook %push-hook-action !>([%add group]))
|
||||
::
|
||||
|
@ -1,4 +1,4 @@
|
||||
/- spider, graph-view, graph=graph-store, *metadata-store, *group
|
||||
/- spider, graph-view, graph=graph-store, met=metadata-store, *group
|
||||
/+ strandio, resource
|
||||
=>
|
||||
|%
|
||||
@ -9,16 +9,14 @@
|
||||
++ scry-metadata
|
||||
|= rid=resource
|
||||
=/ m (strand ,(unit resource))
|
||||
;< paxs=(unit (set path)) bind:m
|
||||
%+ scry:strandio ,(unit (set path))
|
||||
;< group=(unit resource) bind:m
|
||||
%+ scry:strandio ,(unit resource)
|
||||
;: weld
|
||||
/gx/metadata-store/resource/graph
|
||||
(en-path:resource rid)
|
||||
/noun
|
||||
==
|
||||
?~ paxs (pure:m ~)
|
||||
?~ u.paxs (pure:m ~)
|
||||
(pure:m `(de-path:resource n.u.paxs))
|
||||
(pure:m group)
|
||||
::
|
||||
++ scry-group
|
||||
|= rid=resource
|
||||
@ -42,11 +40,7 @@
|
||||
;< ~ bind:m
|
||||
(poke-our %graph-push-hook %push-hook-action !>([%remove rid]))
|
||||
;< ~ bind:m
|
||||
%+ poke-our %metadata-hook
|
||||
:- %metadata-action
|
||||
!> :+ %remove
|
||||
(en-path:resource group-rid)
|
||||
[%graph (en-path:resource rid)]
|
||||
(poke-our %metadata-push-hook %push-hook-action !>([%remove rid]))
|
||||
(pure:m ~)
|
||||
--
|
||||
::
|
||||
@ -74,6 +68,5 @@
|
||||
(poke-our %group-push-hook %push-hook-action !>([%remove rid.action]))
|
||||
;< ~ bind:m (delete-graph u.ugroup-rid rid.action)
|
||||
;< ~ bind:m
|
||||
%+ poke-our %metadata-hook
|
||||
metadata-hook-action+!>([%remove (en-path:resource u.ugroup-rid)])
|
||||
(poke-our %metadata-push-hook %push-hook-action !>([%remove rid.action]))
|
||||
(pure:m !>(~))
|
||||
|
@ -22,6 +22,8 @@ export default class ContactsApi extends BaseApi<StoreState> {
|
||||
{color: 'fff'} // with no 0x prefix
|
||||
{avatar: null}
|
||||
{avatar: ''}
|
||||
{add-group: {ship, name}}
|
||||
{remove-group: {ship, name}}
|
||||
*/
|
||||
console.log(ship, editField);
|
||||
return this.storeAction({
|
||||
|
@ -116,7 +116,7 @@ export default function index(contacts, associations, apps, currentGroup, groups
|
||||
title,
|
||||
`/~landscape${group}/join/${app}${each.resource}`,
|
||||
app.charAt(0).toUpperCase() + app.slice(1),
|
||||
(associations?.contacts?.[each.group]?.metadata?.title || null)
|
||||
(associations?.groups?.[each.group]?.metadata?.title || null)
|
||||
);
|
||||
subscriptions.push(obj);
|
||||
}
|
||||
|
@ -8,7 +8,7 @@ export function getTitleFromWorkspace(
|
||||
case "home":
|
||||
return "DMs + Drafts";
|
||||
case "group":
|
||||
const association = associations.contacts[workspace.group];
|
||||
const association = associations.groups[workspace.group];
|
||||
return association?.metadata?.title || "";
|
||||
}
|
||||
}
|
||||
|
@ -18,7 +18,7 @@ export type Serial = string;
|
||||
export type Jug<K,V> = Map<K,Set<V>>;
|
||||
|
||||
// name of app
|
||||
export type AppName = 'chat' | 'link' | 'contacts' | 'publish' | 'graph';
|
||||
export type AppName = 'contacts' | 'groups' | 'graph';
|
||||
|
||||
export function getTagFromFrond<O>(frond: O): keyof O {
|
||||
const tags = Object.keys(frond) as Array<keyof O>;
|
||||
|
@ -36,7 +36,7 @@ const getGraphNotifications = (associations: Associations, unreads: Unreads) =>
|
||||
export default function Groups(props: GroupsProps & Parameters<typeof Box>[0]) {
|
||||
const { associations, unreads, inbox, ...boxProps } = props;
|
||||
|
||||
const groups = Object.values(associations?.contacts || {})
|
||||
const groups = Object.values(associations?.groups || {})
|
||||
.filter((e) => e?.group in props.groups)
|
||||
.sort(sortGroupsAlph);
|
||||
const graphUnreads = getGraphUnreads(associations || {}, unreads);
|
||||
|
@ -63,7 +63,7 @@ export function Header(props: {
|
||||
|
||||
const time = moment(props.time).format("HH:mm");
|
||||
const groupTitle =
|
||||
props.associations.contacts?.[props.group]?.metadata?.title;
|
||||
props.associations.groups?.[props.group]?.metadata?.title;
|
||||
|
||||
const app = props.chat ? 'chat' : 'graph';
|
||||
const channelTitle =
|
||||
|
@ -31,7 +31,7 @@ export function Invites(props: InvitesProps) {
|
||||
const resourcePath = resourceAsPath(invite.resource);
|
||||
if (app === "contacts") {
|
||||
await api.contacts.join(resource);
|
||||
await waiter((p) => resourcePath in p.associations?.contacts);
|
||||
await waiter((p) => resourcePath in p.associations?.groups);
|
||||
await api.invite.accept(app, uid);
|
||||
history.push(`/~landscape${resourcePath}`);
|
||||
} else if (app === "graph") {
|
||||
|
@ -44,7 +44,7 @@ export default function NotificationsScreen(props: any) {
|
||||
filter.groups.length === 0
|
||||
? "All"
|
||||
: filter.groups
|
||||
.map((g) => props.associations?.contacts?.[g]?.metadata?.title)
|
||||
.map((g) => props.associations?.groups?.[g]?.metadata?.title)
|
||||
.join(", ");
|
||||
return (
|
||||
<Switch>
|
||||
|
@ -21,6 +21,8 @@ import { AsyncButton } from "~/views/components/AsyncButton";
|
||||
import { ColorInput } from "~/views/components/ColorInput";
|
||||
import { ImageInput } from "~/views/components/ImageInput";
|
||||
import { MarkdownField } from "~/views/apps/publish/components/MarkdownField";
|
||||
import { resourceFromPath } from "~/logic/lib/group";
|
||||
import GroupSearch from "~/views/components/GroupSearch";
|
||||
|
||||
|
||||
const formSchema = Yup.object({
|
||||
@ -62,8 +64,15 @@ export function EditProfile(props: any) {
|
||||
return acc.then(() =>
|
||||
api.contacts.setPublic(newValue)
|
||||
);
|
||||
} else if (key === 'groups') {
|
||||
newValue.map((e) => {
|
||||
if (!contact['groups']?.[e]) {
|
||||
return acc.then(() => {
|
||||
api.contacts.edit(ship, { 'add-group': resourceFromPath(e) });
|
||||
});
|
||||
}
|
||||
})
|
||||
} else if (
|
||||
key !== "groups" &&
|
||||
key !== "last-updated" &&
|
||||
key !== "isPublic"
|
||||
) {
|
||||
@ -105,7 +114,8 @@ export function EditProfile(props: any) {
|
||||
</Col>
|
||||
</Row>
|
||||
<Checkbox mb={3} id="isPublic" label="Public Profile" />
|
||||
<AsyncButton primary loadingText="Updating..." border>
|
||||
<GroupSearch label="Pinned Groups" id="groups" groups={props.groups} associations={props.associations} />
|
||||
<AsyncButton primary loadingText="Updating..." border mt={3}>
|
||||
Submit
|
||||
</AsyncButton>
|
||||
</Form>
|
||||
|
@ -66,6 +66,8 @@ export function Profile(props: any) {
|
||||
contact={contact}
|
||||
s3={props.s3}
|
||||
api={props.api}
|
||||
groups={props.groups}
|
||||
associations={props.associations}
|
||||
isPublic={isPublic}/>
|
||||
) : (
|
||||
<ViewProfile ship={ship} contact={contact} isPublic={isPublic} />
|
||||
|
@ -45,6 +45,8 @@ export default function ProfileScreen(props: any) {
|
||||
<Box>
|
||||
<Profile
|
||||
ship={ship}
|
||||
associations={props.associations}
|
||||
groups={props.groups}
|
||||
contact={contact}
|
||||
api={props.api}
|
||||
s3={props.s3}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React, { useMemo, useCallback } from "react";
|
||||
import React, { useMemo, useCallback, useState, useEffect } from 'react';
|
||||
import {
|
||||
Box,
|
||||
Text,
|
||||
@ -6,17 +6,17 @@ import {
|
||||
Row,
|
||||
Col,
|
||||
Icon,
|
||||
ErrorLabel,
|
||||
} from "@tlon/indigo-react";
|
||||
import _ from "lodash";
|
||||
import { useField } from "formik";
|
||||
import styled from "styled-components";
|
||||
ErrorLabel
|
||||
} from '@tlon/indigo-react';
|
||||
import _ from 'lodash';
|
||||
import { useField } from 'formik';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { roleForShip } from "~/logic/lib/group";
|
||||
import { roleForShip } from '~/logic/lib/group';
|
||||
|
||||
import { DropdownSearch } from "./DropdownSearch";
|
||||
import { Groups } from "~/types";
|
||||
import { Associations, Association } from "~/types/metadata-update";
|
||||
import { DropdownSearch } from './DropdownSearch';
|
||||
import { Groups } from '~/types';
|
||||
import { Associations, Association } from '~/types/metadata-update';
|
||||
|
||||
interface InviteSearchProps {
|
||||
disabled?: boolean;
|
||||
@ -26,11 +26,12 @@ interface InviteSearchProps {
|
||||
label: string;
|
||||
caption?: string;
|
||||
id: string;
|
||||
maxLength?: number;
|
||||
}
|
||||
|
||||
const CandidateBox = styled(Box)<{ selected: boolean }>`
|
||||
&:hover {
|
||||
background-color: ${(p) => p.theme.colors.washedGray};
|
||||
background-color: ${p => p.theme.colors.washedGray};
|
||||
}
|
||||
`;
|
||||
|
||||
@ -64,38 +65,45 @@ function renderCandidate(
|
||||
|
||||
export function GroupSearch(props: InviteSearchProps) {
|
||||
const { id, caption, label } = props;
|
||||
const [selected, setSelected] = useState([] as string[]);
|
||||
const groups: Association[] = useMemo(() => {
|
||||
return props.adminOnly
|
||||
? Object.values(
|
||||
Object.keys(props.associations?.contacts)
|
||||
Object.keys(props.associations?.groups)
|
||||
.filter(
|
||||
(e) => roleForShip(props.groups[e], window.ship) === "admin"
|
||||
e => roleForShip(props.groups[e], window.ship) === 'admin'
|
||||
)
|
||||
.reduce((obj, key) => {
|
||||
obj[key] = props.associations?.contacts[key];
|
||||
obj[key] = props.associations?.groups[key];
|
||||
return obj;
|
||||
}, {}) || {}
|
||||
)
|
||||
: Object.values(props.associations?.contacts || {});
|
||||
}, [props.associations?.contacts]);
|
||||
: Object.values(props.associations?.groups || {});
|
||||
}, [props.associations?.groups]);
|
||||
|
||||
const [{ value }, meta, { setValue, setTouched }] = useField(props.id);
|
||||
|
||||
useEffect(() => {
|
||||
setValue(selected);
|
||||
}, [selected])
|
||||
|
||||
const { title: groupTitle } =
|
||||
props.associations.contacts?.[value]?.metadata || {};
|
||||
props.associations.groups?.[value]?.metadata || {};
|
||||
|
||||
const onSelect = useCallback(
|
||||
(a: Association) => {
|
||||
setValue(a.group);
|
||||
(s: string) => {
|
||||
setTouched(true);
|
||||
setSelected(v => _.uniq([...v, s]));
|
||||
},
|
||||
[setValue]
|
||||
[setTouched, setSelected]
|
||||
);
|
||||
|
||||
const onUnselect = useCallback(() => {
|
||||
setValue(undefined);
|
||||
setTouched(true);
|
||||
}, [setValue]);
|
||||
const onRemove = useCallback(
|
||||
(s: string) => {
|
||||
setSelected(groups => groups.filter(group => group !== s))
|
||||
},
|
||||
[setSelected]
|
||||
);
|
||||
|
||||
return (
|
||||
<Col>
|
||||
@ -105,8 +113,23 @@ export function GroupSearch(props: InviteSearchProps) {
|
||||
{caption}
|
||||
</Label>
|
||||
)}
|
||||
{value && (
|
||||
<DropdownSearch<Association>
|
||||
mt="2"
|
||||
candidates={groups}
|
||||
placeholder="Search for groups..."
|
||||
disabled={props.maxLength ? selected.length >= props.maxLength : false}
|
||||
renderCandidate={renderCandidate}
|
||||
search={(s: string, a: Association) =>
|
||||
a.metadata.title.toLowerCase().startsWith(s.toLowerCase())
|
||||
}
|
||||
getKey={(a: Association) => a.group}
|
||||
onSelect={onSelect}
|
||||
/>
|
||||
{value?.length > 0 && (
|
||||
value.map((e) => {
|
||||
return (
|
||||
<Row
|
||||
key={e}
|
||||
borderRadius="1"
|
||||
mt="2"
|
||||
width="fit-content"
|
||||
@ -116,23 +139,13 @@ export function GroupSearch(props: InviteSearchProps) {
|
||||
px="2"
|
||||
alignItems="center"
|
||||
>
|
||||
<Text mr="2">{groupTitle || value}</Text>
|
||||
<Icon onClick={onUnselect} icon="X" />
|
||||
<Text mr="2">{groupTitle || e}</Text>
|
||||
<Icon onClick={onRemove} icon="X" />
|
||||
</Row>
|
||||
);
|
||||
})
|
||||
)}
|
||||
{!value && (
|
||||
<DropdownSearch<Association>
|
||||
mt="2"
|
||||
candidates={groups}
|
||||
renderCandidate={renderCandidate}
|
||||
search={(s: string, a: Association) =>
|
||||
a.metadata.title.toLowerCase().startsWith(s.toLowerCase())
|
||||
}
|
||||
getKey={(a: Association) => a.group}
|
||||
onSelect={onSelect}
|
||||
/>
|
||||
)}
|
||||
<ErrorLabel hasError={!!(meta.touched && meta.error)}>
|
||||
<ErrorLabel hasError={Boolean(meta.touched && meta.error)}>
|
||||
{meta.error}
|
||||
</ErrorLabel>
|
||||
</Col>
|
||||
|
@ -173,7 +173,7 @@ export function ShipSearch(props: InviteSearchProps) {
|
||||
const result = ob.isValidPatp(ship);
|
||||
return result ? deSig(s) ?? undefined : undefined;
|
||||
}}
|
||||
placeholder="Search for ships"
|
||||
placeholder="Search for ships..."
|
||||
candidates={peers}
|
||||
renderCandidate={renderCandidate}
|
||||
disabled={props.maxLength ? selected.length >= props.maxLength : false}
|
||||
|
@ -42,9 +42,9 @@ function RecentGroups(props: { recent: string[]; associations: Associations }) {
|
||||
Recent Groups
|
||||
</Box>
|
||||
{props.recent.filter((e) => {
|
||||
return (e in associations?.contacts);
|
||||
return (e in associations?.groups);
|
||||
}).slice(1, 5).map((g) => {
|
||||
const assoc = associations.contacts[g];
|
||||
const assoc = associations.groups[g];
|
||||
const color = uxToHex(assoc?.metadata?.color || '0x0');
|
||||
return (
|
||||
<Link key={g} style={{ minWidth: 0 }} to={`/~landscape${g}`}>
|
||||
@ -78,7 +78,7 @@ export function GroupSwitcher(props: {
|
||||
}) {
|
||||
const { associations, workspace, isAdmin } = props;
|
||||
const title = getTitleFromWorkspace(associations, workspace);
|
||||
const metadata = workspace.type === 'home' ? undefined : associations.contacts[workspace.group].metadata;
|
||||
const metadata = workspace.type === 'home' ? undefined : associations.groups[workspace.group].metadata;
|
||||
const navTo = (to: string) => `${props.baseUrl}${to}`;
|
||||
return (
|
||||
<Row width="100%" alignItems="center" height='48px' backgroundColor="white" zIndex="2" position="sticky" top="0px" pl='3' borderBottom='1px solid' borderColor='washedGray'>
|
||||
|
@ -14,7 +14,7 @@ const formSchema = Yup.object({
|
||||
});
|
||||
|
||||
interface FormSchema {
|
||||
group: string | null;
|
||||
group: string[] | null;
|
||||
}
|
||||
|
||||
interface GroupifyFormProps {
|
||||
@ -37,7 +37,7 @@ export function GroupifyForm(props: GroupifyFormProps) {
|
||||
await props.api.graph.groupifyGraph(
|
||||
ship,
|
||||
name,
|
||||
values.group || undefined
|
||||
values.group?.toString() || undefined
|
||||
);
|
||||
const mod = association.metadata.module || association['app-name'];
|
||||
const newGroup = values.group || association.group;
|
||||
@ -79,6 +79,7 @@ export function GroupifyForm(props: GroupifyFormProps) {
|
||||
groups={props.groups}
|
||||
associations={props.associations}
|
||||
adminOnly
|
||||
maxLength={1}
|
||||
/>
|
||||
<AsyncButton primary loadingText="Groupifying..." border>
|
||||
Groupify
|
||||
|
@ -43,7 +43,7 @@ export function GroupsPane(props: GroupsPaneProps) {
|
||||
const groupContacts = (groupPath && contacts[groupPath]) || undefined;
|
||||
const rootIdentity = contacts?.["/~/default"]?.[window.ship];
|
||||
const groupAssociation =
|
||||
(groupPath && associations.contacts[groupPath]) || undefined;
|
||||
(groupPath && associations.groups[groupPath]) || undefined;
|
||||
const group = (groupPath && groups[groupPath]) || undefined;
|
||||
const [recentGroups, setRecentGroups] = useLocalStorageState<string[]>(
|
||||
"recent-groups",
|
||||
|
@ -65,7 +65,7 @@ export function NewGroup(props: NewGroupProps & RouteComponentProps) {
|
||||
await api.contacts.create(name, policy, title, description);
|
||||
const path = `/ship/~${window.ship}/${name}`;
|
||||
await waiter(({ contacts, groups, associations }) => {
|
||||
return path in contacts && path in groups && path in associations.contacts;
|
||||
return path in contacts && path in groups && path in associations.groups;
|
||||
});
|
||||
|
||||
actions.setStatus({ success: null });
|
||||
|
@ -37,8 +37,8 @@ export function Resource(props: ResourceProps) {
|
||||
const skelProps = { api, association };
|
||||
let title = props.association.metadata.title;
|
||||
if ('workspace' in props) {
|
||||
if ('group' in props.workspace && props.workspace.group in props.associations.contacts) {
|
||||
title = `${props.associations.contacts[props.workspace.group].metadata.title} - ${props.association.metadata.title}`;
|
||||
if ('group' in props.workspace && props.workspace.group in props.associations.groups) {
|
||||
title = `${props.associations.groups[props.workspace.group].metadata.title} - ${props.association.metadata.title}`;
|
||||
}
|
||||
}
|
||||
return (
|
||||
|
@ -57,7 +57,7 @@ export function SidebarList(props: {
|
||||
const assoc = associations[a];
|
||||
return group
|
||||
? assoc.group === group
|
||||
: !(assoc.group in props.associations.contacts);
|
||||
: !(assoc.group in props.associations.groups);
|
||||
})
|
||||
.sort(sidebarSort(associations, props.apps)[config.sortBy]);
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user