Skip to content
Merged
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
610 changes: 292 additions & 318 deletions package-lock.json

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "solid-panes",
"version": "4.4.2-5",
"version": "4.4.2-6",
"description": "Solid-compatible Panes: applets and views for the mashlib and databrowser",
"main": "dist/index.cjs.js",
"types": "dist/index.d.ts",
Expand Down Expand Up @@ -61,7 +61,7 @@
"chat-pane": "3.0.4-3",
"contacts-pane": "3.2.1-5",
"dompurify": "^3.4.4",
"folder-pane": "3.1.1-2",
"folder-pane": "3.1.1-3",
"issue-pane": "3.0.3-1",
"lit-html": "^3.3.2",
"marked": "^18.0.3",
Expand All @@ -72,7 +72,7 @@
"rdflib": "2.4.0",
"solid-logic": "4.0.8-2",
"solid-namespace": "^0.5.4",
"solid-ui": "3.1.3-13",
"solid-ui": "3.1.3-15",
"source-pane": "3.1.1-5"
},
"overrides": {
Expand Down
12 changes: 12 additions & 0 deletions src/components/file-explorer-header/FileExplorerHeader.styles.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
:host {
header {
display: flex;
justify-content: space-between;
align-items: center;
gap: 6px;
flex-wrap: nowrap;
width: 100%;
text-align: right;
padding: 15px;
}
}
86 changes: 86 additions & 0 deletions src/components/file-explorer-header/FileExplorerHeader.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import { sym } from 'rdflib'
import { WebComponent, type FileExplorerContext, fileExplorerContext } from 'solid-ui'
import { customElement, property, state } from 'lit/decorators.js'
import { consume } from '@lit/context'
import { html } from 'lit'
import '~icons/lucide/share-2'
import '~icons/lucide/pencil'
import '~icons/lucide/ellipsis-vertical'
import styles from './FileExplorerHeader.styles.css'
import './FileExplorerHeaderSummary'
import './FileExplorerHeaderControls'
import { PaneIcon } from './types'
import { fetchContentAndMetadata, type FileExplorerResourceMetadata } from './helper'

@customElement('file-explorer-header')
export default class FileExplorerHeader extends WebComponent {
static styles = styles

private _loadedMetadataForUri: string | undefined

@consume({ context: fileExplorerContext, subscribe: true })
accessor fileExplorerContext: FileExplorerContext = undefined as unknown as FileExplorerContext

@property({ attribute: false })
accessor menuItems: Array<{ label: string, action: (event: Event) => void }> = []

@property({ attribute: false })
accessor paneIcon: PaneIcon = undefined as unknown as PaneIcon

@state()
accessor responseMetadata: Pick<FileExplorerResourceMetadata, 'modified' | 'isPublic' | 'canEdit' | 'aclUri'> = {
modified: undefined,
isPublic: false,
canEdit: false,
aclUri: undefined
}

private getDefaultResponseMetadata (): Pick<FileExplorerResourceMetadata, 'modified' | 'isPublic' | 'canEdit' | 'aclUri'> {
return {
modified: undefined,
isPublic: false,
canEdit: false,
aclUri: undefined
}
}

protected updated () {
if (this.fileExplorerContext?.store && this.fileExplorerContext.subjectUri && this._loadedMetadataForUri !== this.fileExplorerContext.subjectUri) {
this._loadedMetadataForUri = this.fileExplorerContext.subjectUri
this.loadResponseMetadata()
}
}

private async loadResponseMetadata () {
if (!this.fileExplorerContext?.store || !this.fileExplorerContext.subjectUri) return

try {
const { metadata } = await fetchContentAndMetadata(this.fileExplorerContext.store, sym(this.fileExplorerContext.subjectUri))
this.responseMetadata = {
modified: metadata.modified,
isPublic: metadata.isPublic,
canEdit: metadata.canEdit,
aclUri: metadata.aclUri
}
} catch (error) {
this.responseMetadata = this.getDefaultResponseMetadata()
console.warn('Failed to load response metadata', error)
}
}

render () {
return html`
<header>
<file-explorer-header-summary
.paneIcon=${this.paneIcon}
.onBackClick=${this.fileExplorerContext?.onBack}
.responseMetadata=${this.responseMetadata}
></file-explorer-header-summary>
<file-explorer-header-controls
.menuItems=${this.menuItems}
.canEdit=${this.responseMetadata.canEdit}
></file-explorer-header-controls>
</header>
`
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
:host {
div {
display: flex;
justify-content: flex-end;
align-items: center;
gap: 6px;
flex: 0 0 auto;
text-align: right;
}

/* generative ai */
div > * {
flex: 0 1 auto;
}

/* TODO: look into what colors to use this is just to show */
.dirtyIndicator {
display: inline-flex;
align-items: center;
border: 1px solid #F59E0B;
color: #92400E;
background: #FFFBEB;
border-radius: 999px;
font-size: 0.72rem;
font-weight: 600;
line-height: 1;
padding: 4px 8px;
white-space: nowrap;
}

@media (max-width: 900px) {
div {
padding-right: 3px;
}
}

@media (max-width: 600px) {
div {
justify-content: flex-start;
gap: 5px;
}
}
}
74 changes: 74 additions & 0 deletions src/components/file-explorer-header/FileExplorerHeaderControls.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { WebComponent } from 'solid-ui'
import { customElement, property } from 'lit/decorators.js'
import { consume } from '@lit/context'
import { html, nothing } from 'lit'
import 'solid-ui/components/button'
import '~icons/lucide/share-2'
import '~icons/lucide/pencil'
import styles from './FileExplorerHeaderControls.styles.css'
import '../resource-actions-menu/ResourceActionsMenu'
import { fileExplorerContext, type FileExplorerContext } from 'solid-ui'

@customElement('file-explorer-header-controls')
export default class FileExplorerHeaderControls extends WebComponent {
static styles = styles

@consume({ context: fileExplorerContext, subscribe: true })
accessor fileExplorerContext: FileExplorerContext = undefined as unknown as FileExplorerContext

@property({ attribute: false })
accessor menuItems: Array<{ label: string, action: (event: Event) => void }> = []

@property({ type: Boolean })
accessor canEdit: boolean = false

// TODO: Add broken then use this function to set tooltip and disable edit button
/* private setEditable() {
const sourcePaneState = this.sourceContext?.sourcePaneState
const { canEdit } = this.sourceContext?.headerMetadata ?? { canEdit: false }
const subject = this.sourceContext?.subject
if (!sourcePaneState || !canEdit || sourcePaneState.broken || (subject && subject.endsWith('/'))) return

this.sourceContext?.setEditing?.()
} */

private handleEditingClick () {
this.fileExplorerContext.edit?.onEdit?.()
}

private getEditTooltip () {
if (!this.fileExplorerContext.paneSupportsEditing) return 'Not Supported'
if (!this.canEdit) return 'No Access'
return 'Edit'
}

private renderDirtyIndicator () {
if (!this.fileExplorerContext.edit?.isDirty) return nothing

return html`<span class="dirtyIndicator" title="This file has unsaved changes">Unsaved</span>`
}

render () {
return html`
<div>
${this.renderDirtyIndicator()}
<solid-ui-button variant="ghost" title="Share" @click=${this.fileExplorerContext.handleSharingClick}>
<icon-lucide-share-2 slot="icon"></icon-lucide-share-2>
</solid-ui-button>
<solid-ui-button
variant="ghost"
title=${this.getEditTooltip()}
?disabled=${!this.fileExplorerContext.paneSupportsEditing || !this.canEdit}
@click=${this.handleEditingClick}
>
<icon-lucide-pencil slot="icon"></icon-lucide-pencil>
</solid-ui-button>
<resource-actions-menu
.store=${this.fileExplorerContext?.store}
.subjectUri=${this.fileExplorerContext?.subjectUri}
.menuItems=${this.menuItems}
></resource-actions-menu>
</div>
`
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
:host {
.file-explorer-header-summary {
display: flex;
justify-content: flex-start;
align-items: center;
gap: 6px;
flex-wrap: wrap;
flex: 1 1 auto;
min-width: 0;
text-align: left;
}

h1 {
color: var(--solid-ui-color-gray-700, #364153);
font-size: var(--solid-ui-font-size-2xl, 1.5rem);
font-weight: 500;
}

.pane-icon {
display: flex;
width: 40px;
height: 40px;
padding: 7.5px 7px 6.5px 7px;
justify-content: center;
align-items: center;
aspect-ratio: 1/1;
/* border-radius: 100px; */
/* background: var(--slate-600, #45556C); */
}

.pane-icon img {
width: 100%;
height: 100%;
object-fit: contain;
display: block;
}

p {
display: flex;
align-items: center;
gap: 5px;
color: var(--solid-ui-gray-500, #6A7282);
font-size: var(--solid-ui-font-size-xs, .75rem);
font-weight: 500;
}

.public {
display: flex;
padding: 2px 5px;
justify-content: center;
align-items: center;
gap: 3px;
color: var(--blue-400, #51A2FF);
border-radius: 5px;
background: var(--blue-50, #EFF6FF);
}

icon-lucide-globe {
stroke-width: 1.021px;
stroke: var(--blue-400, #51A2FF);
}

.private {
display: flex;
padding: 2px 5px;
justify-content: center;
align-items: center;
gap: 3px;
color: var(--yellow-600, #D08700);
border-radius: 5px;
background: var(--yellow-100, #FEF9C2);
}

icon-lucide-lock-keyhole {
stroke-width: 1.021px;
stroke: var(--yellow-600, #D08700);
}

icon-lucide-globe,
icon-lucide-lock-keyhole {
width: 0.875rem;
height: 0.875rem;
}

icon-lucide-arrow-left {
width: 1.125rem;
height: 1.125rem;
}
}
Loading
Loading