Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dynamic collections: Creation of a new panel by using local tree modal #562

Merged
merged 12 commits into from
Feb 2, 2025
3 changes: 2 additions & 1 deletion eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ export default tseslint.config(
},
],
semi: ["error", "never"],
"object-curly-spacing": ["error", "always"]
"object-curly-spacing": ["error", "always"],
"indent": ["error", 2]
},
}
);
3 changes: 3 additions & 0 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import PanelsWrapper from './components/PanelsWrapper'
import { FC } from 'react'
import { configStore } from '@/store/ConfigStore.tsx'

import TopBar from '@/components/TopBar'

interface AppProps {
customConfig: Config
}
Expand All @@ -12,6 +14,7 @@ const App: FC<AppProps> = ({ customConfig }) => {

return (
<div className="tido t-flex t-flex-col">
<TopBar />
<PanelsWrapper />
</div>
)
Expand Down
29 changes: 29 additions & 0 deletions src/components/LocalTreeModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { FC, ReactNode } from 'react'

import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'
import ContentModal from '@/components/tree-modal/ContentModal'

interface LocalTreeProps {
TriggerButton: ReactNode
}

const LocalTreeModal: FC<LocalTreeProps> = ({ TriggerButton }) => {

// TODO: add a [loading, setLoading] => which shows the pop over when the tree has been loaded -> TreeView Component updates the loading of its parent

// function of this component: create a new Panel


return <div className="local-tree-modal">
<Popover>
<PopoverTrigger className="open-tree-button t-h-8 t-w-10 t-relative">
{TriggerButton}
</PopoverTrigger>
<PopoverContent className="t-bg-white t-absolute t-z-10">
<ContentModal/>
</PopoverContent>
</Popover>
</div>
}

export default LocalTreeModal
20 changes: 20 additions & 0 deletions src/components/TopBar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { FC } from 'react'


import LocalTreeModal from '@/components/LocalTreeModal'

const TopBar: FC = () => {

const addButton =
<span className="t-bg-blue-500 t-text-white t-rounded t-flex t-pl-4 t-items-center t-justify-items-center t-w-16 t-h-10">
New
</span>

Check failure on line 11 in src/components/TopBar.tsx

View workflow job for this annotation

GitHub Actions / build (20)

Expected indentation of 10 spaces but found 8


return <div className="t-flex t-flex-row t-ml-[6%] t-mt-10">

Check failure on line 14 in src/components/TopBar.tsx

View workflow job for this annotation

GitHub Actions / build (20)

Expected indentation of 2 spaces but found 0
<LocalTreeModal TriggerButton={addButton} />

Check failure on line 15 in src/components/TopBar.tsx

View workflow job for this annotation

GitHub Actions / build (20)

Expected indentation of 4 spaces but found 10
</div>

Check failure on line 16 in src/components/TopBar.tsx

View workflow job for this annotation

GitHub Actions / build (20)

Expected indentation of 2 spaces but found 8

}

export default TopBar
42 changes: 42 additions & 0 deletions src/components/Tree.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { FC } from 'react'


import { TreeProvider } from '@/contexts/TreeContext.tsx'

import TreeNode from '@/components/tree/TreeNode'

interface TreeProps {
nodes: TreeNode[],

onSelect(node: TreeNode): void,

onExpand(node: TreeNode): void,

onCollapse(node: TreeNode): void,
}

// ({ nodes, onSelect, onExpand })
const Tree: FC<TreeProps> = ({ nodes, onSelect, onExpand, onCollapse }) => {


const tree =
nodes.length > 0 &&
nodes.map((collection, i) => (
<div
key={i}
className=""
>
<TreeNode node={collection}/>
</div>
))


return <div className="tree t-h-96 t-overflow-hidden t-overflow-y-auto">
<TreeProvider onSelect={onSelect} onExpand={onExpand} onCollapse={onCollapse}
>
{tree}
</TreeProvider>
</div>
}

export default Tree
15 changes: 15 additions & 0 deletions src/components/base/InputField.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { FC } from 'react'

interface InputFieldProps {
updateInputValue: (newValue: string) => void
}

const InputField: FC<InputFieldProps> = ({ updateInputValue }) => {

return <>

Check failure on line 9 in src/components/base/InputField.tsx

View workflow job for this annotation

GitHub Actions / build (20)

Expected indentation of 2 spaces but found 4
<input className="t-border-solid t-border-[1.5px] t-w-[200px] t-h-[30px] t-mb-[10px]"

Check failure on line 10 in src/components/base/InputField.tsx

View workflow job for this annotation

GitHub Actions / build (20)

Expected indentation of 4 spaces but found 8
onChange={(e) => updateInputValue(e.target.value)}/>

Check failure on line 11 in src/components/base/InputField.tsx

View workflow job for this annotation

GitHub Actions / build (20)

Expected indentation of 6 spaces but found 15
</>

Check failure on line 12 in src/components/base/InputField.tsx

View workflow job for this annotation

GitHub Actions / build (20)

Expected indentation of 2 spaces but found 4
}

export default InputField
13 changes: 7 additions & 6 deletions src/components/panel/Panel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
}

const Panel: FC<Props> = ({ config }) => {
const initCollection = dataStore(state => state.initCollection)
const getCollection = dataStore(state => state.getCollection)
const addPanelContent = contentStore((state) => state.addPanelContent)

const [error, setError] = useState<boolean | string>(false)
Expand All @@ -29,7 +29,8 @@
const init = async () => {
try {
setLoading(true)
const collection = await initCollection(collectionUrl)
const collection = await getCollection(collectionUrl)

const manifest = await apiRequest<Manifest>(collection.sequence[config.manifestIndex ?? 0].id)
const item = await apiRequest<Item>(manifest.sequence[config.itemIndex ?? 0].id)
const contentTypes: string[] = getContentTypes(item.content)
Expand All @@ -49,18 +50,18 @@
}
}
init()
}, [config])

Check warning on line 53 in src/components/panel/Panel.tsx

View workflow job for this annotation

GitHub Actions / build (20)

React Hook useEffect has missing dependencies: 'addPanelContent' and 'getCollection'. Either include them or remove the dependency array

if (error) {
return <ErrorComponent message={error} />
return <ErrorComponent message={error}/>
}

return (
<div
className="panel t-flex t-flex-col t-w-[600px] t-mr-6 t-border-solid t-border-2 t-border-slate-200 t-rounded-lg t-mt-4 t-px-2.5 t-pt-8 t-pb-6">
{ loading && <div> Loading data ... Please wait a sec</div> }
{ !loading && error && <ErrorComponent message={error} /> }
{ !loading && !error && panelId &&
{loading && <div> Loading data ... Please wait a sec</div>}
{!loading && error && <ErrorComponent message={error}/>}
{!loading && !error && panelId &&
<PanelProvider id={panelId}>
<PanelTopBar/>
<div className="t-flex t-flex-col t-items-center t-mb-6">
Expand Down
153 changes: 153 additions & 0 deletions src/components/tree-modal/ContentModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
import { FC, useEffect, useRef, useState } from 'react'

Check failure on line 1 in src/components/tree-modal/ContentModal.tsx

View workflow job for this annotation

GitHub Actions / build (20)

'useState' is defined but never used

import { configStore } from '@/store/ConfigStore'
import { dataStore } from '@/store/DataStore'


import TreeView from '@/components/Tree.tsx'
import InputField from '@/components/base/InputField.tsx'
import { ClosePopover } from '@/components/ui/popover'
import { createTree, getItemIndices, getManifestIndices, getChildren } from '@/utils/tree'


const ContentModal: FC = () => {

const addNewPanel = configStore(state => state.addNewPanel)

const setTreeNodes = dataStore(state => state.setTreeNodes)
const initCollection = dataStore(state => state.initCollection)
const collections = dataStore(state => state.collections)
const nodes = dataStore(state => state.treeNodes)


const inputValue = useRef('')
const clickedItemUrl = useRef('')

const selectedItemIndices = useRef({
collectionUrl: '',
manifestIndex: -1,
itemIndex: -1,
})


useEffect(() => {
async function initTree(collections: CollectionMap) {

const collectionsUrls = Object.keys(collections)
if (collectionsUrls.length === 0) return

const nodes = await createTree(collectionsUrls)
setTreeNodes(nodes)
}

initTree(collections)
}, [collections])

Check warning on line 44 in src/components/tree-modal/ContentModal.tsx

View workflow job for this annotation

GitHub Actions / build (20)

React Hook useEffect has a missing dependency: 'setTreeNodes'. Either include it or remove the dependency array


function updateInputValue(newValue: string) {
inputValue.current = newValue
}

async function handleSelectClick() {

let collectionUrl: string | undefined

if (clickedItemUrl.current) {
// transfer the clicked item indices
const { collectionUrl, manifestIndex, itemIndex } = selectedItemIndices.current
addNewPanel({
entrypoint: {
url: collectionUrl,
type: 'collection',
},
manifestIndex: manifestIndex,
itemIndex: itemIndex
})
}

if (inputValue.current !== '') {
collectionUrl = inputValue.current

addNewPanel(
{
entrypoint: {
url: collectionUrl,
type: 'collection',
},
manifestIndex: 0,
itemIndex: 0
}
)

await initCollection(collectionUrl)
}
}


async function onExpand(node: TreeNode) {
const { type } = node
const updatedTree = [...nodes]

if (type === 'collection') {
const collectionIndex = nodes.findIndex((n) => (n.id === node.id))
if (!('children' in updatedTree[collectionIndex])) updatedTree[collectionIndex]['children'] = await getChildren(node)

updatedTree[collectionIndex].expanded = true

} else if (type === 'manifest') {
const { collectionIndex, manifestIndex } = getManifestIndices(node.key)
const manifestNode = { ...updatedTree[collectionIndex].children[manifestIndex] }

if (!('children' in manifestNode)) manifestNode['children'] = await getChildren(node)

updatedTree[collectionIndex].children[manifestIndex] = { ...manifestNode }
updatedTree[collectionIndex].children[manifestIndex].expanded = true
}

setTreeNodes(updatedTree)
}

async function onCollapse(node: TreeNode) {
const { type } = node
const updatedTree = [...nodes]

if (type === 'collection') {
const collectionIndex = nodes.findIndex((n) => (n.id === node.id))
updatedTree[collectionIndex].expanded = false
} else if (type === 'manifest') {
const { collectionIndex, manifestIndex } = getManifestIndices(node.key)

updatedTree[collectionIndex].children[manifestIndex].expanded = false
}

setTreeNodes(updatedTree)
}

function onSelect(node: TreeNode) {
const { id } = node
clickedItemUrl.current = id
selectedItemIndices.current = getItemIndices(node.key, nodes)
}


return <div
className="t-flex t-flex-col t-pt-4 t-pl-3 t-w-[500px] t-shadow-md t-border-[1px] t-border-solid t-border-gray-300 t-rounded-md">

<span className="t-font-bold">Enter a Collection Url</span>
<InputField updateInputValue={updateInputValue}/>
<span>Or choose:</span>

<TreeView nodes={nodes} onSelect={onSelect} onExpand={onExpand} onCollapse={onCollapse}/>

<div className="t-pb-4">
<ClosePopover
className='t-bg-blue-500 t-text-white t-rounded t-flex t-text-center t-pl-2 t-ml-[80%] t-mt-10 t-items-center t-justify-items-center t-w-16 t-h-10'
onClick={() => handleSelectClick()}>
Select
</ClosePopover>
</div>
</div>
}


export default ContentModal
38 changes: 38 additions & 0 deletions src/components/tree/TreeNode.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { FC, useRef } from 'react'

import { useTree } from '@/contexts/TreeContext'


interface TreeNodeProps {
node: TreeNode,
}

const TreeNode: FC<TreeNodeProps> = ({ node }) => {

const { onClick } = useTree()

const itemRef = useRef(null)

function handleNodeClick() {
onClick(node)
// add class t-bg-primary
}


if ('children' in node && node.expanded)
return <div>
<div className="t-mb-1 t-py-[2px] t-px-2 hover:t-bg-gray-100 hover:t-cursor-pointer hover:t-round-md"
onClick={() => handleNodeClick()}> {node.label}</div>

Check failure on line 25 in src/components/tree/TreeNode.tsx

View workflow job for this annotation

GitHub Actions / build (20)

Expected indentation of 8 spaces but found 11
{node.children?.map((item: TreeNode, i) => (
<ul className="t-ml-2" key={i}>
<TreeNode node={item}/>
</ul>
))}
</div>

return <div ref={itemRef}
className="t-mb-1 t-py-[2px] t-px-2 hover:t-bg-gray-100 hover:t-cursor-pointer hover:t-rounded-md"
onClick={() => handleNodeClick()}>{node.label}</div>
}

export default TreeNode
3 changes: 2 additions & 1 deletion src/components/ui/popover.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,5 +23,6 @@ const PopoverContent = React.forwardRef<
/>
))
PopoverContent.displayName = PopoverPrimitive.Content.displayName
const ClosePopover = PopoverPrimitive.Close

export { Popover, PopoverTrigger, PopoverContent }
export { Popover, PopoverTrigger, PopoverContent, ClosePopover }
Loading
Loading