diff --git a/package-lock.json b/package-lock.json
index e47dd8b3..c4a51d20 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "solid-panes",
- "version": "4.4.2-2",
+ "version": "4.4.2-3",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "solid-panes",
- "version": "4.4.2-2",
+ "version": "4.4.2-3",
"license": "MIT",
"dependencies": {
"@solid/better-simple-slideshow": "^0.1.0",
@@ -29,6 +29,7 @@
"source-pane": "3.1.1-1"
},
"devDependencies": {
+ "@iconify/json": "2.2.494",
"@testing-library/dom": "^10.4.1",
"@testing-library/jest-dom": "^6.9.1",
"@types/node": "^22.20.0",
@@ -40,6 +41,7 @@
"neostandard": "^0.13.0",
"solidos-toolkit": "dev",
"typescript": "^6.0.3",
+ "unplugin-icons": "^23.0.1",
"vite": "^8.0.16",
"vitest": "^4.0.18"
}
@@ -2548,6 +2550,17 @@
"url": "https://github.com/sponsors/nzakas"
}
},
+ "node_modules/@iconify/json": {
+ "version": "2.2.494",
+ "resolved": "https://registry.npmjs.org/@iconify/json/-/json-2.2.494.tgz",
+ "integrity": "sha512-ssY0bQgG+W2uAd4G+iK/FhjiUu4UG/wdCTIt6dqItBO6DFTtvkHo2nQqWOJTG0orNFl9eDxB+YgVUzXnpvAhkw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@iconify/types": "*",
+ "pathe": "^2.0.3"
+ }
+ },
"node_modules/@iconify/types": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/@iconify/types/-/types-2.0.0.tgz",
diff --git a/package.json b/package.json
index b906fb34..d06dd470 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "solid-panes",
- "version": "4.4.2-2",
+ "version": "4.4.2-3",
"description": "Solid-compatible Panes: applets and views for the mashlib and databrowser",
"main": "dist/index.cjs.js",
"types": "dist/index.d.ts",
@@ -85,6 +85,7 @@
"pane-registry": "$pane-registry"
},
"devDependencies": {
+ "@iconify/json": "2.2.494",
"@testing-library/dom": "^10.4.1",
"@testing-library/jest-dom": "^6.9.1",
"@types/node": "^22.20.0",
@@ -96,6 +97,7 @@
"neostandard": "^0.13.0",
"solidos-toolkit": "dev",
"typescript": "^6.0.3",
+ "unplugin-icons": "^23.0.1",
"vite": "^8.0.16",
"vitest": "^4.0.18"
},
diff --git a/src/components/header/Header.styles.css b/src/components/header/Header.styles.css
new file mode 100644
index 00000000..41298c17
--- /dev/null
+++ b/src/components/header/Header.styles.css
@@ -0,0 +1,38 @@
+@import "solid-ui/breakpoints.css";
+
+:host {
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ justify-content: space-between;
+ background: var(--solid-ui-color-slate-800);
+ color: var(--solid-ui-color-white);
+ padding: 7px 20px;
+ gap: 10px;
+
+ a.logo {
+ height: 40px;
+
+ solid-ui-solid-emblem {
+ height: 100%;
+ }
+ }
+
+ .spacer {
+ flex: 1;
+ }
+
+ .separator {
+ background: var(--solid-ui-color-body-grey);
+ width: 1px;
+ align-self: stretch;
+ }
+
+ @media (--solid-ui-mobile) {
+ a.logo,
+ solid-ui-menu,
+ .separator {
+ display: none;
+ }
+ }
+}
diff --git a/src/components/header/Header.ts b/src/components/header/Header.ts
new file mode 100644
index 00000000..97e3fe88
--- /dev/null
+++ b/src/components/header/Header.ts
@@ -0,0 +1,42 @@
+import { customElement, WebComponent } from 'solid-ui'
+import { html } from 'lit'
+import { property } from 'lit/decorators.js'
+import type { AccountMenuItem } from 'solid-ui/components/account'
+
+import 'solid-ui/components/account'
+import 'solid-ui/components/button'
+import 'solid-ui/components/menu-item'
+import 'solid-ui/components/menu'
+import 'solid-ui/components/solid-emblem'
+import '~icons/lucide/help-circle'
+
+import styles from './Header.styles.css'
+
+@customElement('solid-panes-header')
+export default class Header extends WebComponent {
+ static styles = styles
+
+ @property({ type: Array })
+ accessor menuItems: AccountMenuItem[] = []
+
+ render () {
+ return html`
+
+ Home
+
+
+
+
+
+
+
+ Open help
+
+
+
+ User guide
+ Report a problem
+
+ `
+ }
+}
diff --git a/src/components/header/index.ts b/src/components/header/index.ts
new file mode 100644
index 00000000..e0567ca9
--- /dev/null
+++ b/src/components/header/index.ts
@@ -0,0 +1,4 @@
+import Header from './Header'
+
+export { Header }
+export default Header
diff --git a/src/mainPage/header.ts b/src/mainPage/header.ts
index f6546e7f..3a661f0b 100644
--- a/src/mainPage/header.ts
+++ b/src/mainPage/header.ts
@@ -1,224 +1,53 @@
-import { authSession, authn, store } from 'solid-logic'
-import { widgets, utils } from 'solid-ui'
-import 'solid-ui/components/header'
-import { Header } from 'solid-ui/components/header'
-import type { HeaderMenuItem, HeaderAccountMenuItem, HeaderAuthState } from 'solid-ui/components/header'
-import { OutlineManager } from '../outline/manager'
-import { LiveStore } from 'rdflib'
-import helpIconSvg from '../icons/help.svg?raw'
-import loginIconSvg from '../icons/person.svg?raw'
-import signOutIconSvg from '../icons/signOut.svg?raw'
-import defaultAvatarIconSvg from '../icons/personInCircle.svg?raw'
-import downArrowIconSvg from '../icons/downArrow.svg?raw'
-import signupIconSvg from '../icons/signUp.svg?raw'
-import { createUiIcon } from '../icons/iconHelper'
-import { setActiveMenuPane } from './menu'
-/**
- * menu icons
-*/
-const HELP_MENU_ICON = createUiIcon(helpIconSvg, 'Help Icon', '#ffffff')
-const LOGIN_ICON = createUiIcon(loginIconSvg, 'LogIn Icon', '#ffffff')
-const SIGNUP_ICON = createUiIcon(signupIconSvg, 'SignUp Icon', '#ffffff')
-const LOGOUT_ICON = createUiIcon(signOutIconSvg, 'LogOut Icon', '#000000')
-const DEFAULT_AVATAR_ICON = createUiIcon(defaultAvatarIconSvg, 'Default Avatar Icon', '#6A7282')
-const DOWN_ARROW_ICON = createUiIcon(downArrowIconSvg, 'Down Arrow Icon', '#ffffff')
-const SOLID_ICON_URL = 'https://solidproject.org/assets/img/solid-emblem.svg'
-/**
- * menu elements
-*/
-const SIGN_IN_MENU_ITEM = 'Log In'
-const SIGN_OUT_MENU_ITEM = 'Log Out'
-const SIGN_UP_MENU_ITEM = 'Sign Up'
-const SIGN_UP__MENU_LINK = 'https://solidproject.org/get_a_pod'
-
-// data structure extracted for solid-ui-header binding
-export const HELP_MENU_LIST = [
- { label: 'User guide', url: 'https://solidos.github.io/userguide/', target: '_blank' },
- { label: 'Report a problem', url: 'https://github.com/solidos/solidos/issues', target: '_blank' }
-]
+import { authn } from 'solid-logic'
+import { html, render } from 'lit-html'
+import type { AccountMenuItem } from 'solid-ui/components/account'
-const HEADER_MOBILE_STYLE_ID = 'solid-ui-header-mobile-style'
+import { setActiveMenuPane } from './menu'
+import type { OutlineManager } from '../outline/manager'
-type ManagedHeader = Header & {
- __solidPanesListenersAttached?: boolean
- __solidPanesOutliner?: OutlineManager
-}
+import '~icons/lucide/user'
+import '../components/header'
-function ensureMobileHeaderStyles () {
- if (document.getElementById(HEADER_MOBILE_STYLE_ID)) return
- const style = document.createElement('style')
- style.id = HEADER_MOBILE_STYLE_ID
- style.textContent = `
- solid-ui-header[layout="mobile"]::part(logo) {
- display: none !important;
- }
- `
- document.head.appendChild(style)
-}
+export async function createHeader (outliner: OutlineManager) {
+ const existingHeader = document.querySelector('solid-panes-header')
-export async function createHeader (store: LiveStore, outliner: OutlineManager) {
- ensureMobileHeaderStyles()
-
- const header = (document.querySelector('solid-ui-header') || document.createElement('solid-ui-header')) as ManagedHeader
- const isNewHeader = !header.isConnected
- if (!header.id) {
- header.id = 'mainSolidUiHeader'
+ if (existingHeader) {
+ return existingHeader
}
- header.__solidPanesOutliner = outliner
- // ensure it is in DOM (before MainContent for consistency)
const main = document.getElementById('MainContent')
- if (!header.isConnected) {
- if (main && main.parentNode) {
- main.parentNode.insertBefore(header, main)
- } else {
- document.body.prepend(header)
- }
- }
-
- await refreshHeader(outliner, header)
-
- if (isNewHeader) {
- header.__solidPanesListenersAttached = false
- }
-
- attachHeaderListeners(header)
-
- return header
-}
-
-function attachHeaderListeners (header: ManagedHeader) {
- if (header.__solidPanesListenersAttached) return
-
- const refreshCurrentHeader = async () => {
- const outliner = header.__solidPanesOutliner
- if (!outliner) return
- await refreshHeader(outliner, header)
- }
-
- authSession.events.on('login', refreshCurrentHeader)
- authSession.events.on('logout', refreshCurrentHeader)
- authSession.events.on('sessionRestore', refreshCurrentHeader)
-
- header.addEventListener('auth-action-select', async (e: Event) => {
- const outliner = header.__solidPanesOutliner
- if (!outliner) return
-
- const detail = (e as CustomEvent).detail
- if (detail?.role === 'login') {
- await refreshCurrentHeader()
- // Do not auto-open the profile pane after login.
- // outliner.showDashboard({ pane: 'profile' })
- }
- })
-
- header.addEventListener('signup-success', async () => {
- // do nothing
- })
-
- header.addEventListener('account-menu-select', async (e: Event) => {
- const outliner = header.__solidPanesOutliner
- if (!outliner) return
-
- const detail = (e as CustomEvent).detail
- if (detail?.action === 'logout') {
- await refreshCurrentHeader()
- // Do not navigate to the profile after logout.
- } else if (detail?.action === 'show-profile') {
- // TODO see if this can be consolidated
- const currentUser = authn.currentUser()
- if (currentUser) {
- outliner.showDashboard(currentUser, { pane: 'profile' })
- setActiveMenuPane('profile')
+ const tmpContainer = document.createElement('div')
+ const menuItems: AccountMenuItem[] = [
+ {
+ label: html` Profile`,
+ onSelected () {
+ const currentUser = authn.currentUser()
+
+ if (currentUser) {
+ outliner.showDashboard(currentUser, { pane: 'profile' })
+ setActiveMenuPane('profile')
+ }
}
}
- })
-
- header.__solidPanesListenersAttached = true
-}
-
-export async function refreshHeader (outliner: OutlineManager, headerElement?: Header) {
- ensureMobileHeaderStyles()
- const headerOptions = setHeaderOptions(outliner)
- const header = headerElement || document.querySelector('solid-ui-header') as Header | null
- if (!header) return null
-
- header.theme = headerOptions.theme
- header.layout = headerOptions.layout
- header.brandLink = headerOptions.brandLink
- header.logo = headerOptions.layout === 'desktop' ? headerOptions.logo : ''
- header.helpIcon = headerOptions.helpIcon
- header.helpMenuList = headerOptions.helpMenuList
- header.authState = headerOptions.authState
- header.loginAction = headerOptions.loginAction
- header.signUpAction = headerOptions.signUpAction
- header.accountMenu = await setUserMenu()
- header.logoutLabel = headerOptions.logoutLabel
- header.accountIcon = headerOptions.accountIcon
- header.accountAvatar = headerOptions.accountAvatar
- header.accountAvatarFallback = headerOptions.accountAvatarFallback
- header.loginIcon = headerOptions.loginIcon
- header.signUpIcon = headerOptions.signUpIcon
- header.logoutIcon = headerOptions.logoutIcon
-
- return header
-}
-
-function setHeaderOptions (outliner: OutlineManager) {
- const currentUser = authn.currentUser()
- const isAuthenticated = !!currentUser
- const authState: HeaderAuthState = isAuthenticated ? 'logged-in' : 'logged-out'
- const layout: Header['layout'] = outliner.context?.environment?.layout === 'mobile' ? 'mobile' : 'desktop'
- const theme: Header['theme'] = outliner.context?.environment?.theme === 'dark' ? 'dark' : 'light'
+ ]
- const headerOptions = {
- logo: SOLID_ICON_URL,
- helpIcon: HELP_MENU_ICON,
- helpMenuList: HELP_MENU_LIST,
- layout,
- theme,
- brandLink: '/',
- authState,
- loginAction: {
- label: SIGN_IN_MENU_ITEM
- } as HeaderMenuItem,
- signUpAction: {
- label: SIGN_UP_MENU_ITEM,
- url: SIGN_UP__MENU_LINK,
- } as HeaderMenuItem,
- logoutLabel: SIGN_OUT_MENU_ITEM,
- accountIcon: isAuthenticated ? DOWN_ARROW_ICON : '',
- accountAvatar: isAuthenticated ? widgets.findImage(currentUser) : undefined,
- accountAvatarFallback: DEFAULT_AVATAR_ICON,
- loginIcon: LOGIN_ICON,
- signUpIcon: SIGNUP_ICON,
- logoutIcon: LOGOUT_ICON
- }
+ render(
+ html``,
+ tmpContainer
+ )
- return headerOptions
-}
+ const header = tmpContainer.firstElementChild
-async function setUserMenu () {
- const me = authn.currentUser()
- if (!me) {
- return []
+ if (!header) {
+ throw new Error('Failed to create header')
}
- try {
- await store.fetcher.load(me.doc())
- } catch (err) {
- console.error('Unable to load user profile', err)
+ // ensure it is in DOM (before MainContent for consistency)
+ if (main && main.parentNode) {
+ main.parentNode.insertBefore(header, main)
+ } else {
+ document.body.prepend(header)
}
- const accountMenu: HeaderAccountMenuItem[] = [
- {
- label: utils.label(me),
- avatar: widgets.findImage(me),
- webid: me.value,
- action: 'show-profile'
- },
- // TODO add all my available accounts
- ]
-
- return accountMenu
+ return header
}
diff --git a/src/mainPage/index.ts b/src/mainPage/index.ts
index 947e2510..7af05d2b 100644
--- a/src/mainPage/index.ts
+++ b/src/mainPage/index.ts
@@ -6,7 +6,7 @@
import { LiveStore, NamedNode } from 'rdflib'
import type { RenderEnvironment } from 'pane-registry'
import { getOutliner, OutlineManager } from '../index'
-import { createHeader, refreshHeader } from './header'
+import { createHeader } from './header'
import { createFooter } from './footer'
import { createLeftSideMenu, refreshMenu } from './menu'
@@ -20,7 +20,6 @@ function renderEnvSignature (env?: RenderEnvironment): string {
}
export { refreshMenu as updateMenuLayout } from './menu'
-export { refreshHeader } from './header'
function ensureMainContent () {
let main = document.getElementById('MainContent') as HTMLElement | null
@@ -47,7 +46,7 @@ export async function initMainPage (
const subject: NamedNode = typeof uri === 'string' ? store.sym(uri) : uri
outliner.GotoSubject(subject, true, undefined, true, undefined)
- const header = await createHeader(store, outliner)
+ const header = await createHeader(outliner)
const menu = createLeftSideMenu(subject, outliner)
const footer = menu.then(() => createFooter(store))
return Promise.all([header, menu, footer])
@@ -71,6 +70,5 @@ export async function refreshUI (outliner: OutlineManager) {
;(outliner as any)[LAST_RENDER_ENV_KEY] = currentSignature
}
- await refreshHeader(outliner)
refreshMenu(outliner.context.environment?.layout === 'mobile' ? 'mobile' : 'desktop')
}
diff --git a/typings/env.d.ts b/typings/env.d.ts
new file mode 100644
index 00000000..2043d9c4
--- /dev/null
+++ b/typings/env.d.ts
@@ -0,0 +1 @@
+///
diff --git a/vite.config.mts b/vite.config.mts
index 50b4ee18..66af8feb 100644
--- a/vite.config.mts
+++ b/vite.config.mts
@@ -1,9 +1,41 @@
+import { isAbsolute } from 'node:path';
import { fileURLToPath } from 'node:url'
import { solidPane, buildConfig } from 'solidos-toolkit/vite'
import { defineConfig } from 'vitest/config'
export default defineConfig({
- build: buildConfig({ entry: "src/index.ts" }),
+ build: buildConfig({
+ entry: "src/index.ts",
+
+ // TODO remove these overrides once the following PR from solidos-toolkit has been merged:
+ // https://github.com/SolidOS/toolkit/pull/6
+ overrides: {
+ rolldownOptions: {
+ output: [
+ {
+ format: 'es',
+ preserveModules: true,
+ preserveModulesRoot: 'src',
+ entryFileNames: '[name].esm.js',
+ },
+ {
+ format: 'cjs',
+ preserveModules: true,
+ preserveModulesRoot: 'src',
+ entryFileNames: '[name].cjs.js',
+ },
+ ],
+ external(id: string) {
+ return (
+ !id.startsWith('~icons/') &&
+ !id.startsWith('@/') &&
+ !id.startsWith('.') &&
+ !isAbsolute(id)
+ );
+ },
+ },
+ }
+ }),
resolve: {
tsconfigPaths: true,
@@ -16,7 +48,7 @@ export default defineConfig({
},
},
plugins: solidPane({
- litDecoratorPaths: [],
+ litDecoratorPaths: ['src/components'],
sandbox: {
subject: "https://testingsolidos.solidcommunity.net/profile/card#me",
entry: "./src/dev.ts",