(fileId: string, name: string) => T | undefined
setFileAttribute: (fileId: string, name: string, value: unknown) => void
onCancel: (fileId: string) => void
onOpenPreview?: (fileId: string) => void
onThumbnailImageError?: (fileId: string) => void
}
/**
* Render a filename with optional middle-truncation. The split-or-not decision
* lives in `splitFilenameForTruncation` (shared with React) so the threshold
* matches across both runtimes.
*/
const renderTruncatedFilename = (filename: string, truncateTail?: number) => {
const split = splitFilenameForTruncation(filename, truncateTail)
if (!split) return html`${filename}`
return html`${split.head}${split.tail}`
}
const renderTitleWithSize = (filename: string, fileSize: string, truncateTail?: number) => html`
${renderTruncatedFilename(filename, truncateTail)}${fileSize
? html` (${fileSize})`
: null}
`
const getProgressState = (progress: TTransferProgress): 'in-progress' | 'error' | 'idle' => {
if (typeof progress === 'number') return 'in-progress'
if (progress === 'error') return 'error'
return 'idle'
}
const renderLoadingText = (loadingText?: string) =>
html`${loadingText ?? 'Laster opp...'}`
const createOperationContext = ({
operationId,
file,
inputName,
disabled,
isActive,
onActivateOperation,
onCloseOperation,
getFileAttribute,
setFileAttribute,
}: {
operationId: string
file: FileItem
inputName: string
disabled: boolean
isActive: boolean
onActivateOperation: (fileId: string, operationId: string) => void
onCloseOperation: (fileId: string) => void
getFileAttribute: (fileId: string, name: string) => T | undefined
setFileAttribute: (fileId: string, name: string, value: unknown) => void
}): TQueueOperationContext => ({
file,
inputName,
disabled,
isActive,
activate: () => onActivateOperation(file.fileId, operationId),
close: () => onCloseOperation(file.fileId),
getAttribute: (name: string) => getFileAttribute(file.fileId, name),
setAttribute: (name: string, value: unknown) => setFileAttribute(file.fileId, name, value),
})
const renderTransferInProgress = ({
file,
fileSize,
disabled,
transferProgress,
transferShowProgress,
loadingText,
truncateTail,
onCancel,
iconName = 'document-text',
}: {
file: FileItem
fileSize: string
disabled: boolean
transferProgress: number
transferShowProgress?: boolean
loadingText?: string
truncateTail?: number
onCancel: (fileId: string) => void
iconName?: string
}) => {
const filename = getDisplayFilename(file)
const showProgressBar = transferShowProgress ?? transferProgress !== 0
return html`
${renderTitleWithSize(filename, fileSize, truncateTail)}
${showProgressBar
? html`
${Math.round(transferProgress * 100)}%
`
: renderLoadingText(loadingText)}
`
}
const renderTransferError = ({
file,
fileSize,
disabled,
transferShowProgress,
transferLastProgress,
transferErrorMessage,
truncateTail,
onCancel,
}: {
file: FileItem
fileSize: string
disabled: boolean
transferShowProgress?: boolean
transferLastProgress?: number
transferErrorMessage?: string
truncateTail?: number
onCancel: (fileId: string) => void
}) => {
const filename = getDisplayFilename(file)
const showProgressError = !!transferShowProgress
const progressValue = Math.round((transferLastProgress ?? 1) * 100)
return html`
${renderTitleWithSize(filename, fileSize, truncateTail)}
${showProgressError
? html`
`
: null}
${transferErrorMessage
? html`${transferErrorMessage}`
: null}
`
}
const renderOperationButtons = ({
file,
disabled,
inputName,
operations,
activeOperationId,
onActivateOperation,
onCloseOperation,
getFileAttribute,
setFileAttribute,
}: {
file: FileItem
disabled: boolean
inputName: string
operations: TQueueItemOperation[]
activeOperationId?: string
onActivateOperation: (fileId: string, operationId: string) => void
onCloseOperation: (fileId: string) => void
getFileAttribute: (fileId: string, name: string) => T | undefined
setFileAttribute: (fileId: string, name: string, value: unknown) => void
}) => {
const activeOperation = operations.find((operation) => operation.id === activeOperationId)
const hasOperationUIActive = !!(
activeOperation?.renderInlineUI || activeOperation?.renderExtendedUI
)
if (hasOperationUIActive) return null
const operationButtons = operations
.map((operation) => {
const context = createOperationContext({
operationId: operation.id,
file,
inputName,
disabled,
isActive: operation.id === activeOperationId,
onActivateOperation,
onCloseOperation,
getFileAttribute,
setFileAttribute,
})
const title = typeof operation.title === 'function' ? operation.title(file) : operation.title
if (!title) return null
const ariaLabel =
typeof operation.ariaLabel === 'function' ? operation.ariaLabel(file) : operation.ariaLabel
return html``
})
.filter(Boolean)
if (operationButtons.length === 0) return null
return html`${operationButtons}
`
}
const renderOperationContent = ({
file,
disabled,
inputName,
operations,
activeOperationId,
onActivateOperation,
onCloseOperation,
getFileAttribute,
setFileAttribute,
}: {
file: FileItem
disabled: boolean
inputName: string
operations: TQueueItemOperation[]
activeOperationId?: string
onActivateOperation: (fileId: string, operationId: string) => void
onCloseOperation: (fileId: string) => void
getFileAttribute: (fileId: string, name: string) => T | undefined
setFileAttribute: (fileId: string, name: string, value: unknown) => void
}) =>
operations.map((operation) => {
if (!operation.renderContent) return null
const context = createOperationContext({
operationId: operation.id,
file,
inputName,
disabled,
isActive: operation.id === activeOperationId,
onActivateOperation,
onCloseOperation,
getFileAttribute,
setFileAttribute,
})
const content = operation.renderContent(context)
if (content === null || content === undefined) {
return null
}
return html`
${content}
`
})
const renderExpandedOperationContent = ({
file,
disabled,
inputName,
operations,
activeOperationId,
onActivateOperation,
onCloseOperation,
getFileAttribute,
setFileAttribute,
}: {
file: FileItem
disabled: boolean
inputName: string
operations: TQueueItemOperation[]
activeOperationId?: string
onActivateOperation: (fileId: string, operationId: string) => void
onCloseOperation: (fileId: string) => void
getFileAttribute: (fileId: string, name: string) => T | undefined
setFileAttribute: (fileId: string, name: string, value: unknown) => void
}) => {
if (!activeOperationId) return null
const activeOperation = operations.find((operation) => operation.id === activeOperationId)
if (!activeOperation?.renderExtendedUI) return null
const context = createOperationContext({
operationId: activeOperation.id,
file,
inputName,
disabled,
isActive: true,
onActivateOperation,
onCloseOperation,
getFileAttribute,
setFileAttribute,
})
return html`
${activeOperation.renderExtendedUI(context)}
`
}
const renderHiddenOperationInputs = ({
file,
disabled,
inputName,
operations,
activeOperationId,
onActivateOperation,
onCloseOperation,
getFileAttribute,
setFileAttribute,
}: {
file: FileItem
disabled: boolean
inputName: string
operations: TQueueItemOperation[]
activeOperationId?: string
onActivateOperation: (fileId: string, operationId: string) => void
onCloseOperation: (fileId: string) => void
getFileAttribute: (fileId: string, name: string) => T | undefined
setFileAttribute: (fileId: string, name: string, value: unknown) => void
}) =>
operations.map((operation) => {
if (!operation.renderHidden) return null
const context = createOperationContext({
operationId: operation.id,
file,
inputName,
disabled,
isActive: operation.id === activeOperationId,
onActivateOperation,
onCloseOperation,
getFileAttribute,
setFileAttribute,
})
return operation.renderHidden(context)
})
const renderFilenameIdleMain = (file: FileItem, fileSize: string, truncateTail?: number) => {
const filename = getDisplayFilename(file)
return html`
${renderTitleWithSize(filename, fileSize, truncateTail)}
`
}
const renderThumbnailIdleMain = ({
file,
thumbnailUrl,
previewEnabled,
canOpenPreview,
truncateTail,
onOpenPreview,
onThumbnailImageError,
}: {
file: FileItem
thumbnailUrl?: string
previewEnabled?: boolean
canOpenPreview?: boolean
truncateTail?: number
onOpenPreview?: (fileId: string) => void
onThumbnailImageError?: (fileId: string) => void
}) => {
const filename = getDisplayFilename(file)
const hasThumbnail = !!thumbnailUrl
const showExpandButton = !!previewEnabled && !!canOpenPreview
const isInteractive = showExpandButton && !!onOpenPreview
return html`
${renderTruncatedFilename(filename, truncateTail)}
`
}
export const renderFilenameQueueItem = ({
file,
inputName,
disabled,
fileSize,
operations,
activeOperationId,
onActivateOperation,
onCloseOperation,
getFileAttribute,
setFileAttribute,
transferProgress,
transferErrorMessage,
transferShowProgress,
transferLastProgress,
loadingText,
truncateTail,
onCancel,
}: TRenderQueueItemProps) => {
const state = getProgressState(transferProgress)
const activeOperation = operations.find((operation) => operation.id === activeOperationId)
const inlineOperationTemplate = activeOperation?.renderInlineUI
? activeOperation.renderInlineUI(
createOperationContext({
operationId: activeOperation.id,
file,
inputName,
disabled,
isActive: true,
onActivateOperation,
onCloseOperation,
getFileAttribute,
setFileAttribute,
}),
)
: null
return html`
${renderHiddenOperationInputs({
file,
disabled,
inputName,
operations,
activeOperationId,
onActivateOperation,
onCloseOperation,
getFileAttribute,
setFileAttribute,
})}
${state === 'in-progress'
? renderTransferInProgress({
file,
fileSize,
disabled,
transferProgress: transferProgress as number,
transferShowProgress,
loadingText,
truncateTail,
onCancel,
})
: state === 'error'
? renderTransferError({
file,
fileSize,
disabled,
transferShowProgress,
transferLastProgress,
transferErrorMessage,
truncateTail,
onCancel,
})
: inlineOperationTemplate
? html`
${inlineOperationTemplate}
`
: renderFilenameIdleMain(file, fileSize, truncateTail)}
${state === 'idle'
? renderOperationButtons({
file,
disabled,
inputName,
operations,
activeOperationId,
onActivateOperation,
onCloseOperation,
getFileAttribute,
setFileAttribute,
})
: null}
${state === 'idle'
? renderOperationContent({
file,
disabled,
inputName,
operations,
activeOperationId,
onActivateOperation,
onCloseOperation,
getFileAttribute,
setFileAttribute,
})
: null}
${state === 'idle'
? renderExpandedOperationContent({
file,
disabled,
inputName,
operations,
activeOperationId,
onActivateOperation,
onCloseOperation,
getFileAttribute,
setFileAttribute,
})
: null}
`
}
export const renderThumbnailQueueItem = ({
file,
inputName,
disabled,
fileSize,
thumbnailUrl,
previewEnabled,
canOpenPreview,
operations,
activeOperationId,
onActivateOperation,
onCloseOperation,
getFileAttribute,
setFileAttribute,
transferProgress,
transferErrorMessage,
transferShowProgress,
transferLastProgress,
loadingText,
truncateTail,
onCancel,
onOpenPreview,
onThumbnailImageError,
}: TRenderQueueItemProps) => {
const state = getProgressState(transferProgress)
const activeOperation = operations.find((operation) => operation.id === activeOperationId)
const inlineOperationTemplate = activeOperation?.renderInlineUI
? activeOperation.renderInlineUI(
createOperationContext({
operationId: activeOperation.id,
file,
inputName,
disabled,
isActive: true,
onActivateOperation,
onCloseOperation,
getFileAttribute,
setFileAttribute,
}),
)
: null
return html`
${renderHiddenOperationInputs({
file,
disabled,
inputName,
operations,
activeOperationId,
onActivateOperation,
onCloseOperation,
getFileAttribute,
setFileAttribute,
})}
${state === 'in-progress'
? renderTransferInProgress({
file,
fileSize,
disabled,
transferProgress: transferProgress as number,
transferShowProgress,
loadingText,
truncateTail,
onCancel,
iconName: 'picture',
})
: state === 'error'
? renderTransferError({
file,
fileSize,
disabled,
transferShowProgress,
transferLastProgress,
transferErrorMessage,
truncateTail,
onCancel,
})
: inlineOperationTemplate
? html`
${inlineOperationTemplate}
`
: renderThumbnailIdleMain({
file,
thumbnailUrl,
previewEnabled,
canOpenPreview,
truncateTail,
onOpenPreview,
onThumbnailImageError,
})}
${state === 'idle'
? renderOperationButtons({
file,
disabled,
inputName,
operations,
activeOperationId,
onActivateOperation,
onCloseOperation,
getFileAttribute,
setFileAttribute,
})
: null}
${state === 'idle'
? renderOperationContent({
file,
disabled,
inputName,
operations,
activeOperationId,
onActivateOperation,
onCloseOperation,
getFileAttribute,
setFileAttribute,
})
: null}
${state === 'idle'
? renderExpandedOperationContent({
file,
disabled,
inputName,
operations,
activeOperationId,
onActivateOperation,
onCloseOperation,
getFileAttribute,
setFileAttribute,
})
: null}
`
}