Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions dashboard/datasister-dashboard/Dashboard.tsx
Original file line number Diff line number Diff line change
@@ -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'
Expand All @@ -13,6 +14,9 @@ export const Dashboard: React.FC<{
<div className="column">
<ProfileWidget/>
</div>
<div className="column">
<ContactsWidget/>
</div>
<div className="column">
<AppsWidget/>
</div>
Expand Down
32 changes: 32 additions & 0 deletions dashboard/datasister-dashboard/components/ProfileBadge.tsx
Original file line number Diff line number Diff line change
@@ -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> = (props) => {
const { store, fetcher } = React.useContext(DataBrowserContext)
const [ name, setName ] = React.useState<string>(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 (
<>
<a href={props.webId} title="View this person's profile">
{name}
</a>
</>
)
}
107 changes: 107 additions & 0 deletions dashboard/datasister-dashboard/widgets/Contacts.tsx
Original file line number Diff line number Diff line change
@@ -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<React.Reducer<string[], string>>(
(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 (
<div className="card">
<section className="section">
<h2 className="title">Contacts</h2>
<div className="content">
<ul>
{contacts.map((contact) => <li key={contact}><ProfileBadge webId={contact}/></li>)}
</ul>
</div>
<h3>Add a contact</h3>
<WebIdForm onSubmit={onAddContact}/>
</section>
</div>
)
}

const WebIdForm: React.FC<{ onSubmit: (webId: string) => void }> = (props) => {
const [webId, setWebId] = React.useState<string>('')

function handleSubmit (event: React.FormEvent) {
event.preventDefault()

props.onSubmit(webId)
setWebId('')
}

return (
<form onSubmit={handleSubmit}>
<div className="field">
<label className="label">
WebID:
<input
type="url"
onChange={(e) => setWebId(e.target.value)}
value={webId}
placeholder="https://www.w3.org/People/Berners-Lee/card#i"
name="webid"
id="webid"
className="input"
/>
</label>
</div>
<div className="control">
<input type="submit" value="Add" className="button"/>
</div>
</form>
)
}

function useContacts (store: $rdf.IndexedFormula, fetcher: $rdf.Fetcher) {
const webId = useWebId()
const [contacts, setContacts] = React.useState<string[]>()

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)
}