11// Joao Mendes November 2018, SPFx reusable Control ListItemAttachments
22import * as React from 'react' ;
33import { Dialog , DialogType , DialogFooter } from '@fluentui/react/lib/Dialog' ;
4- import { PrimaryButton , DefaultButton } from '@fluentui/react/lib/Button' ;
4+ import { PrimaryButton , DefaultButton , IconButton } from '@fluentui/react/lib/Button' ;
55import { DirectionalHint } from '@fluentui/react/lib/Callout' ;
66import { Label } from "@fluentui/react/lib/Label" ;
7+ import { Link } from '@fluentui/react/lib/Link' ;
8+ import { DetailsList , DetailsListLayoutMode , SelectionMode } from '@fluentui/react/lib/DetailsList' ;
79import * as strings from 'ControlStrings' ;
810import styles from './ListItemAttachments.module.scss' ;
911import { UploadAttachment } from './UploadAttachment' ;
@@ -17,6 +19,7 @@ import {
1719import { ImageFit } from '@fluentui/react/lib/Image' ;
1820import { IListItemAttachmentsProps } from './IListItemAttachmentsProps' ;
1921import { IListItemAttachmentsState } from './IListItemAttachmentsState' ;
22+ import { AttachmentsDisplayMode } from './AttachmentsDisplayMode' ;
2023import SPservice from "../../services/SPService" ;
2124import { TooltipHost } from '@fluentui/react/lib/Tooltip' ;
2225import { Spinner , SpinnerSize } from '@fluentui/react/lib/Spinner' ;
@@ -251,35 +254,26 @@ export class ListItemAttachments extends React.Component<IListItemAttachmentsPro
251254 }
252255
253256 /**
254- * Default React render method
257+ * Get file extension from filename
258+ * @param fileName - The file name to extract extension from
259+ * @returns The file extension (without the dot) or empty string if no extension
255260 */
256- public render ( ) : React . ReactElement < IListItemAttachmentsProps > {
257- const { openAttachmentsInNewWindow } = this . props ;
258- return (
259- < div className = { styles . ListItemAttachments } >
260- < UploadAttachment
261- listId = { this . props . listId }
262- itemId = { this . state . itemId }
263- disabled = { this . props . disabled }
264- context = { this . props . context }
265- onAttachmentUpload = { this . _onAttachmentUpload }
266- fireUpload = { this . state . fireUpload }
267- onUploadDialogClosed = { ( ) => this . setState ( { fireUpload : false } ) }
268- onAttachmentChange = { this . props . onAttachmentChange }
269- />
270-
271- {
272- this . state . showPlaceHolder ?
273- < Placeholder
274- iconName = 'Upload'
275- iconText = { this . props . label || strings . ListItemAttachmentslPlaceHolderIconText }
276- description = { this . props . description || strings . ListItemAttachmentslPlaceHolderDescription }
277- buttonLabel = { strings . ListItemAttachmentslPlaceHolderButtonLabel }
278- hideButton = { this . props . disabled }
279- onConfigure = { ( ) => this . setState ( { fireUpload : true } ) } />
280- :
261+ private getFileExtension ( fileName : string ) : string {
262+ const lastDotIndex = fileName . lastIndexOf ( '.' ) ;
263+ if ( lastDotIndex === - 1 || lastDotIndex === fileName . length - 1 ) {
264+ return '' ;
265+ }
266+ return fileName . substring ( lastDotIndex + 1 ) . toLowerCase ( ) ;
267+ }
281268
282- this . state . attachments . map ( file => {
269+ /**
270+ * Renders attachments in tile/thumbnail mode using DocumentCard components
271+ * @returns JSX element containing attachment tiles
272+ */
273+ private renderTiles ( ) : JSX . Element {
274+ const { openAttachmentsInNewWindow } = this . props ;
275+ return < React . Fragment > {
276+ this . state . attachments . map ( file => {
283277 const fileName = file . FileName ;
284278 const previewImage = this . previewImages [ fileName ] ;
285279 const clickDisabled = ! this . state . itemId ;
@@ -321,7 +315,133 @@ export class ListItemAttachments extends React.Component<IListItemAttachmentsPro
321315 </ TooltipHost >
322316 </ div >
323317 ) ;
324- } ) }
318+ } )
319+ } </ React . Fragment >
320+ }
321+
322+ /**
323+ * Renders attachments in list mode using DetailsList component
324+ * Supports both normal and compact display modes
325+ * @returns JSX element containing attachment list
326+ */
327+ private renderDetailsList ( ) : JSX . Element {
328+ const { displayMode, openAttachmentsInNewWindow } = this . props ;
329+ const columns = [
330+ {
331+ key : 'columnFileType' ,
332+ name : 'File Type' ,
333+ iconName : 'Page' ,
334+ isIconOnly : true ,
335+ minWidth : 16 ,
336+ maxWidth : 16 ,
337+ onRender : ( file : IListItemAttachmentFile ) => {
338+ const fileExtension = this . getFileExtension ( file . FileName ) ;
339+ const previewImage = this . previewImages [ file . FileName ] ;
340+ const iconUrl = previewImage ?. previewImageSrc || '' ;
341+ return (
342+ < TooltipHost content = { `${ fileExtension || 'file' } ` } >
343+ < img src = { iconUrl } className = { styles . detailsListIcon } alt = { `${ fileExtension } file icon` } />
344+ </ TooltipHost >
345+ ) ;
346+ } ,
347+ } ,
348+ {
349+ key : 'columnFileName' ,
350+ name : 'File Name' ,
351+ fieldName : 'FileName' ,
352+ minWidth : 150 ,
353+ maxWidth : 800 ,
354+ isResizable : true ,
355+ onRender : ( file : IListItemAttachmentFile ) => {
356+ const clickDisabled = ! this . state . itemId ;
357+
358+ if ( clickDisabled ) {
359+ return < span > { file . FileName } </ span > ;
360+ }
361+
362+ if ( openAttachmentsInNewWindow ) {
363+ return (
364+ < Link
365+ onClick = { ( ) => window . open ( `${ file . ServerRelativeUrl } ?web=1` , "_blank" ) }
366+ >
367+ { file . FileName }
368+ </ Link >
369+ ) ;
370+ }
371+
372+ return (
373+ < Link href = { `${ file . ServerRelativeUrl } ?web=1` } >
374+ { file . FileName }
375+ </ Link >
376+ ) ;
377+ }
378+ } ,
379+ {
380+ key : 'columnDeleteIcon' ,
381+ name : '' ,
382+ minWidth : 32 ,
383+ maxWidth : 32 ,
384+ isResizable : true ,
385+ onRender : ( file : IListItemAttachmentFile ) => {
386+ return (
387+ < IconButton
388+ className = { styles . detailsListIcon }
389+ iconProps = { { iconName : "Delete" } }
390+ disabled = { this . props . disabled }
391+ onClick = {
392+ ( ev ) => {
393+ ev . preventDefault ( ) ;
394+ ev . stopPropagation ( ) ;
395+ this . onDeleteAttachment ( file ) ; } } />
396+
397+ ) ;
398+ } ,
399+ }
400+ ] ;
401+ return < DetailsList
402+ className = { styles . detailsList }
403+ items = { this . state . attachments }
404+ columns = { columns }
405+ selectionMode = { SelectionMode . none }
406+ layoutMode = { DetailsListLayoutMode . justified }
407+ compact = { displayMode === AttachmentsDisplayMode . DetailsListCompact }
408+ />
409+ }
410+
411+ /**
412+ * Default React render method
413+ */
414+ public render ( ) : React . ReactElement < IListItemAttachmentsProps > {
415+ const { displayMode } = this . props ;
416+ return (
417+ < div className = { styles . ListItemAttachments } >
418+ < UploadAttachment
419+ listId = { this . props . listId }
420+ itemId = { this . state . itemId }
421+ disabled = { this . props . disabled }
422+ context = { this . props . context }
423+ onAttachmentUpload = { this . _onAttachmentUpload }
424+ fireUpload = { this . state . fireUpload }
425+ onUploadDialogClosed = { ( ) => this . setState ( { fireUpload : false } ) }
426+ onAttachmentChange = { this . props . onAttachmentChange }
427+ />
428+
429+ {
430+ this . state . showPlaceHolder ?
431+ < Placeholder
432+ iconName = 'Upload'
433+ iconText = { this . props . label || strings . ListItemAttachmentslPlaceHolderIconText }
434+ description = { this . props . description || strings . ListItemAttachmentslPlaceHolderDescription }
435+ buttonLabel = { strings . ListItemAttachmentslPlaceHolderButtonLabel }
436+ hideButton = { this . props . disabled }
437+ onConfigure = { ( ) => this . setState ( { fireUpload : true } ) } />
438+ :
439+
440+ < >
441+ { ( ! displayMode || displayMode === AttachmentsDisplayMode . Tiles ) && this . renderTiles ( ) }
442+ { ( displayMode === AttachmentsDisplayMode . DetailsList || displayMode === AttachmentsDisplayMode . DetailsListCompact ) && this . renderDetailsList ( ) }
443+ </ >
444+ }
325445 { ! this . state . hideDialog &&
326446
327447 < Dialog
0 commit comments