import type { DownloadSortColumn } from './downloadTableSorting'; export type DownloadTableColumnKey = DownloadSortColumn; export type DownloadColumnAlignment = 'left' | 'center' | 'right'; export const DEFAULT_COLUMN_ORDER = [ 'File Name', 'Size', 'Status', 'Speed', 'ETA', 'Date Added', ] as const satisfies readonly DownloadTableColumnKey[]; export const DEFAULT_COLUMN_WIDTHS = [340, 100, 220, 100, 80, 170] as const; export const COLUMN_MINIMUMS = [160, 58, 92, 58, 48, 144] as const; // Keep the viewport-anchored rail clear of the window edge and scrollbar. export const DOWNLOAD_ACTIONS_VIEWPORT_INSET = 8; // Match the 28px master-control group height. export const DOWNLOAD_ACTIONS_MAX_HEIGHT = 28; export const DOWNLOAD_ACTIONS_VIEWPORT_TOLERANCE = 1; export interface DownloadActionViewportRect { top: number; right: number; bottom: number; left: number; } export interface DownloadActionPosition { top: number; right: number; height: number; overflow: 'hidden' | 'visible'; visibility: 'hidden' | 'visible'; } export const getDownloadActionPosition = ( rowRect: DownloadActionViewportRect, horizontalViewportRect: DownloadActionViewportRect, verticalViewportRect: DownloadActionViewportRect, windowWidth: number, actionInset = DOWNLOAD_ACTIONS_VIEWPORT_INSET ): DownloadActionPosition => { const visibleTop = Math.max(rowRect.top, verticalViewportRect.top); const visibleBottom = Math.min(rowRect.bottom, verticalViewportRect.bottom); const visibleHeight = Math.max(0, visibleBottom - visibleTop); const visibleLeft = Math.max(rowRect.left, horizontalViewportRect.left); const visibleRight = Math.min(rowRect.right, horizontalViewportRect.right); const visibleWidth = Math.max(0, visibleRight - visibleLeft); const isVisible = visibleHeight > DOWNLOAD_ACTIONS_VIEWPORT_TOLERANCE && visibleWidth > DOWNLOAD_ACTIONS_VIEWPORT_TOLERANCE; const actionHeight = Math.min(DOWNLOAD_ACTIONS_MAX_HEIGHT, rowRect.bottom - rowRect.top, visibleHeight); const actionRightEdge = Math.min(rowRect.right, horizontalViewportRect.right); const safeActionInset = Number.isFinite(actionInset) ? Math.max(0, actionInset) : DOWNLOAD_ACTIONS_VIEWPORT_INSET; return { top: visibleTop + Math.max(0, (visibleHeight - actionHeight) / 2), right: Math.max(0, windowWidth - actionRightEdge) + safeActionInset, height: actionHeight, overflow: actionHeight < rowRect.bottom - rowRect.top ? 'hidden' : 'visible', visibility: isVisible ? 'visible' : 'hidden', }; }; export const COLUMN_WIDTHS_STORAGE_KEY = 'firelink-download-column-widths'; export const COLUMN_ORDER_STORAGE_KEY = 'firelink-download-column-order'; export const COLUMN_ALIGNMENTS_STORAGE_KEY = 'firelink-download-column-alignments'; export const DEFAULT_COLUMN_ALIGNMENTS: Record = { 'File Name': 'left', Size: 'left', Status: 'left', Speed: 'left', ETA: 'left', 'Date Added': 'left', }; export const COLUMN_ALIGNMENT_JUSTIFY: Record = { left: 'flex-start', center: 'center', right: 'flex-end', }; const isColumnKey = (value: unknown): value is DownloadTableColumnKey => typeof value === 'string' && (DEFAULT_COLUMN_ORDER as readonly string[]).includes(value); const isColumnAlignment = (value: unknown): value is DownloadColumnAlignment => value === 'left' || value === 'center' || value === 'right'; export const normalizeColumnOrder = (value: unknown): DownloadTableColumnKey[] => { const seen = new Set(); const normalized: DownloadTableColumnKey[] = []; if (Array.isArray(value)) { for (const candidate of value) { if (isColumnKey(candidate) && !seen.has(candidate)) { seen.add(candidate); normalized.push(candidate); } } } for (const key of DEFAULT_COLUMN_ORDER) { if (!seen.has(key)) normalized.push(key); } return normalized; }; export const normalizeColumnWidths = (value: unknown): number[] => { if (!Array.isArray(value) || value.length !== DEFAULT_COLUMN_WIDTHS.length) { return [...DEFAULT_COLUMN_WIDTHS]; } return value.map((width, index) => typeof width === 'number' && Number.isFinite(width) ? Math.max(COLUMN_MINIMUMS[index], width) : DEFAULT_COLUMN_WIDTHS[index] ); }; export const normalizeColumnAlignments = (value: unknown): Record => { const candidate = value && typeof value === 'object' && !Array.isArray(value) ? value as Partial> : {}; return DEFAULT_COLUMN_ORDER.reduce((alignments, key) => { alignments[key] = isColumnAlignment(candidate[key]) ? candidate[key] : DEFAULT_COLUMN_ALIGNMENTS[key]; return alignments; }, {} as Record); }; export const columnIndex = (key: DownloadTableColumnKey): number => DEFAULT_COLUMN_ORDER.indexOf(key); export const buildColumnGridTemplate = ( order: DownloadTableColumnKey[], widths: number[] ): string => { const normalizedWidths = normalizeColumnWidths(widths); const orderedWidths = order.map(key => normalizedWidths[columnIndex(key)]); return [ ...orderedWidths.map(width => `${width}px`), 'minmax(0, 1fr)', ].join(' '); }; export const getColumnGridColumn = ( key: DownloadTableColumnKey, order: DownloadTableColumnKey[] ): string | undefined => key === order[order.length - 1] ? `${order.length}` : undefined;