@@ -26,8 +26,9 @@ import {
2626 __experimentalToggleGroupControlOptionIcon as ToggleGroupControlOptionIcon ,
2727 Button ,
2828} from '@wordpress/components' ;
29- import { useDispatch , useSelect } from '@wordpress/data' ;
29+ import { useDispatch , useSelect , select as dataSelect } from '@wordpress/data' ;
3030import { store as coreStore } from '@wordpress/core-data' ;
31+ import { store as noticesStore } from '@wordpress/notices' ;
3132import { useCallback , useEffect , useMemo , useRef , useState } from '@wordpress/element' ;
3233import { columns , grid , listView , plus } from '@wordpress/icons' ;
3334
@@ -172,6 +173,7 @@ const AddVideoAppender = ( { onSelect } ) => (
172173 < MediaUploadCheck >
173174 < MediaUpload
174175 allowedTypes = { [ 'video' ] }
176+ multiple
175177 onSelect = { onSelect }
176178 render = { ( { open } ) => (
177179 < Button
@@ -214,7 +216,8 @@ export default function Edit( { attributes, setAttributes, clientId } ) {
214216 const [ startDatePopoverOpen , setStartDatePopoverOpen ] = useState ( false ) ;
215217 const [ endDatePopoverOpen , setEndDatePopoverOpen ] = useState ( false ) ;
216218 const [ dateError , setDateError ] = useState ( '' ) ;
217- const { insertBlocks, updateBlockAttributes } = useDispatch ( blockEditorStore ) ;
219+ const { insertBlocks, updateBlockAttributes, removeBlock } = useDispatch ( blockEditorStore ) ;
220+ const { createNotice } = useDispatch ( noticesStore ) ;
218221
219222 // Tracks {virtualId, blockClientId} pairs for GoDAM virtual insertions
220223 // so the godam-virtual-attachment-created event can update the correct block.
@@ -367,28 +370,85 @@ export default function Edit( { attributes, setAttributes, clientId } ) {
367370 ) ;
368371
369372 const insertHandpickedVideo = useCallback (
370- ( mediaItem ) => {
371- if ( ! mediaItem ?. id ) {
373+ ( mediaItemOrArray ) => {
374+ const items = Array . isArray ( mediaItemOrArray ) ? mediaItemOrArray : [ mediaItemOrArray ] ;
375+ if ( ! items . length ) {
372376 return ;
373377 }
374378
375- const numericId = parseInt ( mediaItem . id , 10 ) ;
376- const isVirtual = ! ( numericId > 0 && String ( numericId ) === String ( mediaItem . id ) ) ;
379+ // Get existing video IDs from inner blocks to prevent duplicates.
380+ const { getBlock } = dataSelect ( blockEditorStore ) ;
381+ const parentBlock = getBlock ( clientId ) ;
382+ const existingVideoIds = new Set (
383+ ( parentBlock ?. innerBlocks || [ ] )
384+ . map ( ( block ) => block . attributes ?. videoId )
385+ . filter ( Boolean ) ,
386+ ) ;
387+
388+ const newBlocks = [ ] ;
389+ let skippedNonVideo = false ;
390+ let skippedDuplicate = false ;
391+
392+ items . forEach ( ( mediaItem ) => {
393+ if ( ! mediaItem ?. id ) {
394+ return ;
395+ }
396+
397+ // Only allow video attachments.
398+ if ( mediaItem . type && mediaItem . type !== 'video' ) {
399+ skippedNonVideo = true ;
400+ return ;
401+ }
402+ if ( mediaItem . mime && ! mediaItem . mime . startsWith ( 'video/' ) ) {
403+ skippedNonVideo = true ;
404+ return ;
405+ }
406+
407+ const numericId = parseInt ( mediaItem . id , 10 ) ;
408+ const isVirtual = ! ( numericId > 0 && String ( numericId ) === String ( mediaItem . id ) ) ;
409+
410+ if ( ! isVirtual ) {
411+ // Skip if this video is already in the gallery.
412+ if ( existingVideoIds . has ( numericId ) ) {
413+ skippedDuplicate = true ;
414+ return ;
415+ }
416+ existingVideoIds . add ( numericId ) ;
417+ }
418+
419+ const newBlock = createBlock ( 'godam/gallery-v2-item' , {
420+ videoId : isVirtual ? 0 : numericId ,
421+ } ) ;
422+
423+ if ( isVirtual ) {
424+ pendingVirtualInserts . current . push ( {
425+ virtualId : mediaItem . id ,
426+ blockClientId : newBlock . clientId ,
427+ } ) ;
428+ }
377429
378- const newBlock = createBlock ( 'godam/gallery-v2-item' , {
379- videoId : isVirtual ? 0 : numericId ,
430+ newBlocks . push ( newBlock ) ;
380431 } ) ;
381432
382- if ( isVirtual ) {
383- pendingVirtualInserts . current . push ( {
384- virtualId : mediaItem . id ,
385- blockClientId : newBlock . clientId ,
433+ if ( skippedNonVideo ) {
434+ createNotice ( 'warning' , __ ( 'Only video files can be added to the gallery.' , 'godam' ) , {
435+ type : 'snackbar' ,
436+ isDismissible : true ,
437+ } ) ;
438+ }
439+
440+ if ( skippedDuplicate ) {
441+ createNotice ( 'warning' , __ ( 'Duplicate videos were skipped.' , 'godam' ) , {
442+ type : 'snackbar' ,
443+ isDismissible : true ,
386444 } ) ;
387445 }
388446
389- insertBlocks ( newBlock , undefined , clientId ) ;
447+ if ( newBlocks . length > 0 ) {
448+ insertBlocks ( newBlocks , undefined , clientId ) ;
449+ }
390450 } ,
391- [ clientId , insertBlocks ] ,
451+ [ clientId , createNotice , insertBlocks ] ,
392452 ) ;
393453
394454 // When GoDAM creates a real WP attachment, find the pending child block
@@ -409,6 +469,22 @@ export default function Edit( { attributes, setAttributes, clientId } ) {
409469 }
410470
411471 const [ { blockClientId } ] = pendingVirtualInserts . current . splice ( idx , 1 ) ;
472+
473+ // Skip if this video already exists in another gallery item.
474+ const { getBlock } = dataSelect ( blockEditorStore ) ;
475+ const parentBlock = getBlock ( clientId ) ;
476+ const isDuplicate = ( parentBlock ?. innerBlocks || [ ] ) . some (
477+ ( block ) => block . clientId !== blockClientId && block . attributes ?. videoId === attachment . id ,
478+ ) ;
479+ if ( isDuplicate ) {
480+ createNotice ( 'warning' , __ ( 'Duplicate videos were skipped.' , 'godam' ) , {
481+ type : 'snackbar' ,
482+ isDismissible : true ,
483+ } ) ;
484+ removeBlock ( blockClientId ) ;
485+ return ;
486+ }
487+
412488 updateBlockAttributes ( blockClientId , { videoId : attachment . id } ) ;
413489 } ;
414490
@@ -417,7 +493,7 @@ export default function Edit( { attributes, setAttributes, clientId } ) {
417493 return ( ) => {
418494 document . removeEventListener ( 'godam-virtual-attachment-created' , handleVirtualAttachmentCreated ) ;
419495 } ;
420- } , [ updateBlockAttributes ] ) ;
496+ } , [ clientId , createNotice , removeBlock , updateBlockAttributes ] ) ;
421497
422498 const renderVideoAppender = useCallback (
423499 ( ) => < AddVideoAppender onSelect = { insertHandpickedVideo } /> ,
0 commit comments