From 80710c8f6295b6c95ca9c4c4e0627d3feb692b52 Mon Sep 17 00:00:00 2001 From: orlinmalkja Date: Thu, 30 Jan 2025 17:34:57 +0100 Subject: [PATCH 01/12] feat: add a local tree which helps in creating a new panel --- src/App.tsx | 3 + src/components/LocalTreeModal.tsx | 29 ++++ src/components/TopBar.tsx | 20 +++ src/components/Tree.tsx | 42 ++++++ src/components/base/InputField.tsx | 15 ++ src/components/panel/Panel.tsx | 13 +- src/components/tree-modal/ContentModal.tsx | 161 +++++++++++++++++++++ src/components/tree/TreeNode.tsx | 38 +++++ src/components/ui/popover.tsx | 3 +- src/contexts/TreeContext.tsx | 50 +++++++ src/store/ConfigStore.tsx | 16 +- src/store/DataStore.tsx | 25 +++- src/types.d.ts | 22 ++- src/utils/http.ts | 69 +++++---- src/utils/panel.ts | 11 +- src/utils/tree.ts | 92 ++++++++++++ src/utils/treeModal.ts | 10 ++ 17 files changed, 573 insertions(+), 46 deletions(-) create mode 100644 src/components/LocalTreeModal.tsx create mode 100644 src/components/TopBar.tsx create mode 100644 src/components/Tree.tsx create mode 100644 src/components/base/InputField.tsx create mode 100644 src/components/tree-modal/ContentModal.tsx create mode 100644 src/components/tree/TreeNode.tsx create mode 100644 src/contexts/TreeContext.tsx create mode 100644 src/utils/tree.ts create mode 100644 src/utils/treeModal.ts diff --git a/src/App.tsx b/src/App.tsx index f17c57a8..ace024b9 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -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 } @@ -12,6 +14,7 @@ const App: FC = ({ customConfig }) => { return (
+
) diff --git a/src/components/LocalTreeModal.tsx b/src/components/LocalTreeModal.tsx new file mode 100644 index 00000000..a91389fc --- /dev/null +++ b/src/components/LocalTreeModal.tsx @@ -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 = ({ 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
+ + + {TriggerButton} + + + + + +
+} + +export default LocalTreeModal \ No newline at end of file diff --git a/src/components/TopBar.tsx b/src/components/TopBar.tsx new file mode 100644 index 00000000..f4e200fb --- /dev/null +++ b/src/components/TopBar.tsx @@ -0,0 +1,20 @@ +import { FC } from 'react' + + +import LocalTreeModal from '@/components/LocalTreeModal' + +const TopBar: FC = () => { + + const addButton = + + New + + + +return
+ +
+ +} + +export default TopBar \ No newline at end of file diff --git a/src/components/Tree.tsx b/src/components/Tree.tsx new file mode 100644 index 00000000..5f188c83 --- /dev/null +++ b/src/components/Tree.tsx @@ -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 = ({ nodes, onSelect, onExpand, onCollapse }) => { + + + const tree = + nodes.length > 0 && + nodes.map((collection, i) => ( +
+ +
+ )) + + + return
+ + {tree} + +
+} + +export default Tree diff --git a/src/components/base/InputField.tsx b/src/components/base/InputField.tsx new file mode 100644 index 00000000..0c19a5b4 --- /dev/null +++ b/src/components/base/InputField.tsx @@ -0,0 +1,15 @@ +import { FC } from 'react' + +interface InputFieldProps { + updateInputValue: (newValue: string) => void +} + +const InputField: FC = ({ updateInputValue }) => { + + return <> + updateInputValue(e.target.value)}/> + +} + +export default InputField diff --git a/src/components/panel/Panel.tsx b/src/components/panel/Panel.tsx index 9322d267..dbf281fb 100644 --- a/src/components/panel/Panel.tsx +++ b/src/components/panel/Panel.tsx @@ -16,7 +16,7 @@ interface Props { } const Panel: FC = ({ config }) => { - const initCollection = dataStore(state => state.initCollection) + const getCollection = dataStore(state => state.getCollection) const addPanelContent = contentStore((state) => state.addPanelContent) const [error, setError] = useState(false) @@ -29,7 +29,8 @@ const Panel: FC = ({ config }) => { const init = async () => { try { setLoading(true) - const collection = await initCollection(collectionUrl) + const collection = await getCollection(collectionUrl) + const manifest = await apiRequest(collection.sequence[config.manifestIndex ?? 0].id) const item = await apiRequest(manifest.sequence[config.itemIndex ?? 0].id) const contentTypes: string[] = getContentTypes(item.content) @@ -52,15 +53,15 @@ const Panel: FC = ({ config }) => { }, [config]) if (error) { - return + return } return (
- { loading &&
Loading data ... Please wait a sec
} - { !loading && error && } - { !loading && !error && panelId && + {loading &&
Loading data ... Please wait a sec
} + {!loading && error && } + {!loading && !error && panelId &&
diff --git a/src/components/tree-modal/ContentModal.tsx b/src/components/tree-modal/ContentModal.tsx new file mode 100644 index 00000000..f373339a --- /dev/null +++ b/src/components/tree-modal/ContentModal.tsx @@ -0,0 +1,161 @@ +import { FC, useEffect, useRef, useState } from 'react' + +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' +import { getUniquePanels } from '@/utils/panel' +import { validateUrlInput } from '@/utils/treeModal.ts' + + +const ContentModal: FC = () => { + + const panels = configStore(state => state.config.panels) + const addNewPanel = configStore(state => state.addNewPanel) + + const initTreeNodes = dataStore(state => state.initTreeNodes) + const updateTreeNodes = dataStore(state => state.updateTreeNodes) + const nodes = dataStore(state => state.treeNodes) + + const [uniquePanels] = useState(getUniquePanels(panels)) + + + const inputValue = useRef('') + const clickedItemUrl = useRef('') + + const selectedItemIndices = useRef({ + collectionUrl: '', + manifestIndex: -1, + itemIndex: -1, + }) + + + useEffect(() => { + async function initTree(panels?: PanelConfig[]) { + if (!panels) return + + const nodes = await createTree(panels) + initTreeNodes(nodes) + } + + initTree(uniquePanels) + }, [uniquePanels]) + + + 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 + // should display an error + const isValid = await validateUrlInput(collectionUrl) + + if (!isValid) { + console.error('Please provide a valid URL value for the new collection') + return + } + + addNewPanel( + { + entrypoint: { + url: collectionUrl, + type: 'collection', + }, + manifestIndex: 0, + itemIndex: 0 + } + ) + } + } + + + 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 + } + + updateTreeNodes(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 + } + + updateTreeNodes(updatedTree) + } + + function onSelect(node: TreeNode) { + const { id } = node + clickedItemUrl.current = id + selectedItemIndices.current = getItemIndices(node.key, nodes) + } + + + return
+ + Enter a Collection Url + + Or choose: + + + +
+ handleSelectClick()}> + Select + +
+
+} + +// + +export default ContentModal diff --git a/src/components/tree/TreeNode.tsx b/src/components/tree/TreeNode.tsx new file mode 100644 index 00000000..a7093663 --- /dev/null +++ b/src/components/tree/TreeNode.tsx @@ -0,0 +1,38 @@ +import { FC, useRef } from 'react' + +import { useTree } from '@/contexts/TreeContext' + + +interface TreeNodeProps { + node: TreeNode, +} + +const TreeNode: FC = ({ node }) => { + + const { onClick } = useTree() + + const itemRef = useRef(null) + + function handleNodeClick() { + onClick(node) + // add class t-bg-primary + } + + + if ('children' in node && node.expanded) + return
+
handleNodeClick()}> {node.label}
+ {node.children?.map((item: TreeNode, i) => ( +
    + +
+ ))} +
+ + return
handleNodeClick()}>{node.label}
+} + +export default TreeNode diff --git a/src/components/ui/popover.tsx b/src/components/ui/popover.tsx index 8bfac19e..3da98894 100644 --- a/src/components/ui/popover.tsx +++ b/src/components/ui/popover.tsx @@ -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 } diff --git a/src/contexts/TreeContext.tsx b/src/contexts/TreeContext.tsx new file mode 100644 index 00000000..3cb26122 --- /dev/null +++ b/src/contexts/TreeContext.tsx @@ -0,0 +1,50 @@ +import { ReactNode, createContext, useContext, FC } from 'react' + +const TreeContext = createContext(undefined) + +interface TreeType { + onClick(node: TreeNode): void, + + onSelect(node: TreeNode): void + + onExpand(node: TreeNode): void, + + onCollapse(node: TreeNode): void, +} + +interface TreeProviderProps { + children?: ReactNode, + + onSelect(node: TreeNode): void, + + onExpand(node: TreeNode): void, + + onCollapse(node: TreeNode): void, +} + +const TreeProvider: FC = ({ children, onSelect, onExpand, onCollapse }) => { + + function onClick(node: TreeNode) { + if ('leaf' in node) onSelect(node) + else if (!node.expanded) onExpand(node) + else if (node.expanded) onCollapse(node) + } + + return ( + + {children} + + ) +} + + +function useTree(): TreeType { + const context = useContext(TreeContext) + if (!context) { + throw new Error('useTree must be used inside the TreeProvider') + } + + return context +} + +export { TreeProvider, useTree } diff --git a/src/store/ConfigStore.tsx b/src/store/ConfigStore.tsx index 3cad0d8c..3bf5afec 100644 --- a/src/store/ConfigStore.tsx +++ b/src/store/ConfigStore.tsx @@ -1,13 +1,23 @@ import { create } from 'zustand' + interface ConfigStoreType { config: Config, - addCustomConfig: (customConfig: Config) => void + addCustomConfig: (customConfig: Config) => void, + addNewPanel: (newPanelConfig: PanelConfig) => void, } -export const configStore = create((set) => ({ +export const configStore = create((set, get) => ({ + config: {}, addCustomConfig: (customConfig: Config) => { set({ config: customConfig }) - } + }, + addNewPanel: (newPanelConfig: PanelConfig) => { + + const newConfig = { ...get().config } + newConfig.panels?.push(newPanelConfig) + + set({ config: newConfig }) + }, })) diff --git a/src/store/DataStore.tsx b/src/store/DataStore.tsx index 9af7e649..8369540f 100644 --- a/src/store/DataStore.tsx +++ b/src/store/DataStore.tsx @@ -6,17 +6,38 @@ interface CollectionMap { } interface DataStoreType { - collections: CollectionMap + collections: CollectionMap, + treeNodes: TreeNode[], initCollection: (url: string) => Promise + initTreeNodes: (newTreeNodes: TreeNode[]) => void, + updateTreeNodes: (newNodes: TreeNode[]) => void, + getCollection: (collectionUrl: string) => Promise, } export const dataStore = create((set, get) => ({ collections: {}, + treeNodes: [], + clickedItemUrl: '', initCollection: async (url: string) => { const collection = await apiRequest(url) const collections: CollectionMap = { ...get().collections } collections[collection.id] = collection set({ collections }) return collection - } + }, + initTreeNodes: (newTreeNodes: TreeNode[]) => { + set({ treeNodes: newTreeNodes }) + }, + + async getCollection(collectionUrl: string): Promise { + if (collectionUrl in get().collections) return get().collections[collectionUrl] + + const collection = await get().initCollection(collectionUrl) + return collection + }, + + updateTreeNodes: (newNodes: TreeNode[]) => { + set({ treeNodes: newNodes }) + }, + })) diff --git a/src/types.d.ts b/src/types.d.ts index d01d16cd..2dab1ce6 100644 --- a/src/types.d.ts +++ b/src/types.d.ts @@ -27,6 +27,7 @@ declare global { type: string id: string } + interface AnnotationContent { type: 'TextualBody' value: string @@ -170,6 +171,7 @@ declare global { item: string manifest?: string } + interface License { id: string notes?: string @@ -208,11 +210,12 @@ declare global { info: string warning: string } + interface PanelConfig { entrypoint: Entrypoint - colors: Colors - manifestIndex: number - itemIndex: number + color?: Colors + manifestIndex?: number + itemIndex?: number } type RangeSelector = { @@ -220,6 +223,7 @@ declare global { startSelector: CssSelector endSelector: CssSelector } + interface Repository { '@context': string label?: string @@ -251,6 +255,7 @@ declare global { title: string type: TitleType } + type TitleType = 'main' | 'sub' type SuccessResponse = { @@ -265,6 +270,15 @@ declare global { } type HttpResponse = SuccessResponse | ErrorResponse -} + interface TreeNode { + id: string, + key: string + label: string, + type: string, + leaf?: boolean, + expanded?: boolean, + children?: TreeNode[] + } +} export {} diff --git a/src/utils/http.ts b/src/utils/http.ts index cf449616..d0429fe2 100644 --- a/src/utils/http.ts +++ b/src/utils/http.ts @@ -2,40 +2,51 @@ const BLOB_CONTENT_TYPES = ['application/pdf', 'image/png', 'image/jpeg', 'audio const TEXT_CONTENT_TYPES = ['text/xhtml+xml', 'text/plain', 'text/html', 'text/css'] export async function request(url: string): Promise> { - try { - const response = await fetch(url) - if (!response.ok) { - return createErrorResponse( - 'Error while loading data from this url ' + url, - response.status - ) - } + try { + const response = await fetch(url) + if (!response.ok) { + return createErrorResponse( + 'Error while loading data from this url ' + url, + response.status + ) + } + + const contentType = response.headers.get('Content-Type') + let data + if (!contentType || TEXT_CONTENT_TYPES.some(type => contentType.includes(type))) { + data = await response.text() + } else if (contentType.includes('application/json')) { + data = await response.json() + } else if (BLOB_CONTENT_TYPES.some(type => contentType.includes(type))) { + data = await response.blob() + } - const contentType = response.headers.get('Content-Type') - let data - if (!contentType || TEXT_CONTENT_TYPES.some(type => contentType.includes(type))) { - data = await response.text() - } else if (contentType.includes('application/json')) { - data = await response.json() - } else if (BLOB_CONTENT_TYPES.some(type => contentType.includes(type))) { - data = await response.blob() + return { + success: true, + data + } + } catch (error) { + return createErrorResponse( + (error instanceof Error) ? error.message : 'An unexpected error occurred', + 500, + ) } +} +function createErrorResponse(message: string = '', code: number = 500): ErrorResponse { return { - success: true, - data + success: false, + message, + code, } - } catch (error) { - return createErrorResponse( - (error instanceof Error) ? error.message : 'An unexpected error occurred', - 500, - ) - } } -function createErrorResponse(message: string = '', code: number = 500): ErrorResponse { - return { success: false, - message, - code, - } +export function isValidUrl(value: string) { + try { + new URL(value) // Try to create a new URL object + return true // If no error occurs, it's a valid URL + // eslint-disable-next-line @typescript-eslint/no-unused-vars + } catch (e) { + return false // If an error occurs, it's not a valid URL + } } diff --git a/src/utils/panel.ts b/src/utils/panel.ts index ea0c33db..3b2c9294 100644 --- a/src/utils/panel.ts +++ b/src/utils/panel.ts @@ -4,7 +4,7 @@ import { request } from '@/utils/http' export function getManifestUrl(documentData: Manifest | Collection, documentType: string, index: number): string { let manifestUrl: string = '' if (documentType === 'collection') { - manifestUrl = documentData?.sequence[index].id + manifestUrl = documentData?.sequence[index].id } else if (documentType === 'manifest') { manifestUrl = documentData?.id @@ -46,3 +46,12 @@ export function getContentTypes(content: Content[]): string[] { return types } + +export function getUniquePanels(panels: PanelConfig[] | undefined) { + if (!panels) return [] + + const uniquePanels = panels.filter((p, index, self) => + index === self.findIndex((panel) => panel.entrypoint.url === p.entrypoint.url)) + + return uniquePanels +} \ No newline at end of file diff --git a/src/utils/tree.ts b/src/utils/tree.ts new file mode 100644 index 00000000..6f45b758 --- /dev/null +++ b/src/utils/tree.ts @@ -0,0 +1,92 @@ +import { request } from '@/utils/http' + + +interface ManifestIndices { + collectionIndex: number, + manifestIndex: number +} + +interface ItemIndices { + collectionUrl: string, + manifestIndex: number, + itemIndex: number +} + +export async function createTree(panels: PanelConfig[]) { + if (!panels || panels.length === 0) return [] + + const nodes: TreeNode[] = [] + + for (let i = 0; i < panels.length; i++) { + await createNode(panels[i].entrypoint.url, i).then((node) => { + nodes.push(node) + }) + } + + return nodes +} + +async function createNode(url: string, key: number) { + const node: TreeNode = {} + const response = await request(url) + if (!response.success) return node + node.key = key.toString() + node.id = url + node.type = 'collection' + node.label = response.data.title[0].title + + return node +} + +export async function getChildren(node: TreeNode): Promise { + const { id } = node + const parentKey = node.key + + const childrenNodes: TreeNode[] = [] + const response = await request(id) + + if (!response.success) return null + const data = response.data + + if (!data.sequence) return null + if (data.sequence.length === 0) return null + + const items: Sequence[] = data.sequence + + for (let i = 0; i < items.length; i++) { + + if (!response.success) continue + + const childNode: TreeNode = { + key: parentKey + '-' + i, + id: items[i].id, + label: items[i].label ?? 'label not found', + type: items[i].type, + expanded: false + } + + if (childNode.type === 'item') childNode.leaf = true + + childrenNodes.push(childNode) + } + + return childrenNodes +} + + +export function getManifestIndices(nodeKey: string): ManifestIndices { + const collectionIndex = parseInt(nodeKey.split('-')[0]) + const manifestIndex = parseInt(nodeKey.split('-')[1]) + + return { collectionIndex: collectionIndex, manifestIndex: manifestIndex } +} + +export function getItemIndices(nodeKey: string, treeNodes: TreeNode[]): ItemIndices { + const collectionIndex = parseInt(nodeKey.split('-')[0]) + + const collectionUrl = treeNodes[collectionIndex].id + const manifestIndex = parseInt(nodeKey.split('-')[1]) + const itemIndex = parseInt(nodeKey.split('-')[2]) + + return { collectionUrl: collectionUrl, manifestIndex: manifestIndex, itemIndex: itemIndex } +} diff --git a/src/utils/treeModal.ts b/src/utils/treeModal.ts new file mode 100644 index 00000000..c23b9feb --- /dev/null +++ b/src/utils/treeModal.ts @@ -0,0 +1,10 @@ +import { isValidUrl, request } from '@/utils/http.ts' + +export async function validateUrlInput(value: string) { + if (!isValidUrl(value)) return false + + const response = await request>(value) + if (!response.success) return false + + return true +} From a90b07c74eaee71617139178190808c34e27ed46 Mon Sep 17 00:00:00 2001 From: orlinmalkja Date: Thu, 30 Jan 2025 17:40:48 +0100 Subject: [PATCH 02/12] chore: remove the validation of collection url in local tree --- src/components/tree-modal/ContentModal.tsx | 8 -------- src/utils/treeModal.ts | 10 ---------- 2 files changed, 18 deletions(-) delete mode 100644 src/utils/treeModal.ts diff --git a/src/components/tree-modal/ContentModal.tsx b/src/components/tree-modal/ContentModal.tsx index f373339a..02343f1f 100644 --- a/src/components/tree-modal/ContentModal.tsx +++ b/src/components/tree-modal/ContentModal.tsx @@ -9,7 +9,6 @@ import InputField from '@/components/base/InputField.tsx' import { ClosePopover } from '@/components/ui/popover' import { createTree, getItemIndices, getManifestIndices, getChildren } from '@/utils/tree' import { getUniquePanels } from '@/utils/panel' -import { validateUrlInput } from '@/utils/treeModal.ts' const ContentModal: FC = () => { @@ -69,13 +68,6 @@ const ContentModal: FC = () => { if (inputValue.current !== '') { collectionUrl = inputValue.current - // should display an error - const isValid = await validateUrlInput(collectionUrl) - - if (!isValid) { - console.error('Please provide a valid URL value for the new collection') - return - } addNewPanel( { diff --git a/src/utils/treeModal.ts b/src/utils/treeModal.ts deleted file mode 100644 index c23b9feb..00000000 --- a/src/utils/treeModal.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { isValidUrl, request } from '@/utils/http.ts' - -export async function validateUrlInput(value: string) { - if (!isValidUrl(value)) return false - - const response = await request>(value) - if (!response.success) return false - - return true -} From a588f4ba4614164d4a4de0f95c4661129ac0b8b0 Mon Sep 17 00:00:00 2001 From: Paul Pestov <10750176+paulpestov@users.noreply.github.com> Date: Thu, 30 Jan 2025 21:09:27 +0100 Subject: [PATCH 03/12] chore: add indentation rule for ESLint --- eslint.config.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/eslint.config.js b/eslint.config.js index 3c7027b9..45436f70 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -32,7 +32,8 @@ export default tseslint.config( }, ], semi: ["error", "never"], - "object-curly-spacing": ["error", "always"] + "object-curly-spacing": ["error", "always"], + "indent": ["error", 2] }, } ); From ccdf2677e90ea5ea8f2df24c8974dda7cff3e899 Mon Sep 17 00:00:00 2001 From: orlinmalkja Date: Fri, 31 Jan 2025 10:29:41 +0100 Subject: [PATCH 04/12] refactor: add 'collectionsUrls' as argument to createTree() instead of 'panels' --- src/components/LocalTreeModal.tsx | 28 +++--- src/components/tree-modal/ContentModal.tsx | 30 +++--- src/store/DataStore.tsx | 12 +-- src/types.d.ts | 4 + src/utils/tree.ts | 106 ++++++++++----------- 5 files changed, 87 insertions(+), 93 deletions(-) diff --git a/src/components/LocalTreeModal.tsx b/src/components/LocalTreeModal.tsx index a91389fc..0e786040 100644 --- a/src/components/LocalTreeModal.tsx +++ b/src/components/LocalTreeModal.tsx @@ -4,26 +4,26 @@ import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover import ContentModal from '@/components/tree-modal/ContentModal' interface LocalTreeProps { - TriggerButton: ReactNode + TriggerButton: ReactNode } const LocalTreeModal: FC = ({ 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 + // 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 + // function of this component: create a new Panel - return
- - - {TriggerButton} - - - - - -
+ return
+ + + {TriggerButton} + + + + + +
} -export default LocalTreeModal \ No newline at end of file +export default LocalTreeModal diff --git a/src/components/tree-modal/ContentModal.tsx b/src/components/tree-modal/ContentModal.tsx index 02343f1f..a7dbecc4 100644 --- a/src/components/tree-modal/ContentModal.tsx +++ b/src/components/tree-modal/ContentModal.tsx @@ -8,20 +8,17 @@ 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' -import { getUniquePanels } from '@/utils/panel' const ContentModal: FC = () => { - const panels = configStore(state => state.config.panels) const addNewPanel = configStore(state => state.addNewPanel) - const initTreeNodes = dataStore(state => state.initTreeNodes) - const updateTreeNodes = dataStore(state => state.updateTreeNodes) + 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 [uniquePanels] = useState(getUniquePanels(panels)) - const inputValue = useRef('') const clickedItemUrl = useRef('') @@ -34,15 +31,17 @@ const ContentModal: FC = () => { useEffect(() => { - async function initTree(panels?: PanelConfig[]) { - if (!panels) return + async function initTree(collections: CollectionMap) { + + const collectionsUrls = Object.keys(collections) + if (collectionsUrls.length === 0) return - const nodes = await createTree(panels) - initTreeNodes(nodes) + const nodes = await createTree(collectionsUrls) + setTreeNodes(nodes) } - initTree(uniquePanels) - }, [uniquePanels]) + initTree(collections) + }, [collections]) function updateInputValue(newValue: string) { @@ -79,6 +78,8 @@ const ContentModal: FC = () => { itemIndex: 0 } ) + + await initCollection(collectionUrl) } } @@ -103,7 +104,7 @@ const ContentModal: FC = () => { updatedTree[collectionIndex].children[manifestIndex].expanded = true } - updateTreeNodes(updatedTree) + setTreeNodes(updatedTree) } async function onCollapse(node: TreeNode) { @@ -119,7 +120,7 @@ const ContentModal: FC = () => { updatedTree[collectionIndex].children[manifestIndex].expanded = false } - updateTreeNodes(updatedTree) + setTreeNodes(updatedTree) } function onSelect(node: TreeNode) { @@ -148,6 +149,5 @@ const ContentModal: FC = () => {
} -// export default ContentModal diff --git a/src/store/DataStore.tsx b/src/store/DataStore.tsx index 8369540f..44fb6a60 100644 --- a/src/store/DataStore.tsx +++ b/src/store/DataStore.tsx @@ -1,16 +1,12 @@ import { create } from 'zustand' import { apiRequest } from '@/utils/api.ts' -interface CollectionMap { - [key: string]: Collection -} interface DataStoreType { collections: CollectionMap, treeNodes: TreeNode[], initCollection: (url: string) => Promise - initTreeNodes: (newTreeNodes: TreeNode[]) => void, - updateTreeNodes: (newNodes: TreeNode[]) => void, + setTreeNodes: (newTreeNodes: TreeNode[]) => void, getCollection: (collectionUrl: string) => Promise, } @@ -25,7 +21,7 @@ export const dataStore = create((set, get) => ({ set({ collections }) return collection }, - initTreeNodes: (newTreeNodes: TreeNode[]) => { + setTreeNodes: (newTreeNodes: TreeNode[]) => { set({ treeNodes: newTreeNodes }) }, @@ -36,8 +32,4 @@ export const dataStore = create((set, get) => ({ return collection }, - updateTreeNodes: (newNodes: TreeNode[]) => { - set({ treeNodes: newNodes }) - }, - })) diff --git a/src/types.d.ts b/src/types.d.ts index 2dab1ce6..39125088 100644 --- a/src/types.d.ts +++ b/src/types.d.ts @@ -280,5 +280,9 @@ declare global { expanded?: boolean, children?: TreeNode[] } + + interface CollectionMap { + [key: string]: Collection + } } export {} diff --git a/src/utils/tree.ts b/src/utils/tree.ts index 6f45b758..0349c626 100644 --- a/src/utils/tree.ts +++ b/src/utils/tree.ts @@ -2,91 +2,89 @@ import { request } from '@/utils/http' interface ManifestIndices { - collectionIndex: number, - manifestIndex: number + collectionIndex: number, + manifestIndex: number } interface ItemIndices { - collectionUrl: string, - manifestIndex: number, - itemIndex: number + collectionUrl: string, + manifestIndex: number, + itemIndex: number } -export async function createTree(panels: PanelConfig[]) { - if (!panels || panels.length === 0) return [] +export async function createTree(collectionsUrls: string[]) { + const nodes: TreeNode[] = [] - const nodes: TreeNode[] = [] + for (let i = 0; i < collectionsUrls.length; i++) { + await createNode(collectionsUrls[i], i).then((node) => { + nodes.push(node) + }) + } - for (let i = 0; i < panels.length; i++) { - await createNode(panels[i].entrypoint.url, i).then((node) => { - nodes.push(node) - }) - } - - return nodes + return nodes } async function createNode(url: string, key: number) { - const node: TreeNode = {} - const response = await request(url) - if (!response.success) return node - node.key = key.toString() - node.id = url - node.type = 'collection' - node.label = response.data.title[0].title - - return node + const node: TreeNode = {} + const response = await request(url) + if (!response.success) return node + node.key = key.toString() + node.id = url + node.type = 'collection' + node.label = response.data.title[0].title + + return node } export async function getChildren(node: TreeNode): Promise { - const { id } = node - const parentKey = node.key + const { id } = node + const parentKey = node.key - const childrenNodes: TreeNode[] = [] - const response = await request(id) + const childrenNodes: TreeNode[] = [] + const response = await request(id) - if (!response.success) return null - const data = response.data + if (!response.success) return null + const data = response.data - if (!data.sequence) return null - if (data.sequence.length === 0) return null + if (!data.sequence) return null + if (data.sequence.length === 0) return null - const items: Sequence[] = data.sequence + const items: Sequence[] = data.sequence - for (let i = 0; i < items.length; i++) { + for (let i = 0; i < items.length; i++) { - if (!response.success) continue + if (!response.success) continue - const childNode: TreeNode = { - key: parentKey + '-' + i, - id: items[i].id, - label: items[i].label ?? 'label not found', - type: items[i].type, - expanded: false - } + const childNode: TreeNode = { + key: parentKey + '-' + i, + id: items[i].id, + label: items[i].label ?? 'label not found', + type: items[i].type, + expanded: false + } - if (childNode.type === 'item') childNode.leaf = true + if (childNode.type === 'item') childNode.leaf = true - childrenNodes.push(childNode) - } + childrenNodes.push(childNode) + } - return childrenNodes + return childrenNodes } export function getManifestIndices(nodeKey: string): ManifestIndices { - const collectionIndex = parseInt(nodeKey.split('-')[0]) - const manifestIndex = parseInt(nodeKey.split('-')[1]) + const collectionIndex = parseInt(nodeKey.split('-')[0]) + const manifestIndex = parseInt(nodeKey.split('-')[1]) - return { collectionIndex: collectionIndex, manifestIndex: manifestIndex } + return { collectionIndex: collectionIndex, manifestIndex: manifestIndex } } export function getItemIndices(nodeKey: string, treeNodes: TreeNode[]): ItemIndices { - const collectionIndex = parseInt(nodeKey.split('-')[0]) + const collectionIndex = parseInt(nodeKey.split('-')[0]) - const collectionUrl = treeNodes[collectionIndex].id - const manifestIndex = parseInt(nodeKey.split('-')[1]) - const itemIndex = parseInt(nodeKey.split('-')[2]) + const collectionUrl = treeNodes[collectionIndex].id + const manifestIndex = parseInt(nodeKey.split('-')[1]) + const itemIndex = parseInt(nodeKey.split('-')[2]) - return { collectionUrl: collectionUrl, manifestIndex: manifestIndex, itemIndex: itemIndex } + return { collectionUrl: collectionUrl, manifestIndex: manifestIndex, itemIndex: itemIndex } } From 2b5cefa62c867d1355b3d350e12aff5e39961d9f Mon Sep 17 00:00:00 2001 From: orlinmalkja Date: Fri, 31 Jan 2025 15:24:38 +0100 Subject: [PATCH 05/12] refactor: add getNodeIndices() and remove getManifestIndices() and getItemIndices() --- src/components/Tree.tsx | 4 +-- src/components/tree-modal/ContentModal.tsx | 20 ++++++++------- src/store/ConfigStore.tsx | 1 - src/store/DataStore.tsx | 1 - src/utils/tree.ts | 29 ++-------------------- 5 files changed, 14 insertions(+), 41 deletions(-) diff --git a/src/components/Tree.tsx b/src/components/Tree.tsx index 5f188c83..6408e5b8 100644 --- a/src/components/Tree.tsx +++ b/src/components/Tree.tsx @@ -15,7 +15,6 @@ interface TreeProps { onCollapse(node: TreeNode): void, } -// ({ nodes, onSelect, onExpand }) const Tree: FC = ({ nodes, onSelect, onExpand, onCollapse }) => { @@ -32,8 +31,7 @@ const Tree: FC = ({ nodes, onSelect, onExpand, onCollapse }) => { return
- + {tree}
diff --git a/src/components/tree-modal/ContentModal.tsx b/src/components/tree-modal/ContentModal.tsx index a7dbecc4..786bbd81 100644 --- a/src/components/tree-modal/ContentModal.tsx +++ b/src/components/tree-modal/ContentModal.tsx @@ -4,10 +4,10 @@ import { configStore } from '@/store/ConfigStore' import { dataStore } from '@/store/DataStore' -import TreeView from '@/components/Tree.tsx' +import Tree 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' +import { createTree, getChildren, getNodeIndices } from '@/utils/tree' const ContentModal: FC = () => { @@ -89,13 +89,13 @@ const ContentModal: FC = () => { 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) + const [collectionIndex] = getNodeIndices(node.key) + 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 [collectionIndex, manifestIndex] = getNodeIndices(node.key) const manifestNode = { ...updatedTree[collectionIndex].children[manifestIndex] } if (!('children' in manifestNode)) manifestNode['children'] = await getChildren(node) @@ -112,10 +112,10 @@ const ContentModal: FC = () => { const updatedTree = [...nodes] if (type === 'collection') { - const collectionIndex = nodes.findIndex((n) => (n.id === node.id)) + const [collectionIndex] = getNodeIndices(node.key) updatedTree[collectionIndex].expanded = false } else if (type === 'manifest') { - const { collectionIndex, manifestIndex } = getManifestIndices(node.key) + const [collectionIndex, manifestIndex] = getNodeIndices(node.key) updatedTree[collectionIndex].children[manifestIndex].expanded = false } @@ -126,7 +126,9 @@ const ContentModal: FC = () => { function onSelect(node: TreeNode) { const { id } = node clickedItemUrl.current = id - selectedItemIndices.current = getItemIndices(node.key, nodes) + const [collectionIndex, manifestIndex, itemIndex] = getNodeIndices(node.key) + const collectionUrl = nodes[collectionIndex].id + selectedItemIndices.current = { collectionUrl: collectionUrl, manifestIndex: manifestIndex, itemIndex: itemIndex } } @@ -137,7 +139,7 @@ const ContentModal: FC = () => { Or choose: - +
void, diff --git a/src/store/DataStore.tsx b/src/store/DataStore.tsx index 44fb6a60..41125f17 100644 --- a/src/store/DataStore.tsx +++ b/src/store/DataStore.tsx @@ -13,7 +13,6 @@ interface DataStoreType { export const dataStore = create((set, get) => ({ collections: {}, treeNodes: [], - clickedItemUrl: '', initCollection: async (url: string) => { const collection = await apiRequest(url) const collections: CollectionMap = { ...get().collections } diff --git a/src/utils/tree.ts b/src/utils/tree.ts index 0349c626..e94e54af 100644 --- a/src/utils/tree.ts +++ b/src/utils/tree.ts @@ -1,17 +1,5 @@ import { request } from '@/utils/http' - -interface ManifestIndices { - collectionIndex: number, - manifestIndex: number -} - -interface ItemIndices { - collectionUrl: string, - manifestIndex: number, - itemIndex: number -} - export async function createTree(collectionsUrls: string[]) { const nodes: TreeNode[] = [] @@ -72,19 +60,6 @@ export async function getChildren(node: TreeNode): Promise { } -export function getManifestIndices(nodeKey: string): ManifestIndices { - const collectionIndex = parseInt(nodeKey.split('-')[0]) - const manifestIndex = parseInt(nodeKey.split('-')[1]) - - return { collectionIndex: collectionIndex, manifestIndex: manifestIndex } -} - -export function getItemIndices(nodeKey: string, treeNodes: TreeNode[]): ItemIndices { - const collectionIndex = parseInt(nodeKey.split('-')[0]) - - const collectionUrl = treeNodes[collectionIndex].id - const manifestIndex = parseInt(nodeKey.split('-')[1]) - const itemIndex = parseInt(nodeKey.split('-')[2]) - - return { collectionUrl: collectionUrl, manifestIndex: manifestIndex, itemIndex: itemIndex } +export function getNodeIndices(nodeKey: string) { + return nodeKey.split('-').map((index) => parseInt(index, 10)) } From f7ad3f412e21515387a39da741f7468ac7904539 Mon Sep 17 00:00:00 2001 From: orlinmalkja Date: Fri, 31 Jan 2025 17:05:57 +0100 Subject: [PATCH 06/12] chore: move some popover component styles to its file --- src/components/LocalTreeModal.tsx | 8 +--- src/components/ui/popover.tsx | 20 ++++----- src/utils/http.ts | 71 ++++++++++++++----------------- 3 files changed, 43 insertions(+), 56 deletions(-) diff --git a/src/components/LocalTreeModal.tsx b/src/components/LocalTreeModal.tsx index 0e786040..faa4d909 100644 --- a/src/components/LocalTreeModal.tsx +++ b/src/components/LocalTreeModal.tsx @@ -9,17 +9,13 @@ interface LocalTreeProps { const LocalTreeModal: FC = ({ 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
- + {TriggerButton} - + diff --git a/src/components/ui/popover.tsx b/src/components/ui/popover.tsx index 3da98894..59a52335 100644 --- a/src/components/ui/popover.tsx +++ b/src/components/ui/popover.tsx @@ -11,16 +11,16 @@ const PopoverContent = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef >(({ className, align = 'center', sideOffset = 4, ...props }, ref) => ( - + )) PopoverContent.displayName = PopoverPrimitive.Content.displayName const ClosePopover = PopoverPrimitive.Close diff --git a/src/utils/http.ts b/src/utils/http.ts index d0429fe2..1653467a 100644 --- a/src/utils/http.ts +++ b/src/utils/http.ts @@ -2,51 +2,42 @@ const BLOB_CONTENT_TYPES = ['application/pdf', 'image/png', 'image/jpeg', 'audio const TEXT_CONTENT_TYPES = ['text/xhtml+xml', 'text/plain', 'text/html', 'text/css'] export async function request(url: string): Promise> { - try { - const response = await fetch(url) - if (!response.ok) { - return createErrorResponse( - 'Error while loading data from this url ' + url, - response.status - ) - } - - const contentType = response.headers.get('Content-Type') - let data - if (!contentType || TEXT_CONTENT_TYPES.some(type => contentType.includes(type))) { - data = await response.text() - } else if (contentType.includes('application/json')) { - data = await response.json() - } else if (BLOB_CONTENT_TYPES.some(type => contentType.includes(type))) { - data = await response.blob() - } + try { + const response = await fetch(url) + if (!response.ok) { + return createErrorResponse( + 'Error while loading data from this url ' + url, + response.status + ) + } - return { - success: true, - data - } - } catch (error) { - return createErrorResponse( - (error instanceof Error) ? error.message : 'An unexpected error occurred', - 500, - ) + const contentType = response.headers.get('Content-Type') + let data + if (!contentType || TEXT_CONTENT_TYPES.some(type => contentType.includes(type))) { + data = await response.text() + } else if (contentType.includes('application/json')) { + data = await response.json() + } else if (BLOB_CONTENT_TYPES.some(type => contentType.includes(type))) { + data = await response.blob() } -} -function createErrorResponse(message: string = '', code: number = 500): ErrorResponse { return { - success: false, - message, - code, + success: true, + data } + } catch (error) { + return createErrorResponse( + (error instanceof Error) ? error.message : 'An unexpected error occurred', + 500, + ) + } } -export function isValidUrl(value: string) { - try { - new URL(value) // Try to create a new URL object - return true // If no error occurs, it's a valid URL - // eslint-disable-next-line @typescript-eslint/no-unused-vars - } catch (e) { - return false // If an error occurs, it's not a valid URL - } +function createErrorResponse(message: string = '', code: number = 500): ErrorResponse { + return { + success: false, + message, + code, + } } + From 7f9b8a459f70575195d12506857681ac7afc579a Mon Sep 17 00:00:00 2001 From: orlinmalkja Date: Fri, 31 Jan 2025 17:14:35 +0100 Subject: [PATCH 07/12] style: rename LocalTreeModal to TreeSelectionModal --- src/components/TopBar.tsx | 23 ++++++++++--------- ...alTreeModal.tsx => TreeSelectionModal.tsx} | 4 ++-- 2 files changed, 14 insertions(+), 13 deletions(-) rename src/components/{LocalTreeModal.tsx => TreeSelectionModal.tsx} (81%) diff --git a/src/components/TopBar.tsx b/src/components/TopBar.tsx index f4e200fb..1ed26097 100644 --- a/src/components/TopBar.tsx +++ b/src/components/TopBar.tsx @@ -1,20 +1,21 @@ import { FC } from 'react' -import LocalTreeModal from '@/components/LocalTreeModal' +import TreeSelectionModal from '@/components/TreeSelectionModal' const TopBar: FC = () => { - - const addButton = - + + const addButton = + New - - + + + + return
+ +
-return
- -
- } -export default TopBar \ No newline at end of file +export default TopBar diff --git a/src/components/LocalTreeModal.tsx b/src/components/TreeSelectionModal.tsx similarity index 81% rename from src/components/LocalTreeModal.tsx rename to src/components/TreeSelectionModal.tsx index faa4d909..083aa818 100644 --- a/src/components/LocalTreeModal.tsx +++ b/src/components/TreeSelectionModal.tsx @@ -7,7 +7,7 @@ interface LocalTreeProps { TriggerButton: ReactNode } -const LocalTreeModal: FC = ({ TriggerButton }) => { +const TreeSelectionModal: FC = ({ TriggerButton }) => { return
@@ -22,4 +22,4 @@ const LocalTreeModal: FC = ({ TriggerButton }) => {
} -export default LocalTreeModal +export default TreeSelectionModal From 266209706ce3a993be79872da0b3bad2562de585 Mon Sep 17 00:00:00 2001 From: orlinmalkja Date: Fri, 31 Jan 2025 17:19:06 +0100 Subject: [PATCH 08/12] style: component name changes --- src/components/TreeSelectionModal.tsx | 4 ++-- .../{ContentModal.tsx => TreeSelectionModalContent.tsx} | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) rename src/components/tree-modal/{ContentModal.tsx => TreeSelectionModalContent.tsx} (97%) diff --git a/src/components/TreeSelectionModal.tsx b/src/components/TreeSelectionModal.tsx index 083aa818..f38cbcab 100644 --- a/src/components/TreeSelectionModal.tsx +++ b/src/components/TreeSelectionModal.tsx @@ -1,7 +1,7 @@ import { FC, ReactNode } from 'react' import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover' -import ContentModal from '@/components/tree-modal/ContentModal' +import TreeSelectionModalContent from '@/components/tree-modal/TreeSelectionModalContent.tsx' interface LocalTreeProps { TriggerButton: ReactNode @@ -16,7 +16,7 @@ const TreeSelectionModal: FC = ({ TriggerButton }) => { {TriggerButton} - +
diff --git a/src/components/tree-modal/ContentModal.tsx b/src/components/tree-modal/TreeSelectionModalContent.tsx similarity index 97% rename from src/components/tree-modal/ContentModal.tsx rename to src/components/tree-modal/TreeSelectionModalContent.tsx index 786bbd81..c84e56a9 100644 --- a/src/components/tree-modal/ContentModal.tsx +++ b/src/components/tree-modal/TreeSelectionModalContent.tsx @@ -1,4 +1,4 @@ -import { FC, useEffect, useRef, useState } from 'react' +import { FC, useEffect, useRef } from 'react' import { configStore } from '@/store/ConfigStore' import { dataStore } from '@/store/DataStore' @@ -10,7 +10,7 @@ import { ClosePopover } from '@/components/ui/popover' import { createTree, getChildren, getNodeIndices } from '@/utils/tree' -const ContentModal: FC = () => { +const TreeSelectionModalContent: FC = () => { const addNewPanel = configStore(state => state.addNewPanel) @@ -152,4 +152,4 @@ const ContentModal: FC = () => { } -export default ContentModal +export default TreeSelectionModalContent From 8ee9c03d5778e6efe327d7900ece367ecde1b44f Mon Sep 17 00:00:00 2001 From: orlinmalkja Date: Fri, 31 Jan 2025 17:34:26 +0100 Subject: [PATCH 09/12] style: minor changes --- src/components/Tree.tsx | 5 +---- src/components/tree/TreeNode.tsx | 7 +++---- src/contexts/TreeContext.tsx | 14 +++++++------- 3 files changed, 11 insertions(+), 15 deletions(-) diff --git a/src/components/Tree.tsx b/src/components/Tree.tsx index 6408e5b8..b02a9965 100644 --- a/src/components/Tree.tsx +++ b/src/components/Tree.tsx @@ -21,10 +21,7 @@ const Tree: FC = ({ nodes, onSelect, onExpand, onCollapse }) => { const tree = nodes.length > 0 && nodes.map((collection, i) => ( -
+
)) diff --git a/src/components/tree/TreeNode.tsx b/src/components/tree/TreeNode.tsx index a7093663..fc07765a 100644 --- a/src/components/tree/TreeNode.tsx +++ b/src/components/tree/TreeNode.tsx @@ -15,14 +15,13 @@ const TreeNode: FC = ({ node }) => { function handleNodeClick() { onClick(node) - // add class t-bg-primary } if ('children' in node && node.expanded) return
handleNodeClick()}> {node.label}
+ onClick={() => handleNodeClick()}> {node.label}
{node.children?.map((item: TreeNode, i) => (
    @@ -31,8 +30,8 @@ const TreeNode: FC = ({ node }) => {
return
handleNodeClick()}>{node.label}
+ 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}
} export default TreeNode diff --git a/src/contexts/TreeContext.tsx b/src/contexts/TreeContext.tsx index 3cb26122..89596552 100644 --- a/src/contexts/TreeContext.tsx +++ b/src/contexts/TreeContext.tsx @@ -3,23 +3,23 @@ import { ReactNode, createContext, useContext, FC } from 'react' const TreeContext = createContext(undefined) interface TreeType { - onClick(node: TreeNode): void, + onClick(node: TreeNode): void onSelect(node: TreeNode): void - onExpand(node: TreeNode): void, + onExpand(node: TreeNode): void - onCollapse(node: TreeNode): void, + onCollapse(node: TreeNode): void } interface TreeProviderProps { - children?: ReactNode, + children?: ReactNode - onSelect(node: TreeNode): void, + onSelect(node: TreeNode): void - onExpand(node: TreeNode): void, + onExpand(node: TreeNode): void - onCollapse(node: TreeNode): void, + onCollapse(node: TreeNode): void } const TreeProvider: FC = ({ children, onSelect, onExpand, onCollapse }) => { From 7f38f75135c742eddc275f44ee5c32300f25ff4c Mon Sep 17 00:00:00 2001 From: orlinmalkja Date: Fri, 31 Jan 2025 17:37:05 +0100 Subject: [PATCH 10/12] style: minor changes --- src/components/base/InputField.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/components/base/InputField.tsx b/src/components/base/InputField.tsx index 0c19a5b4..b4310bda 100644 --- a/src/components/base/InputField.tsx +++ b/src/components/base/InputField.tsx @@ -6,10 +6,10 @@ interface InputFieldProps { const InputField: FC = ({ updateInputValue }) => { - return <> - updateInputValue(e.target.value)}/> - + return <> + updateInputValue(e.target.value)}/> + } export default InputField From b423d2c07fd0185f86685f94bab27165dc746920 Mon Sep 17 00:00:00 2001 From: orlinmalkja Date: Fri, 31 Jan 2025 18:37:55 +0100 Subject: [PATCH 11/12] refactor: add changes which remove typescript complaints on 'onExpand' and 'onCollapse' --- .../tree-modal/TreeSelectionModalContent.tsx | 25 ++++++++++++++----- 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/src/components/tree-modal/TreeSelectionModalContent.tsx b/src/components/tree-modal/TreeSelectionModalContent.tsx index c84e56a9..a7cf5b75 100644 --- a/src/components/tree-modal/TreeSelectionModalContent.tsx +++ b/src/components/tree-modal/TreeSelectionModalContent.tsx @@ -90,18 +90,27 @@ const TreeSelectionModalContent: FC = () => { if (type === 'collection') { const [collectionIndex] = getNodeIndices(node.key) - if (!('children' in updatedTree[collectionIndex])) updatedTree[collectionIndex].children = await getChildren(node) + if (!('children' in updatedTree[collectionIndex])) { + const childrenNodes = await getChildren(node) + if (childrenNodes.length === 0) return + + updatedTree[collectionIndex].children = childrenNodes + } updatedTree[collectionIndex].expanded = true } else if (type === 'manifest') { const [collectionIndex, manifestIndex] = getNodeIndices(node.key) - const manifestNode = { ...updatedTree[collectionIndex].children[manifestIndex] } + const manifests = updatedTree[collectionIndex].children + if (!manifests) return + if (manifests.length === 0) return - if (!('children' in manifestNode)) manifestNode['children'] = await getChildren(node) + const manifestChildren = await getChildren(node) + if (manifestChildren.length === 0) return + manifests[manifestIndex].children = manifestChildren + manifests[manifestIndex].expanded = true - updatedTree[collectionIndex].children[manifestIndex] = { ...manifestNode } - updatedTree[collectionIndex].children[manifestIndex].expanded = true + updatedTree[collectionIndex].children = [...manifests] } setTreeNodes(updatedTree) @@ -117,7 +126,11 @@ const TreeSelectionModalContent: FC = () => { } else if (type === 'manifest') { const [collectionIndex, manifestIndex] = getNodeIndices(node.key) - updatedTree[collectionIndex].children[manifestIndex].expanded = false + const manifests = updatedTree[collectionIndex].children + if (!manifests) return + manifests[manifestIndex].expanded = false + + updatedTree[collectionIndex].children = [...manifests] } setTreeNodes(updatedTree) From d631cde6cf37210acfc6fda3ea9357e8ca8bb644 Mon Sep 17 00:00:00 2001 From: orlinmalkja Date: Fri, 31 Jan 2025 18:42:54 +0100 Subject: [PATCH 12/12] refactor: add changes in createNode() and getChildren() which remove Typescript complaints --- src/utils/tree.ts | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/utils/tree.ts b/src/utils/tree.ts index e94e54af..601b0ead 100644 --- a/src/utils/tree.ts +++ b/src/utils/tree.ts @@ -13,9 +13,11 @@ export async function createTree(collectionsUrls: string[]) { } async function createNode(url: string, key: number) { - const node: TreeNode = {} + const node: TreeNode = { key: '', id: '', type: '', label: '' } + const response = await request(url) if (!response.success) return node + node.key = key.toString() node.id = url node.type = 'collection' @@ -24,18 +26,18 @@ async function createNode(url: string, key: number) { return node } -export async function getChildren(node: TreeNode): Promise { +export async function getChildren(node: TreeNode): Promise { const { id } = node const parentKey = node.key const childrenNodes: TreeNode[] = [] - const response = await request(id) + const response = await request(id) - if (!response.success) return null + if (!response.success) return childrenNodes const data = response.data - if (!data.sequence) return null - if (data.sequence.length === 0) return null + if (!data.sequence) return childrenNodes + if (data.sequence.length === 0) return childrenNodes const items: Sequence[] = data.sequence