Skip to content

Commit 4429da0

Browse files
author
Sharon
committed
added all the needed code
1 parent e39c7ea commit 4429da0

3 files changed

Lines changed: 256 additions & 0 deletions

File tree

src/header/empty-profile.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
export const emptyProfile = `
2+
<svg xmlns="http://www.w3.org/2000/svg" width="26" height="26" viewBox="0 0 26 26" fill="none">
3+
<path fill-rule="evenodd" clip-rule="evenodd" d="M13 25C19.6274 25 25 19.6274 25 13C25 6.37258 19.6274 1 13 1C6.37258 1 1 6.37258 1 13C1 19.6274 6.37258 25 13 25Z" fill="#D8D8D8" stroke="#8B8B8B"/>
4+
<mask id="mask0" mask-type="alpha" maskUnits="userSpaceOnUse" x="0" y="0" width="26" height="26">
5+
<path fill-rule="evenodd" clip-rule="evenodd" d="M13 25C19.6274 25 25 19.6274 25 13C25 6.37258 19.6274 1 13 1C6.37258 1 1 6.37258 1 13C1 19.6274 6.37258 25 13 25Z" fill="white" stroke="white"/>
6+
</mask>
7+
<g mask="url(#mask0)">
8+
<path fill-rule="evenodd" clip-rule="evenodd" d="M17.0468 10.4586C17.0468 14.4979 15.4281 16.9214 12.9999 16.9214C10.5718 16.9214 8.95298 14.4979 8.95298 10.4586C8.95298 6.41931 12.9999 6.41931 12.9999 6.41931C12.9999 6.41931 17.0468 6.41931 17.0468 10.4586ZM4.09668 23.3842C6.52483 17.7293 12.9999 17.7293 12.9999 17.7293C12.9999 17.7293 19.475 17.7293 21.9031 23.3842C21.9031 23.3842 17.8481 25 12.9999 25C8.15169 25 4.09668 23.3842 4.09668 23.3842Z" fill="#8B8B8B"/>
9+
</g>
10+
</svg>`

src/header/headerHelpers.ts

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/*
2+
Copied from mashlib/src/global/metadata.ts
3+
*/
4+
import { NamedNode, sym } from 'rdflib'
5+
6+
type ThrottleOptions = {
7+
leading?: boolean;
8+
throttling?: boolean;
9+
trailing?: boolean;
10+
}
11+
12+
export function getPod (): NamedNode {
13+
// @@ TODO: This is given that mashlib runs on NSS - might need to change when we want it to run on other Pod servers
14+
return sym(document.location.origin).site()
15+
}
16+
17+
export function throttle (func: Function, wait: number, options: ThrottleOptions = {}): (...args: any[]) => any {
18+
let context: any,
19+
args: any,
20+
result: any
21+
let timeout: any = null
22+
let previous = 0
23+
const later = function () {
24+
previous = !options.leading ? 0 : Date.now()
25+
timeout = null
26+
result = func.apply(context, args)
27+
if (!timeout) context = args = null
28+
}
29+
return function () {
30+
const now = Date.now()
31+
if (!previous && !options.leading) previous = now
32+
const remaining = wait - (now - previous)
33+
// @ts-ignore
34+
context = this
35+
args = arguments
36+
if (remaining <= 0 || remaining > wait) {
37+
if (timeout) {
38+
clearTimeout(timeout)
39+
timeout = null
40+
}
41+
previous = now
42+
result = func.apply(context, args)
43+
if (!timeout) context = args = null
44+
} else if (!timeout && options.trailing !== false) {
45+
timeout = setTimeout(later, remaining)
46+
}
47+
return result
48+
}
49+
}

src/header/index.ts

Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
1+
/*
2+
This file was copied from mashlib/src/global/header.ts file. It is modified to
3+
work in solid-ui by adjusting where imported functions are found.
4+
*/
5+
import { IndexedFormula, NamedNode, sym } from 'rdflib'
6+
import { loginStatusBox, solidAuthClient } from '../authn/authn'
7+
import { widgets } from '../widgets'
8+
import { icon } from './icon'
9+
import { emptyProfile } from './empty-profile'
10+
import { throttle, getPod } from './headerHelpers'
11+
import { getOutliner } from 'solid-panes'
12+
13+
// SolidAuthorization, SolidClam, and SolidSession was copied from mashlib/typings/solid-auth-client
14+
// access_token, client_id, id_token, at_hash had to be converted to camelcase for typescript compatibility
15+
interface SolidAuthorization {
16+
accessToken: string;
17+
clientId: string;
18+
idToken: string;
19+
}
20+
21+
interface SolidClaim {
22+
atHash: string;
23+
aud: string;
24+
azp: string;
25+
cnf: {
26+
jwk: string;
27+
};
28+
exp: number;
29+
iat: number;
30+
iss: string;
31+
jti: string;
32+
nonce: string;
33+
sub: string;
34+
}
35+
export interface SolidSession {
36+
authorization: SolidAuthorization;
37+
credentialType: string;
38+
idClaims: SolidClaim;
39+
idp: string;
40+
issuer: string;
41+
sessionKey: string;
42+
webId: string;
43+
}
44+
45+
export async function initHeader (store: IndexedFormula) {
46+
const header = document.getElementById('PageHeader')
47+
if (!header) {
48+
return
49+
}
50+
51+
const pod = getPod()
52+
solidAuthClient.trackSession(rebuildHeader(header, store, pod))
53+
}
54+
55+
function rebuildHeader (header: HTMLElement, store: IndexedFormula, pod: NamedNode) {
56+
return async (session: SolidSession | null) => {
57+
const user = session ? sym(session.webId) : null
58+
header.innerHTML = ''
59+
header.appendChild(await createBanner(store, pod, user))
60+
}
61+
}
62+
63+
async function createBanner (store: IndexedFormula, pod: NamedNode, user: NamedNode | null): Promise<HTMLElement> {
64+
const podLink = document.createElement('a')
65+
podLink.href = pod.uri
66+
podLink.classList.add('header-banner__link')
67+
podLink.innerHTML = icon
68+
69+
const menu = user
70+
? await createUserMenu(store, user)
71+
: createLoginSignUpButtons()
72+
73+
const banner = document.createElement('div')
74+
banner.classList.add('header-banner')
75+
banner.appendChild(podLink)
76+
banner.appendChild(menu)
77+
78+
return banner
79+
}
80+
81+
function createLoginSignUpButtons () {
82+
const profileLoginButtonPre = document.createElement('div')
83+
profileLoginButtonPre.classList.add('header-banner__login')
84+
profileLoginButtonPre.appendChild(loginStatusBox(document, null, {}))
85+
return profileLoginButtonPre
86+
}
87+
88+
async function openDashboardPane (outliner: any, pane: string): Promise<void> {
89+
outliner.showDashboard({
90+
pane
91+
})
92+
}
93+
94+
function createUserMenuButton (label: string, onClick: EventListenerOrEventListenerObject): HTMLElement {
95+
const button = document.createElement('button')
96+
button.classList.add('header-user-menu__button')
97+
button.addEventListener('click', onClick)
98+
button.innerText = label
99+
return button
100+
}
101+
102+
function createUserMenuLink (label: string, href: string): HTMLElement {
103+
const link = document.createElement('a')
104+
link.classList.add('header-user-menu__link')
105+
link.href = href
106+
link.innerText = label
107+
return link
108+
}
109+
110+
async function createUserMenu (store: IndexedFormula, user: NamedNode): Promise<HTMLElement> {
111+
const fetcher = (<any>store).fetcher
112+
if (fetcher) {
113+
// Making sure that Profile is loaded before building menu
114+
await fetcher.load(user)
115+
}
116+
const outliner = getOutliner(document)
117+
118+
const loggedInMenuList = document.createElement('ul')
119+
loggedInMenuList.classList.add('header-user-menu__list')
120+
loggedInMenuList.appendChild(createUserMenuItem(createUserMenuLink('Show your profile', user.uri)))
121+
const menuItems = await getMenuItems(outliner)
122+
menuItems.forEach(item => {
123+
loggedInMenuList.appendChild(createUserMenuItem(createUserMenuButton(item.label, () => openDashboardPane(outliner, item.tabName || item.paneName))))
124+
})
125+
loggedInMenuList.appendChild(createUserMenuItem(createUserMenuButton('Log out', () => solidAuthClient.logout())))
126+
127+
const loggedInMenu = document.createElement('nav')
128+
loggedInMenu.classList.add('header-user-menu__navigation-menu')
129+
loggedInMenu.setAttribute('aria-hidden', 'true')
130+
loggedInMenu.appendChild(loggedInMenuList)
131+
132+
const loggedInMenuTrigger = document.createElement('button')
133+
loggedInMenuTrigger.classList.add('header-user-menu__trigger')
134+
loggedInMenuTrigger.type = 'button'
135+
const profileImg = getProfileImg(store, user)
136+
if (typeof profileImg === 'string') {
137+
loggedInMenuTrigger.innerHTML = profileImg
138+
} else {
139+
loggedInMenuTrigger.appendChild(profileImg)
140+
}
141+
142+
const loggedInMenuContainer = document.createElement('div')
143+
loggedInMenuContainer.classList.add('header-banner__user-menu', 'header-user-menu')
144+
loggedInMenuContainer.appendChild(loggedInMenuTrigger)
145+
loggedInMenuContainer.appendChild(loggedInMenu)
146+
147+
const throttledMenuToggle = throttle((event: Event) => toggleMenu(event, loggedInMenuTrigger, loggedInMenu), 50)
148+
loggedInMenuTrigger.addEventListener('click', throttledMenuToggle)
149+
let timer = setTimeout(() => null, 0)
150+
loggedInMenuContainer.addEventListener('mouseover', event => {
151+
clearTimeout(timer)
152+
throttledMenuToggle(event)
153+
})
154+
loggedInMenuContainer.addEventListener('mouseout', event => {
155+
timer = setTimeout(() => throttledMenuToggle(event), 200)
156+
})
157+
158+
return loggedInMenuContainer
159+
}
160+
161+
function createUserMenuItem (child: HTMLElement): HTMLElement {
162+
const menuProfileItem = document.createElement('li')
163+
menuProfileItem.classList.add('header-user-menu__list-item')
164+
menuProfileItem.appendChild(child)
165+
return menuProfileItem
166+
}
167+
168+
async function getMenuItems (outliner: any): Promise<Array<{
169+
paneName: string;
170+
tabName?: string;
171+
label: string;
172+
icon: string;
173+
}>> {
174+
return outliner.getDashboardItems()
175+
}
176+
177+
function getProfileImg (store: IndexedFormula, user: NamedNode): string | HTMLElement {
178+
const profileUrl = widgets.findImage(user)
179+
if (!profileUrl) {
180+
return emptyProfile
181+
}
182+
const profileImage = document.createElement('div')
183+
profileImage.classList.add('header-user-menu__photo')
184+
profileImage.style.backgroundImage = `url("${profileUrl}")`
185+
return profileImage
186+
}
187+
188+
function toggleMenu (event: Event, trigger: HTMLButtonElement, menu: HTMLElement): void {
189+
const isExpanded = trigger.getAttribute('aria-expanded') === 'true'
190+
const expand = event.type === 'mouseover'
191+
const close = event.type === 'mouseout'
192+
if ((isExpanded && expand) || (!isExpanded && close)) {
193+
return
194+
}
195+
trigger.setAttribute('aria-expanded', (!isExpanded).toString())
196+
menu.setAttribute('aria-hidden', isExpanded.toString())
197+
}

0 commit comments

Comments
 (0)