|
1 | | -import { authSession, authn } from 'solid-logic' |
2 | | -import { icons, initHeader } from 'solid-ui' |
| 1 | +import { authSession, authn, store } from 'solid-logic' |
| 2 | +import { icons, widgets, utils } from 'solid-ui' |
| 3 | +import { Header } from 'solid-ui/dist/components/header/index' |
| 4 | +import type { |
| 5 | + HeaderAccountMenuItem, |
| 6 | + HeaderAuthState, |
| 7 | + HeaderMenuItem |
| 8 | +} from 'solid-ui/dist/components/header/index' |
3 | 9 | import { OutlineManager } from '../outline/manager' |
4 | 10 | import { LiveStore } from 'rdflib' |
5 | 11 | /** |
6 | 12 | * menu icons |
7 | 13 | */ |
8 | 14 | const HELP_MENU_ICON = icons.iconBase + 'noun_help.svg' |
9 | 15 | const SOLID_ICON_URL = 'https://solidproject.org/assets/img/solid-emblem.svg' |
10 | | - |
11 | 16 | /** |
12 | 17 | * menu elements |
13 | 18 | */ |
14 | | -const USER_GUIDE_MENU_ITEM = 'User guide' |
15 | | -const REPORT_A_PROBLEM_MENU_ITEM = 'Report a problem' |
16 | | -const SHOW_YOUR_PROFILE_MENU_ITEM = 'Show your profile' |
17 | | -const LOG_OUT_MENU_ITEM = 'Log out' |
| 19 | +const SIGN_IN_MENU_ITEM = 'Log In' |
| 20 | +const SIGN_OUT_MENU_ITEM = 'Log Out' |
| 21 | +const SIGN_UP_MENU_ITEM = 'Sign Up' |
| 22 | +const ACCOUNT_MENU_LABEL = '▼' |
| 23 | +const SIGN_UP__MENU_LINK = 'https://solidproject.org/get_a_pod' |
18 | 24 |
|
19 | | -type UserMenuItem = { label: string; onclick: () => void } |
20 | | -/** |
21 | | - * URLS |
22 | | - */ |
23 | | -const USER_GUIDE_MENU_URL = 'https://solidos.github.io/userguide/' |
24 | | -const REPORT_A_PROBLEM_MENU_URL = 'https://github.com/solidos/solidos/issues' |
| 25 | +// data structure extracted for solid-ui-header binding |
| 26 | +export const DEFAULT_HELP_MENU_LIST = [ |
| 27 | + { label: 'User guide', url: 'https://solidos.github.io/userguide/', target: '_blank' }, |
| 28 | + { label: 'Report a problem', url: 'https://github.com/solidos/solidos/issues', target: '_blank' } |
| 29 | +] |
| 30 | + |
| 31 | +const HEADER_MOBILE_STYLE_ID = 'solid-ui-header-mobile-style' |
| 32 | + |
| 33 | +function ensureMobileHeaderStyles () { |
| 34 | + if (document.getElementById(HEADER_MOBILE_STYLE_ID)) return |
| 35 | + const style = document.createElement('style') |
| 36 | + style.id = HEADER_MOBILE_STYLE_ID |
| 37 | + style.textContent = ` |
| 38 | + solid-ui-header[layout="mobile"]::part(logo) { |
| 39 | + display: none !important; |
| 40 | + } |
| 41 | + ` |
| 42 | + document.head.appendChild(style) |
| 43 | +} |
25 | 44 |
|
26 | 45 | export async function createHeader (store: LiveStore, outliner: OutlineManager) { |
27 | | - initHeader(store, await setUserMenu(outliner), setHeaderOptions()) |
| 46 | + ensureMobileHeaderStyles() |
| 47 | + |
| 48 | + const header = (document.querySelector('solid-ui-header') || document.createElement('solid-ui-header')) as Header |
| 49 | + const isNewHeader = !header.isConnected |
| 50 | + |
| 51 | + // ensure it is in DOM (before MainContent for consistency) |
| 52 | + const main = document.getElementById('MainContent') |
| 53 | + if (!header.isConnected) { |
| 54 | + if (main && main.parentNode) { |
| 55 | + main.parentNode.insertBefore(header, main) |
| 56 | + } else { |
| 57 | + document.body.prepend(header) |
| 58 | + } |
| 59 | + } |
| 60 | + |
| 61 | + await refreshHeader(outliner, header) |
| 62 | + |
| 63 | + if (isNewHeader) { |
| 64 | + const refreshCurrentHeader = async () => { |
| 65 | + await refreshHeader(outliner, header) |
| 66 | + } |
| 67 | + |
| 68 | + authSession.events.on('login', refreshCurrentHeader) |
| 69 | + authSession.events.on('logout', refreshCurrentHeader) |
| 70 | + authSession.events.on('sessionRestore', refreshCurrentHeader) |
| 71 | + |
| 72 | + header.addEventListener('login-success', async () => { |
| 73 | + await refreshCurrentHeader() |
| 74 | + }) |
| 75 | + |
| 76 | + header.addEventListener('signup-success', async () => { |
| 77 | + await refreshCurrentHeader() |
| 78 | + }) |
| 79 | + |
| 80 | + header.addEventListener('account-menu-select', (e: Event) => { |
| 81 | + const detail = (e as CustomEvent).detail |
| 82 | + if (detail?.action === 'logout') { |
| 83 | + authSession.logout() |
| 84 | + } else if (detail?.action === 'show-profile') { |
| 85 | + openUserProfile(outliner) |
| 86 | + } |
| 87 | + }) |
| 88 | + } |
| 89 | + |
| 90 | + return header |
28 | 91 | } |
29 | 92 |
|
30 | | -function setHeaderOptions () { |
31 | | - const helpMenuList = [ |
32 | | - { label: USER_GUIDE_MENU_ITEM, url: USER_GUIDE_MENU_URL, target: '_blank' }, |
33 | | - { label: REPORT_A_PROBLEM_MENU_ITEM, url: REPORT_A_PROBLEM_MENU_URL, target: '_blank' } |
34 | | - ] |
35 | | - const headerOptions = { logo: SOLID_ICON_URL, helpIcon: HELP_MENU_ICON, helpMenuList } |
| 93 | +export async function refreshHeader (outliner: OutlineManager, headerElement?: Header) { |
| 94 | + ensureMobileHeaderStyles() |
| 95 | + const headerOptions = setHeaderOptions(outliner) |
| 96 | + const header = headerElement || document.querySelector('solid-ui-header') as Header | null |
| 97 | + if (!header) return null |
| 98 | + |
| 99 | + header.theme = headerOptions.theme |
| 100 | + header.layout = headerOptions.layout |
| 101 | + header.brandLink = headerOptions.brandLink |
| 102 | + header.logo = headerOptions.layout === 'desktop' ? headerOptions.logo : '' |
| 103 | + header.helpIcon = headerOptions.helpIcon |
| 104 | + header.helpMenuList = headerOptions.helpMenuList |
| 105 | + header.authState = headerOptions.authState |
| 106 | + header.loginAction = headerOptions.loginAction |
| 107 | + header.signUpAction = headerOptions.signUpAction |
| 108 | + header.accountMenu = await setUserMenu() |
| 109 | + header.logoutLabel = headerOptions.logoutLabel |
| 110 | + header.accountLabel = headerOptions.accountLabel |
| 111 | + header.accountAvatar = headerOptions.accountAvatar |
| 112 | + |
| 113 | + return header |
| 114 | +} |
| 115 | + |
| 116 | +function setHeaderOptions (outliner: OutlineManager) { |
| 117 | + const currentUser = authn.currentUser() |
| 118 | + const isAuthenticated = !!currentUser |
| 119 | + const authState: HeaderAuthState = isAuthenticated ? 'logged-in' : 'logged-out' |
| 120 | + const layout: Header['layout'] = outliner.context?.environment?.layout === 'mobile' ? 'mobile' : 'desktop' |
| 121 | + const theme: Header['theme'] = outliner.context?.environment?.theme === 'dark' ? 'dark' : 'light' |
| 122 | + |
| 123 | + const headerOptions = { |
| 124 | + logo: SOLID_ICON_URL, |
| 125 | + helpIcon: HELP_MENU_ICON, |
| 126 | + helpMenuList: DEFAULT_HELP_MENU_LIST, |
| 127 | + layout, |
| 128 | + theme, |
| 129 | + brandLink: '/', |
| 130 | + authState, |
| 131 | + loginAction: { |
| 132 | + label: SIGN_IN_MENU_ITEM |
| 133 | + } as HeaderMenuItem, |
| 134 | + signUpAction: { |
| 135 | + label: SIGN_UP_MENU_ITEM, |
| 136 | + url: SIGN_UP__MENU_LINK, |
| 137 | + } as HeaderMenuItem, |
| 138 | + logoutLabel: SIGN_OUT_MENU_ITEM, |
| 139 | + accountLabel: isAuthenticated ? ACCOUNT_MENU_LABEL : '', |
| 140 | + accountAvatar: isAuthenticated ? widgets.findImage(currentUser) : undefined |
| 141 | + } |
36 | 142 |
|
37 | 143 | return headerOptions |
38 | 144 | } |
39 | 145 |
|
40 | | -async function setUserMenu (outliner: OutlineManager) { |
41 | | - // @ts-ignore: showProfile is used conditionally |
42 | | - const showProfile = { |
43 | | - label: SHOW_YOUR_PROFILE_MENU_ITEM, |
44 | | - onclick: () => openUserProfile(outliner) |
| 146 | +async function setUserMenu () { |
| 147 | + const me = authn.currentUser() |
| 148 | + if (!me) { |
| 149 | + return [] |
45 | 150 | } |
46 | 151 |
|
47 | | - const logOut: UserMenuItem = { |
48 | | - label: LOG_OUT_MENU_ITEM, |
49 | | - onclick: () => { |
50 | | - authSession.logout() |
51 | | - } |
| 152 | + try { |
| 153 | + await store.fetcher.load(me.doc()) |
| 154 | + } catch (err) { |
| 155 | + console.error('Unable to load user profile', err) |
52 | 156 | } |
53 | 157 |
|
54 | | - // the order of the menu is important here, show profile first and logout last |
55 | | - let userMenuList:UserMenuItem[] = [] // was [showProfile] |
56 | | - userMenuList = userMenuList.concat(await getMenuItems(outliner)) |
57 | | - userMenuList.push(logOut) |
| 158 | + const accountMenu: HeaderAccountMenuItem[] = [ |
| 159 | + { |
| 160 | + label: utils.label(me), |
| 161 | + avatar: widgets.findImage(me), |
| 162 | + webid: me.value, |
| 163 | + action: 'show-profile', |
| 164 | + pane: 'profile-pane' |
| 165 | + }, |
| 166 | + // TODO add all my available accounts |
| 167 | + ] |
58 | 168 |
|
59 | | - return userMenuList |
| 169 | + return accountMenu |
60 | 170 | } |
61 | 171 |
|
62 | 172 | // Does not work to jump to user profile, |
63 | 173 | function openUserProfile (outliner: OutlineManager) { |
64 | 174 | outliner.GotoSubject(authn.currentUser(), true, undefined, true, undefined) |
65 | 175 | location.reload() |
66 | 176 | } |
67 | | - |
68 | | -async function getMenuItems (outliner: OutlineManager) { |
69 | | - const items = await outliner.getDashboardItems() |
70 | | - return items.map((element) => { |
71 | | - return { |
72 | | - label: element.label, |
73 | | - onclick: () => openDashboardPane(outliner, element.tabName || element.paneName) |
74 | | - } |
75 | | - }) |
76 | | -} |
77 | | - |
78 | | -async function openDashboardPane (outliner: OutlineManager, pane: string): Promise<void> { |
79 | | - outliner.showDashboard({ |
80 | | - pane |
81 | | - }) |
82 | | -} |
|
0 commit comments