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

Experimental dark mode #606

Merged
merged 8 commits into from
Oct 2, 2024
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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,7 @@ architecture model:
| `generatr.site.externalTag` | Software systems containing this tag will be considered external | | |
| `generatr.site.nestGroups` | Will show software systems in the left side navigator in collapsable groups | `false` | `true` |
| `generatr.site.cdn` | Specifies the CDN base location for fetching NPM packages for browser runtime dependencies. Defaults to jsDelivr, but can be changed to e.g. an on-premise location. | `https://cdn.jsdelivr.net/npm` | `https://cdn.my-company/npm` |
| `generatr.site.theme` | Experimental: allows to force a light or dark theme or allows to switch between light and dark mode on the website with browser preference or menu item. Possible values are 'light', 'dark' or 'auto'. Note that the 'structurizr' exporter (see 'generatr.site.exporter' setting) generally works better for the dark theme. | `light` | `auto` |

See the included example for usage of some those properties in the
[C4 architecture model example](https://github.com/avisi-cloud/structurizr-site-generatr/blob/main/docs/example/workspace.dsl#L163).
Expand Down
3 changes: 2 additions & 1 deletion docs/example/workspace.dsl
Original file line number Diff line number Diff line change
Expand Up @@ -182,10 +182,11 @@ workspace "Big Bank plc" "This is an example workspace to illustrate the key fea
// default behaviour, if no generatr.markdown.flexmark.extensions property is specified, is to load the Tables extension only
"generatr.markdown.flexmark.extensions" "Abbreviation,Admonition,AnchorLink,Attributes,Autolink,Definition,Emoji,Footnotes,GfmTaskList,GitLab,MediaTags,Tables,TableOfContents,Typographic"

"generatr.site.exporter" "c4"
"generatr.site.exporter" "structurizr"
"generatr.site.externalTag" "External System"
"generatr.site.nestGroups" "false"
"generatr.site.cdn" "https://cdn.jsdelivr.net/npm"
"generatr.site.theme" "auto"
}

systemlandscape "SystemLandscape" {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ fun copySiteWideAssets(exportDir: File) {
copySiteWideAsset(exportDir, "/css/treeview.css")
copySiteWideAsset(exportDir, "/js/treeview.js")
copySiteWideAsset(exportDir, "/js/katex-render.js")
copySiteWideAsset(exportDir, "/js/toggle-theme.js")
}

private fun copySiteWideAsset(exportDir: File, asset: String) {
Expand Down Expand Up @@ -112,10 +113,6 @@ private fun generateStyle(context: GeneratorContext, branchDir: File) {
color: $secondary!important;
background-color: $primary!important;
}
.input.has-site-branding {
color: dimgrey!important;
background-color: white!important;
}
.input.has-site-branding:focus {
border-color: $secondary!important;
box-shadow: 0 0 0 0.125em $secondary;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ class HeaderBarViewModel(pageViewModel: PageViewModel, generatorContext: Generat
.map { BranchHomeLinkViewModel(pageViewModel, it) }
val currentBranch = generatorContext.currentBranch
val version = generatorContext.version
val allowToggleTheme = pageViewModel.allowToggleTheme

private fun logoPath(generatorContext: GeneratorContext) =
generatorContext.workspace.views.configuration.properties
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,16 @@ abstract class PageViewModel(protected val generatorContext: GeneratorContext) {
val includedSoftwareSystems = generatorContext.workspace.includedSoftwareSystems
val configuration = generatorContext.workspace.views.configuration.properties
val includeTreeview = configuration.getOrDefault("generatr.site.nestGroups", "false").toBoolean()
val theme = configuration.getOrDefault("generatr.site.theme", "light").toTheme()
val allowToggleTheme = theme == Theme.AUTO

abstract val url: String
abstract val pageSubTitle: String
}

fun String.toTheme() = when (this) {
"light" -> Theme.LIGHT
"dark" -> Theme.DARK
"auto" -> Theme.AUTO
else -> throw IllegalArgumentException("Unknown theme '$this', allowed values are 'light', 'dark' or 'auto'")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package nl.avisi.structurizr.site.generatr.site.model

enum class Theme {
LIGHT, DARK, AUTO
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,19 @@ package nl.avisi.structurizr.site.generatr.site.views
import kotlinx.html.*
import nl.avisi.structurizr.site.generatr.site.asUrlToFile
import nl.avisi.structurizr.site.generatr.site.model.PageViewModel
import nl.avisi.structurizr.site.generatr.site.model.Theme

fun HTML.page(viewModel: PageViewModel, block: DIV.() -> Unit) {
attributes["lang"] = "en"
attributes["data-theme"] = "light"
classes = setOf("has-background-light")
when (viewModel.theme) {
Theme.LIGHT -> {
attributes["data-theme"] = "light"
}
Theme.DARK -> {
attributes["data-theme"] = "dark"
}
Theme.AUTO -> { }
}

headFragment(viewModel)
bodyFragment(viewModel, block)
Expand All @@ -24,6 +32,8 @@ private fun HTML.headFragment(viewModel: PageViewModel) {
script(type = ScriptType.textJavaScript, src = "../" + "/modal.js".asUrlToFile(viewModel.url)) { }
script(type = ScriptType.textJavaScript, src = "../" + "/svg-modal.js".asUrlToFile(viewModel.url)) { }
script(type = ScriptType.textJavaScript, src = viewModel.cdn.svgpanzoomJs()) { }
if (viewModel.allowToggleTheme)
script(type = ScriptType.textJavaScript, src = "../" + "/toggle-theme.js".asUrlToFile(viewModel.url)) { }

if (viewModel.includeTreeview)
link(rel = "stylesheet", href = "../" + "/treeview.css".asUrlToFile(viewModel.url))
Expand Down Expand Up @@ -59,7 +69,7 @@ private fun HTML.bodyFragment(viewModel: PageViewModel, block: DIV.() -> Unit) {
div(classes = "site-layout") {
id = "site"
menu(viewModel.menu, viewModel.includeTreeview)
div(classes = "container is-fluid has-background-white") {
div(classes = "container is-fluid") {
block()
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ fun BODY.pageHeader(viewModel: HeaderBarViewModel) {
div(classes = "navbar-menu has-site-branding") {
div(classes = "navbar-end") {
div(classes = "navbar-item") {
input(classes = "input is-small is-rounded has-site-branding") {
input(classes = "input is-small is-rounded") {
id = "search"
type = InputType.search
size = "30"
Expand All @@ -46,6 +46,15 @@ fun BODY.pageHeader(viewModel: HeaderBarViewModel) {
+branchLink.title
}
}
if (viewModel.allowToggleTheme) {
hr(classes = "navbar-divider")
a(
classes = "navbar-item",
) {
onClick = "toggleTheme()"
+"Toggle theme"
}
}
hr(classes = "navbar-divider")
div(classes = "navbar-item has-text-grey-light") {
span { +"v" }
Expand Down
12 changes: 12 additions & 0 deletions src/main/resources/assets/css/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -69,3 +69,15 @@ a.navbar-item:hover {
color: #485fc7;
border-bottom-color: #485fc7;
}

.tabs li {
font-weight: bold;
}

.input {
color: dimgrey!important;
background-color: white!important;
}
.input::placeholder {
color: darkgrey!important;
}
18 changes: 18 additions & 0 deletions src/main/resources/assets/js/toggle-theme.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
if (!localStorage.getItem("data-theme")) {
const prefersDarkMode = window.matchMedia &&
window.matchMedia('(prefers-color-scheme: dark)').matches;

localStorage.setItem("data-theme", prefersDarkMode ? "dark" : "light");
}

document.documentElement.setAttribute("data-theme", localStorage.getItem("data-theme"));

function toggleTheme() {
if (localStorage.getItem("data-theme") === "light") {
document.documentElement.setAttribute("data-theme", "dark");
localStorage.setItem("data-theme", "dark");
} else {
document.documentElement.setAttribute("data-theme", "light");
localStorage.setItem("data-theme", "light");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import assertk.assertions.containsExactly
import assertk.assertions.isEqualTo
import assertk.assertions.isFalse
import assertk.assertions.isTrue
import org.junit.jupiter.api.DynamicTest
import org.junit.jupiter.api.TestFactory
import kotlin.test.Test

class HeaderBarViewModelTest : ViewModelTest() {
Expand Down Expand Up @@ -67,4 +69,25 @@ class HeaderBarViewModelTest : ViewModelTest() {

assertThat(viewModel.hasLogo).isFalse()
}

@TestFactory
fun `no dark mode`() = listOf(
"light" to false,
"dark" to false,
"auto" to true,
).map { (theme, allowToggle) ->
DynamicTest.dynamicTest(theme) {
generatorContext.workspace.views.configuration.addProperty(
"generatr.site.theme",
theme
)

val viewModel = HeaderBarViewModel(object : PageViewModel(generatorContext) {
override val url: String = "/master/system"
override val pageSubTitle: String = "subtitle"
}, generatorContext)

assertThat(viewModel.allowToggleTheme).isEqualTo(allowToggle)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,11 @@ import org.junit.jupiter.api.TestFactory

class CDNTest {
@TestFactory
fun `cdn locations`() {
fun `cdn locations`() : List<DynamicTest> {
val workspace = Workspace("workspace name", "")
val cdn = CDN(workspace)

listOf(
return listOf(
cdn.bulmaCss() to "/css/bulma.min.css",
cdn.katexJs() to "/dist/katex.min.js",
cdn.katexCss() to "/dist/katex.min.css",
Expand Down
Loading