Skip to content
Open
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
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ A colorful dependency tree can be seen [here](https://github.com/solidos/solidos
- [Solid-ui & Solid-logic related:](#solid-ui--solid-logic-related)
- [The databrowser hack: upgrading your browser](#the-databrowser-hack-upgrading-your-browser)

### [Generative AI usage](#generative-ai-usage)

## Developing mashlib

As part of the SolidOS stack, mashlib can be developed locally by setting up the SolidOS code. Read more about that on the [SolidOS Readme](https://github.com/solidos/solidos#-getting-started-with-the-solidos-code).
Expand Down Expand Up @@ -151,3 +153,13 @@ The mashlib part of SolidOS Databrowser Frontend is *read-write;* that is, the u

A major limitation of this data browser hack is that current web browsers are made to distrust any code loaded from one domain that uses data from another domain. This makes it hard, strangely complicated, and sometimes impossible to do some things.

## Generative AI usage
The SolidOS team is using GitHub Copilot integrated in Visual Studio Code.
We have added comments in the code to make it explicit which parts are 100% written by AI.

### Prompt usage history:
* Auto model: Looking at these 2 files (databrowser.html and index.ts), I want to redesign mashlib and underlying panes. I have a design for web and one for mobile. How would I go about making sure I can also have a mobile version?

* Claude-Opes 4.6: I don't think this is correct. Mashlib is bundling together all the panes. I do not need to add mashlib.layout or theme to the globals. I can just call the render of each pane with an interface of values or?

* Raptor mini: I want to rdesign the main page which contains GlobalDashboard and OtlineView. I keep the header and footer and I want to have a left side menu that chnages content base on logged in or not and also chnages if it is mobile or not. If it is mobile it should fold up in the header menu if it is web it should be rendered on the left side. I want a menu placeholder that changes according to mobile or not. Its functionality I want to implement in solid-panes.
753 changes: 403 additions & 350 deletions package-lock.json

Large diffs are not rendered by default.

4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,13 +52,15 @@
"dependencies": {
"rdflib": "^2.3.6",
"solid-logic": "^4.0.6",
"pane-registry": "^3.1.0",
"solid-panes": "^4.2.5",
"solid-ui": "^3.0.6"
},
"overrides": {
"rdflib": "$rdflib",
"solid-logic": "$solid-logic",
"solid-ui": "$solid-ui"
"solid-ui": "$solid-ui",
"pane-registry": "$pane-registry"
},
"devDependencies": {
"@babel/cli": "^7.28.6",
Expand Down
85 changes: 64 additions & 21 deletions src/databrowser.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,32 +2,75 @@
<html>
<head>
<meta charset="utf-8"/>
<title><%= htmlWebpackPlugin.options.title %></title>
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover"/>
<title>SolidOS</title>
<script>
document.addEventListener('DOMContentLoaded', function() {
panes.runDataBrowser()
})
</script>
</head>
<body id="PageBody">
<!-- solid-panes' OutlineManager injects into this element -->
<header id="PageHeader" role="banner"></header>
<main id="mainContent" tabindex="-1">
<div class="TabulatorOutline" id="DummyUUID">
<table id="outline">
<thead>
<tr>
<th id="outlineHeader" scope="col"></th>
<!-- Add more <th> as needed for columns -->
</tr>
</thead>
<tbody>
<!-- Table rows injected by JS -->
</tbody>
</table>
<div id="GlobalDashboard" aria-label="Global Dashboard"></div>
</div>
</main>
<footer id="PageFooter" role="contentinfo"></footer>
<body id="PageBody" data-app-shell="databrowser">

<!-- Skip-navigation link (accessibility) -->
<a href="#MainContent" class="skip-link">Skip to main content</a>

<!-- Header — populated by solid-ui initHeader(), plus responsive menu toggle -->
<header id="PageHeader" role="banner">
<div class="page-header-inner">
<button id="MenuToggleBtn" class="menu-toggle" aria-controls="NavMenu" aria-expanded="false" hidden>
<span class="sr-only">Menu</span>
</button>
</div>
</header>

<!-- Main content area — single visible region at a time -->
<main id="MainContent" role="main" tabindex="-1" aria-live="polite">
<div class="app-shell">
<aside id="NavMenu" class="app-nav" aria-label="Application menu" hidden>
<div id="NavMenuContent" class="menu-content">Menu placeholder</div>
</aside>

<div class="app-view">
<!--
Outline view: the primary RDF-subject browser.
Replaces the old <table id="outline"> with a semantic <section>.
JS appends property-table <div>s (not <tr>s) here.
-->
<section
id="OutlineView"
class="outline-view"
aria-label="Resource browser"
>
<!--
Pane icon tray (nav) and pane content are injected here by
OutlineManager.GotoSubject / propertyTable / outlineExpand.
Each subject block is a <article class="subject-block">.
-->
</section>

<!--
Global Dashboard: preferences, profile, contacts, etc.
Hidden by default; toggled by showDashboard / closeDashboard.
-->
<section
id="GlobalDashboard"
class="global-dashboard"
aria-label="Dashboard"
hidden
>
<!-- globalAppTabs appends tab widget here -->
</section>
</div> <!-- .app-view -->
</div> <!-- .app-shell -->

<div id="MenuOverlay" class="menu-overlay" hidden aria-hidden="true"></div>
</main>

<!-- Footer — populated by solid-ui initFooter() -->
<footer id="PageFooter" role="contentinfo">
<!-- solid-ui injects: "Powered by Solid" link -->
</footer>
</body>
</html>
79 changes: 45 additions & 34 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,39 +1,43 @@
import * as $rdf from 'rdflib'
import * as panes from 'solid-panes'
import { authn, solidLogicSingleton, authSession, store } from 'solid-logic'
import { layout } from './layout'
import { theme } from './theme'
import type { RenderEnvironment } from 'pane-registry'
import versionInfo from './versionInfo'
import './styles/mash.css'

const global: any = window

// Theme Management
const initializeTheme = () => {
const savedTheme = localStorage.getItem('mashlib-theme')
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches
const theme = savedTheme || (prefersDark ? 'dark' : 'light')
// Build a snapshot of the current render environment
const buildRenderEnvironment = (): RenderEnvironment => ({
layout: layout.get(),
layoutPreference: layout.getPreference(),
inputMode: layout.getInputMode(),
theme: theme.get(),
viewport: layout.getViewport()
})

// Inject or update the environment on the pane context
const syncEnvironmentToContext = () => {

if (theme === 'dark') {
document.documentElement.setAttribute('data-theme', 'dark')
} else {
document.documentElement.removeAttribute('data-theme')
const outliner = panes.getOutliner(document) as any

if (!outliner) {
console.warn('outliner not ready yet')
return
}
}

const setTheme = (theme: 'light' | 'dark') => {
if (theme === 'dark') {
document.documentElement.setAttribute('data-theme', 'dark')
} else {
document.documentElement.removeAttribute('data-theme')
if (!outliner.context) {
console.warn('outliner.context missing: creating fallback context')
outliner.context = {}
}
localStorage.setItem('mashlib-theme', theme)
}

const getTheme = (): 'light' | 'dark' => {
return document.documentElement.getAttribute('data-theme') === 'dark' ? 'dark' : 'light'
panes.updateEnvironment(outliner, buildRenderEnvironment())

Check failure on line 36 in src/index.ts

View workflow job for this annotation

GitHub Actions / build (22)

Property 'updateEnvironment' does not exist on type 'typeof import("/home/runner/work/mashlib/mashlib/node_modules/solid-panes/dist/index")'.

Check failure on line 36 in src/index.ts

View workflow job for this annotation

GitHub Actions / build (24)

Property 'updateEnvironment' does not exist on type 'typeof import("/home/runner/work/mashlib/mashlib/node_modules/solid-panes/dist/index")'.
}

// Initialize theme on load
initializeTheme()
// Keep environment in sync on layout/theme changes
window.addEventListener('mashlib:layoutchange', syncEnvironmentToContext)

global.$rdf = $rdf
global.panes = panes
Expand All @@ -43,14 +47,7 @@
store,
solidLogicSingleton
}
global.mashlib = {
versionInfo,
theme: {
set: setTheme,
get: getTheme,
init: initializeTheme
}
}
global.mashlib = { versionInfo }

global.panes.runDataBrowser = function (uri?:string|$rdf.NamedNode|null) {
// Set up cross-site proxy
Expand All @@ -67,11 +64,24 @@
console.error('Failed to add web monetization tag to page header')
}

window.addEventListener('load', syncEnvironmentToContext)

// Authenticate the user
authn.checkUser().then(function (_profile: any) {
const mainPage = panes.initMainPage(solidLogicSingleton.store, uri)
return mainPage
})
authn.checkUser()
.then(() => panes.initMainPage(solidLogicSingleton.store, uri))
.then(() => {
// Inject render environment into pane context after outliner exists
syncEnvironmentToContext()
window.requestAnimationFrame(syncEnvironmentToContext)

// Set up menu depending on whether mobile or desktop
panes.updateMenuLayout(layout.get())

Check failure on line 78 in src/index.ts

View workflow job for this annotation

GitHub Actions / build (22)

Property 'updateMenuLayout' does not exist on type 'typeof import("/home/runner/work/mashlib/mashlib/node_modules/solid-panes/dist/index")'.

Check failure on line 78 in src/index.ts

View workflow job for this annotation

GitHub Actions / build (24)

Property 'updateMenuLayout' does not exist on type 'typeof import("/home/runner/work/mashlib/mashlib/node_modules/solid-panes/dist/index")'.

})
.catch((err: any) => {
console.error('runDataBrowser failed', err)
})

}

window.onpopstate = function (_event: any) {
Expand All @@ -85,5 +95,6 @@
}

export {
versionInfo
versionInfo,
buildRenderEnvironment
}
107 changes: 107 additions & 0 deletions src/layout.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
/* generated by AI - see readme for details*/
import type { LayoutMode, LayoutPreference } from 'pane-registry'

const LAYOUT_STORAGE_KEY = 'mashlib-layout'
const MOBILE_BREAKPOINT_PX = 768

const getStoredLayoutPreference = (): LayoutPreference => {
const storedLayout = localStorage.getItem(LAYOUT_STORAGE_KEY)

if (storedLayout === 'mobile' || storedLayout === 'desktop' || storedLayout === 'auto') {
return storedLayout
}

return 'auto'
}

const resolveAutomaticLayout = (): LayoutMode => {
return window.innerWidth <= MOBILE_BREAKPOINT_PX ? 'mobile' : 'desktop'
}

const applyLayoutAttributes = (layout: LayoutMode, preference: LayoutPreference) => {
const root = document.documentElement
const inputMode = window.matchMedia('(pointer: coarse)').matches ? 'touch' : 'pointer'

root.setAttribute('data-layout', layout)
root.setAttribute('data-layout-preference', preference)
root.setAttribute('data-input-mode', inputMode)
root.style.setProperty('--app-height', `${window.innerHeight}px`)

window.dispatchEvent(new CustomEvent('mashlib:layoutchange', {
detail: {
inputMode,
layout,
preference,
viewport: {
height: window.innerHeight,
width: window.innerWidth
}
}
}))
}

const updateLayout = (preference: LayoutPreference = getStoredLayoutPreference()): LayoutMode => {
const layout = preference === 'auto' ? resolveAutomaticLayout() : preference
applyLayoutAttributes(layout, preference)
return layout
}

const setLayoutPreference = (preference: LayoutPreference): LayoutMode => {
if (preference === 'auto') {
localStorage.removeItem(LAYOUT_STORAGE_KEY)
} else {
localStorage.setItem(LAYOUT_STORAGE_KEY, preference)
}

return updateLayout(preference)
}

const getLayoutPreference = (): LayoutPreference => {
return getStoredLayoutPreference()
}

const getLayoutMode = (): LayoutMode => {
return (document.documentElement.getAttribute('data-layout') as LayoutMode) || resolveAutomaticLayout()
}

const initializeLayout = () => {
let resizeFrame = 0
const syncLayout = () => {
updateLayout()
}

const onResize = () => {
if (resizeFrame) {
window.cancelAnimationFrame(resizeFrame)
}

resizeFrame = window.requestAnimationFrame(() => {
resizeFrame = 0
syncLayout()
})
}

syncLayout()
window.addEventListener('resize', onResize)

window.matchMedia('(pointer: coarse)').addEventListener('change', syncLayout)
window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT_PX}px)`).addEventListener('change', syncLayout)
}

const getInputMode = (): 'touch' | 'pointer' => {
return window.matchMedia('(pointer: coarse)').matches ? 'touch' : 'pointer'
}

const getViewport = () => ({
width: window.innerWidth,
height: window.innerHeight
})

export const layout = {
get: getLayoutMode,
getInputMode,
getPreference: getLayoutPreference,
getViewport,
init: initializeLayout,
set: setLayoutPreference
}
Loading
Loading