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
21 changes: 21 additions & 0 deletions src/main/frontend/app/components/load-error.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import clsx from 'clsx'
import Button from '~/components/inputs/button'

type LoadErrorProperties = {
message?: string
onRetry: () => void
className?: string
}

export default function LoadError({
message = 'Something went wrong while loading.',
onRetry,
className,
}: Readonly<LoadErrorProperties>) {
return (
<div className={clsx('flex flex-col items-center justify-center gap-3 px-4 text-center', className)}>
<p className="text-foreground-muted text-sm whitespace-pre-line">{message}</p>
<Button onClick={onRetry}>Retry</Button>
</div>
)
}
23 changes: 19 additions & 4 deletions src/main/frontend/app/providers/frankconfig-xsd-provider.tsx
Original file line number Diff line number Diff line change
@@ -1,27 +1,42 @@
import { createContext, useContext, useEffect, useMemo, useState, type ReactNode } from 'react'
import { createContext, useCallback, useContext, useEffect, useMemo, useState, type ReactNode } from 'react'
import { fetchFrankConfigXsd } from '~/services/xsd-service'
import { parseXsd } from '~/utils/xsd-utils'
import { logApiWarning } from '~/utils/logger'

type FrankConfigXsdContextValue = {
xsdContent: string | null
xsdDoc: Document | null
error: Error | null
refetch: () => void
}

const FrankConfigXsdContext = createContext<FrankConfigXsdContextValue | null>(null)

export function FrankConfigXsdProvider({ children }: { children: ReactNode }) {
const [xsdContent, setXsdContent] = useState<string | null>(null)
const [error, setError] = useState<Error | null>(null)

useEffect(() => {
const load = useCallback(() => {
setError(null)
fetchFrankConfigXsd()
.then(setXsdContent)
.catch((error) => logApiWarning('Failed to load FrankConfig XSD:', error as Error))
.catch((error_) => {
setError(error_ as Error)
logApiWarning('Failed to load FrankConfig XSD:', error_ as Error)
})
}, [])

useEffect(() => {
load()
}, [load])

const xsdDoc = useMemo(() => (xsdContent ? parseXsd(xsdContent) : null), [xsdContent])

return <FrankConfigXsdContext.Provider value={{ xsdContent, xsdDoc }}>{children}</FrankConfigXsdContext.Provider>
return (
<FrankConfigXsdContext.Provider value={{ xsdContent, xsdDoc, error, refetch: load }}>
{children}
</FrankConfigXsdContext.Provider>
)
}

export function useFrankConfigXsd(): FrankConfigXsdContextValue {
Expand Down
36 changes: 32 additions & 4 deletions src/main/frontend/app/routes/studio/context/studio-context.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,25 @@
import useNodeContextStore from '~/stores/node-context-store'
import { useState, useMemo } from 'react'
import { useEffect, useState, useMemo } from 'react'
import SortedElements from '~/routes/studio/context/sorted-elements'
import Search from '~/components/search/search'
import { useProjectStore } from '~/stores/project-store'
import type { ElementDetails } from '@frankframework/doc-library-core'
import { useFFDoc } from '@frankframework/doc-library-react'
import LoadingSpinner from '~/components/loading-spinner'
import LoadError from '~/components/load-error'
import { useFrankConfigXsd } from '~/providers/frankconfig-xsd-provider'
import { getChildrenForType, getFirstLevelElementsForType } from '~/utils/xsd-utils'
import { DEFAULT_ELEMENTS, NON_CANVAS_ELEMENTS } from './palette-config'

const ROOT_TYPES = ['PipelineType', 'ReceiverType']
const PALETTE_LOAD_TIMEOUT_MS = 15_000

export default function StudioContext() {
const { setDraggedName, setAllowedOnCanvas } = useNodeContextStore((state) => state)
const [searchTerm, setSearchTerm] = useState('')
const project = useProjectStore((state) => state.project)
const { filters, elements, isLoading } = useFFDoc()
const { xsdDoc } = useFrankConfigXsd()
const { filters, elements, isLoading, error: ffDocError, refetch: refetchFFDoc } = useFFDoc()
const { xsdDoc, error: xsdError, refetch: refetchXsd } = useFrankConfigXsd()

const { allowed, elementsAllowedOnCanvas } = useMemo(() => {
if (!xsdDoc) return { allowed: null, elementsAllowedOnCanvas: [] }
Expand All @@ -33,7 +35,33 @@ export default function StudioContext() {
}
}, [xsdDoc])

if (isLoading || !elements || allowed === null) {
const isReady = !isLoading && !!elements && allowed !== null
const hasError = ffDocError !== null || xsdError !== null

const [timedOut, setTimedOut] = useState(false)

useEffect(() => {
if (isReady || hasError) return

const timer = setTimeout(() => setTimedOut(true), PALETTE_LOAD_TIMEOUT_MS)
return () => clearTimeout(timer)
}, [isReady, hasError])

const retry = () => {
setTimedOut(false)
refetchFFDoc()
refetchXsd()
}

if (!isReady) {
if (hasError || timedOut) {
return (
<div className="flex h-full items-center justify-center">
<LoadError message={"Couldn't load the palette.\nCheck your connection and try again."} onRetry={retry} />
</div>
)
}

return (
<div className="flex h-full items-center justify-center">
<LoadingSpinner message="Loading palette..." />
Expand Down
Loading