Skip to content

Commit

Permalink
Add member icons to navigation menu (#2578)
Browse files Browse the repository at this point in the history
  • Loading branch information
IgnatBeresnev committed Jul 29, 2022
1 parent 26dde5b commit 7a875ee
Show file tree
Hide file tree
Showing 25 changed files with 757 additions and 538 deletions.
30 changes: 26 additions & 4 deletions plugins/base/api/base.api
Expand Up @@ -400,22 +400,44 @@ public abstract class org/jetbrains/dokka/base/renderers/html/NavigationDataProv
}

public final class org/jetbrains/dokka/base/renderers/html/NavigationNode : org/jetbrains/dokka/model/WithChildren {
public fun <init> (Ljava/lang/String;Lorg/jetbrains/dokka/links/DRI;Ljava/util/Set;Ljava/util/List;)V
public fun <init> (Ljava/lang/String;Lorg/jetbrains/dokka/links/DRI;Ljava/util/Set;Lorg/jetbrains/dokka/base/renderers/html/NavigationNodeIcon;Ljava/util/List;)V
public final fun component1 ()Ljava/lang/String;
public final fun component2 ()Lorg/jetbrains/dokka/links/DRI;
public final fun component3 ()Ljava/util/Set;
public final fun component4 ()Ljava/util/List;
public final fun copy (Ljava/lang/String;Lorg/jetbrains/dokka/links/DRI;Ljava/util/Set;Ljava/util/List;)Lorg/jetbrains/dokka/base/renderers/html/NavigationNode;
public static synthetic fun copy$default (Lorg/jetbrains/dokka/base/renderers/html/NavigationNode;Ljava/lang/String;Lorg/jetbrains/dokka/links/DRI;Ljava/util/Set;Ljava/util/List;ILjava/lang/Object;)Lorg/jetbrains/dokka/base/renderers/html/NavigationNode;
public final fun component4 ()Lorg/jetbrains/dokka/base/renderers/html/NavigationNodeIcon;
public final fun component5 ()Ljava/util/List;
public final fun copy (Ljava/lang/String;Lorg/jetbrains/dokka/links/DRI;Ljava/util/Set;Lorg/jetbrains/dokka/base/renderers/html/NavigationNodeIcon;Ljava/util/List;)Lorg/jetbrains/dokka/base/renderers/html/NavigationNode;
public static synthetic fun copy$default (Lorg/jetbrains/dokka/base/renderers/html/NavigationNode;Ljava/lang/String;Lorg/jetbrains/dokka/links/DRI;Ljava/util/Set;Lorg/jetbrains/dokka/base/renderers/html/NavigationNodeIcon;Ljava/util/List;ILjava/lang/Object;)Lorg/jetbrains/dokka/base/renderers/html/NavigationNode;
public fun equals (Ljava/lang/Object;)Z
public fun getChildren ()Ljava/util/List;
public final fun getDri ()Lorg/jetbrains/dokka/links/DRI;
public final fun getIcon ()Lorg/jetbrains/dokka/base/renderers/html/NavigationNodeIcon;
public final fun getName ()Ljava/lang/String;
public final fun getSourceSets ()Ljava/util/Set;
public fun hashCode ()I
public fun toString ()Ljava/lang/String;
}

public final class org/jetbrains/dokka/base/renderers/html/NavigationNodeIcon : java/lang/Enum {
public static final field ABSTRACT_CLASS Lorg/jetbrains/dokka/base/renderers/html/NavigationNodeIcon;
public static final field ABSTRACT_CLASS_KT Lorg/jetbrains/dokka/base/renderers/html/NavigationNodeIcon;
public static final field ANNOTATION_CLASS Lorg/jetbrains/dokka/base/renderers/html/NavigationNodeIcon;
public static final field ANNOTATION_CLASS_KT Lorg/jetbrains/dokka/base/renderers/html/NavigationNodeIcon;
public static final field CLASS Lorg/jetbrains/dokka/base/renderers/html/NavigationNodeIcon;
public static final field CLASS_KT Lorg/jetbrains/dokka/base/renderers/html/NavigationNodeIcon;
public static final field ENUM_CLASS Lorg/jetbrains/dokka/base/renderers/html/NavigationNodeIcon;
public static final field ENUM_CLASS_KT Lorg/jetbrains/dokka/base/renderers/html/NavigationNodeIcon;
public static final field EXCEPTION Lorg/jetbrains/dokka/base/renderers/html/NavigationNodeIcon;
public static final field FUNCTION Lorg/jetbrains/dokka/base/renderers/html/NavigationNodeIcon;
public static final field INTERFACE Lorg/jetbrains/dokka/base/renderers/html/NavigationNodeIcon;
public static final field INTERFACE_KT Lorg/jetbrains/dokka/base/renderers/html/NavigationNodeIcon;
public static final field OBJECT Lorg/jetbrains/dokka/base/renderers/html/NavigationNodeIcon;
public static final field VAL Lorg/jetbrains/dokka/base/renderers/html/NavigationNodeIcon;
public static final field VAR Lorg/jetbrains/dokka/base/renderers/html/NavigationNodeIcon;
public static fun valueOf (Ljava/lang/String;)Lorg/jetbrains/dokka/base/renderers/html/NavigationNodeIcon;
public static fun values ()[Lorg/jetbrains/dokka/base/renderers/html/NavigationNodeIcon;
}

public final class org/jetbrains/dokka/base/renderers/html/NavigationPage : org/jetbrains/dokka/pages/RendererSpecificPage {
public fun <init> (Lorg/jetbrains/dokka/base/renderers/html/NavigationNode;Ljava/lang/String;Lorg/jetbrains/dokka/plugability/DokkaContext;)V
public fun getChildren ()Ljava/util/List;
Expand Down
@@ -0,0 +1,90 @@
package org.jetbrains.dokka.base.renderers.html

import org.jetbrains.dokka.base.renderers.sourceSets
import org.jetbrains.dokka.base.transformers.documentables.isException
import org.jetbrains.dokka.base.translators.documentables.DocumentableLanguage
import org.jetbrains.dokka.base.translators.documentables.documentableLanguage
import org.jetbrains.dokka.model.*
import org.jetbrains.dokka.pages.*

abstract class NavigationDataProvider {
open fun navigableChildren(input: RootPageNode): NavigationNode = input.withDescendants()
.first { it is ModulePage || it is MultimoduleRootPage }.let { visit(it as ContentPage) }

open fun visit(page: ContentPage): NavigationNode =
NavigationNode(
name = page.displayableName(),
dri = page.dri.first(),
sourceSets = page.sourceSets(),
icon = chooseNavigationIcon(page),
children = page.navigableChildren()
)

/**
* Parenthesis is applied in 1 case:
* - page only contains functions (therefore documentable from this page is [DFunction])
*/
private fun ContentPage.displayableName(): String =
if (this is WithDocumentables && documentables.all { it is DFunction }) {
"$name()"
} else {
name
}

private fun chooseNavigationIcon(contentPage: ContentPage): NavigationNodeIcon? {
return if (contentPage is WithDocumentables) {
val documentable = contentPage.documentables.firstOrNull()
val isJava = documentable?.hasAnyJavaSources() ?: false

when (documentable) {
is DClass -> when {
documentable.isException -> NavigationNodeIcon.EXCEPTION
documentable.isAbstract() -> {
if (isJava) NavigationNodeIcon.ABSTRACT_CLASS else NavigationNodeIcon.ABSTRACT_CLASS_KT
}
else -> if (isJava) NavigationNodeIcon.CLASS else NavigationNodeIcon.CLASS_KT
}
is DFunction -> NavigationNodeIcon.FUNCTION
is DProperty -> {
val isVar = documentable.extra[IsVar] != null
if (isVar) NavigationNodeIcon.VAR else NavigationNodeIcon.VAL
}
is DInterface -> if (isJava) NavigationNodeIcon.INTERFACE else NavigationNodeIcon.INTERFACE_KT
is DEnum,
is DEnumEntry -> if (isJava) NavigationNodeIcon.ENUM_CLASS else NavigationNodeIcon.ENUM_CLASS_KT
is DAnnotation -> {
if (isJava) NavigationNodeIcon.ANNOTATION_CLASS else NavigationNodeIcon.ANNOTATION_CLASS_KT
}
is DObject -> NavigationNodeIcon.OBJECT
else -> null
}
} else {
null
}
}

private fun Documentable.hasAnyJavaSources(): Boolean {
val withSources = this as? WithSources ?: return false
return this.sourceSets.any { withSources.documentableLanguage(it) == DocumentableLanguage.JAVA }
}

private fun DClass.isAbstract(): Boolean {
return modifier.values.all { it is KotlinModifier.Abstract || it is JavaModifier.Abstract }
}

private fun ContentPage.navigableChildren(): List<NavigationNode> {
return if (this !is ClasslikePageNode) {
children
.filterIsInstance<ContentPage>()
.map { visit(it) }
.sortedBy { it.name.toLowerCase() }
} else if (documentables.any { it is DEnum }) {
// no sorting for enum entries, should be the same as in source code
children
.filter { child -> child is WithDocumentables && child.documentables.any { it is DEnumEntry } }
.map { visit(it as ContentPage) }
} else {
emptyList()
}
}
}
63 changes: 56 additions & 7 deletions plugins/base/src/main/kotlin/renderers/html/NavigationPage.kt
Expand Up @@ -7,13 +7,15 @@ import org.jetbrains.dokka.base.templating.AddToNavigationCommand
import org.jetbrains.dokka.links.DRI
import org.jetbrains.dokka.model.DisplaySourceSet
import org.jetbrains.dokka.model.WithChildren
import org.jetbrains.dokka.pages.PageNode
import org.jetbrains.dokka.pages.RendererSpecificPage
import org.jetbrains.dokka.pages.RenderingStrategy
import org.jetbrains.dokka.pages.*
import org.jetbrains.dokka.plugability.DokkaContext

class NavigationPage(val root: NavigationNode, val moduleName: String, val context: DokkaContext) :
RendererSpecificPage {
class NavigationPage(
val root: NavigationNode,
val moduleName: String,
val context: DokkaContext
) : RendererSpecificPage {

override val name = "navigation"

override val children = emptyList<PageNode>()
Expand Down Expand Up @@ -46,22 +48,69 @@ class NavigationPage(val root: NavigationNode, val moduleName: String, val conte
span("navButtonContent")
}
}
buildLink(node.dri, node.sourceSets.toList()) { buildBreakableText(node.name) }
buildLink(node.dri, node.sourceSets.toList()) {
// special condition for Enums as it has children enum entries in navigation
val withIcon = node.icon != null && (node.children.isEmpty() || node.isEnum())
if (withIcon) {
// in case link text is so long that it needs to have word breaks,
// and it stretches to two or more lines, make sure the icon
// is always on the left in the grid and is not wrapped with text
span("nav-link-grid") {
span("nav-link-child ${node.icon?.style()}")
span("nav-link-child") {
buildBreakableText(node.name)
}
}
} else {
buildBreakableText(node.name)
}
}
}
node.children.withIndex().forEach { (n, p) -> visit(p, "$navId-$n", renderer) }
}
}

private fun NavigationNode.isEnum(): Boolean {
return icon == NavigationNodeIcon.ENUM_CLASS || icon == NavigationNodeIcon.ENUM_CLASS_KT
}
}

data class NavigationNode(
val name: String,
val dri: DRI,
val sourceSets: Set<DisplaySourceSet>,
val icon: NavigationNodeIcon?,
override val children: List<NavigationNode>
) : WithChildren<NavigationNode>

/**
* [CLASS] represents a neutral (a.k.a Java-style) icon,
* whereas [CLASS_KT] should be Kotlin-styled
*/
enum class NavigationNodeIcon(
private val cssClass: String
) {
CLASS("class"),
CLASS_KT("class-kt"),
ABSTRACT_CLASS("abstract-class"),
ABSTRACT_CLASS_KT("abstract-class-kt"),
ENUM_CLASS("enum-class"),
ENUM_CLASS_KT("enum-class-kt"),
ANNOTATION_CLASS("annotation-class"),
ANNOTATION_CLASS_KT("annotation-class-kt"),
INTERFACE("interface"),
INTERFACE_KT("interface-kt"),
FUNCTION("function"),
EXCEPTION("exception-class"),
OBJECT("object"),
VAL("val"),
VAR("var");

internal fun style(): String = "nav-icon $cssClass"
}

fun NavigationPage.transform(block: (NavigationNode) -> NavigationNode) =
NavigationPage(root.transform(block), moduleName, context)

fun NavigationNode.transform(block: (NavigationNode) -> NavigationNode) =
run(block).let { NavigationNode(it.name, it.dri, it.sourceSets, it.children.map(block)) }
run(block).let { NavigationNode(it.name, it.dri, it.sourceSets, it.icon, it.children.map(block)) }
67 changes: 20 additions & 47 deletions plugins/base/src/main/kotlin/renderers/html/htmlPreprocessors.kt
Expand Up @@ -2,60 +2,16 @@ package org.jetbrains.dokka.base.renderers.html

import org.jetbrains.dokka.base.DokkaBase
import org.jetbrains.dokka.base.DokkaBaseConfiguration
import org.jetbrains.dokka.base.renderers.sourceSets
import org.jetbrains.dokka.base.templating.AddToSourcesetDependencies
import org.jetbrains.dokka.base.templating.toJsonString
import org.jetbrains.dokka.model.DEnum
import org.jetbrains.dokka.model.DEnumEntry
import org.jetbrains.dokka.model.DFunction
import org.jetbrains.dokka.model.withDescendants
import org.jetbrains.dokka.pages.*
import org.jetbrains.dokka.pages.RendererSpecificResourcePage
import org.jetbrains.dokka.pages.RenderingStrategy
import org.jetbrains.dokka.pages.RootPageNode
import org.jetbrains.dokka.plugability.DokkaContext
import org.jetbrains.dokka.plugability.configuration
import org.jetbrains.dokka.transformers.pages.PageTransformer

abstract class NavigationDataProvider {
open fun navigableChildren(input: RootPageNode): NavigationNode = input.withDescendants()
.first { it is ModulePage || it is MultimoduleRootPage }.let { visit(it as ContentPage) }

open fun visit(page: ContentPage): NavigationNode =
NavigationNode(
name = page.displayableName,
dri = page.dri.first(),
sourceSets = page.sourceSets(),
children = page.navigableChildren()
)

private fun ContentPage.navigableChildren(): List<NavigationNode> {
return if (this !is ClasslikePageNode) {
children
.filterIsInstance<ContentPage>()
.map { visit(it) }
.sortedBy { it.name.toLowerCase() }
} else if (documentables.any { it is DEnum }) {
// no sorting for enum entries, should be the same as in source code
children
.filter { child -> child is WithDocumentables && child.documentables.any { it is DEnumEntry } }
.map { visit(it as ContentPage) }
} else {
emptyList()
}
}

/**
* Parenthesis is applied in 1 case:
* - page only contains functions (therefore documentable from this page is [DFunction])
*/
private val ContentPage.displayableName: String
get() = if (this is WithDocumentables && documentables.all { it is DFunction }) {
"$name()"
} else {
name
}
}

open class NavigationPageInstaller(val context: DokkaContext) : NavigationDataProvider(), PageTransformer {

override fun invoke(input: RootPageNode): RootPageNode =
input.modified(
children = input.children + NavigationPage(
Expand Down Expand Up @@ -138,6 +94,23 @@ object AssetsInstaller : PageTransformer {
"images/copy-icon.svg",
"images/copy-successful-icon.svg",
"images/theme-toggle.svg",

// navigation icons
"images/nav-icons/abstract-class.svg",
"images/nav-icons/abstract-class-kotlin.svg",
"images/nav-icons/annotation.svg",
"images/nav-icons/annotation-kotlin.svg",
"images/nav-icons/class.svg",
"images/nav-icons/class-kotlin.svg",
"images/nav-icons/enum.svg",
"images/nav-icons/enum-kotlin.svg",
"images/nav-icons/exception-class.svg",
"images/nav-icons/field-value.svg",
"images/nav-icons/field-variable.svg",
"images/nav-icons/function.svg",
"images/nav-icons/interface.svg",
"images/nav-icons/interface-kotlin.svg",
"images/nav-icons/object.svg",
)

override fun invoke(input: RootPageNode) = input.modified(
Expand Down
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit 7a875ee

Please sign in to comment.