diff --git a/dashboard/datasister-dashboard/Dashboard.tsx b/dashboard/datasister-dashboard/Dashboard.tsx index d1b1e2e8..3c393384 100644 --- a/dashboard/datasister-dashboard/Dashboard.tsx +++ b/dashboard/datasister-dashboard/Dashboard.tsx @@ -1,5 +1,6 @@ import React from 'react' import { ProfileWidget } from './widgets/Profile' +import { ContactsWidget } from './widgets/Contacts' import { BookmarksWidget } from './widgets/Bookmarks' import { FolderWidget } from './widgets/Folder' import { AppsWidget } from './widgets/Apps' @@ -13,6 +14,9 @@ export const Dashboard: React.FC<{
+
+ +
diff --git a/dashboard/datasister-dashboard/components/ProfileBadge.tsx b/dashboard/datasister-dashboard/components/ProfileBadge.tsx new file mode 100644 index 00000000..80e82268 --- /dev/null +++ b/dashboard/datasister-dashboard/components/ProfileBadge.tsx @@ -0,0 +1,32 @@ +import React from 'react' +import $rdf from 'rdflib' +import namespaces from 'solid-namespace' +import { DataBrowserContext } from '../context' + +const ns = namespaces($rdf) + +interface Props { + webId: string; +}; + +export const ProfileBadge: React.FC = (props) => { + const { store, fetcher } = React.useContext(DataBrowserContext) + const [ name, setName ] = React.useState(props.webId) + + React.useEffect(() => { + fetcher.load(props.webId).then(() => { + const [ nameStatement ] = store.statementsMatching($rdf.sym(props.webId), ns.foaf('name'), null as any, null as any, true) + if (nameStatement) { + setName(nameStatement.object.value) + } + }) + }) + + return ( + <> + + {name} + + + ) +} diff --git a/dashboard/datasister-dashboard/widgets/Contacts.tsx b/dashboard/datasister-dashboard/widgets/Contacts.tsx new file mode 100644 index 00000000..2a12dae8 --- /dev/null +++ b/dashboard/datasister-dashboard/widgets/Contacts.tsx @@ -0,0 +1,107 @@ +import React from 'react' +import $rdf from 'rdflib' +import namespaces from 'solid-namespace' +import { DataBrowserContext } from '../context' +import { useWebId } from '../hooks/useWebId' +import { ProfileBadge } from '../components/ProfileBadge' + +const ns = namespaces($rdf) + +export const ContactsWidget: React.FC = () => { + const { store, fetcher, updater } = React.useContext(DataBrowserContext) + const webId = useWebId() + + const storedContacts = useContacts(store, fetcher) + const [addedContacts, addContact] = React.useReducer>( + (previouslyAdded, newContact) => previouslyAdded.concat(newContact), + [] + ) + + const contacts = (storedContacts || []).concat(addedContacts) + function onAddContact (contactWebId: string) { + if (!webId || !contactWebId) { + return + } + + const profile = $rdf.sym(webId) + updater.update( + [], + [$rdf.st(profile, ns.foaf('knows'), $rdf.sym(contactWebId), profile.doc())], + (_uri, success, _errorBody) => { + if (success) { + addContact(contactWebId) + } + } + ) + } + + return ( +
+
+

Contacts

+
+
    + {contacts.map((contact) =>
  • )} +
+
+

Add a contact

+ +
+
+ ) +} + +const WebIdForm: React.FC<{ onSubmit: (webId: string) => void }> = (props) => { + const [webId, setWebId] = React.useState('') + + function handleSubmit (event: React.FormEvent) { + event.preventDefault() + + props.onSubmit(webId) + setWebId('') + } + + return ( +
+
+ +
+
+ +
+
+ ) +} + +function useContacts (store: $rdf.IndexedFormula, fetcher: $rdf.Fetcher) { + const webId = useWebId() + const [contacts, setContacts] = React.useState() + + React.useEffect(() => { + if (!webId) { + return + } + getContacts(store, fetcher, webId) + .then(setContacts) + .catch((e) => console.log('Error fetching contacts:', e)) + }, [store, fetcher, webId]) + + return contacts +} + +async function getContacts (store: $rdf.IndexedFormula, fetcher: $rdf.Fetcher, webId: string) { + const profile = $rdf.sym(webId) + const knowsStatements = store.statementsMatching(profile, ns.foaf('knows'), null, profile.doc()) + return knowsStatements.map(st => st.object.value) +}