Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Multiple refactorings in common/app/layout #22392

Merged
merged 6 commits into from
Mar 12, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions common/app/layout/BreakpointWidth.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package layout

case class BreakpointWidth(breakpoint: Breakpoint, width: BrowserWidth) {
private val MaximumMobileImageWidth = 620
private val SourcesToEmitOnMobile = 3

def toPixels: (Seq[BreakpointWidth]) => Seq[Int] = (breakpointWidths: Seq[BreakpointWidth]) => this match {
case BreakpointWidth(_, PixelWidth(pixels)) =>
Seq(pixels)
case BreakpointWidth(Mobile, _: PercentageWidth | _: ViewportWidth) =>
// Percentage and viewport widths are not explicitly associated with any pixel widths that could be used with a srcset.
// So we create a series of profiles by combining usable widths in the class with predefined sensible widths.
val pixelWidths = breakpointWidths.collect { case BreakpointWidth(_,width: PixelWidth) => width.get }
val widths: Seq[Int] = pixelWidths.dropWhile(_ > MaximumMobileImageWidth).take(SourcesToEmitOnMobile)
widths ++ FaciaWidths.ExtraPixelWidthsForMediaMobile.map(_.get)
case _ => Seq.empty
}
}
21 changes: 21 additions & 0 deletions common/app/layout/Byline.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package layout

case class Byline(
get: String,
contributorTags: Seq[model.Tag]
) {
private def primaryContributor = {
if (contributorTags.length > 2) {
contributorTags.sortBy({ tag =>
get.indexOf(tag.metadata.webTitle) match {
case -1 => Int.MaxValue
case n => n
}
}).headOption
} else {
None
}
}

def shortByline: String = primaryContributor map { tag => s"${tag.metadata.webTitle} and others" } getOrElse get
}
38 changes: 38 additions & 0 deletions common/app/layout/CollectionEssentials.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package layout

import model.facia.PressedCollection
import model.pressed.{PressedContent}


case class CollectionEssentials(
items: Seq[PressedContent],
treats: Seq[PressedContent],
displayName: Option[String],
href: Option[String],
lastUpdated: Option[String],
showMoreLimit: Option[Int]
)

object CollectionEssentials {
/* FAPI Integration */

def fromPressedCollection(collection: PressedCollection): CollectionEssentials = CollectionEssentials(
collection.curatedPlusBackfillDeduplicated,
collection.treats,
Option(collection.displayName),
collection.href,
collection.lastUpdated.map(_.toString),
if (collection.curated.isEmpty) Some(9) else None
)

def fromFaciaContent(trails: Seq[PressedContent]): CollectionEssentials = CollectionEssentials(
trails,
Nil,
None,
None,
None,
None
)

val empty = CollectionEssentials(Nil, Nil, None, None, None, None)
}
132 changes: 0 additions & 132 deletions common/app/layout/Column.scala
Original file line number Diff line number Diff line change
@@ -1,35 +1,5 @@
package layout

import cards.{CardType, ListItem, MediaList, Standard}
import model.pressed.CollectionConfig
import play.twirl.api.Html
import slices.{MobileShowMore, RestrictTo}
import scala.annotation.tailrec

object ItemClasses {
val showMore = ItemClasses(mobile = ListItem, tablet = ListItem)

val liveBlogMore = ItemClasses(mobile = MediaList, tablet = Standard)
}

case class ItemClasses(mobile: CardType, tablet: CardType, desktop: Option[CardType] = None) {
/** Template helper */
def classes: String = s"fc-item--${mobile.cssClassName}-mobile fc-item--${tablet.cssClassName}-tablet" +
desktop.map(d => s" fc-item--${d.cssClassName}-desktop").getOrElse("")

def allTypes: Set[CardType] = Set(mobile, tablet) ++ desktop.toSet

def showVideoPlayer: Boolean = allTypes.exists(_.videoPlayer.show)
def showVideoEndSlate: Boolean = allTypes.exists(_.videoPlayer.showEndSlate)
def showYouTubeMediaAtomPlayer: Boolean = allTypes.exists(_.youTubeMediaAtomPlayer.show)
def showCutOut: Boolean = allTypes.exists(_.showCutOut)
def canShowSlideshow: Boolean = allTypes.exists(_.canShowSlideshow)
def canBeDynamicLayout: Boolean = allTypes.exists(_.canBeDynamicLayout)
}
case class SliceLayout(cssClassName: String, columns: Seq[Column]) {
def numItems: Int = columns.map(_.numItems).sum
}

object Column {
def cardStyle(column: Column, index: Int): Option[ItemClasses] = column match {
case SingleItem(_, itemClasses) => Some(itemClasses)
Expand Down Expand Up @@ -57,105 +27,3 @@ case class SplitColumn(colSpan: Int, topItemRows: Int, topItemClasses: ItemClass
case class MPU(colSpan: Int) extends Column {
val numItems: Int = 0
}

case class HtmlAndClasses(index: Int, html: Html, classes: Seq[String])

object SliceWithCards {
def fromBlobs(layout: SliceLayout, blobs: Seq[HtmlAndClasses]): SliceWithCards = {

@tailrec
def columnsWithCards(columns: List[Column],
items: Seq[HtmlAndClasses],
accumulation: Vector[ColumnAndCards] = Vector.empty
): Seq[ColumnAndCards] = {

columns match {
case Nil => accumulation
case column :: remainingColumns =>
val (itemsForColumn, itemsNotConsumed) = items splitAt column.numItems

val columnAndCards = ColumnAndCards(column, itemsForColumn.zipWithIndex map {
case (HtmlAndClasses(index, html, classes), positionInColumn) =>
FaciaCardAndIndex(
index,
HtmlBlob(html, classes, Column.cardStyle(column, positionInColumn).getOrElse(ItemClasses.showMore)),
None
)
})
columnsWithCards(remainingColumns, itemsNotConsumed, accumulation :+ columnAndCards)
}

}

SliceWithCards(
layout.cssClassName,
columnsWithCards(layout.columns.toList, blobs)
)
}

/** The slice with cards assigned to columns, and the remaining cards that were not consumed, and the new
* context for creating further cards.
*/
def fromItems(
items: Seq[IndexedTrail],
layout: SliceLayout,
context: ContainerLayoutContext,
config: CollectionConfig,
mobileShowMore: MobileShowMore,
showSeriesAndBlogKickers: Boolean
): (SliceWithCards, Seq[IndexedTrail], ContainerLayoutContext) = {
val (columns, unconsumed, endContext) = layout.columns.foldLeft((Seq.empty[ColumnAndCards], items, context)) {
case ((acc, itemsRemaining, currentContext), column) =>
val (itemsForColumn, itemsNotConsumed) = itemsRemaining splitAt column.numItems

val (finalContext, cards) = itemsForColumn.zipWithIndex.foldLeft((currentContext, Seq.empty[FaciaCardAndIndex])) {
case ((contextSoFar, accumulator), (IndexedTrail(trail, index), positionInColumn)) =>
val (card, contextForNext) = contextSoFar.transform(
FaciaCardAndIndex(
index,
FaciaCard.fromTrail(
trail,
config,
Column.cardStyle(column, positionInColumn).getOrElse(ItemClasses.showMore),
showSeriesAndBlogKickers
),
mobileShowMore match {
case RestrictTo(nToShowOnMobile) if index >= nToShowOnMobile => Some(Mobile)
case _ => None
}
)
)

(contextForNext, accumulator :+ card)
}

(acc :+ ColumnAndCards(column, cards), itemsNotConsumed, finalContext)
}

(SliceWithCards(layout.cssClassName, columns), unconsumed, endContext)
}
}

case class SliceWithCards(cssClassName: String, columns: Seq[ColumnAndCards]) {
def numberOfItems: Int = (columns map { columnAndCards: ColumnAndCards =>
columnAndCards.column match {
case Rows(_, cols, _, _) => cols
case _ => 1
}
}).sum

def numberOfCols: Int = (columns map { columnAndCards: ColumnAndCards =>
columnAndCards.column.colSpan
}).sum

def transformCards(f: ContentCard => ContentCard): SliceWithCards = copy(columns = columns map { column =>
column.copy(cards = column.cards map { cardAndIndex =>
cardAndIndex.copy(item = cardAndIndex.item match {
case content: ContentCard => f(content)
case other => other
})
})
})
}

case class ColumnAndCards(column: Column, cards: Seq[FaciaCardAndIndex])
3 changes: 3 additions & 0 deletions common/app/layout/ColumnAndCards.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package layout

case class ColumnAndCards(column: Column, cards: Seq[FaciaCardAndIndex])
3 changes: 3 additions & 0 deletions common/app/layout/ContainerCommercialOptions.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package layout

case class ContainerCommercialOptions(omitMPU: Boolean, adFree: Boolean)
10 changes: 5 additions & 5 deletions common/app/layout/ContainerDisplayConfig.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@ package layout
import model.pressed.CollectionConfig
import services.CollectionConfigWithId

case class ContainerDisplayConfig(
collectionConfigWithId: CollectionConfigWithId,
showSeriesAndBlogKickers: Boolean
)

object ContainerDisplayConfig {
val empty = ContainerDisplayConfig(
CollectionConfigWithId(
Expand All @@ -17,8 +22,3 @@ object ContainerDisplayConfig {
false
)
}

case class ContainerDisplayConfig(
collectionConfigWithId: CollectionConfigWithId,
showSeriesAndBlogKickers: Boolean
)
34 changes: 17 additions & 17 deletions common/app/layout/ContainerLayout.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,23 @@ import slices._
import model.pressed.PressedContent
import scala.annotation.tailrec

case class IndexedTrail(faciaContent: PressedContent, index: Int)
case class ContainerLayout(
slices: Seq[SliceWithCards],
remainingCards: Seq[FaciaCardAndIndex],
hasMore: Boolean = false
) {
def transformCards(f: ContentCard => ContentCard): ContainerLayout = copy(
slices = slices.map(_.transformCards(f)),
remainingCards.map(cardAndIndex => cardAndIndex.transformCard(f))
)

def hasMobileShowMore: Boolean =
slices.flatMap(_.columns.flatMap(_.cards)).exists(_.hideUpTo.contains(Mobile))

def hasDesktopShowMore: Boolean = remainingCards.nonEmpty || hasMore

def hasShowMore: Boolean = hasDesktopShowMore || hasMobileShowMore
}

object ContainerLayout extends implicits.Collections {
def apply(
Expand Down Expand Up @@ -114,20 +130,4 @@ object ContainerLayout extends implicits.Collections {
}
}

case class ContainerLayout(
slices: Seq[SliceWithCards],
remainingCards: Seq[FaciaCardAndIndex],
hasMore: Boolean = false
) {
def transformCards(f: ContentCard => ContentCard): ContainerLayout = copy(
slices = slices.map(_.transformCards(f)),
remainingCards.map(cardAndIndex => cardAndIndex.transformCard(f))
)

def hasMobileShowMore: Boolean =
slices.flatMap(_.columns.flatMap(_.cards)).exists(_.hideUpTo.contains(Mobile))

def hasDesktopShowMore: Boolean = remainingCards.nonEmpty || hasMore

def hasShowMore: Boolean = hasDesktopShowMore || hasMobileShowMore
}
52 changes: 52 additions & 0 deletions common/app/layout/ContainerLayoutContext.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package layout

import views.support.CutOut
import scala.Function._

case class ContainerLayoutContext(
cutOutsSeen: Set[CutOut],
hideCutOuts: Boolean
) {
def addCutOuts(cutOut: Set[CutOut]): ContainerLayoutContext = copy(cutOutsSeen = cutOutsSeen ++ cutOut)

type CardAndContext = (ContentCard, ContainerLayoutContext)

private def dedupCutOut(cardAndContext: CardAndContext): CardAndContext = {
val (content, context) = cardAndContext

val maybeSnapType: Option[SnapType] = content.snapStuff.map(_.snapType)
if (maybeSnapType.contains(FrontendLatestSnap)) {
(content, context)
} else {
val newCard = if (content.cutOut.exists(cutOutsSeen.contains)) {
content.copy(cutOut = None)
} else {
content
}
(newCard, context.addCutOuts(content.cutOut.filter(const(content.cardTypes.showCutOut)).toSet))
}
}

private val transforms = Seq(
dedupCutOut _
).reduce(_ compose _)

def transform(card: FaciaCardAndIndex): (FaciaCardAndIndex, ContainerLayoutContext) = {
if (hideCutOuts) {
(card.transformCard(_.copy(cutOut = None)), this)
} else {
// Latest snaps are sometimes used to promote journalists, and the cut out should always show on these snaps.
card.item match {
case content: ContentCard =>
val (newCard, newContext) = transforms((content, this))
(card.copy(item = newCard), newContext)

case _ => (card, this)
}
}
}
}

object ContainerLayoutContext {
val empty = ContainerLayoutContext(Set.empty, hideCutOuts = false)
}
Loading