Skip to content

Commit

Permalink
Add a new experimental EPUB FXL navigator (#567)
Browse files Browse the repository at this point in the history
  • Loading branch information
qnga authored Nov 15, 2024
1 parent bbf59e9 commit 846a6f1
Show file tree
Hide file tree
Showing 132 changed files with 12,141 additions and 12 deletions.
34 changes: 32 additions & 2 deletions .github/workflows/checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ jobs:
- name: Lint
run: ./gradlew ktlintCheck

lint-js:
lint-legacy-js:
name: Lint JavaScript
runs-on: macos-latest
if: ${{ !github.event.pull_request.draft }}
Expand Down Expand Up @@ -67,5 +67,35 @@ jobs:
run: pnpm --dir "$scripts" run checkformat
- name: Check if bundled scripts are up-to-date
run: |
make scripts
make scripts-legacy
git diff --exit-code --name-only src/main/assets/readium/scripts/*.js
lint-new-ts:
name: Lint TypeScript
runs-on: macos-latest
if: ${{ !github.event.pull_request.draft }}
env:
scripts: ${{ 'readium/navigators/web/scripts' }}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
package_json_file: readium/navigators/web/scripts/package.json
run_install: false
- name: Setup cache
uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
cache-dependency-path: readium/navigators/web/scripts/pnpm-lock.yaml
- name: Install dependencies
run: pnpm --dir "$scripts" install --frozen-lockfile
- name: Lint
run: pnpm --dir "$scripts" run lint
- name: Check formatting
run: pnpm --dir "$scripts" run checkformat
- name: Check if bundled scripts are up-to-date
run: |
make scripts-new
git diff --exit-code --name-only readium/navigators/web/src/main/assets/*
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -94,3 +94,6 @@ lcp.patch

# Kotlin
.kotlin/

# Script outpouts
readium/navigators/web/scripts/dist/
41 changes: 41 additions & 0 deletions .idea/inspectionProfiles/Project_Default.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

18 changes: 17 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
SCRIPTS_PATH := readium/navigator/src/main/assets/_scripts
SCRIPTS_NAVIGATOR_WEB_PATH := readium/navigators/web/scripts

help:
@echo "Usage: make <target>\n\n\
Expand All @@ -16,7 +17,7 @@ format:
./gradlew ktlintFormat

.PHONY: scripts
scripts:
scripts-legacy:
@which corepack >/dev/null 2>&1 || (echo "ERROR: corepack is required, please install it first\nhttps://pnpm.io/installation#using-corepack"; exit 1)

cd $(SCRIPTS_PATH); \
Expand All @@ -25,3 +26,18 @@ scripts:
pnpm run format; \
pnpm run lint; \
pnpm run bundle

.PHONY: scripts
scripts-new:
@which corepack >/dev/null 2>&1 || (echo "ERROR: corepack is required, please install it first\nhttps://pnpm.io/installation#using-corepack"; exit 1)

cd $(SCRIPTS_NAVIGATOR_WEB_PATH); \
corepack install; \
pnpm install --frozen-lockfile; \
pnpm run format; \
pnpm run lint; \
pnpm run bundle; \
mv dist/* ../src/main/assets/readium/navigators/web/

.PHONY: scripts
scripts: scripts-legacy scripts-new
16 changes: 8 additions & 8 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,21 @@

kotlin = "2.0.21"
agp = "8.7.2"
desugar_jdk_libs = "2.0.4"
desugar_jdk_libs = "2.1.2"
gradle-maven-publish-plugin = "0.28.0"

androidx-activity = "1.9.3"
androidx-annotation = "1.9.1"
androidx-appcompat = "1.7.0"
androidx-browser = "1.8.0"
androidx-cardview = "1.0.0"
androidx-compose-animation = "1.6.7"
androidx-compose-foundation = "1.6.7"
androidx-compose-material = "1.6.7"
androidx-compose-material3 = "1.2.1"
androidx-compose-runtime = "1.6.7"
androidx-compose-ui = "1.6.7"
androidx-constraintlayout = "2.1.4"
androidx-compose-animation = "1.7.5"
androidx-compose-foundation = "1.7.5"
androidx-compose-material = "1.7.5"
androidx-compose-material3 = "1.3.1"
androidx-compose-runtime = "1.7.5"
androidx-compose-ui = "1.7.5"
androidx-constraintlayout = "2.2.0"
androidx-core = "1.15.0"
androidx-datastore = "1.1.1"
androidx-fragment-ktx = "1.8.5"
Expand Down
29 changes: 29 additions & 0 deletions readium/navigators/common/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
* Copyright 2022 Readium Foundation. All rights reserved.
* Use of this source code is governed by the BSD-style license
* available in the top-level LICENSE file of the project.
*/

plugins {
id("readium.library-conventions")
alias(libs.plugins.kotlin.serialization)
alias(libs.plugins.compose.compiler)
}

android {
namespace = "org.readium.navigators.common"

buildFeatures {
compose = true
}
}

dependencies {
api(project(":readium:readium-shared"))
api(project(":readium:readium-navigator"))

implementation(libs.kotlinx.serialization.json)
implementation(libs.bundles.compose)
implementation(libs.timber)
implementation(libs.kotlinx.coroutines.android)
}
1 change: 1 addition & 0 deletions readium/navigators/common/gradle.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pom.artifactId=readium-navigator-common
2 changes: 2 additions & 0 deletions readium/navigators/common/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest />
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
/*
* Copyright 2024 Readium Foundation. All rights reserved.
* Use of this source code is governed by the BSD-style license
* available in the top-level LICENSE file of the project.
*/

package org.readium.navigator.common

import androidx.compose.runtime.Composable
import androidx.compose.runtime.rememberCoroutineScope
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import org.readium.r2.shared.ExperimentalReadiumApi
import org.readium.r2.shared.util.AbsoluteUrl
import org.readium.r2.shared.util.Url

/**
* This listener lets you decide what to do when hyperlinks are activated, whether they point to
* a readingOrder item, a non-linear resource or external content.
*/
@ExperimentalReadiumApi
public interface HyperlinkListener {

public fun onReadingOrderLinkActivated(location: HyperlinkLocation, context: LinkContext?)

public fun onNonLinearLinkActivated(location: HyperlinkLocation, context: LinkContext?)

public fun onExternalLinkActivated(url: AbsoluteUrl, context: LinkContext?)
}

@ExperimentalReadiumApi
public data class HyperlinkLocation(
public val href: Url,
public val fragment: String? = null
)

@ExperimentalReadiumApi
public sealed interface LinkContext

@ExperimentalReadiumApi
public data class FootnoteContext(
public val noteContent: String
) : LinkContext

@ExperimentalReadiumApi
public class NullHyperlinkListener : HyperlinkListener {
override fun onReadingOrderLinkActivated(location: HyperlinkLocation, context: LinkContext?) {
}

override fun onNonLinearLinkActivated(location: HyperlinkLocation, context: LinkContext?) {
}

override fun onExternalLinkActivated(url: AbsoluteUrl, context: LinkContext?) {
}
}

/**
* A [HyperlinkListener] following links to readingOrder items.
*
* Activations of links to external content or non-linear items are ignored by default.
* To handle them, pass [onNonLinearLinkActivated] and [onExternalLinkActivated] delegates.
*/
@ExperimentalReadiumApi
@Composable
public fun <L : Location> defaultHyperlinkListener(
controller: NavigationController<L, *>,
shouldFollowReadingOrderLink: (HyperlinkLocation, LinkContext?) -> Boolean = { _, _ -> true },
onNonLinearLinkActivated: (HyperlinkLocation, LinkContext?) -> Unit = { _, _ -> },
onExternalLinkActivated: (AbsoluteUrl, LinkContext?) -> Unit = { _, _ -> }
): HyperlinkListener {
val coroutineScope = rememberCoroutineScope()

return DefaultHyperlinkListener(
coroutineScope = coroutineScope,
controller = controller,
shouldFollowReadingOrderLink = shouldFollowReadingOrderLink,
onNonLinearLinkActivatedDelegate = onNonLinearLinkActivated,
onExternalLinkActivatedDelegate = onExternalLinkActivated
)
}

@ExperimentalReadiumApi
private class DefaultHyperlinkListener<L : Location>(
private val coroutineScope: CoroutineScope,
private val controller: NavigationController<L, *>,
private val shouldFollowReadingOrderLink: (HyperlinkLocation, LinkContext?) -> Boolean,
private val onNonLinearLinkActivatedDelegate: (HyperlinkLocation, LinkContext?) -> Unit,
private val onExternalLinkActivatedDelegate: (AbsoluteUrl, LinkContext?) -> Unit
) : HyperlinkListener {

override fun onReadingOrderLinkActivated(location: HyperlinkLocation, context: LinkContext?) {
if (shouldFollowReadingOrderLink(location, context)) {
coroutineScope.launch { controller.goTo(location) }
}
}

override fun onNonLinearLinkActivated(location: HyperlinkLocation, context: LinkContext?) {
onNonLinearLinkActivatedDelegate(location, context)
}

override fun onExternalLinkActivated(url: AbsoluteUrl, context: LinkContext?) {
onExternalLinkActivatedDelegate(url, context)
}
}
Loading

0 comments on commit 846a6f1

Please sign in to comment.