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

Loading resources from workbook #453

Merged
merged 18 commits into from
Jun 3, 2019
Merged
Show file tree
Hide file tree
Changes from 9 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
3 changes: 3 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,9 @@ dependencies {
implementation 'org.wycliffeassociates:8woc2018-common'
implementation 'org.wycliffeassociates:kotlin-resource-container'
implementation 'com.github.WycliffeAssociates:jdenticon-kotlin:-SNAPSHOT'

//Atlassian commonmark (for rendering markdown)
implementation 'com.atlassian.commonmark:commonmark:0.12.1'
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Uses the BSD 2-clause license. Should be fine, but I'm not sure where/how we're keeping track of licenses.

}

//tell gradle what to put in the jar
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package org.wycliffeassociates.otter.jvm.app.ui.resources.view

import org.wycliffeassociates.otter.jvm.app.ui.mainscreen.view.MainScreenStyles
import org.wycliffeassociates.otter.jvm.app.widgets.workbookheader.workbookheader
import org.wycliffeassociates.otter.jvm.app.widgets.resourcecard.styles.ResourceListStyles
import org.wycliffeassociates.otter.jvm.app.widgets.resourcecard.view.ResourceListView
import org.wycliffeassociates.otter.jvm.app.ui.resources.viewmodel.ResourcesViewModel
import tornadofx.*

class ResourceListFragment : Fragment() {
val viewModel: ResourcesViewModel by inject()

init {
importStylesheet<MainScreenStyles>()
importStylesheet<ResourceListStyles>()
}
override val root = vbox {

addClass(MainScreenStyles.main)

add(
workbookheader {
labelText = viewModel.chapter.title + " Resources"
filterText = "Hide Completed"
aunger marked this conversation as resolved.
Show resolved Hide resolved
}
)
add(
ResourceListView(viewModel.resourceGroups)
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package org.wycliffeassociates.otter.jvm.app.ui.resources.viewmodel

import javafx.application.Platform
import javafx.beans.property.SimpleObjectProperty
import javafx.beans.property.SimpleStringProperty
import javafx.collections.FXCollections
import javafx.collections.ObservableList
import org.wycliffeassociates.otter.common.data.workbook.*
import org.wycliffeassociates.otter.jvm.app.widgets.resourcecard.model.ResourceCardItem
import org.wycliffeassociates.otter.jvm.app.widgets.resourcecard.model.ResourceGroupCardItem
import tornadofx.*

class ResourcesViewModel : ViewModel() {
val activeWorkbookProperty = SimpleObjectProperty<Workbook>()
val workbook: Workbook
get() = activeWorkbookProperty.value

val activeChapterProperty = SimpleObjectProperty<Chapter>()
val chapter: Chapter
get() = activeChapterProperty.value

val activeResourceSlugProperty = SimpleStringProperty()
val resourceSlug: String
get() = activeResourceSlugProperty.value

var resourceGroups: ObservableList<ResourceGroupCardItem> = FXCollections.observableArrayList()

fun loadResourceGroups() {
chapter.chunks.map {
resourceGroupCardItem(it, resourceSlug)
}.startWith(
resourceGroupCardItem(chapter, resourceSlug)
).buffer(2).subscribe { // Buffering by 2 prevents the list UI from jumping while groups are loading
aunger marked this conversation as resolved.
Show resolved Hide resolved
Platform.runLater {
resourceGroups.addAll(it)
}
}
}

private fun resourceGroupCardItem(element: BookElement, slug: String): ResourceGroupCardItem? {
val resourceGroup = element.resources.firstOrNull {
it.info.slug == slug
}
return resourceGroup?.let { rg ->
ResourceGroupCardItem(
element.title,
rg.resources.map {
ResourceCardItem(it) {
navigateToTakesPage(it)
}
}
)
}
}

private fun navigateToTakesPage(resource: Resource) {
// TODO use navigator
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,33 @@
package org.wycliffeassociates.otter.jvm.app.widgets.resourcecard.model

data class ResourceCardItem(
val title: String
)
import javafx.beans.property.DoubleProperty
import javafx.beans.property.SimpleDoubleProperty
import org.wycliffeassociates.otter.common.data.workbook.AssociatedAudio
import org.wycliffeassociates.otter.common.data.workbook.Resource
import org.commonmark.parser.Parser
import org.commonmark.renderer.text.TextContentRenderer

data class ResourceCardItem(val resource: Resource, val onSelect: () -> Unit) {
val title: String = getTitleTextContent()
val titleProgressProperty: DoubleProperty = resource.titleAudio.progressProperty()
val bodyProgressProperty: DoubleProperty? = resource.bodyAudio?.progressProperty()
val hasBodyAudio: Boolean = resource.bodyAudio != null

private fun AssociatedAudio.progressProperty(): DoubleProperty {
val progressProperty = SimpleDoubleProperty(0.0)
this.selected.subscribe {
progressProperty.set( if (it.value != null) 1.0 else 0.0)
}
return progressProperty
aunger marked this conversation as resolved.
Show resolved Hide resolved
}

companion object {
val parser: Parser = Parser.builder().build()
val renderer: TextContentRenderer = TextContentRenderer.builder().build()
}

private fun getTitleTextContent(): String {
val document = parser.parse(resource.title.text)
return renderer.render(document)
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not really a getter, so I'd rename this to something like "renderTitleAsHtml".

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not needed yet, but the second time you need to transform MD to HTML, you should probably pull this commonmark stuff out into an dagger-injectable MarkdownToHtml singleton.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good points. I'm actually just rendering the title as plain text though and saving html rendering for the body when it appears on the takes page.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@SarabiaJ I would like to talk to you about dagger-injectable components like this, as opposed to an object in common, like ParseMd.kt.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd prefer to keep dagger to injection in the framework classes (Android Activities/Fragments, TornadoFX Views/ Fragments, maybe viewmodels)

for a component like this, I'd rather you just pass the dependency into the constructor rather than make dagger do it for you

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure. In this case, it seems like this can be solved without passing a dependency into the constructor. I think I can just create a MarkdownToHtml object like ParseMd that basically just holds static functions to do what I need.

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package org.wycliffeassociates.otter.jvm.app.widgets.resourcecard.styles

import javafx.scene.paint.Color
import tornadofx.*

typealias LinearU = Dimension<Dimension.LinearUnits>

class ResourceListStyles : Stylesheet() {

companion object {
val resourceGroupList by cssclass()
}

init {
resourceGroupList {
borderColor += box(Color.TRANSPARENT) // Necessary for border under status bar banner to stay visible
padding = box(0.px, 0.px, 0.px, 80.px) // Left "margin"
scrollBar {
+margin(0.px, 0.px, 0.px, 80.px) // Margin between scrollbar and right side of cards
}

listCell {
// Add space between the cards (top margin)
// But need to make the "margin" at least as large as the dropshadow offsets
+margin(30.px, 4.px, 0.px, 0.px)
}
}
}

private fun margin(top: LinearU, right: LinearU, bottom: LinearU, left: LinearU) = mixin {
padding = box(top, right, bottom, left)
backgroundInsets += box(top, right, bottom, left)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,7 @@ import org.wycliffeassociates.otter.jvm.statusindicator.control.statusindicator
import tornadofx.*
import tornadofx.FX.Companion.messages

class ResourceCard(private val resource: ResourceCardItem) : HBox() {

class ResourceCard(private val item: ResourceCardItem) : HBox() {
val isCurrentResourceProperty = SimpleBooleanProperty(false)
var primaryColorProperty = SimpleObjectProperty<Color>(Color.ORANGE)
var primaryColor: Color by primaryColorProperty
Expand All @@ -33,17 +32,18 @@ class ResourceCard(private val resource: ResourceCardItem) : HBox() {
add(
statusindicator {
initForResourceCard()
progress = 1.0
progressProperty.bind(item.titleProgressProperty)
}
)
add(
statusindicator {
initForResourceCard()
progress = 0.0
item.bodyProgressProperty?.let { progressProperty.bind(it) }
isVisible = item.hasBodyAudio
}
)
}
text(resource.title)
text(item.title)
maxWidth = 150.0
}

Expand All @@ -59,6 +59,9 @@ class ResourceCard(private val resource: ResourceCardItem) : HBox() {
graphic = MaterialIconView(MaterialIcon.APPS, "25px")
maxWidth = 500.0
text = messages["viewRecordings"]
action {
item.onSelect()
}
}
)
}
Expand All @@ -70,7 +73,6 @@ class ResourceCard(private val resource: ResourceCardItem) : HBox() {
trackFill = Color.LIGHTGRAY
indicatorRadius = 3.0
}

}

fun resourcecard(resource: ResourceCardItem, init: ResourceCard.() -> Unit = {}): ResourceCard {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package org.wycliffeassociates.otter.jvm.app.widgets.resourcecard.view

import javafx.application.Platform
import javafx.scene.layout.VBox
import org.wycliffeassociates.otter.jvm.app.widgets.resourcecard.model.ResourceGroupCardItem
import tornadofx.*
Expand All @@ -10,10 +11,15 @@ class ResourceGroupCard(group: ResourceGroupCardItem) : VBox() {

addClass(ResourceGroupCardStyles.resourceGroupCard)
label(group.title)
group.resources.subscribe {
add(
resourcecard(it)
)

group.resources.buffer(10).subscribe { items ->
aunger marked this conversation as resolved.
Show resolved Hide resolved
Platform.runLater {
items.forEach {
add(
resourcecard(it)
)
}
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import javafx.scene.text.FontWeight
import tornadofx.*

class ResourceGroupCardStyles : Stylesheet() {

companion object {
val resourceGroupCard by cssclass()
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package org.wycliffeassociates.otter.jvm.app.widgets.resourcecard.view

import javafx.collections.ObservableList
import javafx.scene.control.ListView
import javafx.scene.layout.Priority
import org.wycliffeassociates.otter.jvm.app.widgets.resourcecard.model.ResourceGroupCardItem
import org.wycliffeassociates.otter.jvm.app.widgets.resourcecard.styles.ResourceListStyles
import tornadofx.*

class ResourceListView(items: ObservableList<ResourceGroupCardItem>): ListView<ResourceGroupCardItem>(items) {
init {
vgrow = Priority.ALWAYS // This needs to be here
jsarabia marked this conversation as resolved.
Show resolved Hide resolved
cellFormat {
graphic = cache(it.title) {
resourcegroupcard(it)
}
}
isFocusTraversable = false
addClass(ResourceListStyles.resourceGroupList)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,16 @@ import io.reactivex.rxkotlin.toObservable
import io.reactivex.schedulers.Schedulers
import jooq.Tables.*
import org.jooq.Condition
import org.jooq.SelectConditionStep
import org.jooq.Record
import org.jooq.DSLContext
import org.wycliffeassociates.otter.common.collections.multimap.MultiMap
import org.wycliffeassociates.otter.common.data.model.Collection
import org.wycliffeassociates.otter.common.data.model.Content
import org.wycliffeassociates.otter.common.data.workbook.ResourceInfo
import org.wycliffeassociates.otter.common.persistence.repositories.IResourceRepository
import org.wycliffeassociates.otter.jvm.persistence.database.AppDatabase
import org.wycliffeassociates.otter.jvm.persistence.database.daos.ContentEntityTable
import org.wycliffeassociates.otter.jvm.persistence.database.daos.RecordMappers
import org.wycliffeassociates.otter.jvm.persistence.entities.CollectionEntity
import org.wycliffeassociates.otter.jvm.persistence.entities.ContentEntity
Expand Down Expand Up @@ -70,9 +73,8 @@ class ResourceRepository(private val database: AppDatabase) : IResourceRepositor
return database.dsl
.selectDistinct(DUBLIN_CORE_ENTITY.asterisk())
.from(RESOURCE_LINK)
.join(CONTENT_ENTITY).on(CONTENT_ENTITY.ID.eq(RESOURCE_LINK.CONTENT_FK))
.join(DUBLIN_CORE_ENTITY).on(DUBLIN_CORE_ENTITY.ID.eq(RESOURCE_LINK.DUBLIN_CORE_FK))
.where(CONTENT_ENTITY.COLLECTION_FK.eq(collection.id))
.where(RESOURCE_LINK.COLLECTION_FK.eq(collection.id))
.fetch(RecordMappers.Companion::mapToResourceMetadataEntity)
.map(this::buildResourceInfo)
}
Expand All @@ -87,14 +89,41 @@ class ResourceRepository(private val database: AppDatabase) : IResourceRepositor
.map(this::buildResourceInfo)
}

override fun getResources(collection: Collection, resourceInfo: ResourceInfo): Observable<Content> {
/**
* Returns all resources for which the resource content's COLLECTION_FK field references this collection.
* This will return resources about the chapter as well as all resources about the chapter's chunks.
*/
override fun getResourcesForCollectionAndChildren(
aunger marked this conversation as resolved.
Show resolved Hide resolved
collection: Collection,
resourceInfo: ResourceInfo
): Observable<Content> {
return getResources({ table -> table.COLLECTION_FK.eq(collection.id) }, resourceInfo)
}

override fun getResources(content: Content, resourceInfo: ResourceInfo): Observable<Content> {
return getResources({ table -> table.ID.eq(content.id) }, resourceInfo)
}

/**
* Returns collection-specific resources
*/
override fun getResources(collection: Collection, resourceInfo: ResourceInfo): Observable<Content> {
aunger marked this conversation as resolved.
Show resolved Hide resolved
val metadata = mapToResourceMetadataEntity[resourceInfo]
?: return Observable.empty()

val help = CONTENT_ENTITY.`as`("help")

val selectStatement = database.dsl
.selectDistinct(help.asterisk())
.from(RESOURCE_LINK)
.join(COLLECTION_ENTITY).on(COLLECTION_ENTITY.ID.eq(RESOURCE_LINK.COLLECTION_FK))
.join(help).on(RESOURCE_LINK.RESOURCE_CONTENT_FK.eq(help.ID))
.where(RESOURCE_LINK.DUBLIN_CORE_FK.eq(metadata.id))
.and(COLLECTION_ENTITY.ID.eq(collection.id))

return getResources(help, selectStatement)
}

aunger marked this conversation as resolved.
Show resolved Hide resolved
private fun getResources(
condition: (jooq.tables.ContentEntity) -> Condition,
resourceInfo: ResourceInfo
Expand All @@ -105,14 +134,23 @@ class ResourceRepository(private val database: AppDatabase) : IResourceRepositor
val main = CONTENT_ENTITY.`as`("main")
val help = CONTENT_ENTITY.`as`("help")

val selectStatement = database.dsl
.selectDistinct(help.asterisk())
.from(RESOURCE_LINK)
.join(main).on(main.ID.eq(RESOURCE_LINK.CONTENT_FK))
.join(help).on(help.ID.eq(RESOURCE_LINK.RESOURCE_CONTENT_FK))
.where(RESOURCE_LINK.DUBLIN_CORE_FK.eq(metadata.id))
.and(condition(main))

return getResources(help, selectStatement)
}

private fun getResources(
help: ContentEntityTable,
selectStatement: SelectConditionStep<Record>
): Observable<Content> {
val contentStreamObservable = Observable.fromCallable {
database.dsl
.selectDistinct(help.asterisk())
.from(RESOURCE_LINK)
.join(main).on(main.ID.eq(RESOURCE_LINK.CONTENT_FK))
.join(help).on(help.ID.eq(RESOURCE_LINK.RESOURCE_CONTENT_FK))
.where(RESOURCE_LINK.DUBLIN_CORE_FK.eq(metadata.id))
.and(condition(main))
selectStatement
.orderBy(help.START, help.SORT)
.fetchStream()
.map { RecordMappers.mapToContentEntity(it, help) }
Expand Down
1 change: 1 addition & 0 deletions src/main/resources/Messages_en.properties
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ load = Load
open = Open
record = RECORD
viewTakes =VIEW TAKES
viewRecordings = VIEW RECORDINGS
edit = EDIT
chapter = Chapter
verse=Verse
Expand Down