diff --git a/app/model/editions/EditionsCard.scala b/app/model/editions/EditionsCard.scala index 158f47c17f9..64f8f0ba314 100644 --- a/app/model/editions/EditionsCard.scala +++ b/app/model/editions/EditionsCard.scala @@ -189,7 +189,14 @@ object EditionsArticle extends Logging { } } -case class EditionsRecipe(id: String, addedOn: Long) extends EditionsCard { +// Only certain cards are permitted within a FeastCollection. +sealed trait EditionsFeastCollectionItem extends EditionsCard + +object EditionsFeastCollectionItem { + implicit val format: OFormat[EditionsFeastCollectionItem] = Json.format[EditionsFeastCollectionItem] +} + +case class EditionsRecipe(id: String, addedOn: Long) extends EditionsCard with EditionsFeastCollectionItem { val cardType: CardType = CardType.Recipe } @@ -217,7 +224,8 @@ object EditionsChef { case class EditionsFeastCollectionMetadata( title: Option[String] = None, - theme: Option[FeastCollectionTheme] = None + theme: Option[FeastCollectionTheme] = None, + collectionItems: List[EditionsFeastCollectionItem] = List.empty ) object EditionsFeastCollectionMetadata { diff --git a/app/model/editions/client/ClientCardMetadata.scala b/app/model/editions/client/ClientCardMetadata.scala index c899bbd004c..e2d6c477f43 100644 --- a/app/model/editions/client/ClientCardMetadata.scala +++ b/app/model/editions/client/ClientCardMetadata.scala @@ -42,6 +42,7 @@ case class ClientCardMetadata( chefImageOverride: Option[Image] = None, // Chef title: Option[String] = None, // FeastCollection feastCollectionTheme: Option[FeastCollectionTheme] = None, // FeastCollection + supporting: List[EditionsSupportingClientCard] = List.empty ) { def toChefMetadata: EditionsChefMetadata = EditionsChefMetadata( @@ -54,6 +55,7 @@ case class ClientCardMetadata( EditionsFeastCollectionMetadata( title, feastCollectionTheme, + collectionItems = supporting.map(EditionsSupportingClientCard.toFeastCollectionItem) ) def toArticleMetadata: EditionsArticleMetadata = { @@ -106,7 +108,8 @@ object ClientCardMetadata { def fromCardMetadata(cardMetadata: EditionsFeastCollectionMetadata): ClientCardMetadata = { ClientCardMetadata( title = cardMetadata.title, - feastCollectionTheme = cardMetadata.theme + feastCollectionTheme = cardMetadata.theme, + supporting = cardMetadata.collectionItems.map(card => EditionsSupportingClientCard.fromFeastCollectionItem(card)) ) } diff --git a/app/model/editions/client/EditionsClientCollection.scala b/app/model/editions/client/EditionsClientCollection.scala index 8f8b0eb44f1..8370c201ae1 100644 --- a/app/model/editions/client/EditionsClientCollection.scala +++ b/app/model/editions/client/EditionsClientCollection.scala @@ -5,9 +5,9 @@ import services.editions.prefills.CapiQueryTimeWindow import model.editions.EditionsCard import model.editions.EditionsArticle import model.editions.{CapiPrefillQuery, EditionsCollection, EditionsRecipe, EditionsChef, EditionsFeastCollection, CardType} +import model.editions.EditionsFeastCollectionItem // Ideally the frontend can be changed so we don't have this weird modelling! - case class EditionsClientCard(id: String, cardType: Option[CardType], frontPublicationDate: Long, meta: Option[ClientCardMetadata] = None) object EditionsClientCard { @@ -71,6 +71,19 @@ object EditionsClientCard { } } +case class EditionsSupportingClientCard(id: String, cardType: Option[CardType], frontPublicationDate: Long) + +object EditionsSupportingClientCard { + implicit def format: OFormat[EditionsSupportingClientCard] = Json.format[EditionsSupportingClientCard] + + def fromFeastCollectionItem(item: EditionsFeastCollectionItem) = item match { + case EditionsRecipe(id, addedOn) => EditionsSupportingClientCard(id, Some(CardType.Recipe), addedOn) + } + + def toFeastCollectionItem(supportingCard: EditionsSupportingClientCard) = + EditionsRecipe(supportingCard.id, supportingCard.frontPublicationDate) +} + case class EditionsClientCollection( id: String, displayName: String, diff --git a/fronts-client/src/components/FrontsEdit/Collection.tsx b/fronts-client/src/components/FrontsEdit/Collection.tsx index 5797df016c3..9d4ac123854 100644 --- a/fronts-client/src/components/FrontsEdit/Collection.tsx +++ b/fronts-client/src/components/FrontsEdit/Collection.tsx @@ -15,6 +15,7 @@ import { connect } from 'react-redux'; import type { State } from 'types/State'; import { createSelectArticleVisibilityDetails } from 'selectors/frontsSelectors'; import FocusWrapper from 'components/FocusWrapper'; +import { CardTypes } from 'constants/cardTypes'; const getArticleNotifications = ( id: string, @@ -184,6 +185,10 @@ class CollectionContext extends React.Component cardId={card.uuid} onMove={handleMove} onDrop={handleInsert} + cardTypeAllowList={this.getPermittedCardTypes( + card.cardType + )} + dropMessage={this.getDropMessage(card.cardType)} > {(supporting, getSupportingProps) => ( ); } + + private getPermittedCardTypes = ( + cardType?: CardTypes + ): CardTypes[] | undefined => + cardType === 'feast-collection' ? ['recipe'] : undefined; + + private getDropMessage = (cardType?: CardTypes) => + cardType === 'feast-collection' ? 'Place recipe here' : 'Sublink'; } const createMapStateToProps = () => { diff --git a/fronts-client/src/components/FrontsEdit/CollectionComponents/DragToAddFeastCollection.tsx b/fronts-client/src/components/FrontsEdit/CollectionComponents/DragToAddFeastCollection.tsx index 4d81a8b462e..438d7cd8771 100644 --- a/fronts-client/src/components/FrontsEdit/CollectionComponents/DragToAddFeastCollection.tsx +++ b/fronts-client/src/components/FrontsEdit/CollectionComponents/DragToAddFeastCollection.tsx @@ -1,18 +1,14 @@ import React, { useRef } from 'react'; import v4 from 'uuid/v4'; - -import { - DraggingArticleComponent, - dragOffsetX, - dragOffsetY, -} from './ArticleDrag'; +import { DraggingArticleComponent } from './ArticleDrag'; import { DragToAdd } from './DragToAdd'; import { Card } from 'types/Collection'; import { CardTypesMap } from 'constants/cardTypes'; +import { handleDragStartForCard } from 'util/dragAndDrop'; const handleDragStart = ( event: React.DragEvent, - dragImageElement: HTMLDivElement | null + dragImageElement: HTMLDivElement ) => { const feastCollectionCard: Card = { cardType: CardTypesMap.FEAST_COLLECTION, @@ -21,13 +17,11 @@ const handleDragStart = ( uuid: v4(), frontPublicationDate: Date.now(), }; - event.dataTransfer.setData( + + return handleDragStartForCard( CardTypesMap.FEAST_COLLECTION, - JSON.stringify(feastCollectionCard) - ); - if (dragImageElement) { - event.dataTransfer.setDragImage(dragImageElement, dragOffsetX, dragOffsetY); - } + feastCollectionCard + )(event, dragImageElement); }; export const DragToAddFeastCollection = () => { @@ -37,7 +31,7 @@ export const DragToAddFeastCollection = () => { dragImage={} dragImageRef={ref} onDragStart={(e: React.DragEvent) => - handleDragStart(e, ref.current) + ref.current && handleDragStart(e, ref.current) } > Drag to add a feast collection diff --git a/fronts-client/src/components/FrontsEdit/CollectionComponents/Sublinks.tsx b/fronts-client/src/components/FrontsEdit/CollectionComponents/Sublinks.tsx index 1a2d291400c..c8fc356e4f2 100644 --- a/fronts-client/src/components/FrontsEdit/CollectionComponents/Sublinks.tsx +++ b/fronts-client/src/components/FrontsEdit/CollectionComponents/Sublinks.tsx @@ -7,8 +7,7 @@ import CardContainer from 'components/card/CardContainer'; import CardContent from 'components/card/CardContent'; import CardMetaContainer from 'components/card/CardMetaContainer'; import DragIntentContainer from 'components/DragIntentContainer'; -import { dragEventIsDenylisted } from 'lib/dnd/Level'; -import { collectionDropTypeDenylist } from 'constants/fronts'; +import { denyDragEvent } from 'lib/dnd/CardTypeLevel'; const SublinkCardBody = styled(CardBody)<{ dragHoverActive: boolean; @@ -41,6 +40,8 @@ interface SublinkProps { toggleShowArticleSublinks: (e?: React.MouseEvent) => void; showArticleSublinks: boolean; parentId: string; + // The singular label to show the user, e.g. 'sublink'. + sublinkLabel?: string; } class Sublinks extends React.Component { @@ -54,6 +55,7 @@ class Sublinks extends React.Component { toggleShowArticleSublinks, showArticleSublinks, parentId, + sublinkLabel = 'sublink', } = this.props; const isClipboard = parentId === 'clipboard'; @@ -69,7 +71,7 @@ class Sublinks extends React.Component { this.setState({ dragHoverActive: false }); }} delay={100} - filterRegisterEvent={this.dragEventNotDenylisted} + filterRegisterEvent={e => !denyDragEvent()(e)} onIntentConfirm={() => { toggleShowArticleSublinks(); }} @@ -82,7 +84,7 @@ class Sublinks extends React.Component { {!isClipboard && } - {numSupportingArticles} sublink + {numSupportingArticles} {sublinkLabel} {numSupportingArticles > 1 && 's'} { ); } - - private dragEventNotDenylisted = (e: React.DragEvent) => - !dragEventIsDenylisted(e, collectionDropTypeDenylist); } export default Sublinks; diff --git a/fronts-client/src/components/card/Card.tsx b/fronts-client/src/components/card/Card.tsx index b16d9f0aebc..bdefadc2363 100644 --- a/fronts-client/src/components/card/Card.tsx +++ b/fronts-client/src/components/card/Card.tsx @@ -276,7 +276,13 @@ class Card extends React.Component { textSize={textSize} showMeta={showMeta} /> - {getSublinks} + {/* If there are no supporting articles, the children still need to be rendered, because the dropzone is a child */} {numSupportingArticles === 0 ? children diff --git a/fronts-client/src/components/clipboard/CardLevel.tsx b/fronts-client/src/components/clipboard/CardLevel.tsx index 946ea828555..f53a455fe69 100644 --- a/fronts-client/src/components/clipboard/CardLevel.tsx +++ b/fronts-client/src/components/clipboard/CardLevel.tsx @@ -1,19 +1,16 @@ import React from 'react'; -import { Level, LevelChild, MoveHandler, DropHandler } from 'lib/dnd'; +import { LevelChild, MoveHandler, DropHandler } from 'lib/dnd'; import type { State } from 'types/State'; import { connect } from 'react-redux'; import { Card } from 'types/Collection'; -import ArticleDrag, { - dragOffsetX, - dragOffsetY, -} from 'components/FrontsEdit/CollectionComponents/ArticleDrag'; import DropZone, { DefaultDropContainer, DefaultDropIndicator, } from 'components/DropZone'; import { createSelectSupportingArticles } from 'selectors/shared'; -import { collectionDropTypeDenylist } from 'constants/fronts'; import { theme, styled } from 'constants/theme'; +import { CardTypeLevel } from 'lib/dnd/CardTypeLevel'; +import { CardTypes } from 'constants/cardTypes'; interface OuterProps { cardId: string; @@ -21,6 +18,8 @@ interface OuterProps { onMove: MoveHandler; onDrop: DropHandler; isUneditable?: boolean; + dropMessage?: string; + cardTypeAllowList?: CardTypes[]; } interface InnerProps { @@ -48,20 +47,17 @@ const CardLevel = ({ onMove, onDrop, isUneditable, + dropMessage, + cardTypeAllowList, }: Props) => ( - uuid} onMove={onMove} onDrop={onDrop} canDrop={!isUneditable} - renderDrag={(af) => } - dragImageOffsetX={dragOffsetX} - dragImageOffsetY={dragOffsetY} + cardTypeAllowList={cardTypeAllowList} renderDrop={ isUneditable ? undefined @@ -69,7 +65,7 @@ const CardLevel = ({ @@ -77,7 +73,7 @@ const CardLevel = ({ } > {children} - + ); const createMapStateToProps = () => { diff --git a/fronts-client/src/components/clipboard/ClipboardLevel.tsx b/fronts-client/src/components/clipboard/ClipboardLevel.tsx index 0733477c3fc..74b974e85bb 100644 --- a/fronts-client/src/components/clipboard/ClipboardLevel.tsx +++ b/fronts-client/src/components/clipboard/ClipboardLevel.tsx @@ -1,16 +1,12 @@ import React from 'react'; -import { Level, LevelChild, MoveHandler, DropHandler } from 'lib/dnd'; +import { LevelChild, MoveHandler, DropHandler } from 'lib/dnd'; import type { State } from 'types/State'; import { selectClipboardArticles } from 'selectors/clipboardSelectors'; import { connect } from 'react-redux'; import { Card } from 'types/Collection'; -import ArticleDrag, { - dragOffsetX, - dragOffsetY, -} from 'components/FrontsEdit/CollectionComponents/ArticleDrag'; import DropZone, { DefaultDropContainer } from 'components/DropZone'; -import { collectionDropTypeDenylist } from 'constants/fronts'; import { styled, theme } from 'constants/theme'; +import { CardTypeLevel } from 'lib/dnd/CardTypeLevel'; interface OuterProps { children: LevelChild; @@ -42,19 +38,13 @@ const ClipboardDropContainer = styled(DefaultDropContainer)<{ `; const ClipboardLevel = ({ children, cards, onMove, onDrop }: Props) => ( - uuid} onMove={onMove} onDrop={onDrop} - renderDrag={(af) => } renderDrop={(props) => ( ( )} > {children} - + ); const mapStateToProps = (state: State) => ({ diff --git a/fronts-client/src/components/clipboard/GroupLevel.tsx b/fronts-client/src/components/clipboard/GroupLevel.tsx index ed10fd4251f..40f5ba6ad06 100644 --- a/fronts-client/src/components/clipboard/GroupLevel.tsx +++ b/fronts-client/src/components/clipboard/GroupLevel.tsx @@ -1,16 +1,12 @@ import React from 'react'; -import { Level, LevelChild, MoveHandler, DropHandler } from 'lib/dnd'; +import { LevelChild, MoveHandler, DropHandler } from 'lib/dnd'; import type { State } from 'types/State'; import { connect } from 'react-redux'; import { Card } from 'types/Collection'; -import ArticleDrag, { - dragOffsetX, - dragOffsetY, -} from 'components/FrontsEdit/CollectionComponents/ArticleDrag'; import DropZone, { DefaultDropContainer } from 'components/DropZone'; -import { collectionDropTypeDenylist } from 'constants/fronts'; import { createSelectArticlesFromIds } from 'selectors/shared'; import { theme, styled } from 'constants/theme'; +import { CardTypeLevel } from 'lib/dnd/CardTypeLevel'; interface OuterProps { groupId: string; @@ -65,19 +61,13 @@ const GroupLevel = ({ onDrop, isUneditable, }: Props) => ( - uuid} onMove={onMove} onDrop={onDrop} canDrop={!isUneditable} - renderDrag={(af) => } renderDrop={ isUneditable ? () => @@ -92,7 +82,7 @@ const GroupLevel = ({ } > {children} - + ); const createMapStateToProps = () => { diff --git a/fronts-client/src/components/feed/ChefFeedItem.tsx b/fronts-client/src/components/feed/ChefFeedItem.tsx index dd1510632db..55e1e7f3ce0 100644 --- a/fronts-client/src/components/feed/ChefFeedItem.tsx +++ b/fronts-client/src/components/feed/ChefFeedItem.tsx @@ -1,9 +1,5 @@ import { Chef } from '../../types/Chef'; import React, { useCallback } from 'react'; -import { - dragOffsetX, - dragOffsetY, -} from '../FrontsEdit/CollectionComponents/ArticleDrag'; import { FeedItem } from './FeedItem'; import { ContentInfo } from './ContentInfo'; import { selectFeatureValue } from '../../selectors/featureSwitchesSelectors'; @@ -12,6 +8,7 @@ import { CardTypesMap } from 'constants/cardTypes'; import { connect, useDispatch, useSelector } from 'react-redux'; import { insertCardWithCreate } from '../../actions/Cards'; import { selectors as chefSelectors } from 'bundles/chefsBundle'; +import { handleDragStartForCard } from 'util/dragAndDrop'; interface ComponentProps { id: string; @@ -37,16 +34,6 @@ export const ChefFeedItemComponent = ({ ); }, [chef]); - const handleDragStart = ( - event: React.DragEvent, - dragNode: HTMLDivElement - ) => { - event.dataTransfer.setData('chef', JSON.stringify(chef)); - if (dragNode) { - event.dataTransfer.setDragImage(dragNode, dragOffsetX, dragOffsetY); - } - }; - return ( Chef} /> diff --git a/fronts-client/src/components/feed/RecipeFeedItem.tsx b/fronts-client/src/components/feed/RecipeFeedItem.tsx index b3e3505947a..88bf4f107c6 100644 --- a/fronts-client/src/components/feed/RecipeFeedItem.tsx +++ b/fronts-client/src/components/feed/RecipeFeedItem.tsx @@ -2,16 +2,13 @@ import { Recipe } from '../../types/Recipe'; import { FeedItem } from './FeedItem'; import React, { useCallback } from 'react'; import { State } from '../../types/State'; -import { - dragOffsetX, - dragOffsetY, -} from '../FrontsEdit/CollectionComponents/ArticleDrag'; import { selectFeatureValue } from '../../selectors/featureSwitchesSelectors'; import { ContentInfo } from './ContentInfo'; import { CardTypesMap } from 'constants/cardTypes'; import { useSelector } from 'react-redux'; import { useDispatch } from 'react-redux'; import { insertCardWithCreate } from 'actions/Cards'; +import { handleDragStartForCard } from 'util/dragAndDrop'; interface ComponentProps { recipe: Recipe; @@ -34,16 +31,6 @@ export const RecipeFeedItem = ({ recipe }: ComponentProps) => { ); }, [recipe]); - const handleDragStart = ( - event: React.DragEvent, - dragNode: HTMLDivElement - ) => { - event.dataTransfer.setData('recipe', JSON.stringify(recipe)); - if (dragNode) { - event.dataTransfer.setDragImage(dragNode, dragOffsetX, dragOffsetY); - } - }; - return ( { liveUrl={`https://theguardian.com/${recipe.canonicalArticle}`} hasVideo={false} isLive={true} // We do not yet serve preview recipes - handleDragStart={handleDragStart} + handleDragStart={handleDragStartForCard(CardTypesMap.RECIPE, recipe)} onAddToClipboard={onAddToClipboard} shouldObscureFeed={shouldObscureFeed} metaContent={Recipe} diff --git a/fronts-client/src/lib/dnd/CardTypeLevel.tsx b/fronts-client/src/lib/dnd/CardTypeLevel.tsx new file mode 100644 index 00000000000..789ae4cf7e0 --- /dev/null +++ b/fronts-client/src/lib/dnd/CardTypeLevel.tsx @@ -0,0 +1,54 @@ +import React from 'react'; +import { Card } from 'types/Collection'; +import Level, { LevelProps } from './Level'; +import { CardTypes, CardTypesMap } from 'constants/cardTypes'; +import { collectionDropTypeDenylist } from 'constants/fronts'; +import { CARD_TYPE } from './constants'; +import ArticleDrag, { + dragOffsetX, + dragOffsetY, +} from 'components/FrontsEdit/CollectionComponents/ArticleDrag'; + +type Props = Omit< + LevelProps, + | 'denyDragEvent' + | 'getDropType' + | 'dragImageOffsetX' + | 'dragImageOffsetY' + | 'renderDrag' + | 'type' + | 'getId' +> & { + cardTypeAllowList?: CardTypes[]; +}; + +export const denyDragEvent = + (cardTypeAllowList?: string[] | undefined) => (e: React.DragEvent) => { + const dropEventIsNotPermitted = e.dataTransfer.types.some((type) => + collectionDropTypeDenylist.includes(type) + ); + + const cardTypeBeingDroppedIsNotPermitted = + !!cardTypeAllowList && + !!e.dataTransfer.getData(CARD_TYPE) && + !cardTypeAllowList.includes(e.dataTransfer.getData(CARD_TYPE)); + + return dropEventIsNotPermitted || cardTypeBeingDroppedIsNotPermitted; + }; + +/** + * A Level that only accepts a Card type. Useful for providing common drag and + * behaviour for Card components. + */ +export const CardTypeLevel: React.FC = (props: Props) => ( + card.cardType || CardTypesMap.ARTICLE} + dragImageOffsetX={dragOffsetX} + dragImageOffsetY={dragOffsetY} + renderDrag={(af) => } + type="card" + getId={({ uuid }) => uuid} + /> +); diff --git a/fronts-client/src/lib/dnd/Level.tsx b/fronts-client/src/lib/dnd/Level.tsx index 9fddd6eb387..f5069f098db 100644 --- a/fronts-client/src/lib/dnd/Level.tsx +++ b/fronts-client/src/lib/dnd/Level.tsx @@ -23,13 +23,6 @@ const adjustToIndexForMove = (from: PosSpec, to: PosSpec): PosSpec => const isMoveToSamePosition = (from: PosSpec, to: PosSpec): boolean => isOnSameLevel(from, to) && from.index === to.index; -const dragEventIsDenyListed = ( - e: React.DragEvent, - denyList: string[] | undefined -) => { - return e.dataTransfer.types.some((type) => (denyList || []).includes(type)); -}; - interface Move { data: T; from: false | PosSpec; @@ -58,12 +51,13 @@ type ContainerDragTypes = Pick< 'onDragOver' >; -interface OuterProps { +export interface LevelProps { arr: T[]; children: LevelChild; parentId: string; parentType: string; type: string; + getDropType?: (item: T) => string; dragImageOffsetX?: number; dragImageOffsetY?: number; canDrop?: boolean; @@ -72,9 +66,8 @@ interface OuterProps { onDrop: (e: React.DragEvent, to: PosSpec) => void; renderDrag?: (data: T) => React.ReactNode; renderDrop?: (props: DropProps) => React.ReactNode | null; - // Any occurence of these in the data transfer will cause all dragging - // behaviour to be bypassed. - denylistedDataTransferTypes?: string[]; + // If this function returns false, all dragging behaviour is bypassed. + denyDragEvent?: (e: React.DragEvent) => boolean; containerElement?: React.ComponentType; } @@ -83,7 +76,7 @@ interface ContextProps { parents: Parent[]; } -type Props = OuterProps & ContextProps; +type Props = LevelProps & ContextProps; interface State { isDraggedOver: boolean; @@ -109,6 +102,7 @@ class Level extends React.Component, State> { type, dragImageOffsetX, dragImageOffsetY, + getDropType, store, } = this.props; const Container = this.props.containerElement || DefaultContainer; @@ -127,6 +121,7 @@ class Level extends React.Component, State> { dragImageOffsetY={dragImageOffsetY} id={getId(node)} type={type} + dropType={getDropType?.(node)} index={i} data={node} > @@ -151,25 +146,26 @@ class Level extends React.Component, State> { ); } + private denyDragEvent = (e: React.DragEvent) => + this.props.denyDragEvent?.(e) ?? false; + private onDragOver = (i: number | null) => (e: React.DragEvent) => { if (!this.props.store) { throw new Error(NO_STORE_ERROR); } - if ( - e.defaultPrevented || - dragEventIsDenyListed(e, this.props.denylistedDataTransferTypes) - ) { + + if (e.defaultPrevented || this.denyDragEvent(e)) { + e.preventDefault(); + e.stopPropagation(); return; } + e.preventDefault(); this.props.store.update(this.key, i, true); }; private onDrop = (i: number) => (e: React.DragEvent) => { - if ( - e.defaultPrevented || - dragEventIsDenyListed(e, this.props.denylistedDataTransferTypes) - ) { + if (e.defaultPrevented || this.denyDragEvent(e)) { return; } @@ -231,7 +227,7 @@ class Level extends React.Component, State> { } } -export default (props: OuterProps) => ( +export default (props: LevelProps) => ( {(store: Store | null) => ( @@ -250,11 +246,4 @@ export default (props: OuterProps) => ( ); -export { - Move, - PosSpec, - LevelChild, - MoveHandler, - DropHandler, - dragEventIsDenyListed as dragEventIsDenylisted, -}; +export { Move, PosSpec, LevelChild, MoveHandler, DropHandler }; diff --git a/fronts-client/src/lib/dnd/Node.tsx b/fronts-client/src/lib/dnd/Node.tsx index 10a3b0de640..bf8d9528fb8 100644 --- a/fronts-client/src/lib/dnd/Node.tsx +++ b/fronts-client/src/lib/dnd/Node.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { PathConsumer, Parent } from './AddParentInfo'; -import { TRANSFER_TYPE } from './constants'; +import { CARD_TYPE, TRANSFER_TYPE } from './constants'; interface ChildrenProps { draggable: true; @@ -11,6 +11,7 @@ interface OuterProps { children: ( getProps: (forceClone: boolean) => ChildrenProps ) => React.ReactNode; + dropType?: string; renderDrag?: (data: T) => React.ReactNode; dragImageOffsetX?: number; dragImageOffsetY?: number; @@ -66,11 +67,15 @@ class Node extends React.Component> { dragImageOffsetY ); } - const { parents, type, id, index, data } = this.props; + const { parents, type, id, index, data, dropType } = this.props; e.dataTransfer.setData( TRANSFER_TYPE, JSON.stringify({ parents, type, id, index, data, forceClone }) ); + + if (dropType) { + e.dataTransfer.setData(CARD_TYPE, dropType); + } }; } diff --git a/fronts-client/src/lib/dnd/constants.ts b/fronts-client/src/lib/dnd/constants.ts index 66c2caea725..35668789363 100644 --- a/fronts-client/src/lib/dnd/constants.ts +++ b/fronts-client/src/lib/dnd/constants.ts @@ -1,3 +1,4 @@ export const TRANSFER_TYPE = '@@TRANSFER@@'; +export const CARD_TYPE = '@@CARD_TYPE@@'; export const NO_STORE_ERROR = 'Store is not set, make sure there is `Root` component above this'; diff --git a/fronts-client/src/util/collectionUtils.ts b/fronts-client/src/util/collectionUtils.ts index 8b4dacd987d..489c4bdb971 100644 --- a/fronts-client/src/util/collectionUtils.ts +++ b/fronts-client/src/util/collectionUtils.ts @@ -37,27 +37,29 @@ export type MappableDropType = | ChefDrop | FeastCollectionDrop; -const dropToCard = (e: React.DragEvent): MappableDropType | null => { - const map = { - capi: (data: string): CAPIDrop => ({ - type: 'CAPI', - data: JSON.parse(data), - }), - [CardTypesMap.RECIPE]: (data: string): RecipeDrop => ({ - type: 'RECIPE', - data: JSON.parse(data), - }), - [CardTypesMap.CHEF]: (data: string): ChefDrop => ({ - type: 'CHEF', - data: JSON.parse(data), - }), - [CardTypesMap.FEAST_COLLECTION]: (data: string): FeastCollectionDrop => ({ - type: 'FEAST_COLLECTION', - }), - text: (url: string): RefDrop => ({ type: 'REF', data: url }), - }; +const dropToCardMap = { + capi: (data: string): CAPIDrop => ({ + type: 'CAPI', + data: JSON.parse(data), + }), + [CardTypesMap.RECIPE]: (data: string): RecipeDrop => ({ + type: 'RECIPE', + data: JSON.parse(data), + }), + [CardTypesMap.CHEF]: (data: string): ChefDrop => ({ + type: 'CHEF', + data: JSON.parse(data), + }), + [CardTypesMap.FEAST_COLLECTION]: (): FeastCollectionDrop => ({ + type: 'FEAST_COLLECTION', + }), + text: (url: string): RefDrop => ({ type: 'REF', data: url }), +}; - for (const [type, fn] of Object.entries(map)) { +export type InsertDropType = keyof typeof dropToCardMap; + +const dropToCard = (e: React.DragEvent): MappableDropType | null => { + for (const [type, fn] of Object.entries(dropToCardMap)) { const data = e.dataTransfer.getData(type); if (data) { return fn(data); diff --git a/fronts-client/src/util/dragAndDrop.ts b/fronts-client/src/util/dragAndDrop.ts new file mode 100644 index 00000000000..5515b273c9c --- /dev/null +++ b/fronts-client/src/util/dragAndDrop.ts @@ -0,0 +1,17 @@ +import { + dragOffsetX, + dragOffsetY, +} from 'components/FrontsEdit/CollectionComponents/ArticleDrag'; +import { CardTypesMap } from 'constants/cardTypes'; +import { CARD_TYPE } from 'lib/dnd/constants'; +import { InsertDropType } from './collectionUtils'; + +export const handleDragStartForCard = + (cardType: InsertDropType, data: unknown) => + (event: React.DragEvent, dragNode: HTMLDivElement) => { + event.dataTransfer.setData(cardType, JSON.stringify(data)); + event.dataTransfer.setData(CARD_TYPE, CardTypesMap.CHEF); + if (dragNode) { + event.dataTransfer.setDragImage(dragNode, dragOffsetX, dragOffsetY); + } + };