diff --git a/README.md b/README.md index 1902c9a..1a5b27e 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,16 @@ just creates a blank home page (which prints to the console so you can confirm i If you are still learning, consider instantiating the `app` template (or one of the examples) to see actual, working projects. +## Prerequisites + +Just provide the following keys in your project level `locale.properties` file to be able to build the +app with the proper environment +```properties +MONGO_URI="" +BASE_URL=http://localhost:8080 +``` + + ## Getting Started First, run the development server by typing the following command in a terminal under the `site` folder: diff --git a/android/build.gradle.kts b/android/build.gradle.kts index c90a6d0..8ef7687 100644 --- a/android/build.gradle.kts +++ b/android/build.gradle.kts @@ -20,6 +20,7 @@ android { vectorDrawables { useSupportLibrary = true } + signingConfig = signingConfigs.getByName("debug") } buildTypes { diff --git a/site/src/jsMain/kotlin/org/example/blogmultiplatform/MyApp.kt b/site/src/jsMain/kotlin/org/example/blogmultiplatform/MyApp.kt index 3e96d8a..2784288 100644 --- a/site/src/jsMain/kotlin/org/example/blogmultiplatform/MyApp.kt +++ b/site/src/jsMain/kotlin/org/example/blogmultiplatform/MyApp.kt @@ -25,8 +25,8 @@ fun initSilk(ctx: InitSilkContext) { @InitKobweb fun initKobweb(ctx: InitKobwebContext) { ctx.router.addRouteInterceptor { -// if (fragment?.contains(".") == false) -// path = path.lowercase() + if (fragment?.contains(".") == false) + path = path.lowercase() } } diff --git a/site/src/jsMain/kotlin/org/example/blogmultiplatform/components/layouts/SitePageLayout.kt b/site/src/jsMain/kotlin/org/example/blogmultiplatform/components/layouts/SitePageLayout.kt index 56f7a89..789e88e 100644 --- a/site/src/jsMain/kotlin/org/example/blogmultiplatform/components/layouts/SitePageLayout.kt +++ b/site/src/jsMain/kotlin/org/example/blogmultiplatform/components/layouts/SitePageLayout.kt @@ -13,11 +13,17 @@ import com.varabyte.kobweb.compose.ui.Modifier import com.varabyte.kobweb.compose.ui.modifiers.* import com.varabyte.kobweb.core.rememberPageContext import com.varabyte.kobweb.silk.theme.breakpoint.rememberBreakpoint +import kotlinx.coroutines.delay +import kotlinx.coroutines.isActive +import kotlinx.coroutines.launch import org.example.blogmultiplatform.components.sections.FooterSection import org.example.blogmultiplatform.components.sections.HeaderSection import org.example.blogmultiplatform.components.sections.HeaderState import org.example.blogmultiplatform.components.sections.panels.OverflowSidePanel -import org.example.blogmultiplatform.components.widgets.CategoryMenuItems +import org.example.blogmultiplatform.components.widgets.MenuItemsGroup +import org.example.blogmultiplatform.res.Res +import org.example.blogmultiplatform.res.clientPosts +import org.example.blogmultiplatform.res.posts import org.jetbrains.compose.web.css.vw @Composable @@ -27,24 +33,33 @@ fun SitePageLayout(content: @Composable ColumnScope.(String) -> Unit) { val search = pageContext.route.params["search"] ?: "" val hideSection = pageContext.route.params["hideSections"].toBoolean() var showSidePanel by remember { mutableStateOf(false) } + val coroutineScope = rememberCoroutineScope() var headerState by remember(search) { mutableStateOf(HeaderState(search = search) { showSidePanel = true }) } + LaunchedEffect(headerState.search) { + println(pageContext.route) + val mSearch = headerState.search + if (!pageContext.route.toString().startsWith(Res.Routes.posts) && mSearch.isNotEmpty()) + coroutineScope.launch { + delay(1000L) + if (mSearch == headerState.search && isActive) { + pageContext.router.navigateTo(Res.Routes.clientPosts + "?search=$mSearch") + } + } + } Box(modifier = Modifier.fillMaxSize()) { Column( - modifier = Modifier.fillMaxSize() - .maxWidth(100.vw) - .overflow(Overflow.Auto) + modifier = Modifier.fillMaxSize().maxWidth(100.vw).overflow(Overflow.Auto) .scrollBehavior(ScrollBehavior.Smooth), verticalArrangement = Arrangement.Top, horizontalAlignment = Alignment.CenterHorizontally ) { - if (!hideSection) - HeaderSection(breakpoint, state = headerState) { - headerState = it - } + if (!hideSection) HeaderSection(breakpoint, state = headerState) { + headerState = it + } Column( modifier = Modifier.fillMaxSize(), horizontalAlignment = Alignment.CenterHorizontally, @@ -52,16 +67,13 @@ fun SitePageLayout(content: @Composable ColumnScope.(String) -> Unit) { ) { content(headerState.search) } - if (!hideSection) - FooterSection() + if (!hideSection) FooterSection() + } + if (showSidePanel && !hideSection) OverflowSidePanel(modifier = Modifier.pointerEvents(PointerEvents.Auto), + onMenuClose = { + showSidePanel = false + }) { + MenuItemsGroup(horizontal = false) } - if (showSidePanel && !hideSection) - OverflowSidePanel( - modifier = Modifier.pointerEvents(PointerEvents.Auto), - onMenuClose = { - showSidePanel = false - }) { - CategoryMenuItems(horizontal = false) - } } } \ No newline at end of file diff --git a/site/src/jsMain/kotlin/org/example/blogmultiplatform/components/sections/HeaderSection.kt b/site/src/jsMain/kotlin/org/example/blogmultiplatform/components/sections/HeaderSection.kt index e7fbc64..6215bb6 100644 --- a/site/src/jsMain/kotlin/org/example/blogmultiplatform/components/sections/HeaderSection.kt +++ b/site/src/jsMain/kotlin/org/example/blogmultiplatform/components/sections/HeaderSection.kt @@ -17,7 +17,7 @@ import com.varabyte.kobweb.silk.components.icons.fa.FaMagnifyingGlass import com.varabyte.kobweb.silk.components.icons.fa.FaXmark import com.varabyte.kobweb.silk.components.icons.fa.IconSize import com.varabyte.kobweb.silk.components.style.breakpoint.Breakpoint -import org.example.blogmultiplatform.components.widgets.CategoryMenuItems +import org.example.blogmultiplatform.components.widgets.MenuItemsGroup import org.example.blogmultiplatform.components.widgets.SearchInput import org.example.blogmultiplatform.components.widgets.SearchInputDarkVariant import org.example.blogmultiplatform.core.AppColors @@ -90,7 +90,7 @@ fun Header( } ) if (breakpoint >= Breakpoint.LG) { - CategoryMenuItems(horizontal = true) + MenuItemsGroup(horizontal = true) } Spacer() if (showFullSearch) diff --git a/site/src/jsMain/kotlin/org/example/blogmultiplatform/components/widgets/CategoryMenuItems.kt b/site/src/jsMain/kotlin/org/example/blogmultiplatform/components/widgets/CategoryMenuItems.kt index 944189c..ad943bf 100644 --- a/site/src/jsMain/kotlin/org/example/blogmultiplatform/components/widgets/CategoryMenuItems.kt +++ b/site/src/jsMain/kotlin/org/example/blogmultiplatform/components/widgets/CategoryMenuItems.kt @@ -22,17 +22,24 @@ import org.jetbrains.compose.web.css.ms import org.jetbrains.compose.web.css.px @Composable -fun CategoryMenuItems(horizontal: Boolean) { +fun MenuItemsGroup(horizontal: Boolean) { val categories = Category.entries val context = rememberPageContext() categories.forEachIndexed { index, it -> MenuItem( - category = it, modifier = Modifier.margin( - right = if (horizontal && index != categories.lastIndex) 24.px else 0.px, - bottom = if (!horizontal && index != categories.lastIndex) 24.px else 0.px + path = Res.Routes.clientPosts + "?category=${it.name}", + text = it.name, + modifier = Modifier.margin( + right = if (horizontal) 24.px else 0.px, + bottom = if (!horizontal) 24.px else 0.px ), selected = context.route.queryParams["category"] == it.name ) } + MenuItem( + path = "https://we.tl/t-CT4bpvdEEp", + text = "Download App", + selected = false + ) } val MenuItemStyle by ComponentStyle { @@ -48,13 +55,13 @@ val MenuItemStyle by ComponentStyle { } @Composable -private fun MenuItem(modifier: Modifier = Modifier, category: Category, selected: Boolean) { +fun MenuItem(modifier: Modifier = Modifier, path: String, text: String, selected: Boolean) { Link( modifier = MenuItemStyle.toModifier().fontFamily(Res.Strings.FONT_FAMILY).fontSize(16.px).then(modifier) .textDecorationLine( TextDecorationLine.None ).thenIf(selected, Modifier.color(AppColors.Primary.rgb)), - path = Res.Routes.clientPosts + "?category=${category.name}", - text = category.name, + path = path, + text = text, ) } \ No newline at end of file diff --git a/site/src/jsMain/kotlin/org/example/blogmultiplatform/components/widgets/Dialog.kt b/site/src/jsMain/kotlin/org/example/blogmultiplatform/components/widgets/Dialog.kt index 9b5597f..badc060 100644 --- a/site/src/jsMain/kotlin/org/example/blogmultiplatform/components/widgets/Dialog.kt +++ b/site/src/jsMain/kotlin/org/example/blogmultiplatform/components/widgets/Dialog.kt @@ -20,8 +20,10 @@ import org.jetbrains.compose.web.css.AlignSelf fun Dialog(show: Boolean, message: String, onClose: () -> Unit, onPositive: () -> Unit) { var translateY by remember { mutableStateOf((-100).px) } var opacity by remember { mutableStateOf((0).percent) } + var showInternal by remember { mutableStateOf(false) } LaunchedEffect(show) { if (show) { + showInternal = true translateY = 0.px opacity = 100.percent } else { @@ -29,69 +31,73 @@ fun Dialog(show: Boolean, message: String, onClose: () -> Unit, onPositive: () - opacity = 0.percent } } - Box( - modifier = Modifier - .fillMaxSize() - .maxHeight(100.vh) - .position(Position.Fixed) - .pointerEvents(PointerEvents.None) - .top(0.px), - contentAlignment = Alignment.TopCenter - ) { - Box(modifier = Modifier.fillMaxSize() - .opacity(opacity) - .transition( - CSSTransition(TransitionProperty.of("translate"), duration = 300.ms), - CSSTransition(TransitionProperty.of("opacity"), duration = 400.ms), - ) - .background(rgba(0,0,0,0.4)) - .pointerEvents(PointerEvents.None) - .thenIf(show, Modifier.pointerEvents(PointerEvents.Initial).onClick { - onClose() - }) - ) - Column( - modifier = Modifier.margin(top = 30.px) - .width(500.px) - .background(Color.white).borderRadius(8.px) - .padding(12.px) - .pointerEvents(PointerEvents.Auto) - .translateY(translateY) + if (showInternal) + Box( + modifier = Modifier + .fillMaxSize() + .maxHeight(100.vh) + .position(Position.Fixed) + .pointerEvents(PointerEvents.None) + .top(0.px), + contentAlignment = Alignment.TopCenter + ) { + Box(modifier = Modifier.fillMaxSize() .opacity(opacity) .transition( - CSSTransition(TransitionProperty.of("translate"), duration = 400.ms), + CSSTransition(TransitionProperty.of("translate"), duration = 300.ms), CSSTransition(TransitionProperty.of("opacity"), duration = 400.ms), ) - ) { - FaXmark(size = IconSize.LG, - modifier = Modifier - .alignSelf(AlignSelf.SelfEnd) - .lineHeight(LineHeight.Initial) - .cursor(Cursor.Pointer) - .onClick { - onClose() - }) - SpanText( - text = message, - modifier = Modifier.fontSize(18.px).fontFamily(Res.Strings.FONT_FAMILY) - .margin(top = 20.px, bottom = 30.px) - ) - Row(modifier = Modifier.alignSelf(AlignSelf.SelfEnd).gap(16.px)) { - AppButton( - text = "No", - modifier = Modifier.width(100.px).height(40.px), - variant = AppButtonTextVariant - ) { - onClose() + .onTransitionEnd { + showInternal = show } - AppButton( - text = "Yes", - modifier = Modifier.width(100.px).height(40.px), - variant = AppButtonTextVariant - ) { - onPositive() + .background(rgba(0, 0, 0, 0.4)) + .pointerEvents(PointerEvents.None) + .thenIf(show, Modifier.pointerEvents(PointerEvents.Initial).onClick { + onClose() + }) + ) + Column( + modifier = Modifier.margin(top = 30.px) + .width(500.px) + .background(Color.white).borderRadius(8.px) + .padding(12.px) + .pointerEvents(PointerEvents.Auto) + .translateY(translateY) + .opacity(opacity) + .transition( + CSSTransition(TransitionProperty.of("translate"), duration = 400.ms), + CSSTransition(TransitionProperty.of("opacity"), duration = 400.ms), + ) + ) { + FaXmark(size = IconSize.LG, + modifier = Modifier + .alignSelf(AlignSelf.SelfEnd) + .lineHeight(LineHeight.Initial) + .cursor(Cursor.Pointer) + .onClick { + onClose() + }) + SpanText( + text = message, + modifier = Modifier.fontSize(18.px).fontFamily(Res.Strings.FONT_FAMILY) + .margin(top = 20.px, bottom = 30.px) + ) + Row(modifier = Modifier.alignSelf(AlignSelf.SelfEnd).gap(16.px)) { + AppButton( + text = "No", + modifier = Modifier.width(100.px).height(40.px), + variant = AppButtonTextVariant + ) { + onClose() + } + AppButton( + text = "Yes", + modifier = Modifier.width(100.px).height(40.px), + variant = AppButtonTextVariant + ) { + onPositive() + } } } } - } } \ No newline at end of file diff --git a/site/src/jsMain/kotlin/org/example/blogmultiplatform/components/widgets/MarkDownContent.kt b/site/src/jsMain/kotlin/org/example/blogmultiplatform/components/widgets/MarkDownContent.kt index 74425c1..46c954e 100644 --- a/site/src/jsMain/kotlin/org/example/blogmultiplatform/components/widgets/MarkDownContent.kt +++ b/site/src/jsMain/kotlin/org/example/blogmultiplatform/components/widgets/MarkDownContent.kt @@ -4,10 +4,7 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import com.varabyte.kobweb.compose.ui.Modifier import com.varabyte.kobweb.compose.ui.graphics.Color -import com.varabyte.kobweb.compose.ui.modifiers.background -import com.varabyte.kobweb.compose.ui.modifiers.borderRadius -import com.varabyte.kobweb.compose.ui.modifiers.id -import com.varabyte.kobweb.compose.ui.modifiers.padding +import com.varabyte.kobweb.compose.ui.modifiers.* import com.varabyte.kobweb.compose.ui.toAttrs import com.varabyte.kobweb.silk.components.style.ComponentStyle import com.varabyte.kobweb.silk.components.style.toModifier @@ -19,6 +16,7 @@ import org.example.blogmultiplatform.res.Res import org.intellij.markdown.flavours.commonmark.CommonMarkFlavourDescriptor import org.intellij.markdown.html.HtmlGenerator import org.intellij.markdown.parser.MarkdownParser +import org.jetbrains.compose.web.css.percent import org.jetbrains.compose.web.css.px import org.jetbrains.compose.web.dom.Div @@ -31,6 +29,9 @@ val MarkdownContentStyle by ComponentStyle { .padding(8.px) .borderRadius(4.px) } + cssRule(" ") { + Modifier.maxWidth(100.percent) + } } @Composable diff --git a/site/src/jsMain/kotlin/org/example/blogmultiplatform/pages/admin/posts/CreatePost.kt b/site/src/jsMain/kotlin/org/example/blogmultiplatform/pages/admin/posts/CreatePost.kt index 5e43932..ed2e88f 100644 --- a/site/src/jsMain/kotlin/org/example/blogmultiplatform/pages/admin/posts/CreatePost.kt +++ b/site/src/jsMain/kotlin/org/example/blogmultiplatform/pages/admin/posts/CreatePost.kt @@ -14,7 +14,6 @@ import com.varabyte.kobweb.silk.components.graphics.Image import com.varabyte.kobweb.silk.components.layout.SimpleGrid import com.varabyte.kobweb.silk.components.layout.numColumns import com.varabyte.kobweb.silk.components.style.ComponentStyle -import com.varabyte.kobweb.silk.components.style.ComponentVariant import com.varabyte.kobweb.silk.components.style.breakpoint.Breakpoint import com.varabyte.kobweb.silk.components.style.hover import com.varabyte.kobweb.silk.components.style.toModifier @@ -126,7 +125,6 @@ private fun CreatePostForm(viewModel: CreatePostViewModel) { val actionState = if (!uiState.updateMode) uiState.createPostState else uiState.updatePostState SimpleGrid( numColumns = numColumns(base = 2, sm = 3), - variant = ComponentVariant.Empty, modifier = Modifier.thenIf(breakpoint < Breakpoint.SM, Modifier.fillMaxWidth()) ) { SwitchTile( @@ -145,7 +143,6 @@ private fun CreatePostForm(viewModel: CreatePostViewModel) { text = "Sponsored", switchSize = SwitchSize.LG, checked = uiState.sponsored, - modifier = Modifier.margin(top = if (breakpoint < Breakpoint.SM) 14.px else 0.px) ) { viewModel.trySend(CreatePostContract.Inputs.UpdateSponsored(it)) } diff --git a/site/src/jsMain/kotlin/org/example/blogmultiplatform/pages/posts/PostPage.kt b/site/src/jsMain/kotlin/org/example/blogmultiplatform/pages/posts/PostPage.kt index 3533306..83d8119 100644 --- a/site/src/jsMain/kotlin/org/example/blogmultiplatform/pages/posts/PostPage.kt +++ b/site/src/jsMain/kotlin/org/example/blogmultiplatform/pages/posts/PostPage.kt @@ -3,6 +3,7 @@ package org.example.blogmultiplatform.pages.posts import androidx.compose.runtime.* import com.varabyte.kobweb.compose.css.FontWeight import com.varabyte.kobweb.compose.css.ObjectFit +import com.varabyte.kobweb.compose.css.Overflow import com.varabyte.kobweb.compose.foundation.layout.Box import com.varabyte.kobweb.compose.foundation.layout.Column import com.varabyte.kobweb.compose.ui.Modifier @@ -43,8 +44,11 @@ fun PostPage() { } SitePageLayout { Column( - modifier = Modifier.fillMaxWidth(Res.Dimens.MAX_WIDTH(breakpoint = breakpoint).percent).maxWidth(800.px) + modifier = Modifier + .fillMaxWidth(Res.Dimens.MAX_WIDTH(breakpoint = breakpoint).percent) + .maxWidth(800.px) .padding(top = 40.px, bottom = 200.px) + .overflow(Overflow.Auto) ) { when (postState) { is UiState.Error -> AppErrorView(text = postState.errorMessage) {