diff --git a/dashboard/dashboardPane.ts b/dashboard/dashboardPane.ts new file mode 100644 index 00000000..49c2ce9c --- /dev/null +++ b/dashboard/dashboardPane.ts @@ -0,0 +1,69 @@ +import { PaneDefinition, SolidSession } from "../types" +import solidUi, { SolidUi } from "solid-ui" +import paneRegistry from "pane-registry" +import { NamedNode, sym } from "rdflib" +import { generateHomepage } from "./homepage" + +let panes: any +let UI: SolidUi + +const nodeMode = (typeof module !== "undefined") + +if (nodeMode) { + UI = solidUi + panes = paneRegistry +} else { // Add to existing mashlib + panes = (window as any).panes + UI = panes.UI +} + +export const dashboardPane: PaneDefinition = { + icon: UI.icons.iconBase + "noun_547570.svg", + name: "dashboard", + label: (subject) => { + if (subject.uri === subject.site().uri) { + return "Dashboard" + } + return null + }, + render: (subject, dom) => { + const container = dom.createElement("div") + const webId = UI.authn.currentUser() + buildPage(container, webId, dom, subject) + UI.authn.solidAuthClient.trackSession(async (session: SolidSession) => { + container.innerHTML = "" + buildPage(container, session ? sym(session.webId) : null, dom, subject) + }) + + return container + } +} + +function buildPage (container: HTMLElement, webId: NamedNode | null, dom: HTMLDocument, subject: NamedNode) { + if (!webId) { + return buildHomePage(container, subject) + } + if (webId.site().uri === subject.site().uri) { + return buildDashboard(container, dom) + } + return buildHomePage(container, subject) +} + +function buildDashboard (container: HTMLElement, dom: HTMLDocument) { + const outliner = panes.getOutliner(dom) + outliner.showDashboard(container) +} + +function buildHomePage (container: HTMLElement, subject: NamedNode) { + const wrapper = document.createElement('div') + container.appendChild(wrapper) + const shadow = wrapper.attachShadow({ mode: 'open' }) + const link = document.createElement('link') + link.rel = 'stylesheet' + link.href = '/common/css/bootstrap.min.css' + shadow.appendChild(link) + generateHomepage(subject, UI.store, UI.store.fetcher).then(homepage => shadow.appendChild(homepage)) +} + + +export default dashboardPane diff --git a/dashboard/homepage.ts b/dashboard/homepage.ts new file mode 100644 index 00000000..83ab7a0f --- /dev/null +++ b/dashboard/homepage.ts @@ -0,0 +1,67 @@ +import $rdf, { Fetcher, IndexedFormula, NamedNode } from "rdflib" +import UI from "solid-ui" + +const ns = UI.ns + +export async function generateHomepage(subject: NamedNode, store: IndexedFormula, fetcher: Fetcher): Promise { + const pod = subject.site().uri + const ownersProfile = await loadProfile(`${pod}/profile/card#me`, fetcher) + const name = getName(store, ownersProfile) + + const wrapper = document.createElement('div') + wrapper.classList.add('container') + wrapper.appendChild(createTitle(ownersProfile.uri, name)) + wrapper.appendChild(createDataSection(name)) + + return wrapper +} + +function createDataSection(name: string): HTMLElement { + const dataSection = document.createElement('section') + + const title = document.createElement('h2') + title.innerText = 'Data' + dataSection.appendChild(title) + + const listGroup = document.createElement('div') + listGroup.classList.add('list-group') + dataSection.appendChild(listGroup) + + const publicDataLink = document.createElement('a') + publicDataLink.classList.add('list-group-item') + publicDataLink.href = '/public/' + publicDataLink.innerText = `View ${name}'s files` + listGroup.appendChild(publicDataLink) + + return dataSection +} + +function createTitle(uri: string, name: string): HTMLElement { + const profileLink = document.createElement('a') + profileLink.href = uri + profileLink.innerText = name + + const profileLinkPost = document.createElement('span') + profileLinkPost.innerText = `'s Pod` + + const title = document.createElement('h1') + title.appendChild(profileLink) + title.appendChild(profileLinkPost) + + return title +} + + +async function loadProfile(profileUrl: string, fetcher: Fetcher): Promise { + const webId = $rdf.sym(profileUrl) + await fetcher.load(webId) + return webId +} + + + +function getName (store: IndexedFormula, ownersProfile: NamedNode): string { + return (store.anyValue as any)(ownersProfile, ns.vcard("fn"), null, ownersProfile.doc()) + || (store.anyValue as any)(ownersProfile, ns.foaf("name"), null, ownersProfile.doc()) + || new URL(ownersProfile.uri).host.split('.')[0] +} diff --git a/index.js b/index.js index eae3a56c..8692b537 100644 --- a/index.js +++ b/index.js @@ -47,6 +47,7 @@ if (typeof window !== 'undefined') { let register = panes.register +register(require('./dashboard/dashboardPane').default) register(require('issue-pane')) register(require('contacts-pane')) diff --git a/outline/manager.js b/outline/manager.js index d10d0272..3a97d5ab 100644 --- a/outline/manager.js +++ b/outline/manager.js @@ -274,7 +274,7 @@ module.exports = function (doc) { /** Render Tabbed set of home app panes * @returns {Element} - the div */ - function globalAppTabs () { + function globalAppTabs (selectedTab) { const div = dom.createElement('div') const me = UI.authn.currentUser() if (!me) { @@ -282,9 +282,12 @@ module.exports = function (doc) { throw new Error('Not logged in') } function renderTab (div, item) { - const map = { 'home': 'Your stuff', - 'trustedApplications': 'Web apps you trust', - 'profile': 'Edit your profile' } + const map = { + 'home': 'Your stuff', + 'trustedApplications': 'Preferences', + 'profile': 'Edit your profile' + } + div.dataset.name = item div.textContent = map[item] || item } @@ -304,45 +307,24 @@ module.exports = function (doc) { renderTab, ordered: true, orientation: 0, - backgroundColor: '#eeeeee'} // black? + backgroundColor: '#eeeeee', + selectedTab} // black? // options.renderTabSettings = renderTabSettings No tab-specific settings div.appendChild(UI.tabs.tabWidget(options)) - div.appendChild(UI.widgets.cancelButton(dom, event => { - div.parentNode.removeChild(div) - })) return div } - /** Global Navigation tool - ** - ** This gives the user the ability to find and do stuff sfrom no context - */ - function globalNavigationBox (tr, menuButtonId) { - const buttonStyle = 'padding: 0.3em 0.5em; border-radius:0.2em; margin: 0 0.4em; font-size: 100%;' // @@ - const globalNav = dom.createElement('nav') - var expanded = false - var expandedControl - globalNav.style = 'padding: 0; margin: 0; height: 100%; max-height: 2em;' + - 'display:flex; justify-content: flex-end; flex-grow: 1; align-items: center;' - // globalNav.style.backgroundColor = '#884488' // @@ placeholder - - var menuButton = dom.createElement('img') - menuButton.id = menuButtonId - menuButton.setAttribute('src', UI.icons.iconBase + 'noun_547570.svg') // Lines (could also use dots or home or hamburger - menuButton.style = 'padding: 0.2em;' - menuButton.addEventListener('click', event => { - if (expanded) { - expandedControl.parentNode.removeChild(expandedControl) - } else { - if (tr.nextSibling) tr.parentElement.removeChild(tr.nextSibling) // @@ hack - should use pane code - expandedControl = tr.parentElement.appendChild(globalAppTabs()) - } - expanded = !expanded - }) - menuButton.style = buttonStyle - menuButton.style.maxHeight = iconHeight - globalNav.appendChild(menuButton) - return globalNav + + function showDashboard (container, unselectCurrentPane, globalPaneToSelect) { + container.innerHTML = '' + // console.log(container) + const currentPane = dom.querySelector('#outline .paneShown') + if (unselectCurrentPane && currentPane) { + // eslint-disable-next-line no-undef + // currentPane.dispatchEvent(new Event('click')) + } + return container.appendChild(globalAppTabs(globalPaneToSelect)) } + this.showDashboard = showDashboard function expandedHeaderTR (subject, requiredPane, options) { function renderPaneIconTray (td) { @@ -433,10 +415,13 @@ module.exports = function (doc) { dom.getElementById('queryButton').removeAttribute('style') } var second = t.firstChild.nextSibling - if (second) t.insertBefore(paneDiv, second) - else t.appendChild(paneDiv) - paneDiv.pane = pane - paneDiv.paneButton = ico + var row = dom.createElement('tr') + var cell = row.appendChild(dom.createElement('td')) + cell.appendChild(paneDiv) + if (second) t.insertBefore(row, second) + else t.appendChild(row) + row.pane = pane + row.paneButton = ico } var state state = ico.getAttribute('class') @@ -501,24 +486,6 @@ module.exports = function (doc) { header.appendChild(renderPaneIconTray(td)) - if (options.solo) { - const menuButtonId = 'GlobalUserMenuButton' - td.appendChild(globalNavigationBox(tr, menuButtonId)) - UI.authn.solidAuthClient.trackSession(function (session) { - const menuButton = document.getElementById(menuButtonId) - if (!menuButton) { - return - } - const isHidden = menuButton.style.display === 'none' - console.log(isHidden) - if (session) { - menuButton.style.display = 'block' - } else { - menuButton.style.display = 'none' - } - }) - } - // set DOM methods tr.firstChild.tabulatorSelect = function () { setSelected(this, true) @@ -588,12 +555,15 @@ module.exports = function (doc) { pre.appendChild(dom.createTextNode(UI.utils.stackString(e))) } + var row = dom.createElement('tr') + var cell = row.appendChild(dom.createElement('td')) + cell.appendChild(paneDiv) if (tr1.firstPane.requireQueryButton && dom.getElementById('queryButton')) { dom.getElementById('queryButton').removeAttribute('style') } - table.appendChild(paneDiv) - paneDiv.pane = tr1.firstPane - paneDiv.paneButton = tr1.paneButton + table.appendChild(row) + row.pane = tr1.firstPane + row.paneButton = tr1.paneButton } return table diff --git a/types.ts b/types.ts index af65b484..1cfebf23 100644 --- a/types.ts +++ b/types.ts @@ -54,3 +54,34 @@ interface NewPaneOptions { pane: PaneDefinition; refreshTarget: HTMLTableElement; } + +export interface SolidSession { + authorization: SolidAuthorization; + credentialType: string; + idClaims: SolidClaim; + idp: string; + issuer: string; + sessionKey: string; + webId: string; +} + +interface SolidAuthorization { + access_token: string; + client_id: string; + id_token: string; +} + +interface SolidClaim { + at_hash: string; + aud: string; + azp: string; + cnf: { + jwk: string; + }; + exp: number; + iat: number; + iss: string; + jti: string; + nonce: string; + sub: string; +}