Skip to content

Commit

Permalink
Merge pull request #700 from CruGlobal/cyoaPageCollections
Browse files Browse the repository at this point in the history
GT-2450 Add support for PageCollectionPages in CYOA tools
  • Loading branch information
frett authored Oct 29, 2024
2 parents b1461ed + 7311a03 commit ea8f779
Show file tree
Hide file tree
Showing 20 changed files with 698 additions and 87 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ data class ParserConfig private constructor(
const val FEATURE_CONTENT_CARD = "content_card"
const val FEATURE_FLOW = "flow"
const val FEATURE_MULTISELECT = "multiselect"
const val FEATURE_PAGE_COLLECTION = "page-collection"
internal const val FEATURE_REQUIRED_VERSIONS = "required-versions"
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package org.cru.godtools.shared.tool.parser.model

import kotlin.experimental.ExperimentalObjCRefinement
import kotlin.js.ExperimentalJsExport
import kotlin.js.JsExport
import kotlin.js.JsName
import kotlin.native.HiddenFromObjC
import kotlin.reflect.KClass
import org.ccci.gto.support.androidx.annotation.RestrictTo
import org.ccci.gto.support.androidx.annotation.RestrictToScope
import org.cru.godtools.shared.tool.parser.model.page.Page

@JsExport
@OptIn(ExperimentalJsExport::class, ExperimentalObjCRefinement::class)
interface HasPages : Base {
@JsName("_pages")
val pages: List<Page>

fun findPage(id: String?) = id?.let { pages.find { it.id == id } }

@HiddenFromObjC
@JsExport.Ignore
@RestrictTo(RestrictToScope.LIBRARY)
fun <T : Page> supportsPageType(type: KClass<T>): Boolean

// region Kotlin/JS interop
@HiddenFromObjC
@JsName("pages")
val jsPages get() = pages.toTypedArray()
// endregion Kotlin/JS interop
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import kotlin.js.ExperimentalJsExport
import kotlin.js.JsExport
import kotlin.js.JsName
import kotlin.native.HiddenFromObjC
import kotlin.reflect.KClass
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.coroutineScope
Expand All @@ -18,6 +19,7 @@ import org.cru.godtools.shared.common.model.Uri
import org.cru.godtools.shared.common.model.isHttpUrl
import org.cru.godtools.shared.common.model.toUriOrNull
import org.cru.godtools.shared.tool.parser.ParserConfig
import org.cru.godtools.shared.tool.parser.ParserConfig.Companion.FEATURE_PAGE_COLLECTION
import org.cru.godtools.shared.tool.parser.internal.AndroidColorInt
import org.cru.godtools.shared.tool.parser.internal.DeprecationException
import org.cru.godtools.shared.tool.parser.internal.fluidlocale.toLocaleOrNull
Expand All @@ -27,15 +29,20 @@ import org.cru.godtools.shared.tool.parser.model.Multiselect.Companion.XML_MULTI
import org.cru.godtools.shared.tool.parser.model.Multiselect.Companion.XML_MULTISELECT_OPTION_SELECTED_COLOR
import org.cru.godtools.shared.tool.parser.model.Styles.Companion.DEFAULT_TEXT_SCALE
import org.cru.godtools.shared.tool.parser.model.lesson.DEFAULT_LESSON_NAV_BAR_COLOR
import org.cru.godtools.shared.tool.parser.model.lesson.LessonPage
import org.cru.godtools.shared.tool.parser.model.lesson.XMLNS_LESSON
import org.cru.godtools.shared.tool.parser.model.page.CardCollectionPage
import org.cru.godtools.shared.tool.parser.model.page.ContentPage
import org.cru.godtools.shared.tool.parser.model.page.DEFAULT_CONTROL_COLOR
import org.cru.godtools.shared.tool.parser.model.page.Page
import org.cru.godtools.shared.tool.parser.model.page.PageCollectionPage
import org.cru.godtools.shared.tool.parser.model.page.XMLNS_PAGE
import org.cru.godtools.shared.tool.parser.model.page.XML_CONTROL_COLOR
import org.cru.godtools.shared.tool.parser.model.shareable.Shareable
import org.cru.godtools.shared.tool.parser.model.shareable.Shareable.Companion.parseShareableItems
import org.cru.godtools.shared.tool.parser.model.shareable.XMLNS_SHAREABLE
import org.cru.godtools.shared.tool.parser.model.tips.Tip
import org.cru.godtools.shared.tool.parser.model.tract.TractPage
import org.cru.godtools.shared.tool.parser.util.setOnce
import org.cru.godtools.shared.tool.parser.xml.XmlPullParser
import org.cru.godtools.shared.tool.parser.xml.parseChildren
Expand Down Expand Up @@ -67,7 +74,7 @@ private const val XML_TIPS_TIP_SRC = "src"

@JsExport
@OptIn(ExperimentalJsExport::class, ExperimentalObjCRefinement::class)
class Manifest : BaseModel, Styles {
class Manifest : BaseModel, Styles, HasPages {
internal companion object {
@AndroidColorInt
internal val DEFAULT_PRIMARY_COLOR = color(59, 164, 219, 1.0)
Expand All @@ -94,8 +101,8 @@ class Manifest : BaseModel, Styles {
// parse pages
if (config.parsePages) {
launch {
manifest.pages = manifest.pagesToParse
.map { (fileName, src) -> async { Page.parse(manifest, fileName, parseFile(src)) } }
manifest.pages = manifest.pageXmlFiles
.map { (name, src) -> async { Page.parse(manifest, name, parseFile(src), parseFile) } }
.awaitAll().filterNotNull()
}
} else {
Expand All @@ -105,8 +112,8 @@ class Manifest : BaseModel, Styles {
// parse tips
if (config.parseTips) {
launch {
manifest.tips = manifest.tipsToParse
.map { (id, src) -> async { Tip(manifest, id, parseFile(src)) } }
manifest.tips = manifest.tipXmlFiles
.map { (id, src) -> async { Tip(manifest, id.orEmpty(), parseFile(src)) } }
.awaitAll()
.associateBy { it.id }
}
Expand Down Expand Up @@ -182,8 +189,7 @@ class Manifest : BaseModel, Styles {

val aemImports: List<Uri>
val categories: List<Category>
@JsName("_pages")
var pages: List<Page> by setOnce()
override var pages: List<Page> by setOnce()
private set
@VisibleForTesting
internal val resources: Map<String?, Resource>
Expand All @@ -192,12 +198,12 @@ class Manifest : BaseModel, Styles {
internal var tips: Map<String, Tip> by setOnce()
private set

private val pagesToParse: List<Pair<String?, String>>
private val tipsToParse: List<Pair<String, String>>
internal val pageXmlFiles: List<XmlFile>
private val tipXmlFiles: List<XmlFile>

val relatedFiles get() = buildSet {
addAll(pagesToParse.map { it.second })
addAll(tipsToParse.map { it.second })
addAll(pageXmlFiles.map { it.src })
addAll(tipXmlFiles.map { it.src })
addAll(resources.values.mapNotNull { it.localName })
}

Expand Down Expand Up @@ -249,8 +255,8 @@ class Manifest : BaseModel, Styles {
categories = mutableListOf()
resources = mutableMapOf()
val shareables = mutableListOf<Shareable>()
pagesToParse = mutableListOf()
tipsToParse = mutableListOf()
pageXmlFiles = mutableListOf()
tipXmlFiles = mutableListOf()
parser.parseChildren {
@Suppress("ktlint:standard:blank-line-between-when-conditions")
when (parser.namespace) {
Expand All @@ -260,10 +266,10 @@ class Manifest : BaseModel, Styles {
XML_PAGES -> {
val result = parser.parsePages()
aemImports += result.aemImports
pagesToParse += result.pages
pageXmlFiles += result.pages
}
XML_RESOURCES -> resources += parser.parseResources().associateBy { it.name }
XML_TIPS -> tipsToParse += parser.parseTips()
XML_TIPS -> tipXmlFiles += parser.parseTips()
}

XMLNS_SHAREABLE -> when (parser.name) {
Expand Down Expand Up @@ -298,7 +304,8 @@ class Manifest : BaseModel, Styles {
resources: ((Manifest) -> List<Resource>)? = null,
shareables: ((Manifest) -> List<Shareable>)? = null,
tips: ((Manifest) -> List<Tip>)? = null,
pages: ((Manifest) -> List<Page>)? = null
pages: ((Manifest) -> List<Page>)? = null,
pageXmlFiles: List<XmlFile> = emptyList(),
) {
this.config = config

Expand Down Expand Up @@ -338,22 +345,34 @@ class Manifest : BaseModel, Styles {
this.shareables = shareables?.invoke(this).orEmpty()
this.tips = tips?.invoke(this)?.associateBy { it.id }.orEmpty()

pagesToParse = emptyList()
tipsToParse = emptyList()
this.pageXmlFiles = pageXmlFiles
tipXmlFiles = emptyList()
}

override val manifest get() = this
val hasTips get() = tips.isNotEmpty() || (!config.parseTips && tipsToParse.isNotEmpty())
val hasTips get() = tips.isNotEmpty() || (!config.parseTips && tipXmlFiles.isNotEmpty())
internal fun getResource(name: String?) = name?.let { resources[name] }

@JsExport.Ignore
fun findCategory(category: String?) = categories.firstOrNull { it.id == category }
fun findPage(id: String?) = id?.let { pages.firstOrNull { it.id == id } }
@JsExport.Ignore
fun findShareable(id: String?) = id?.let { shareables.firstOrNull { it.id == id } }
@JsExport.Ignore
fun findTip(id: String?) = tips[id]

override fun <T : Page> supportsPageType(type: KClass<T>) = when (this.type) {
Type.ARTICLE -> false
Type.CYOA -> when (type) {
CardCollectionPage::class,
ContentPage::class -> true
PageCollectionPage::class -> config.supportsFeature(FEATURE_PAGE_COLLECTION)
else -> false
}
Type.LESSON -> type == LessonPage::class
Type.TRACT -> type == TractPage::class
Type.UNKNOWN -> false
}

private fun XmlPullParser.parseCategories() = buildList {
require(XmlPullParser.START_TAG, XMLNS_MANIFEST, XML_CATEGORIES)
parseChildren {
Expand All @@ -367,7 +386,7 @@ class Manifest : BaseModel, Styles {

private class PagesData {
val aemImports = mutableListOf<Uri>()
val pages = mutableListOf<Pair<String?, String>>()
val pages = mutableListOf<XmlFile>()
}

private fun XmlPullParser.parsePages() = PagesData().also { result ->
Expand All @@ -380,7 +399,7 @@ class Manifest : BaseModel, Styles {
XML_PAGES_PAGE -> {
val src = getAttributeValue(XML_PAGES_PAGE_SRC) ?: return@parseChildren
val fileName = getAttributeValue(XML_PAGES_PAGE_FILENAME)
result.pages += fileName to src
result.pages += XmlFile(fileName, src)
}
}

Expand Down Expand Up @@ -411,7 +430,7 @@ class Manifest : BaseModel, Styles {
XML_TIPS_TIP -> {
val id = getAttributeValue(XML_TIPS_TIP_ID) ?: return@parseChildren
val src = getAttributeValue(XML_TIPS_TIP_SRC) ?: return@parseChildren
add(id to src)
add(XmlFile(id, src))
}
}
}
Expand All @@ -422,10 +441,6 @@ class Manifest : BaseModel, Styles {
@HiddenFromObjC
@JsName("dismissListeners")
val jsDismissListeners get() = dismissListeners.toTypedArray()

@HiddenFromObjC
@JsName("pages")
val jsPages get() = pages.toTypedArray()
// endregion Kotlin/JS interop

enum class Type {
Expand All @@ -448,6 +463,8 @@ class Manifest : BaseModel, Styles {
}
}
}

data class XmlFile(internal val name: String?, internal val src: String)
}

@get:AndroidColorInt
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import org.cru.godtools.shared.tool.parser.model.AnalyticsEvent
import org.cru.godtools.shared.tool.parser.model.AnalyticsEvent.Companion.parseAnalyticsEvents
import org.cru.godtools.shared.tool.parser.model.Content
import org.cru.godtools.shared.tool.parser.model.Gravity
import org.cru.godtools.shared.tool.parser.model.HasPages
import org.cru.godtools.shared.tool.parser.model.ImageScaleType
import org.cru.godtools.shared.tool.parser.model.Manifest
import org.cru.godtools.shared.tool.parser.model.Parent
Expand All @@ -27,10 +28,10 @@ class LessonPage : Page, Parent {
override val content: List<Content>

internal constructor(
manifest: Manifest,
container: HasPages,
fileName: String?,
parser: XmlPullParser
) : super(manifest, fileName, parser) {
) : super(container, fileName, parser) {
parser.require(XmlPullParser.START_TAG, XMLNS_LESSON, XML_PAGE)

analyticsEvents = mutableListOf()
Expand Down Expand Up @@ -71,6 +72,4 @@ class LessonPage : Page, Parent {

content = emptyList()
}

override fun supports(type: Manifest.Type) = type == Manifest.Type.LESSON
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import org.cru.godtools.shared.tool.parser.model.AnalyticsEvent.Trigger
import org.cru.godtools.shared.tool.parser.model.BaseModel
import org.cru.godtools.shared.tool.parser.model.Content
import org.cru.godtools.shared.tool.parser.model.HasAnalyticsEvents
import org.cru.godtools.shared.tool.parser.model.HasPages
import org.cru.godtools.shared.tool.parser.model.Manifest
import org.cru.godtools.shared.tool.parser.model.Parent
import org.cru.godtools.shared.tool.parser.model.PlatformColor
Expand All @@ -32,10 +33,10 @@ class CardCollectionPage : Page {
val cards: List<Card>

internal constructor(
manifest: Manifest,
container: HasPages,
fileName: String?,
parser: XmlPullParser
) : super(manifest, fileName, parser) {
) : super(container, fileName, parser) {
parser.require(XmlPullParser.START_TAG, XMLNS_PAGE, XML_PAGE)
parser.requirePageType(TYPE_CARD_COLLECTION)

Expand Down Expand Up @@ -72,8 +73,6 @@ class CardCollectionPage : Page {
cards = emptyList()
}

override fun supports(type: Manifest.Type) = type == Manifest.Type.CYOA

class Card : BaseModel, Parent, HasAnalyticsEvents {
internal companion object {
internal const val XML_CARD = "card"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import org.ccci.gto.support.androidx.annotation.RestrictToScope
import org.cru.godtools.shared.tool.parser.model.AnalyticsEvent
import org.cru.godtools.shared.tool.parser.model.AnalyticsEvent.Companion.parseAnalyticsEvents
import org.cru.godtools.shared.tool.parser.model.Content
import org.cru.godtools.shared.tool.parser.model.Manifest
import org.cru.godtools.shared.tool.parser.model.HasPages
import org.cru.godtools.shared.tool.parser.model.Parent
import org.cru.godtools.shared.tool.parser.model.XMLNS_ANALYTICS
import org.cru.godtools.shared.tool.parser.model.parseContent
Expand All @@ -23,10 +23,10 @@ class ContentPage : Page, Parent {
override val content: List<Content>

internal constructor(
manifest: Manifest,
container: HasPages,
fileName: String?,
parser: XmlPullParser
) : super(manifest, fileName, parser) {
) : super(container, fileName, parser) {
parser.require(XmlPullParser.START_TAG, XMLNS_PAGE, XML_PAGE)
parser.requirePageType(TYPE_CONTENT)

Expand All @@ -47,13 +47,11 @@ class ContentPage : Page, Parent {

@RestrictTo(RestrictToScope.TESTS)
internal constructor(
manifest: Manifest,
container: HasPages,
id: String? = null,
parentPage: String? = null
) : super(manifest, id = id, parentPage = parentPage) {
) : super(container, id = id, parentPage = parentPage) {
analyticsEvents = emptyList()
content = emptyList()
}

override fun supports(type: Manifest.Type) = type == Manifest.Type.CYOA
}
Loading

0 comments on commit ea8f779

Please sign in to comment.