diff --git a/app/src/main/scala/mdr/pdb/app/Main.scala b/app/src/main/scala/mdr/pdb/app/Main.scala index 081b4ee..b9f4fe1 100644 --- a/app/src/main/scala/mdr/pdb/app/Main.scala +++ b/app/src/main/scala/mdr/pdb/app/Main.scala @@ -79,7 +79,7 @@ pages.detail.UpravDukaz.Connector(state)(_).apply ) .collectStatic(Page.Dashboard)( - connectors.DashboardPageConnector(state.actionBus).apply + connectors.DashboardPageConnector(state).apply ) .collect[Page.NotFound](pg => pages.errors.NotFoundPage(Routes.homePage, pg.url, state.actionBus) @@ -94,7 +94,7 @@ ) .collectStatic(Page.Directory)( connectors - .DirectoryPageConnector(state.users, state.actionBus) + .DirectoryPageConnector(state) .apply ) div(cls := "h-full", child <-- pageSplitter.$view) diff --git a/app/src/main/scala/mdr/pdb/app/Main.scala b/app/src/main/scala/mdr/pdb/app/Main.scala index 081b4ee..b9f4fe1 100644 --- a/app/src/main/scala/mdr/pdb/app/Main.scala +++ b/app/src/main/scala/mdr/pdb/app/Main.scala @@ -79,7 +79,7 @@ pages.detail.UpravDukaz.Connector(state)(_).apply ) .collectStatic(Page.Dashboard)( - connectors.DashboardPageConnector(state.actionBus).apply + connectors.DashboardPageConnector(state).apply ) .collect[Page.NotFound](pg => pages.errors.NotFoundPage(Routes.homePage, pg.url, state.actionBus) @@ -94,7 +94,7 @@ ) .collectStatic(Page.Directory)( connectors - .DirectoryPageConnector(state.users, state.actionBus) + .DirectoryPageConnector(state) .apply ) div(cls := "h-full", child <-- pageSplitter.$view) diff --git a/app/src/main/scala/mdr/pdb/app/components/AppPage.scala b/app/src/main/scala/mdr/pdb/app/components/AppPage.scala index 130c817..043f50e 100644 --- a/app/src/main/scala/mdr/pdb/app/components/AppPage.scala +++ b/app/src/main/scala/mdr/pdb/app/components/AppPage.scala @@ -9,6 +9,11 @@ import mdr.pdb.UserFunction object AppPage: + trait AppState { + def online: Signal[Boolean] + def actionBus: Observer[Action] + } + // TODO: pages by logged in user val pages: List[Page] = List(Page.Directory, Page.Dashboard) @@ -52,31 +57,34 @@ val $userInfo = $userProfile.signal.map(_.userInfo) type ViewModel = Option[HtmlElement] + def apply( - actionBus: Observer[Action] + state: AppState )($m: Signal[ViewModel], mods: Modifier[HtmlElement]*)(using router: Router[Page] ): HtmlElement = - PageLayout(actionBus)( - $m.combineWith($userInfo, router.$currentPage).map((c, u, cp) => - PageLayout.ViewModel( - NavigationBar.ViewModel( - u, - pages.map(p => - NavigationBar.Link( - () => - PageLink( - p, - actionBus - ), - p == cp - ) + PageLayout(state.actionBus)( + $m.combineWith($userInfo, router.$currentPage, state.online).map( + (c, u, cp, o) => + PageLayout.ViewModel( + NavigationBar.ViewModel( + u, + pages.map(p => + NavigationBar.Link( + () => + PageLink( + p, + state.actionBus + ), + p == cp + ) + ), + userMenu, + logo, + o ), - userMenu, - logo - ), - c - ) + c + ) ), mods ) diff --git a/app/src/main/scala/mdr/pdb/app/Main.scala b/app/src/main/scala/mdr/pdb/app/Main.scala index 081b4ee..b9f4fe1 100644 --- a/app/src/main/scala/mdr/pdb/app/Main.scala +++ b/app/src/main/scala/mdr/pdb/app/Main.scala @@ -79,7 +79,7 @@ pages.detail.UpravDukaz.Connector(state)(_).apply ) .collectStatic(Page.Dashboard)( - connectors.DashboardPageConnector(state.actionBus).apply + connectors.DashboardPageConnector(state).apply ) .collect[Page.NotFound](pg => pages.errors.NotFoundPage(Routes.homePage, pg.url, state.actionBus) @@ -94,7 +94,7 @@ ) .collectStatic(Page.Directory)( connectors - .DirectoryPageConnector(state.users, state.actionBus) + .DirectoryPageConnector(state) .apply ) div(cls := "h-full", child <-- pageSplitter.$view) diff --git a/app/src/main/scala/mdr/pdb/app/components/AppPage.scala b/app/src/main/scala/mdr/pdb/app/components/AppPage.scala index 130c817..043f50e 100644 --- a/app/src/main/scala/mdr/pdb/app/components/AppPage.scala +++ b/app/src/main/scala/mdr/pdb/app/components/AppPage.scala @@ -9,6 +9,11 @@ import mdr.pdb.UserFunction object AppPage: + trait AppState { + def online: Signal[Boolean] + def actionBus: Observer[Action] + } + // TODO: pages by logged in user val pages: List[Page] = List(Page.Directory, Page.Dashboard) @@ -52,31 +57,34 @@ val $userInfo = $userProfile.signal.map(_.userInfo) type ViewModel = Option[HtmlElement] + def apply( - actionBus: Observer[Action] + state: AppState )($m: Signal[ViewModel], mods: Modifier[HtmlElement]*)(using router: Router[Page] ): HtmlElement = - PageLayout(actionBus)( - $m.combineWith($userInfo, router.$currentPage).map((c, u, cp) => - PageLayout.ViewModel( - NavigationBar.ViewModel( - u, - pages.map(p => - NavigationBar.Link( - () => - PageLink( - p, - actionBus - ), - p == cp - ) + PageLayout(state.actionBus)( + $m.combineWith($userInfo, router.$currentPage, state.online).map( + (c, u, cp, o) => + PageLayout.ViewModel( + NavigationBar.ViewModel( + u, + pages.map(p => + NavigationBar.Link( + () => + PageLink( + p, + state.actionBus + ), + p == cp + ) + ), + userMenu, + logo, + o ), - userMenu, - logo - ), - c - ) + c + ) ), mods ) diff --git a/app/src/main/scala/mdr/pdb/app/components/NavigationBar.scala b/app/src/main/scala/mdr/pdb/app/components/NavigationBar.scala index 4bb8b8d..1ed5b9a 100644 --- a/app/src/main/scala/mdr/pdb/app/components/NavigationBar.scala +++ b/app/src/main/scala/mdr/pdb/app/components/NavigationBar.scala @@ -17,7 +17,8 @@ userInfo: UserInfo, pages: List[Link], userMenu: List[MenuItem], - logo: Logo + logo: Logo, + online: Boolean ) def apply($m: Signal[ViewModel]): HtmlElement = @@ -30,6 +31,14 @@ inline def avatarImage(size: Int = 8) = Avatar($userInfo.map(_.img)).avatarImage(size) + def offlineIcon = button( + tpe := "button", + cls <-- $m.map(m => List("hidden" -> m.online)), + cls("bg-indigo-600 text-indigo-200"), + span(cls := "sr-only", "Server odpojen"), + Icons.outline.`status-offline`() + ) + def notificationButton = button( tpe := "button", cls := List( @@ -44,7 +53,7 @@ "rounded-full", "text-indigo-200" ), - span(cls := "sr-only", "View notifications"), + span(cls := "sr-only", "Zobrazit upozornění"), Icons.outline.bell() ) @@ -132,7 +141,11 @@ ) ) ), - notificationButton.amend(cls := List("flex-shrink-0", "ml-auto")) + div( + cls("flex-shrink-0 ml-auto flex"), + offlineIcon, + notificationButton + ) ), div( cls := "mt-3 px-2 space-y-1", @@ -200,6 +213,7 @@ desktopOnly, div( cls := "ml-4 flex items-center md:ml-6", + offlineIcon, notificationButton, userProfile ) diff --git a/app/src/main/scala/mdr/pdb/app/Main.scala b/app/src/main/scala/mdr/pdb/app/Main.scala index 081b4ee..b9f4fe1 100644 --- a/app/src/main/scala/mdr/pdb/app/Main.scala +++ b/app/src/main/scala/mdr/pdb/app/Main.scala @@ -79,7 +79,7 @@ pages.detail.UpravDukaz.Connector(state)(_).apply ) .collectStatic(Page.Dashboard)( - connectors.DashboardPageConnector(state.actionBus).apply + connectors.DashboardPageConnector(state).apply ) .collect[Page.NotFound](pg => pages.errors.NotFoundPage(Routes.homePage, pg.url, state.actionBus) @@ -94,7 +94,7 @@ ) .collectStatic(Page.Directory)( connectors - .DirectoryPageConnector(state.users, state.actionBus) + .DirectoryPageConnector(state) .apply ) div(cls := "h-full", child <-- pageSplitter.$view) diff --git a/app/src/main/scala/mdr/pdb/app/components/AppPage.scala b/app/src/main/scala/mdr/pdb/app/components/AppPage.scala index 130c817..043f50e 100644 --- a/app/src/main/scala/mdr/pdb/app/components/AppPage.scala +++ b/app/src/main/scala/mdr/pdb/app/components/AppPage.scala @@ -9,6 +9,11 @@ import mdr.pdb.UserFunction object AppPage: + trait AppState { + def online: Signal[Boolean] + def actionBus: Observer[Action] + } + // TODO: pages by logged in user val pages: List[Page] = List(Page.Directory, Page.Dashboard) @@ -52,31 +57,34 @@ val $userInfo = $userProfile.signal.map(_.userInfo) type ViewModel = Option[HtmlElement] + def apply( - actionBus: Observer[Action] + state: AppState )($m: Signal[ViewModel], mods: Modifier[HtmlElement]*)(using router: Router[Page] ): HtmlElement = - PageLayout(actionBus)( - $m.combineWith($userInfo, router.$currentPage).map((c, u, cp) => - PageLayout.ViewModel( - NavigationBar.ViewModel( - u, - pages.map(p => - NavigationBar.Link( - () => - PageLink( - p, - actionBus - ), - p == cp - ) + PageLayout(state.actionBus)( + $m.combineWith($userInfo, router.$currentPage, state.online).map( + (c, u, cp, o) => + PageLayout.ViewModel( + NavigationBar.ViewModel( + u, + pages.map(p => + NavigationBar.Link( + () => + PageLink( + p, + state.actionBus + ), + p == cp + ) + ), + userMenu, + logo, + o ), - userMenu, - logo - ), - c - ) + c + ) ), mods ) diff --git a/app/src/main/scala/mdr/pdb/app/components/NavigationBar.scala b/app/src/main/scala/mdr/pdb/app/components/NavigationBar.scala index 4bb8b8d..1ed5b9a 100644 --- a/app/src/main/scala/mdr/pdb/app/components/NavigationBar.scala +++ b/app/src/main/scala/mdr/pdb/app/components/NavigationBar.scala @@ -17,7 +17,8 @@ userInfo: UserInfo, pages: List[Link], userMenu: List[MenuItem], - logo: Logo + logo: Logo, + online: Boolean ) def apply($m: Signal[ViewModel]): HtmlElement = @@ -30,6 +31,14 @@ inline def avatarImage(size: Int = 8) = Avatar($userInfo.map(_.img)).avatarImage(size) + def offlineIcon = button( + tpe := "button", + cls <-- $m.map(m => List("hidden" -> m.online)), + cls("bg-indigo-600 text-indigo-200"), + span(cls := "sr-only", "Server odpojen"), + Icons.outline.`status-offline`() + ) + def notificationButton = button( tpe := "button", cls := List( @@ -44,7 +53,7 @@ "rounded-full", "text-indigo-200" ), - span(cls := "sr-only", "View notifications"), + span(cls := "sr-only", "Zobrazit upozornění"), Icons.outline.bell() ) @@ -132,7 +141,11 @@ ) ) ), - notificationButton.amend(cls := List("flex-shrink-0", "ml-auto")) + div( + cls("flex-shrink-0 ml-auto flex"), + offlineIcon, + notificationButton + ) ), div( cls := "mt-3 px-2 space-y-1", @@ -200,6 +213,7 @@ desktopOnly, div( cls := "ml-4 flex items-center md:ml-6", + offlineIcon, notificationButton, userProfile ) diff --git a/app/src/main/scala/mdr/pdb/app/connectors/DashboardPageConnector.scala b/app/src/main/scala/mdr/pdb/app/connectors/DashboardPageConnector.scala index 7682772..20d426a 100644 --- a/app/src/main/scala/mdr/pdb/app/connectors/DashboardPageConnector.scala +++ b/app/src/main/scala/mdr/pdb/app/connectors/DashboardPageConnector.scala @@ -5,9 +5,12 @@ import mdr.pdb.app.pages.dashboard.DashboardPage import com.raquo.waypoint.Router import mdr.pdb.app.components.AppPage +import state.AppState -class DashboardPageConnector(actionBus: Observer[Action])(using +class DashboardPageConnector(state: AppState)(using router: Router[Page] ): def apply: HtmlElement = - AppPage(actionBus)(Val(Some(DashboardPage.render))) + AppPage(state)( + Val(Some(DashboardPage.render)) + ) diff --git a/app/src/main/scala/mdr/pdb/app/Main.scala b/app/src/main/scala/mdr/pdb/app/Main.scala index 081b4ee..b9f4fe1 100644 --- a/app/src/main/scala/mdr/pdb/app/Main.scala +++ b/app/src/main/scala/mdr/pdb/app/Main.scala @@ -79,7 +79,7 @@ pages.detail.UpravDukaz.Connector(state)(_).apply ) .collectStatic(Page.Dashboard)( - connectors.DashboardPageConnector(state.actionBus).apply + connectors.DashboardPageConnector(state).apply ) .collect[Page.NotFound](pg => pages.errors.NotFoundPage(Routes.homePage, pg.url, state.actionBus) @@ -94,7 +94,7 @@ ) .collectStatic(Page.Directory)( connectors - .DirectoryPageConnector(state.users, state.actionBus) + .DirectoryPageConnector(state) .apply ) div(cls := "h-full", child <-- pageSplitter.$view) diff --git a/app/src/main/scala/mdr/pdb/app/components/AppPage.scala b/app/src/main/scala/mdr/pdb/app/components/AppPage.scala index 130c817..043f50e 100644 --- a/app/src/main/scala/mdr/pdb/app/components/AppPage.scala +++ b/app/src/main/scala/mdr/pdb/app/components/AppPage.scala @@ -9,6 +9,11 @@ import mdr.pdb.UserFunction object AppPage: + trait AppState { + def online: Signal[Boolean] + def actionBus: Observer[Action] + } + // TODO: pages by logged in user val pages: List[Page] = List(Page.Directory, Page.Dashboard) @@ -52,31 +57,34 @@ val $userInfo = $userProfile.signal.map(_.userInfo) type ViewModel = Option[HtmlElement] + def apply( - actionBus: Observer[Action] + state: AppState )($m: Signal[ViewModel], mods: Modifier[HtmlElement]*)(using router: Router[Page] ): HtmlElement = - PageLayout(actionBus)( - $m.combineWith($userInfo, router.$currentPage).map((c, u, cp) => - PageLayout.ViewModel( - NavigationBar.ViewModel( - u, - pages.map(p => - NavigationBar.Link( - () => - PageLink( - p, - actionBus - ), - p == cp - ) + PageLayout(state.actionBus)( + $m.combineWith($userInfo, router.$currentPage, state.online).map( + (c, u, cp, o) => + PageLayout.ViewModel( + NavigationBar.ViewModel( + u, + pages.map(p => + NavigationBar.Link( + () => + PageLink( + p, + state.actionBus + ), + p == cp + ) + ), + userMenu, + logo, + o ), - userMenu, - logo - ), - c - ) + c + ) ), mods ) diff --git a/app/src/main/scala/mdr/pdb/app/components/NavigationBar.scala b/app/src/main/scala/mdr/pdb/app/components/NavigationBar.scala index 4bb8b8d..1ed5b9a 100644 --- a/app/src/main/scala/mdr/pdb/app/components/NavigationBar.scala +++ b/app/src/main/scala/mdr/pdb/app/components/NavigationBar.scala @@ -17,7 +17,8 @@ userInfo: UserInfo, pages: List[Link], userMenu: List[MenuItem], - logo: Logo + logo: Logo, + online: Boolean ) def apply($m: Signal[ViewModel]): HtmlElement = @@ -30,6 +31,14 @@ inline def avatarImage(size: Int = 8) = Avatar($userInfo.map(_.img)).avatarImage(size) + def offlineIcon = button( + tpe := "button", + cls <-- $m.map(m => List("hidden" -> m.online)), + cls("bg-indigo-600 text-indigo-200"), + span(cls := "sr-only", "Server odpojen"), + Icons.outline.`status-offline`() + ) + def notificationButton = button( tpe := "button", cls := List( @@ -44,7 +53,7 @@ "rounded-full", "text-indigo-200" ), - span(cls := "sr-only", "View notifications"), + span(cls := "sr-only", "Zobrazit upozornění"), Icons.outline.bell() ) @@ -132,7 +141,11 @@ ) ) ), - notificationButton.amend(cls := List("flex-shrink-0", "ml-auto")) + div( + cls("flex-shrink-0 ml-auto flex"), + offlineIcon, + notificationButton + ) ), div( cls := "mt-3 px-2 space-y-1", @@ -200,6 +213,7 @@ desktopOnly, div( cls := "ml-4 flex items-center md:ml-6", + offlineIcon, notificationButton, userProfile ) diff --git a/app/src/main/scala/mdr/pdb/app/connectors/DashboardPageConnector.scala b/app/src/main/scala/mdr/pdb/app/connectors/DashboardPageConnector.scala index 7682772..20d426a 100644 --- a/app/src/main/scala/mdr/pdb/app/connectors/DashboardPageConnector.scala +++ b/app/src/main/scala/mdr/pdb/app/connectors/DashboardPageConnector.scala @@ -5,9 +5,12 @@ import mdr.pdb.app.pages.dashboard.DashboardPage import com.raquo.waypoint.Router import mdr.pdb.app.components.AppPage +import state.AppState -class DashboardPageConnector(actionBus: Observer[Action])(using +class DashboardPageConnector(state: AppState)(using router: Router[Page] ): def apply: HtmlElement = - AppPage(actionBus)(Val(Some(DashboardPage.render))) + AppPage(state)( + Val(Some(DashboardPage.render)) + ) diff --git a/app/src/main/scala/mdr/pdb/app/connectors/DetailKriteriaPageConnector.scala b/app/src/main/scala/mdr/pdb/app/connectors/DetailKriteriaPageConnector.scala index 76b9133..9217c19 100644 --- a/app/src/main/scala/mdr/pdb/app/connectors/DetailKriteriaPageConnector.scala +++ b/app/src/main/scala/mdr/pdb/app/connectors/DetailKriteriaPageConnector.scala @@ -12,10 +12,9 @@ import mdr.pdb.ParameterCriteria object DetailKriteriaPageConnector { - trait AppState { + trait AppState extends AppPage.AppState { def details: EventStream[UserInfo] def parameters: EventStream[List[Parameter]] - def actionBus: Observer[Action] } } @@ -47,7 +46,7 @@ ) def apply: HtmlElement = - AppPage(state.actionBus)( + AppPage(state)( $merged.map(_.map(buildModel)) .split(_ => ())((_, s, $s) => DetailKriteriaPage($s)(state.actionBus.contramap { diff --git a/app/src/main/scala/mdr/pdb/app/Main.scala b/app/src/main/scala/mdr/pdb/app/Main.scala index 081b4ee..b9f4fe1 100644 --- a/app/src/main/scala/mdr/pdb/app/Main.scala +++ b/app/src/main/scala/mdr/pdb/app/Main.scala @@ -79,7 +79,7 @@ pages.detail.UpravDukaz.Connector(state)(_).apply ) .collectStatic(Page.Dashboard)( - connectors.DashboardPageConnector(state.actionBus).apply + connectors.DashboardPageConnector(state).apply ) .collect[Page.NotFound](pg => pages.errors.NotFoundPage(Routes.homePage, pg.url, state.actionBus) @@ -94,7 +94,7 @@ ) .collectStatic(Page.Directory)( connectors - .DirectoryPageConnector(state.users, state.actionBus) + .DirectoryPageConnector(state) .apply ) div(cls := "h-full", child <-- pageSplitter.$view) diff --git a/app/src/main/scala/mdr/pdb/app/components/AppPage.scala b/app/src/main/scala/mdr/pdb/app/components/AppPage.scala index 130c817..043f50e 100644 --- a/app/src/main/scala/mdr/pdb/app/components/AppPage.scala +++ b/app/src/main/scala/mdr/pdb/app/components/AppPage.scala @@ -9,6 +9,11 @@ import mdr.pdb.UserFunction object AppPage: + trait AppState { + def online: Signal[Boolean] + def actionBus: Observer[Action] + } + // TODO: pages by logged in user val pages: List[Page] = List(Page.Directory, Page.Dashboard) @@ -52,31 +57,34 @@ val $userInfo = $userProfile.signal.map(_.userInfo) type ViewModel = Option[HtmlElement] + def apply( - actionBus: Observer[Action] + state: AppState )($m: Signal[ViewModel], mods: Modifier[HtmlElement]*)(using router: Router[Page] ): HtmlElement = - PageLayout(actionBus)( - $m.combineWith($userInfo, router.$currentPage).map((c, u, cp) => - PageLayout.ViewModel( - NavigationBar.ViewModel( - u, - pages.map(p => - NavigationBar.Link( - () => - PageLink( - p, - actionBus - ), - p == cp - ) + PageLayout(state.actionBus)( + $m.combineWith($userInfo, router.$currentPage, state.online).map( + (c, u, cp, o) => + PageLayout.ViewModel( + NavigationBar.ViewModel( + u, + pages.map(p => + NavigationBar.Link( + () => + PageLink( + p, + state.actionBus + ), + p == cp + ) + ), + userMenu, + logo, + o ), - userMenu, - logo - ), - c - ) + c + ) ), mods ) diff --git a/app/src/main/scala/mdr/pdb/app/components/NavigationBar.scala b/app/src/main/scala/mdr/pdb/app/components/NavigationBar.scala index 4bb8b8d..1ed5b9a 100644 --- a/app/src/main/scala/mdr/pdb/app/components/NavigationBar.scala +++ b/app/src/main/scala/mdr/pdb/app/components/NavigationBar.scala @@ -17,7 +17,8 @@ userInfo: UserInfo, pages: List[Link], userMenu: List[MenuItem], - logo: Logo + logo: Logo, + online: Boolean ) def apply($m: Signal[ViewModel]): HtmlElement = @@ -30,6 +31,14 @@ inline def avatarImage(size: Int = 8) = Avatar($userInfo.map(_.img)).avatarImage(size) + def offlineIcon = button( + tpe := "button", + cls <-- $m.map(m => List("hidden" -> m.online)), + cls("bg-indigo-600 text-indigo-200"), + span(cls := "sr-only", "Server odpojen"), + Icons.outline.`status-offline`() + ) + def notificationButton = button( tpe := "button", cls := List( @@ -44,7 +53,7 @@ "rounded-full", "text-indigo-200" ), - span(cls := "sr-only", "View notifications"), + span(cls := "sr-only", "Zobrazit upozornění"), Icons.outline.bell() ) @@ -132,7 +141,11 @@ ) ) ), - notificationButton.amend(cls := List("flex-shrink-0", "ml-auto")) + div( + cls("flex-shrink-0 ml-auto flex"), + offlineIcon, + notificationButton + ) ), div( cls := "mt-3 px-2 space-y-1", @@ -200,6 +213,7 @@ desktopOnly, div( cls := "ml-4 flex items-center md:ml-6", + offlineIcon, notificationButton, userProfile ) diff --git a/app/src/main/scala/mdr/pdb/app/connectors/DashboardPageConnector.scala b/app/src/main/scala/mdr/pdb/app/connectors/DashboardPageConnector.scala index 7682772..20d426a 100644 --- a/app/src/main/scala/mdr/pdb/app/connectors/DashboardPageConnector.scala +++ b/app/src/main/scala/mdr/pdb/app/connectors/DashboardPageConnector.scala @@ -5,9 +5,12 @@ import mdr.pdb.app.pages.dashboard.DashboardPage import com.raquo.waypoint.Router import mdr.pdb.app.components.AppPage +import state.AppState -class DashboardPageConnector(actionBus: Observer[Action])(using +class DashboardPageConnector(state: AppState)(using router: Router[Page] ): def apply: HtmlElement = - AppPage(actionBus)(Val(Some(DashboardPage.render))) + AppPage(state)( + Val(Some(DashboardPage.render)) + ) diff --git a/app/src/main/scala/mdr/pdb/app/connectors/DetailKriteriaPageConnector.scala b/app/src/main/scala/mdr/pdb/app/connectors/DetailKriteriaPageConnector.scala index 76b9133..9217c19 100644 --- a/app/src/main/scala/mdr/pdb/app/connectors/DetailKriteriaPageConnector.scala +++ b/app/src/main/scala/mdr/pdb/app/connectors/DetailKriteriaPageConnector.scala @@ -12,10 +12,9 @@ import mdr.pdb.ParameterCriteria object DetailKriteriaPageConnector { - trait AppState { + trait AppState extends AppPage.AppState { def details: EventStream[UserInfo] def parameters: EventStream[List[Parameter]] - def actionBus: Observer[Action] } } @@ -47,7 +46,7 @@ ) def apply: HtmlElement = - AppPage(state.actionBus)( + AppPage(state)( $merged.map(_.map(buildModel)) .split(_ => ())((_, s, $s) => DetailKriteriaPage($s)(state.actionBus.contramap { diff --git a/app/src/main/scala/mdr/pdb/app/connectors/DetailPageConnector.scala b/app/src/main/scala/mdr/pdb/app/connectors/DetailPageConnector.scala index df3a6b7..4f0c1e2 100644 --- a/app/src/main/scala/mdr/pdb/app/connectors/DetailPageConnector.scala +++ b/app/src/main/scala/mdr/pdb/app/connectors/DetailPageConnector.scala @@ -13,10 +13,9 @@ import fiftyforms.ui.components.tailwind.Color object DetailPageConnector { - trait AppState { + trait AppState extends AppPage.AppState { def details: EventStream[UserInfo] def parameters: EventStream[List[Parameter]] - def actionBus: Observer[Action] } } @@ -36,7 +35,7 @@ val $params = state.parameters.startWithNone def apply: HtmlElement = - AppPage(state.actionBus)( + AppPage(state)( $data.combineWithFn($params)(_ zip _) .map(_.map(buildModel)) .split(_ => ())((_, _, s) => DetailPage(s)), diff --git a/app/src/main/scala/mdr/pdb/app/Main.scala b/app/src/main/scala/mdr/pdb/app/Main.scala index 081b4ee..b9f4fe1 100644 --- a/app/src/main/scala/mdr/pdb/app/Main.scala +++ b/app/src/main/scala/mdr/pdb/app/Main.scala @@ -79,7 +79,7 @@ pages.detail.UpravDukaz.Connector(state)(_).apply ) .collectStatic(Page.Dashboard)( - connectors.DashboardPageConnector(state.actionBus).apply + connectors.DashboardPageConnector(state).apply ) .collect[Page.NotFound](pg => pages.errors.NotFoundPage(Routes.homePage, pg.url, state.actionBus) @@ -94,7 +94,7 @@ ) .collectStatic(Page.Directory)( connectors - .DirectoryPageConnector(state.users, state.actionBus) + .DirectoryPageConnector(state) .apply ) div(cls := "h-full", child <-- pageSplitter.$view) diff --git a/app/src/main/scala/mdr/pdb/app/components/AppPage.scala b/app/src/main/scala/mdr/pdb/app/components/AppPage.scala index 130c817..043f50e 100644 --- a/app/src/main/scala/mdr/pdb/app/components/AppPage.scala +++ b/app/src/main/scala/mdr/pdb/app/components/AppPage.scala @@ -9,6 +9,11 @@ import mdr.pdb.UserFunction object AppPage: + trait AppState { + def online: Signal[Boolean] + def actionBus: Observer[Action] + } + // TODO: pages by logged in user val pages: List[Page] = List(Page.Directory, Page.Dashboard) @@ -52,31 +57,34 @@ val $userInfo = $userProfile.signal.map(_.userInfo) type ViewModel = Option[HtmlElement] + def apply( - actionBus: Observer[Action] + state: AppState )($m: Signal[ViewModel], mods: Modifier[HtmlElement]*)(using router: Router[Page] ): HtmlElement = - PageLayout(actionBus)( - $m.combineWith($userInfo, router.$currentPage).map((c, u, cp) => - PageLayout.ViewModel( - NavigationBar.ViewModel( - u, - pages.map(p => - NavigationBar.Link( - () => - PageLink( - p, - actionBus - ), - p == cp - ) + PageLayout(state.actionBus)( + $m.combineWith($userInfo, router.$currentPage, state.online).map( + (c, u, cp, o) => + PageLayout.ViewModel( + NavigationBar.ViewModel( + u, + pages.map(p => + NavigationBar.Link( + () => + PageLink( + p, + state.actionBus + ), + p == cp + ) + ), + userMenu, + logo, + o ), - userMenu, - logo - ), - c - ) + c + ) ), mods ) diff --git a/app/src/main/scala/mdr/pdb/app/components/NavigationBar.scala b/app/src/main/scala/mdr/pdb/app/components/NavigationBar.scala index 4bb8b8d..1ed5b9a 100644 --- a/app/src/main/scala/mdr/pdb/app/components/NavigationBar.scala +++ b/app/src/main/scala/mdr/pdb/app/components/NavigationBar.scala @@ -17,7 +17,8 @@ userInfo: UserInfo, pages: List[Link], userMenu: List[MenuItem], - logo: Logo + logo: Logo, + online: Boolean ) def apply($m: Signal[ViewModel]): HtmlElement = @@ -30,6 +31,14 @@ inline def avatarImage(size: Int = 8) = Avatar($userInfo.map(_.img)).avatarImage(size) + def offlineIcon = button( + tpe := "button", + cls <-- $m.map(m => List("hidden" -> m.online)), + cls("bg-indigo-600 text-indigo-200"), + span(cls := "sr-only", "Server odpojen"), + Icons.outline.`status-offline`() + ) + def notificationButton = button( tpe := "button", cls := List( @@ -44,7 +53,7 @@ "rounded-full", "text-indigo-200" ), - span(cls := "sr-only", "View notifications"), + span(cls := "sr-only", "Zobrazit upozornění"), Icons.outline.bell() ) @@ -132,7 +141,11 @@ ) ) ), - notificationButton.amend(cls := List("flex-shrink-0", "ml-auto")) + div( + cls("flex-shrink-0 ml-auto flex"), + offlineIcon, + notificationButton + ) ), div( cls := "mt-3 px-2 space-y-1", @@ -200,6 +213,7 @@ desktopOnly, div( cls := "ml-4 flex items-center md:ml-6", + offlineIcon, notificationButton, userProfile ) diff --git a/app/src/main/scala/mdr/pdb/app/connectors/DashboardPageConnector.scala b/app/src/main/scala/mdr/pdb/app/connectors/DashboardPageConnector.scala index 7682772..20d426a 100644 --- a/app/src/main/scala/mdr/pdb/app/connectors/DashboardPageConnector.scala +++ b/app/src/main/scala/mdr/pdb/app/connectors/DashboardPageConnector.scala @@ -5,9 +5,12 @@ import mdr.pdb.app.pages.dashboard.DashboardPage import com.raquo.waypoint.Router import mdr.pdb.app.components.AppPage +import state.AppState -class DashboardPageConnector(actionBus: Observer[Action])(using +class DashboardPageConnector(state: AppState)(using router: Router[Page] ): def apply: HtmlElement = - AppPage(actionBus)(Val(Some(DashboardPage.render))) + AppPage(state)( + Val(Some(DashboardPage.render)) + ) diff --git a/app/src/main/scala/mdr/pdb/app/connectors/DetailKriteriaPageConnector.scala b/app/src/main/scala/mdr/pdb/app/connectors/DetailKriteriaPageConnector.scala index 76b9133..9217c19 100644 --- a/app/src/main/scala/mdr/pdb/app/connectors/DetailKriteriaPageConnector.scala +++ b/app/src/main/scala/mdr/pdb/app/connectors/DetailKriteriaPageConnector.scala @@ -12,10 +12,9 @@ import mdr.pdb.ParameterCriteria object DetailKriteriaPageConnector { - trait AppState { + trait AppState extends AppPage.AppState { def details: EventStream[UserInfo] def parameters: EventStream[List[Parameter]] - def actionBus: Observer[Action] } } @@ -47,7 +46,7 @@ ) def apply: HtmlElement = - AppPage(state.actionBus)( + AppPage(state)( $merged.map(_.map(buildModel)) .split(_ => ())((_, s, $s) => DetailKriteriaPage($s)(state.actionBus.contramap { diff --git a/app/src/main/scala/mdr/pdb/app/connectors/DetailPageConnector.scala b/app/src/main/scala/mdr/pdb/app/connectors/DetailPageConnector.scala index df3a6b7..4f0c1e2 100644 --- a/app/src/main/scala/mdr/pdb/app/connectors/DetailPageConnector.scala +++ b/app/src/main/scala/mdr/pdb/app/connectors/DetailPageConnector.scala @@ -13,10 +13,9 @@ import fiftyforms.ui.components.tailwind.Color object DetailPageConnector { - trait AppState { + trait AppState extends AppPage.AppState { def details: EventStream[UserInfo] def parameters: EventStream[List[Parameter]] - def actionBus: Observer[Action] } } @@ -36,7 +35,7 @@ val $params = state.parameters.startWithNone def apply: HtmlElement = - AppPage(state.actionBus)( + AppPage(state)( $data.combineWithFn($params)(_ zip _) .map(_.map(buildModel)) .split(_ => ())((_, _, s) => DetailPage(s)), diff --git a/app/src/main/scala/mdr/pdb/app/connectors/DetailParametruPageConnector.scala b/app/src/main/scala/mdr/pdb/app/connectors/DetailParametruPageConnector.scala index fa7193d..47c2336 100644 --- a/app/src/main/scala/mdr/pdb/app/connectors/DetailParametruPageConnector.scala +++ b/app/src/main/scala/mdr/pdb/app/connectors/DetailParametruPageConnector.scala @@ -11,10 +11,9 @@ import mdr.pdb.app.components.PageLink object DetailParametruPageConnector { - trait AppState { + trait AppState extends AppPage.AppState { def details: EventStream[UserInfo] def parameters: EventStream[List[Parameter]] - def actionBus: Observer[Action] } } @@ -41,7 +40,7 @@ ) def apply: HtmlElement = - AppPage(state.actionBus)( + AppPage(state)( $merged.map(_.map(buildModel)) .split(_ => ())((_, _, s) => DetailParametruPage(s)), $pageChangeSignal --> state.actionBus diff --git a/app/src/main/scala/mdr/pdb/app/Main.scala b/app/src/main/scala/mdr/pdb/app/Main.scala index 081b4ee..b9f4fe1 100644 --- a/app/src/main/scala/mdr/pdb/app/Main.scala +++ b/app/src/main/scala/mdr/pdb/app/Main.scala @@ -79,7 +79,7 @@ pages.detail.UpravDukaz.Connector(state)(_).apply ) .collectStatic(Page.Dashboard)( - connectors.DashboardPageConnector(state.actionBus).apply + connectors.DashboardPageConnector(state).apply ) .collect[Page.NotFound](pg => pages.errors.NotFoundPage(Routes.homePage, pg.url, state.actionBus) @@ -94,7 +94,7 @@ ) .collectStatic(Page.Directory)( connectors - .DirectoryPageConnector(state.users, state.actionBus) + .DirectoryPageConnector(state) .apply ) div(cls := "h-full", child <-- pageSplitter.$view) diff --git a/app/src/main/scala/mdr/pdb/app/components/AppPage.scala b/app/src/main/scala/mdr/pdb/app/components/AppPage.scala index 130c817..043f50e 100644 --- a/app/src/main/scala/mdr/pdb/app/components/AppPage.scala +++ b/app/src/main/scala/mdr/pdb/app/components/AppPage.scala @@ -9,6 +9,11 @@ import mdr.pdb.UserFunction object AppPage: + trait AppState { + def online: Signal[Boolean] + def actionBus: Observer[Action] + } + // TODO: pages by logged in user val pages: List[Page] = List(Page.Directory, Page.Dashboard) @@ -52,31 +57,34 @@ val $userInfo = $userProfile.signal.map(_.userInfo) type ViewModel = Option[HtmlElement] + def apply( - actionBus: Observer[Action] + state: AppState )($m: Signal[ViewModel], mods: Modifier[HtmlElement]*)(using router: Router[Page] ): HtmlElement = - PageLayout(actionBus)( - $m.combineWith($userInfo, router.$currentPage).map((c, u, cp) => - PageLayout.ViewModel( - NavigationBar.ViewModel( - u, - pages.map(p => - NavigationBar.Link( - () => - PageLink( - p, - actionBus - ), - p == cp - ) + PageLayout(state.actionBus)( + $m.combineWith($userInfo, router.$currentPage, state.online).map( + (c, u, cp, o) => + PageLayout.ViewModel( + NavigationBar.ViewModel( + u, + pages.map(p => + NavigationBar.Link( + () => + PageLink( + p, + state.actionBus + ), + p == cp + ) + ), + userMenu, + logo, + o ), - userMenu, - logo - ), - c - ) + c + ) ), mods ) diff --git a/app/src/main/scala/mdr/pdb/app/components/NavigationBar.scala b/app/src/main/scala/mdr/pdb/app/components/NavigationBar.scala index 4bb8b8d..1ed5b9a 100644 --- a/app/src/main/scala/mdr/pdb/app/components/NavigationBar.scala +++ b/app/src/main/scala/mdr/pdb/app/components/NavigationBar.scala @@ -17,7 +17,8 @@ userInfo: UserInfo, pages: List[Link], userMenu: List[MenuItem], - logo: Logo + logo: Logo, + online: Boolean ) def apply($m: Signal[ViewModel]): HtmlElement = @@ -30,6 +31,14 @@ inline def avatarImage(size: Int = 8) = Avatar($userInfo.map(_.img)).avatarImage(size) + def offlineIcon = button( + tpe := "button", + cls <-- $m.map(m => List("hidden" -> m.online)), + cls("bg-indigo-600 text-indigo-200"), + span(cls := "sr-only", "Server odpojen"), + Icons.outline.`status-offline`() + ) + def notificationButton = button( tpe := "button", cls := List( @@ -44,7 +53,7 @@ "rounded-full", "text-indigo-200" ), - span(cls := "sr-only", "View notifications"), + span(cls := "sr-only", "Zobrazit upozornění"), Icons.outline.bell() ) @@ -132,7 +141,11 @@ ) ) ), - notificationButton.amend(cls := List("flex-shrink-0", "ml-auto")) + div( + cls("flex-shrink-0 ml-auto flex"), + offlineIcon, + notificationButton + ) ), div( cls := "mt-3 px-2 space-y-1", @@ -200,6 +213,7 @@ desktopOnly, div( cls := "ml-4 flex items-center md:ml-6", + offlineIcon, notificationButton, userProfile ) diff --git a/app/src/main/scala/mdr/pdb/app/connectors/DashboardPageConnector.scala b/app/src/main/scala/mdr/pdb/app/connectors/DashboardPageConnector.scala index 7682772..20d426a 100644 --- a/app/src/main/scala/mdr/pdb/app/connectors/DashboardPageConnector.scala +++ b/app/src/main/scala/mdr/pdb/app/connectors/DashboardPageConnector.scala @@ -5,9 +5,12 @@ import mdr.pdb.app.pages.dashboard.DashboardPage import com.raquo.waypoint.Router import mdr.pdb.app.components.AppPage +import state.AppState -class DashboardPageConnector(actionBus: Observer[Action])(using +class DashboardPageConnector(state: AppState)(using router: Router[Page] ): def apply: HtmlElement = - AppPage(actionBus)(Val(Some(DashboardPage.render))) + AppPage(state)( + Val(Some(DashboardPage.render)) + ) diff --git a/app/src/main/scala/mdr/pdb/app/connectors/DetailKriteriaPageConnector.scala b/app/src/main/scala/mdr/pdb/app/connectors/DetailKriteriaPageConnector.scala index 76b9133..9217c19 100644 --- a/app/src/main/scala/mdr/pdb/app/connectors/DetailKriteriaPageConnector.scala +++ b/app/src/main/scala/mdr/pdb/app/connectors/DetailKriteriaPageConnector.scala @@ -12,10 +12,9 @@ import mdr.pdb.ParameterCriteria object DetailKriteriaPageConnector { - trait AppState { + trait AppState extends AppPage.AppState { def details: EventStream[UserInfo] def parameters: EventStream[List[Parameter]] - def actionBus: Observer[Action] } } @@ -47,7 +46,7 @@ ) def apply: HtmlElement = - AppPage(state.actionBus)( + AppPage(state)( $merged.map(_.map(buildModel)) .split(_ => ())((_, s, $s) => DetailKriteriaPage($s)(state.actionBus.contramap { diff --git a/app/src/main/scala/mdr/pdb/app/connectors/DetailPageConnector.scala b/app/src/main/scala/mdr/pdb/app/connectors/DetailPageConnector.scala index df3a6b7..4f0c1e2 100644 --- a/app/src/main/scala/mdr/pdb/app/connectors/DetailPageConnector.scala +++ b/app/src/main/scala/mdr/pdb/app/connectors/DetailPageConnector.scala @@ -13,10 +13,9 @@ import fiftyforms.ui.components.tailwind.Color object DetailPageConnector { - trait AppState { + trait AppState extends AppPage.AppState { def details: EventStream[UserInfo] def parameters: EventStream[List[Parameter]] - def actionBus: Observer[Action] } } @@ -36,7 +35,7 @@ val $params = state.parameters.startWithNone def apply: HtmlElement = - AppPage(state.actionBus)( + AppPage(state)( $data.combineWithFn($params)(_ zip _) .map(_.map(buildModel)) .split(_ => ())((_, _, s) => DetailPage(s)), diff --git a/app/src/main/scala/mdr/pdb/app/connectors/DetailParametruPageConnector.scala b/app/src/main/scala/mdr/pdb/app/connectors/DetailParametruPageConnector.scala index fa7193d..47c2336 100644 --- a/app/src/main/scala/mdr/pdb/app/connectors/DetailParametruPageConnector.scala +++ b/app/src/main/scala/mdr/pdb/app/connectors/DetailParametruPageConnector.scala @@ -11,10 +11,9 @@ import mdr.pdb.app.components.PageLink object DetailParametruPageConnector { - trait AppState { + trait AppState extends AppPage.AppState { def details: EventStream[UserInfo] def parameters: EventStream[List[Parameter]] - def actionBus: Observer[Action] } } @@ -41,7 +40,7 @@ ) def apply: HtmlElement = - AppPage(state.actionBus)( + AppPage(state)( $merged.map(_.map(buildModel)) .split(_ => ())((_, _, s) => DetailParametruPage(s)), $pageChangeSignal --> state.actionBus diff --git a/app/src/main/scala/mdr/pdb/app/connectors/DirectoryPageConnector.scala b/app/src/main/scala/mdr/pdb/app/connectors/DirectoryPageConnector.scala index ab2c045..732aa41 100644 --- a/app/src/main/scala/mdr/pdb/app/connectors/DirectoryPageConnector.scala +++ b/app/src/main/scala/mdr/pdb/app/connectors/DirectoryPageConnector.scala @@ -7,15 +7,18 @@ import mdr.pdb.app.components.PageLink import mdr.pdb.app.components.AppPage -case class DirectoryPageConnector( - $input: EventStream[List[UserInfo]], - actionBus: Observer[Action] -)(using router: Router[Page]): - val $data = $input.startWithNone - val $actionSignal = EventStream.fromValue(FetchDirectory) +object DirectoryPageConnector: + trait AppState extends AppPage.AppState: + def users: EventStream[List[UserInfo]] +class DirectoryPageConnector(state: DirectoryPageConnector.AppState)(using + router: Router[Page] +): def apply: HtmlElement = - AppPage(actionBus)( + val $data = state.users.startWithNone + val $actionSignal = EventStream.fromValue(FetchDirectory) + + AppPage(state)( $data.split(_ => ())((_, _, s) => pages.directory.DirectoryPage( s.map( @@ -23,12 +26,12 @@ _.toUserRow(u => PageLink.container( Page.Detail(Page.Titled(u.personalNumber)), - actionBus + state.actionBus ) ) ) ) ) ), - $actionSignal --> actionBus + $actionSignal --> state.actionBus ) diff --git a/app/src/main/scala/mdr/pdb/app/Main.scala b/app/src/main/scala/mdr/pdb/app/Main.scala index 081b4ee..b9f4fe1 100644 --- a/app/src/main/scala/mdr/pdb/app/Main.scala +++ b/app/src/main/scala/mdr/pdb/app/Main.scala @@ -79,7 +79,7 @@ pages.detail.UpravDukaz.Connector(state)(_).apply ) .collectStatic(Page.Dashboard)( - connectors.DashboardPageConnector(state.actionBus).apply + connectors.DashboardPageConnector(state).apply ) .collect[Page.NotFound](pg => pages.errors.NotFoundPage(Routes.homePage, pg.url, state.actionBus) @@ -94,7 +94,7 @@ ) .collectStatic(Page.Directory)( connectors - .DirectoryPageConnector(state.users, state.actionBus) + .DirectoryPageConnector(state) .apply ) div(cls := "h-full", child <-- pageSplitter.$view) diff --git a/app/src/main/scala/mdr/pdb/app/components/AppPage.scala b/app/src/main/scala/mdr/pdb/app/components/AppPage.scala index 130c817..043f50e 100644 --- a/app/src/main/scala/mdr/pdb/app/components/AppPage.scala +++ b/app/src/main/scala/mdr/pdb/app/components/AppPage.scala @@ -9,6 +9,11 @@ import mdr.pdb.UserFunction object AppPage: + trait AppState { + def online: Signal[Boolean] + def actionBus: Observer[Action] + } + // TODO: pages by logged in user val pages: List[Page] = List(Page.Directory, Page.Dashboard) @@ -52,31 +57,34 @@ val $userInfo = $userProfile.signal.map(_.userInfo) type ViewModel = Option[HtmlElement] + def apply( - actionBus: Observer[Action] + state: AppState )($m: Signal[ViewModel], mods: Modifier[HtmlElement]*)(using router: Router[Page] ): HtmlElement = - PageLayout(actionBus)( - $m.combineWith($userInfo, router.$currentPage).map((c, u, cp) => - PageLayout.ViewModel( - NavigationBar.ViewModel( - u, - pages.map(p => - NavigationBar.Link( - () => - PageLink( - p, - actionBus - ), - p == cp - ) + PageLayout(state.actionBus)( + $m.combineWith($userInfo, router.$currentPage, state.online).map( + (c, u, cp, o) => + PageLayout.ViewModel( + NavigationBar.ViewModel( + u, + pages.map(p => + NavigationBar.Link( + () => + PageLink( + p, + state.actionBus + ), + p == cp + ) + ), + userMenu, + logo, + o ), - userMenu, - logo - ), - c - ) + c + ) ), mods ) diff --git a/app/src/main/scala/mdr/pdb/app/components/NavigationBar.scala b/app/src/main/scala/mdr/pdb/app/components/NavigationBar.scala index 4bb8b8d..1ed5b9a 100644 --- a/app/src/main/scala/mdr/pdb/app/components/NavigationBar.scala +++ b/app/src/main/scala/mdr/pdb/app/components/NavigationBar.scala @@ -17,7 +17,8 @@ userInfo: UserInfo, pages: List[Link], userMenu: List[MenuItem], - logo: Logo + logo: Logo, + online: Boolean ) def apply($m: Signal[ViewModel]): HtmlElement = @@ -30,6 +31,14 @@ inline def avatarImage(size: Int = 8) = Avatar($userInfo.map(_.img)).avatarImage(size) + def offlineIcon = button( + tpe := "button", + cls <-- $m.map(m => List("hidden" -> m.online)), + cls("bg-indigo-600 text-indigo-200"), + span(cls := "sr-only", "Server odpojen"), + Icons.outline.`status-offline`() + ) + def notificationButton = button( tpe := "button", cls := List( @@ -44,7 +53,7 @@ "rounded-full", "text-indigo-200" ), - span(cls := "sr-only", "View notifications"), + span(cls := "sr-only", "Zobrazit upozornění"), Icons.outline.bell() ) @@ -132,7 +141,11 @@ ) ) ), - notificationButton.amend(cls := List("flex-shrink-0", "ml-auto")) + div( + cls("flex-shrink-0 ml-auto flex"), + offlineIcon, + notificationButton + ) ), div( cls := "mt-3 px-2 space-y-1", @@ -200,6 +213,7 @@ desktopOnly, div( cls := "ml-4 flex items-center md:ml-6", + offlineIcon, notificationButton, userProfile ) diff --git a/app/src/main/scala/mdr/pdb/app/connectors/DashboardPageConnector.scala b/app/src/main/scala/mdr/pdb/app/connectors/DashboardPageConnector.scala index 7682772..20d426a 100644 --- a/app/src/main/scala/mdr/pdb/app/connectors/DashboardPageConnector.scala +++ b/app/src/main/scala/mdr/pdb/app/connectors/DashboardPageConnector.scala @@ -5,9 +5,12 @@ import mdr.pdb.app.pages.dashboard.DashboardPage import com.raquo.waypoint.Router import mdr.pdb.app.components.AppPage +import state.AppState -class DashboardPageConnector(actionBus: Observer[Action])(using +class DashboardPageConnector(state: AppState)(using router: Router[Page] ): def apply: HtmlElement = - AppPage(actionBus)(Val(Some(DashboardPage.render))) + AppPage(state)( + Val(Some(DashboardPage.render)) + ) diff --git a/app/src/main/scala/mdr/pdb/app/connectors/DetailKriteriaPageConnector.scala b/app/src/main/scala/mdr/pdb/app/connectors/DetailKriteriaPageConnector.scala index 76b9133..9217c19 100644 --- a/app/src/main/scala/mdr/pdb/app/connectors/DetailKriteriaPageConnector.scala +++ b/app/src/main/scala/mdr/pdb/app/connectors/DetailKriteriaPageConnector.scala @@ -12,10 +12,9 @@ import mdr.pdb.ParameterCriteria object DetailKriteriaPageConnector { - trait AppState { + trait AppState extends AppPage.AppState { def details: EventStream[UserInfo] def parameters: EventStream[List[Parameter]] - def actionBus: Observer[Action] } } @@ -47,7 +46,7 @@ ) def apply: HtmlElement = - AppPage(state.actionBus)( + AppPage(state)( $merged.map(_.map(buildModel)) .split(_ => ())((_, s, $s) => DetailKriteriaPage($s)(state.actionBus.contramap { diff --git a/app/src/main/scala/mdr/pdb/app/connectors/DetailPageConnector.scala b/app/src/main/scala/mdr/pdb/app/connectors/DetailPageConnector.scala index df3a6b7..4f0c1e2 100644 --- a/app/src/main/scala/mdr/pdb/app/connectors/DetailPageConnector.scala +++ b/app/src/main/scala/mdr/pdb/app/connectors/DetailPageConnector.scala @@ -13,10 +13,9 @@ import fiftyforms.ui.components.tailwind.Color object DetailPageConnector { - trait AppState { + trait AppState extends AppPage.AppState { def details: EventStream[UserInfo] def parameters: EventStream[List[Parameter]] - def actionBus: Observer[Action] } } @@ -36,7 +35,7 @@ val $params = state.parameters.startWithNone def apply: HtmlElement = - AppPage(state.actionBus)( + AppPage(state)( $data.combineWithFn($params)(_ zip _) .map(_.map(buildModel)) .split(_ => ())((_, _, s) => DetailPage(s)), diff --git a/app/src/main/scala/mdr/pdb/app/connectors/DetailParametruPageConnector.scala b/app/src/main/scala/mdr/pdb/app/connectors/DetailParametruPageConnector.scala index fa7193d..47c2336 100644 --- a/app/src/main/scala/mdr/pdb/app/connectors/DetailParametruPageConnector.scala +++ b/app/src/main/scala/mdr/pdb/app/connectors/DetailParametruPageConnector.scala @@ -11,10 +11,9 @@ import mdr.pdb.app.components.PageLink object DetailParametruPageConnector { - trait AppState { + trait AppState extends AppPage.AppState { def details: EventStream[UserInfo] def parameters: EventStream[List[Parameter]] - def actionBus: Observer[Action] } } @@ -41,7 +40,7 @@ ) def apply: HtmlElement = - AppPage(state.actionBus)( + AppPage(state)( $merged.map(_.map(buildModel)) .split(_ => ())((_, _, s) => DetailParametruPage(s)), $pageChangeSignal --> state.actionBus diff --git a/app/src/main/scala/mdr/pdb/app/connectors/DirectoryPageConnector.scala b/app/src/main/scala/mdr/pdb/app/connectors/DirectoryPageConnector.scala index ab2c045..732aa41 100644 --- a/app/src/main/scala/mdr/pdb/app/connectors/DirectoryPageConnector.scala +++ b/app/src/main/scala/mdr/pdb/app/connectors/DirectoryPageConnector.scala @@ -7,15 +7,18 @@ import mdr.pdb.app.components.PageLink import mdr.pdb.app.components.AppPage -case class DirectoryPageConnector( - $input: EventStream[List[UserInfo]], - actionBus: Observer[Action] -)(using router: Router[Page]): - val $data = $input.startWithNone - val $actionSignal = EventStream.fromValue(FetchDirectory) +object DirectoryPageConnector: + trait AppState extends AppPage.AppState: + def users: EventStream[List[UserInfo]] +class DirectoryPageConnector(state: DirectoryPageConnector.AppState)(using + router: Router[Page] +): def apply: HtmlElement = - AppPage(actionBus)( + val $data = state.users.startWithNone + val $actionSignal = EventStream.fromValue(FetchDirectory) + + AppPage(state)( $data.split(_ => ())((_, _, s) => pages.directory.DirectoryPage( s.map( @@ -23,12 +26,12 @@ _.toUserRow(u => PageLink.container( Page.Detail(Page.Titled(u.personalNumber)), - actionBus + state.actionBus ) ) ) ) ) ), - $actionSignal --> actionBus + $actionSignal --> state.actionBus ) diff --git a/app/src/main/scala/mdr/pdb/app/pages/detail/UpravDukaz.scala b/app/src/main/scala/mdr/pdb/app/pages/detail/UpravDukaz.scala index b58618a..2c23adb 100644 --- a/app/src/main/scala/mdr/pdb/app/pages/detail/UpravDukaz.scala +++ b/app/src/main/scala/mdr/pdb/app/pages/detail/UpravDukaz.scala @@ -13,11 +13,10 @@ type ThisPage = Page.UpravDukazKriteria type PageKey = (OsobniCislo, String, String) - trait State { + trait State extends AppPage.AppState { def details: EventStream[UserInfo] def parameters: EventStream[List[Parameter]] def availableFiles: EventStream[List[File]] - def actionBus: Observer[Action] } def keyOfPage(page: ThisPage): PageKey = @@ -49,7 +48,7 @@ ) def apply: HtmlElement = - AppPage(state.actionBus)( + AppPage(state)( $merged.split(_ => ())((_, s, $s) => PageComponent( $s.map(buildModel), diff --git a/app/src/main/scala/mdr/pdb/app/Main.scala b/app/src/main/scala/mdr/pdb/app/Main.scala index 081b4ee..b9f4fe1 100644 --- a/app/src/main/scala/mdr/pdb/app/Main.scala +++ b/app/src/main/scala/mdr/pdb/app/Main.scala @@ -79,7 +79,7 @@ pages.detail.UpravDukaz.Connector(state)(_).apply ) .collectStatic(Page.Dashboard)( - connectors.DashboardPageConnector(state.actionBus).apply + connectors.DashboardPageConnector(state).apply ) .collect[Page.NotFound](pg => pages.errors.NotFoundPage(Routes.homePage, pg.url, state.actionBus) @@ -94,7 +94,7 @@ ) .collectStatic(Page.Directory)( connectors - .DirectoryPageConnector(state.users, state.actionBus) + .DirectoryPageConnector(state) .apply ) div(cls := "h-full", child <-- pageSplitter.$view) diff --git a/app/src/main/scala/mdr/pdb/app/components/AppPage.scala b/app/src/main/scala/mdr/pdb/app/components/AppPage.scala index 130c817..043f50e 100644 --- a/app/src/main/scala/mdr/pdb/app/components/AppPage.scala +++ b/app/src/main/scala/mdr/pdb/app/components/AppPage.scala @@ -9,6 +9,11 @@ import mdr.pdb.UserFunction object AppPage: + trait AppState { + def online: Signal[Boolean] + def actionBus: Observer[Action] + } + // TODO: pages by logged in user val pages: List[Page] = List(Page.Directory, Page.Dashboard) @@ -52,31 +57,34 @@ val $userInfo = $userProfile.signal.map(_.userInfo) type ViewModel = Option[HtmlElement] + def apply( - actionBus: Observer[Action] + state: AppState )($m: Signal[ViewModel], mods: Modifier[HtmlElement]*)(using router: Router[Page] ): HtmlElement = - PageLayout(actionBus)( - $m.combineWith($userInfo, router.$currentPage).map((c, u, cp) => - PageLayout.ViewModel( - NavigationBar.ViewModel( - u, - pages.map(p => - NavigationBar.Link( - () => - PageLink( - p, - actionBus - ), - p == cp - ) + PageLayout(state.actionBus)( + $m.combineWith($userInfo, router.$currentPage, state.online).map( + (c, u, cp, o) => + PageLayout.ViewModel( + NavigationBar.ViewModel( + u, + pages.map(p => + NavigationBar.Link( + () => + PageLink( + p, + state.actionBus + ), + p == cp + ) + ), + userMenu, + logo, + o ), - userMenu, - logo - ), - c - ) + c + ) ), mods ) diff --git a/app/src/main/scala/mdr/pdb/app/components/NavigationBar.scala b/app/src/main/scala/mdr/pdb/app/components/NavigationBar.scala index 4bb8b8d..1ed5b9a 100644 --- a/app/src/main/scala/mdr/pdb/app/components/NavigationBar.scala +++ b/app/src/main/scala/mdr/pdb/app/components/NavigationBar.scala @@ -17,7 +17,8 @@ userInfo: UserInfo, pages: List[Link], userMenu: List[MenuItem], - logo: Logo + logo: Logo, + online: Boolean ) def apply($m: Signal[ViewModel]): HtmlElement = @@ -30,6 +31,14 @@ inline def avatarImage(size: Int = 8) = Avatar($userInfo.map(_.img)).avatarImage(size) + def offlineIcon = button( + tpe := "button", + cls <-- $m.map(m => List("hidden" -> m.online)), + cls("bg-indigo-600 text-indigo-200"), + span(cls := "sr-only", "Server odpojen"), + Icons.outline.`status-offline`() + ) + def notificationButton = button( tpe := "button", cls := List( @@ -44,7 +53,7 @@ "rounded-full", "text-indigo-200" ), - span(cls := "sr-only", "View notifications"), + span(cls := "sr-only", "Zobrazit upozornění"), Icons.outline.bell() ) @@ -132,7 +141,11 @@ ) ) ), - notificationButton.amend(cls := List("flex-shrink-0", "ml-auto")) + div( + cls("flex-shrink-0 ml-auto flex"), + offlineIcon, + notificationButton + ) ), div( cls := "mt-3 px-2 space-y-1", @@ -200,6 +213,7 @@ desktopOnly, div( cls := "ml-4 flex items-center md:ml-6", + offlineIcon, notificationButton, userProfile ) diff --git a/app/src/main/scala/mdr/pdb/app/connectors/DashboardPageConnector.scala b/app/src/main/scala/mdr/pdb/app/connectors/DashboardPageConnector.scala index 7682772..20d426a 100644 --- a/app/src/main/scala/mdr/pdb/app/connectors/DashboardPageConnector.scala +++ b/app/src/main/scala/mdr/pdb/app/connectors/DashboardPageConnector.scala @@ -5,9 +5,12 @@ import mdr.pdb.app.pages.dashboard.DashboardPage import com.raquo.waypoint.Router import mdr.pdb.app.components.AppPage +import state.AppState -class DashboardPageConnector(actionBus: Observer[Action])(using +class DashboardPageConnector(state: AppState)(using router: Router[Page] ): def apply: HtmlElement = - AppPage(actionBus)(Val(Some(DashboardPage.render))) + AppPage(state)( + Val(Some(DashboardPage.render)) + ) diff --git a/app/src/main/scala/mdr/pdb/app/connectors/DetailKriteriaPageConnector.scala b/app/src/main/scala/mdr/pdb/app/connectors/DetailKriteriaPageConnector.scala index 76b9133..9217c19 100644 --- a/app/src/main/scala/mdr/pdb/app/connectors/DetailKriteriaPageConnector.scala +++ b/app/src/main/scala/mdr/pdb/app/connectors/DetailKriteriaPageConnector.scala @@ -12,10 +12,9 @@ import mdr.pdb.ParameterCriteria object DetailKriteriaPageConnector { - trait AppState { + trait AppState extends AppPage.AppState { def details: EventStream[UserInfo] def parameters: EventStream[List[Parameter]] - def actionBus: Observer[Action] } } @@ -47,7 +46,7 @@ ) def apply: HtmlElement = - AppPage(state.actionBus)( + AppPage(state)( $merged.map(_.map(buildModel)) .split(_ => ())((_, s, $s) => DetailKriteriaPage($s)(state.actionBus.contramap { diff --git a/app/src/main/scala/mdr/pdb/app/connectors/DetailPageConnector.scala b/app/src/main/scala/mdr/pdb/app/connectors/DetailPageConnector.scala index df3a6b7..4f0c1e2 100644 --- a/app/src/main/scala/mdr/pdb/app/connectors/DetailPageConnector.scala +++ b/app/src/main/scala/mdr/pdb/app/connectors/DetailPageConnector.scala @@ -13,10 +13,9 @@ import fiftyforms.ui.components.tailwind.Color object DetailPageConnector { - trait AppState { + trait AppState extends AppPage.AppState { def details: EventStream[UserInfo] def parameters: EventStream[List[Parameter]] - def actionBus: Observer[Action] } } @@ -36,7 +35,7 @@ val $params = state.parameters.startWithNone def apply: HtmlElement = - AppPage(state.actionBus)( + AppPage(state)( $data.combineWithFn($params)(_ zip _) .map(_.map(buildModel)) .split(_ => ())((_, _, s) => DetailPage(s)), diff --git a/app/src/main/scala/mdr/pdb/app/connectors/DetailParametruPageConnector.scala b/app/src/main/scala/mdr/pdb/app/connectors/DetailParametruPageConnector.scala index fa7193d..47c2336 100644 --- a/app/src/main/scala/mdr/pdb/app/connectors/DetailParametruPageConnector.scala +++ b/app/src/main/scala/mdr/pdb/app/connectors/DetailParametruPageConnector.scala @@ -11,10 +11,9 @@ import mdr.pdb.app.components.PageLink object DetailParametruPageConnector { - trait AppState { + trait AppState extends AppPage.AppState { def details: EventStream[UserInfo] def parameters: EventStream[List[Parameter]] - def actionBus: Observer[Action] } } @@ -41,7 +40,7 @@ ) def apply: HtmlElement = - AppPage(state.actionBus)( + AppPage(state)( $merged.map(_.map(buildModel)) .split(_ => ())((_, _, s) => DetailParametruPage(s)), $pageChangeSignal --> state.actionBus diff --git a/app/src/main/scala/mdr/pdb/app/connectors/DirectoryPageConnector.scala b/app/src/main/scala/mdr/pdb/app/connectors/DirectoryPageConnector.scala index ab2c045..732aa41 100644 --- a/app/src/main/scala/mdr/pdb/app/connectors/DirectoryPageConnector.scala +++ b/app/src/main/scala/mdr/pdb/app/connectors/DirectoryPageConnector.scala @@ -7,15 +7,18 @@ import mdr.pdb.app.components.PageLink import mdr.pdb.app.components.AppPage -case class DirectoryPageConnector( - $input: EventStream[List[UserInfo]], - actionBus: Observer[Action] -)(using router: Router[Page]): - val $data = $input.startWithNone - val $actionSignal = EventStream.fromValue(FetchDirectory) +object DirectoryPageConnector: + trait AppState extends AppPage.AppState: + def users: EventStream[List[UserInfo]] +class DirectoryPageConnector(state: DirectoryPageConnector.AppState)(using + router: Router[Page] +): def apply: HtmlElement = - AppPage(actionBus)( + val $data = state.users.startWithNone + val $actionSignal = EventStream.fromValue(FetchDirectory) + + AppPage(state)( $data.split(_ => ())((_, _, s) => pages.directory.DirectoryPage( s.map( @@ -23,12 +26,12 @@ _.toUserRow(u => PageLink.container( Page.Detail(Page.Titled(u.personalNumber)), - actionBus + state.actionBus ) ) ) ) ) ), - $actionSignal --> actionBus + $actionSignal --> state.actionBus ) diff --git a/app/src/main/scala/mdr/pdb/app/pages/detail/UpravDukaz.scala b/app/src/main/scala/mdr/pdb/app/pages/detail/UpravDukaz.scala index b58618a..2c23adb 100644 --- a/app/src/main/scala/mdr/pdb/app/pages/detail/UpravDukaz.scala +++ b/app/src/main/scala/mdr/pdb/app/pages/detail/UpravDukaz.scala @@ -13,11 +13,10 @@ type ThisPage = Page.UpravDukazKriteria type PageKey = (OsobniCislo, String, String) - trait State { + trait State extends AppPage.AppState { def details: EventStream[UserInfo] def parameters: EventStream[List[Parameter]] def availableFiles: EventStream[List[File]] - def actionBus: Observer[Action] } def keyOfPage(page: ThisPage): PageKey = @@ -49,7 +48,7 @@ ) def apply: HtmlElement = - AppPage(state.actionBus)( + AppPage(state)( $merged.split(_ => ())((_, s, $s) => PageComponent( $s.map(buildModel), diff --git a/app/src/main/scala/mdr/pdb/app/pages/detail/components/UpravDukazForm.scala b/app/src/main/scala/mdr/pdb/app/pages/detail/components/UpravDukazForm.scala index 28753c9..931dd1a 100644 --- a/app/src/main/scala/mdr/pdb/app/pages/detail/components/UpravDukazForm.scala +++ b/app/src/main/scala/mdr/pdb/app/pages/detail/components/UpravDukazForm.scala @@ -7,8 +7,8 @@ import org.scalajs.dom import com.raquo.laminar.nodes.ReactiveHtmlElement import fiftyforms.services.files.components.tailwind.FilePicker -import mdr.pdb.frontend.AutorizujDukaz -import mdr.pdb.frontend.DocumentRef +import mdr.pdb.api.AutorizujDukaz +import mdr.pdb.api.DocumentRef import fiftyforms.services.files.File object UpravDukazForm: diff --git a/app/src/main/scala/mdr/pdb/app/Main.scala b/app/src/main/scala/mdr/pdb/app/Main.scala index 081b4ee..b9f4fe1 100644 --- a/app/src/main/scala/mdr/pdb/app/Main.scala +++ b/app/src/main/scala/mdr/pdb/app/Main.scala @@ -79,7 +79,7 @@ pages.detail.UpravDukaz.Connector(state)(_).apply ) .collectStatic(Page.Dashboard)( - connectors.DashboardPageConnector(state.actionBus).apply + connectors.DashboardPageConnector(state).apply ) .collect[Page.NotFound](pg => pages.errors.NotFoundPage(Routes.homePage, pg.url, state.actionBus) @@ -94,7 +94,7 @@ ) .collectStatic(Page.Directory)( connectors - .DirectoryPageConnector(state.users, state.actionBus) + .DirectoryPageConnector(state) .apply ) div(cls := "h-full", child <-- pageSplitter.$view) diff --git a/app/src/main/scala/mdr/pdb/app/components/AppPage.scala b/app/src/main/scala/mdr/pdb/app/components/AppPage.scala index 130c817..043f50e 100644 --- a/app/src/main/scala/mdr/pdb/app/components/AppPage.scala +++ b/app/src/main/scala/mdr/pdb/app/components/AppPage.scala @@ -9,6 +9,11 @@ import mdr.pdb.UserFunction object AppPage: + trait AppState { + def online: Signal[Boolean] + def actionBus: Observer[Action] + } + // TODO: pages by logged in user val pages: List[Page] = List(Page.Directory, Page.Dashboard) @@ -52,31 +57,34 @@ val $userInfo = $userProfile.signal.map(_.userInfo) type ViewModel = Option[HtmlElement] + def apply( - actionBus: Observer[Action] + state: AppState )($m: Signal[ViewModel], mods: Modifier[HtmlElement]*)(using router: Router[Page] ): HtmlElement = - PageLayout(actionBus)( - $m.combineWith($userInfo, router.$currentPage).map((c, u, cp) => - PageLayout.ViewModel( - NavigationBar.ViewModel( - u, - pages.map(p => - NavigationBar.Link( - () => - PageLink( - p, - actionBus - ), - p == cp - ) + PageLayout(state.actionBus)( + $m.combineWith($userInfo, router.$currentPage, state.online).map( + (c, u, cp, o) => + PageLayout.ViewModel( + NavigationBar.ViewModel( + u, + pages.map(p => + NavigationBar.Link( + () => + PageLink( + p, + state.actionBus + ), + p == cp + ) + ), + userMenu, + logo, + o ), - userMenu, - logo - ), - c - ) + c + ) ), mods ) diff --git a/app/src/main/scala/mdr/pdb/app/components/NavigationBar.scala b/app/src/main/scala/mdr/pdb/app/components/NavigationBar.scala index 4bb8b8d..1ed5b9a 100644 --- a/app/src/main/scala/mdr/pdb/app/components/NavigationBar.scala +++ b/app/src/main/scala/mdr/pdb/app/components/NavigationBar.scala @@ -17,7 +17,8 @@ userInfo: UserInfo, pages: List[Link], userMenu: List[MenuItem], - logo: Logo + logo: Logo, + online: Boolean ) def apply($m: Signal[ViewModel]): HtmlElement = @@ -30,6 +31,14 @@ inline def avatarImage(size: Int = 8) = Avatar($userInfo.map(_.img)).avatarImage(size) + def offlineIcon = button( + tpe := "button", + cls <-- $m.map(m => List("hidden" -> m.online)), + cls("bg-indigo-600 text-indigo-200"), + span(cls := "sr-only", "Server odpojen"), + Icons.outline.`status-offline`() + ) + def notificationButton = button( tpe := "button", cls := List( @@ -44,7 +53,7 @@ "rounded-full", "text-indigo-200" ), - span(cls := "sr-only", "View notifications"), + span(cls := "sr-only", "Zobrazit upozornění"), Icons.outline.bell() ) @@ -132,7 +141,11 @@ ) ) ), - notificationButton.amend(cls := List("flex-shrink-0", "ml-auto")) + div( + cls("flex-shrink-0 ml-auto flex"), + offlineIcon, + notificationButton + ) ), div( cls := "mt-3 px-2 space-y-1", @@ -200,6 +213,7 @@ desktopOnly, div( cls := "ml-4 flex items-center md:ml-6", + offlineIcon, notificationButton, userProfile ) diff --git a/app/src/main/scala/mdr/pdb/app/connectors/DashboardPageConnector.scala b/app/src/main/scala/mdr/pdb/app/connectors/DashboardPageConnector.scala index 7682772..20d426a 100644 --- a/app/src/main/scala/mdr/pdb/app/connectors/DashboardPageConnector.scala +++ b/app/src/main/scala/mdr/pdb/app/connectors/DashboardPageConnector.scala @@ -5,9 +5,12 @@ import mdr.pdb.app.pages.dashboard.DashboardPage import com.raquo.waypoint.Router import mdr.pdb.app.components.AppPage +import state.AppState -class DashboardPageConnector(actionBus: Observer[Action])(using +class DashboardPageConnector(state: AppState)(using router: Router[Page] ): def apply: HtmlElement = - AppPage(actionBus)(Val(Some(DashboardPage.render))) + AppPage(state)( + Val(Some(DashboardPage.render)) + ) diff --git a/app/src/main/scala/mdr/pdb/app/connectors/DetailKriteriaPageConnector.scala b/app/src/main/scala/mdr/pdb/app/connectors/DetailKriteriaPageConnector.scala index 76b9133..9217c19 100644 --- a/app/src/main/scala/mdr/pdb/app/connectors/DetailKriteriaPageConnector.scala +++ b/app/src/main/scala/mdr/pdb/app/connectors/DetailKriteriaPageConnector.scala @@ -12,10 +12,9 @@ import mdr.pdb.ParameterCriteria object DetailKriteriaPageConnector { - trait AppState { + trait AppState extends AppPage.AppState { def details: EventStream[UserInfo] def parameters: EventStream[List[Parameter]] - def actionBus: Observer[Action] } } @@ -47,7 +46,7 @@ ) def apply: HtmlElement = - AppPage(state.actionBus)( + AppPage(state)( $merged.map(_.map(buildModel)) .split(_ => ())((_, s, $s) => DetailKriteriaPage($s)(state.actionBus.contramap { diff --git a/app/src/main/scala/mdr/pdb/app/connectors/DetailPageConnector.scala b/app/src/main/scala/mdr/pdb/app/connectors/DetailPageConnector.scala index df3a6b7..4f0c1e2 100644 --- a/app/src/main/scala/mdr/pdb/app/connectors/DetailPageConnector.scala +++ b/app/src/main/scala/mdr/pdb/app/connectors/DetailPageConnector.scala @@ -13,10 +13,9 @@ import fiftyforms.ui.components.tailwind.Color object DetailPageConnector { - trait AppState { + trait AppState extends AppPage.AppState { def details: EventStream[UserInfo] def parameters: EventStream[List[Parameter]] - def actionBus: Observer[Action] } } @@ -36,7 +35,7 @@ val $params = state.parameters.startWithNone def apply: HtmlElement = - AppPage(state.actionBus)( + AppPage(state)( $data.combineWithFn($params)(_ zip _) .map(_.map(buildModel)) .split(_ => ())((_, _, s) => DetailPage(s)), diff --git a/app/src/main/scala/mdr/pdb/app/connectors/DetailParametruPageConnector.scala b/app/src/main/scala/mdr/pdb/app/connectors/DetailParametruPageConnector.scala index fa7193d..47c2336 100644 --- a/app/src/main/scala/mdr/pdb/app/connectors/DetailParametruPageConnector.scala +++ b/app/src/main/scala/mdr/pdb/app/connectors/DetailParametruPageConnector.scala @@ -11,10 +11,9 @@ import mdr.pdb.app.components.PageLink object DetailParametruPageConnector { - trait AppState { + trait AppState extends AppPage.AppState { def details: EventStream[UserInfo] def parameters: EventStream[List[Parameter]] - def actionBus: Observer[Action] } } @@ -41,7 +40,7 @@ ) def apply: HtmlElement = - AppPage(state.actionBus)( + AppPage(state)( $merged.map(_.map(buildModel)) .split(_ => ())((_, _, s) => DetailParametruPage(s)), $pageChangeSignal --> state.actionBus diff --git a/app/src/main/scala/mdr/pdb/app/connectors/DirectoryPageConnector.scala b/app/src/main/scala/mdr/pdb/app/connectors/DirectoryPageConnector.scala index ab2c045..732aa41 100644 --- a/app/src/main/scala/mdr/pdb/app/connectors/DirectoryPageConnector.scala +++ b/app/src/main/scala/mdr/pdb/app/connectors/DirectoryPageConnector.scala @@ -7,15 +7,18 @@ import mdr.pdb.app.components.PageLink import mdr.pdb.app.components.AppPage -case class DirectoryPageConnector( - $input: EventStream[List[UserInfo]], - actionBus: Observer[Action] -)(using router: Router[Page]): - val $data = $input.startWithNone - val $actionSignal = EventStream.fromValue(FetchDirectory) +object DirectoryPageConnector: + trait AppState extends AppPage.AppState: + def users: EventStream[List[UserInfo]] +class DirectoryPageConnector(state: DirectoryPageConnector.AppState)(using + router: Router[Page] +): def apply: HtmlElement = - AppPage(actionBus)( + val $data = state.users.startWithNone + val $actionSignal = EventStream.fromValue(FetchDirectory) + + AppPage(state)( $data.split(_ => ())((_, _, s) => pages.directory.DirectoryPage( s.map( @@ -23,12 +26,12 @@ _.toUserRow(u => PageLink.container( Page.Detail(Page.Titled(u.personalNumber)), - actionBus + state.actionBus ) ) ) ) ) ), - $actionSignal --> actionBus + $actionSignal --> state.actionBus ) diff --git a/app/src/main/scala/mdr/pdb/app/pages/detail/UpravDukaz.scala b/app/src/main/scala/mdr/pdb/app/pages/detail/UpravDukaz.scala index b58618a..2c23adb 100644 --- a/app/src/main/scala/mdr/pdb/app/pages/detail/UpravDukaz.scala +++ b/app/src/main/scala/mdr/pdb/app/pages/detail/UpravDukaz.scala @@ -13,11 +13,10 @@ type ThisPage = Page.UpravDukazKriteria type PageKey = (OsobniCislo, String, String) - trait State { + trait State extends AppPage.AppState { def details: EventStream[UserInfo] def parameters: EventStream[List[Parameter]] def availableFiles: EventStream[List[File]] - def actionBus: Observer[Action] } def keyOfPage(page: ThisPage): PageKey = @@ -49,7 +48,7 @@ ) def apply: HtmlElement = - AppPage(state.actionBus)( + AppPage(state)( $merged.split(_ => ())((_, s, $s) => PageComponent( $s.map(buildModel), diff --git a/app/src/main/scala/mdr/pdb/app/pages/detail/components/UpravDukazForm.scala b/app/src/main/scala/mdr/pdb/app/pages/detail/components/UpravDukazForm.scala index 28753c9..931dd1a 100644 --- a/app/src/main/scala/mdr/pdb/app/pages/detail/components/UpravDukazForm.scala +++ b/app/src/main/scala/mdr/pdb/app/pages/detail/components/UpravDukazForm.scala @@ -7,8 +7,8 @@ import org.scalajs.dom import com.raquo.laminar.nodes.ReactiveHtmlElement import fiftyforms.services.files.components.tailwind.FilePicker -import mdr.pdb.frontend.AutorizujDukaz -import mdr.pdb.frontend.DocumentRef +import mdr.pdb.api.AutorizujDukaz +import mdr.pdb.api.DocumentRef import fiftyforms.services.files.File object UpravDukazForm: diff --git a/app/src/main/scala/mdr/pdb/app/state/AppState.scala b/app/src/main/scala/mdr/pdb/app/state/AppState.scala index e44b2df..fee56f4 100644 --- a/app/src/main/scala/mdr/pdb/app/state/AppState.scala +++ b/app/src/main/scala/mdr/pdb/app/state/AppState.scala @@ -1,7 +1,8 @@ package mdr.pdb.app package state -import com.raquo.airstream.core.EventStream +import com.raquo.airstream.core.{EventStream, Signal} +import com.raquo.airstream.state.Val import mdr.pdb.{UserInfo, OsobniCislo} import com.raquo.airstream.core.Observer import scala.scalajs.js @@ -17,10 +18,14 @@ import fiftyforms.services.files.File trait AppState - extends connectors.DetailPageConnector.AppState + extends components.AppPage.AppState + with connectors.DirectoryPageConnector.AppState + with connectors.DetailPageConnector.AppState with connectors.DetailParametruPageConnector.AppState with connectors.DetailKriteriaPageConnector.AppState with pages.detail.UpravDukaz.State: + + def online: Signal[Boolean] def users: EventStream[List[UserInfo]] def details: EventStream[UserInfo] def parameters: EventStream[List[Parameter]] @@ -104,6 +109,7 @@ ) } + override def online: Signal[Boolean] = Val(false) override def users: EventStream[List[UserInfo]] = usersStream.debugWithName("users") diff --git a/app/src/main/scala/mdr/pdb/app/Main.scala b/app/src/main/scala/mdr/pdb/app/Main.scala index 081b4ee..b9f4fe1 100644 --- a/app/src/main/scala/mdr/pdb/app/Main.scala +++ b/app/src/main/scala/mdr/pdb/app/Main.scala @@ -79,7 +79,7 @@ pages.detail.UpravDukaz.Connector(state)(_).apply ) .collectStatic(Page.Dashboard)( - connectors.DashboardPageConnector(state.actionBus).apply + connectors.DashboardPageConnector(state).apply ) .collect[Page.NotFound](pg => pages.errors.NotFoundPage(Routes.homePage, pg.url, state.actionBus) @@ -94,7 +94,7 @@ ) .collectStatic(Page.Directory)( connectors - .DirectoryPageConnector(state.users, state.actionBus) + .DirectoryPageConnector(state) .apply ) div(cls := "h-full", child <-- pageSplitter.$view) diff --git a/app/src/main/scala/mdr/pdb/app/components/AppPage.scala b/app/src/main/scala/mdr/pdb/app/components/AppPage.scala index 130c817..043f50e 100644 --- a/app/src/main/scala/mdr/pdb/app/components/AppPage.scala +++ b/app/src/main/scala/mdr/pdb/app/components/AppPage.scala @@ -9,6 +9,11 @@ import mdr.pdb.UserFunction object AppPage: + trait AppState { + def online: Signal[Boolean] + def actionBus: Observer[Action] + } + // TODO: pages by logged in user val pages: List[Page] = List(Page.Directory, Page.Dashboard) @@ -52,31 +57,34 @@ val $userInfo = $userProfile.signal.map(_.userInfo) type ViewModel = Option[HtmlElement] + def apply( - actionBus: Observer[Action] + state: AppState )($m: Signal[ViewModel], mods: Modifier[HtmlElement]*)(using router: Router[Page] ): HtmlElement = - PageLayout(actionBus)( - $m.combineWith($userInfo, router.$currentPage).map((c, u, cp) => - PageLayout.ViewModel( - NavigationBar.ViewModel( - u, - pages.map(p => - NavigationBar.Link( - () => - PageLink( - p, - actionBus - ), - p == cp - ) + PageLayout(state.actionBus)( + $m.combineWith($userInfo, router.$currentPage, state.online).map( + (c, u, cp, o) => + PageLayout.ViewModel( + NavigationBar.ViewModel( + u, + pages.map(p => + NavigationBar.Link( + () => + PageLink( + p, + state.actionBus + ), + p == cp + ) + ), + userMenu, + logo, + o ), - userMenu, - logo - ), - c - ) + c + ) ), mods ) diff --git a/app/src/main/scala/mdr/pdb/app/components/NavigationBar.scala b/app/src/main/scala/mdr/pdb/app/components/NavigationBar.scala index 4bb8b8d..1ed5b9a 100644 --- a/app/src/main/scala/mdr/pdb/app/components/NavigationBar.scala +++ b/app/src/main/scala/mdr/pdb/app/components/NavigationBar.scala @@ -17,7 +17,8 @@ userInfo: UserInfo, pages: List[Link], userMenu: List[MenuItem], - logo: Logo + logo: Logo, + online: Boolean ) def apply($m: Signal[ViewModel]): HtmlElement = @@ -30,6 +31,14 @@ inline def avatarImage(size: Int = 8) = Avatar($userInfo.map(_.img)).avatarImage(size) + def offlineIcon = button( + tpe := "button", + cls <-- $m.map(m => List("hidden" -> m.online)), + cls("bg-indigo-600 text-indigo-200"), + span(cls := "sr-only", "Server odpojen"), + Icons.outline.`status-offline`() + ) + def notificationButton = button( tpe := "button", cls := List( @@ -44,7 +53,7 @@ "rounded-full", "text-indigo-200" ), - span(cls := "sr-only", "View notifications"), + span(cls := "sr-only", "Zobrazit upozornění"), Icons.outline.bell() ) @@ -132,7 +141,11 @@ ) ) ), - notificationButton.amend(cls := List("flex-shrink-0", "ml-auto")) + div( + cls("flex-shrink-0 ml-auto flex"), + offlineIcon, + notificationButton + ) ), div( cls := "mt-3 px-2 space-y-1", @@ -200,6 +213,7 @@ desktopOnly, div( cls := "ml-4 flex items-center md:ml-6", + offlineIcon, notificationButton, userProfile ) diff --git a/app/src/main/scala/mdr/pdb/app/connectors/DashboardPageConnector.scala b/app/src/main/scala/mdr/pdb/app/connectors/DashboardPageConnector.scala index 7682772..20d426a 100644 --- a/app/src/main/scala/mdr/pdb/app/connectors/DashboardPageConnector.scala +++ b/app/src/main/scala/mdr/pdb/app/connectors/DashboardPageConnector.scala @@ -5,9 +5,12 @@ import mdr.pdb.app.pages.dashboard.DashboardPage import com.raquo.waypoint.Router import mdr.pdb.app.components.AppPage +import state.AppState -class DashboardPageConnector(actionBus: Observer[Action])(using +class DashboardPageConnector(state: AppState)(using router: Router[Page] ): def apply: HtmlElement = - AppPage(actionBus)(Val(Some(DashboardPage.render))) + AppPage(state)( + Val(Some(DashboardPage.render)) + ) diff --git a/app/src/main/scala/mdr/pdb/app/connectors/DetailKriteriaPageConnector.scala b/app/src/main/scala/mdr/pdb/app/connectors/DetailKriteriaPageConnector.scala index 76b9133..9217c19 100644 --- a/app/src/main/scala/mdr/pdb/app/connectors/DetailKriteriaPageConnector.scala +++ b/app/src/main/scala/mdr/pdb/app/connectors/DetailKriteriaPageConnector.scala @@ -12,10 +12,9 @@ import mdr.pdb.ParameterCriteria object DetailKriteriaPageConnector { - trait AppState { + trait AppState extends AppPage.AppState { def details: EventStream[UserInfo] def parameters: EventStream[List[Parameter]] - def actionBus: Observer[Action] } } @@ -47,7 +46,7 @@ ) def apply: HtmlElement = - AppPage(state.actionBus)( + AppPage(state)( $merged.map(_.map(buildModel)) .split(_ => ())((_, s, $s) => DetailKriteriaPage($s)(state.actionBus.contramap { diff --git a/app/src/main/scala/mdr/pdb/app/connectors/DetailPageConnector.scala b/app/src/main/scala/mdr/pdb/app/connectors/DetailPageConnector.scala index df3a6b7..4f0c1e2 100644 --- a/app/src/main/scala/mdr/pdb/app/connectors/DetailPageConnector.scala +++ b/app/src/main/scala/mdr/pdb/app/connectors/DetailPageConnector.scala @@ -13,10 +13,9 @@ import fiftyforms.ui.components.tailwind.Color object DetailPageConnector { - trait AppState { + trait AppState extends AppPage.AppState { def details: EventStream[UserInfo] def parameters: EventStream[List[Parameter]] - def actionBus: Observer[Action] } } @@ -36,7 +35,7 @@ val $params = state.parameters.startWithNone def apply: HtmlElement = - AppPage(state.actionBus)( + AppPage(state)( $data.combineWithFn($params)(_ zip _) .map(_.map(buildModel)) .split(_ => ())((_, _, s) => DetailPage(s)), diff --git a/app/src/main/scala/mdr/pdb/app/connectors/DetailParametruPageConnector.scala b/app/src/main/scala/mdr/pdb/app/connectors/DetailParametruPageConnector.scala index fa7193d..47c2336 100644 --- a/app/src/main/scala/mdr/pdb/app/connectors/DetailParametruPageConnector.scala +++ b/app/src/main/scala/mdr/pdb/app/connectors/DetailParametruPageConnector.scala @@ -11,10 +11,9 @@ import mdr.pdb.app.components.PageLink object DetailParametruPageConnector { - trait AppState { + trait AppState extends AppPage.AppState { def details: EventStream[UserInfo] def parameters: EventStream[List[Parameter]] - def actionBus: Observer[Action] } } @@ -41,7 +40,7 @@ ) def apply: HtmlElement = - AppPage(state.actionBus)( + AppPage(state)( $merged.map(_.map(buildModel)) .split(_ => ())((_, _, s) => DetailParametruPage(s)), $pageChangeSignal --> state.actionBus diff --git a/app/src/main/scala/mdr/pdb/app/connectors/DirectoryPageConnector.scala b/app/src/main/scala/mdr/pdb/app/connectors/DirectoryPageConnector.scala index ab2c045..732aa41 100644 --- a/app/src/main/scala/mdr/pdb/app/connectors/DirectoryPageConnector.scala +++ b/app/src/main/scala/mdr/pdb/app/connectors/DirectoryPageConnector.scala @@ -7,15 +7,18 @@ import mdr.pdb.app.components.PageLink import mdr.pdb.app.components.AppPage -case class DirectoryPageConnector( - $input: EventStream[List[UserInfo]], - actionBus: Observer[Action] -)(using router: Router[Page]): - val $data = $input.startWithNone - val $actionSignal = EventStream.fromValue(FetchDirectory) +object DirectoryPageConnector: + trait AppState extends AppPage.AppState: + def users: EventStream[List[UserInfo]] +class DirectoryPageConnector(state: DirectoryPageConnector.AppState)(using + router: Router[Page] +): def apply: HtmlElement = - AppPage(actionBus)( + val $data = state.users.startWithNone + val $actionSignal = EventStream.fromValue(FetchDirectory) + + AppPage(state)( $data.split(_ => ())((_, _, s) => pages.directory.DirectoryPage( s.map( @@ -23,12 +26,12 @@ _.toUserRow(u => PageLink.container( Page.Detail(Page.Titled(u.personalNumber)), - actionBus + state.actionBus ) ) ) ) ) ), - $actionSignal --> actionBus + $actionSignal --> state.actionBus ) diff --git a/app/src/main/scala/mdr/pdb/app/pages/detail/UpravDukaz.scala b/app/src/main/scala/mdr/pdb/app/pages/detail/UpravDukaz.scala index b58618a..2c23adb 100644 --- a/app/src/main/scala/mdr/pdb/app/pages/detail/UpravDukaz.scala +++ b/app/src/main/scala/mdr/pdb/app/pages/detail/UpravDukaz.scala @@ -13,11 +13,10 @@ type ThisPage = Page.UpravDukazKriteria type PageKey = (OsobniCislo, String, String) - trait State { + trait State extends AppPage.AppState { def details: EventStream[UserInfo] def parameters: EventStream[List[Parameter]] def availableFiles: EventStream[List[File]] - def actionBus: Observer[Action] } def keyOfPage(page: ThisPage): PageKey = @@ -49,7 +48,7 @@ ) def apply: HtmlElement = - AppPage(state.actionBus)( + AppPage(state)( $merged.split(_ => ())((_, s, $s) => PageComponent( $s.map(buildModel), diff --git a/app/src/main/scala/mdr/pdb/app/pages/detail/components/UpravDukazForm.scala b/app/src/main/scala/mdr/pdb/app/pages/detail/components/UpravDukazForm.scala index 28753c9..931dd1a 100644 --- a/app/src/main/scala/mdr/pdb/app/pages/detail/components/UpravDukazForm.scala +++ b/app/src/main/scala/mdr/pdb/app/pages/detail/components/UpravDukazForm.scala @@ -7,8 +7,8 @@ import org.scalajs.dom import com.raquo.laminar.nodes.ReactiveHtmlElement import fiftyforms.services.files.components.tailwind.FilePicker -import mdr.pdb.frontend.AutorizujDukaz -import mdr.pdb.frontend.DocumentRef +import mdr.pdb.api.AutorizujDukaz +import mdr.pdb.api.DocumentRef import fiftyforms.services.files.File object UpravDukazForm: diff --git a/app/src/main/scala/mdr/pdb/app/state/AppState.scala b/app/src/main/scala/mdr/pdb/app/state/AppState.scala index e44b2df..fee56f4 100644 --- a/app/src/main/scala/mdr/pdb/app/state/AppState.scala +++ b/app/src/main/scala/mdr/pdb/app/state/AppState.scala @@ -1,7 +1,8 @@ package mdr.pdb.app package state -import com.raquo.airstream.core.EventStream +import com.raquo.airstream.core.{EventStream, Signal} +import com.raquo.airstream.state.Val import mdr.pdb.{UserInfo, OsobniCislo} import com.raquo.airstream.core.Observer import scala.scalajs.js @@ -17,10 +18,14 @@ import fiftyforms.services.files.File trait AppState - extends connectors.DetailPageConnector.AppState + extends components.AppPage.AppState + with connectors.DirectoryPageConnector.AppState + with connectors.DetailPageConnector.AppState with connectors.DetailParametruPageConnector.AppState with connectors.DetailKriteriaPageConnector.AppState with pages.detail.UpravDukaz.State: + + def online: Signal[Boolean] def users: EventStream[List[UserInfo]] def details: EventStream[UserInfo] def parameters: EventStream[List[Parameter]] @@ -104,6 +109,7 @@ ) } + override def online: Signal[Boolean] = Val(false) override def users: EventStream[List[UserInfo]] = usersStream.debugWithName("users") diff --git a/build.sbt b/build.sbt index 6152097..7f91099 100644 --- a/build.sbt +++ b/build.sbt @@ -14,7 +14,6 @@ .settings( IWDeps.useZIO(Test), IWDeps.tapirCore, - IWDeps.tapirZIO, IWDeps.tapirZIOJson ) @@ -43,7 +42,8 @@ IWDeps.laminextCore, IWDeps.laminextUI, IWDeps.laminextTailwind, - IWDeps.laminextValidationCore + IWDeps.laminextValidationCore, + IWDeps.tapirSttpClient ) .settings( scalaJSLinkerConfig ~= { _.withModuleKind(ModuleKind.ESModule) }, diff --git a/app/src/main/scala/mdr/pdb/app/Main.scala b/app/src/main/scala/mdr/pdb/app/Main.scala index 081b4ee..b9f4fe1 100644 --- a/app/src/main/scala/mdr/pdb/app/Main.scala +++ b/app/src/main/scala/mdr/pdb/app/Main.scala @@ -79,7 +79,7 @@ pages.detail.UpravDukaz.Connector(state)(_).apply ) .collectStatic(Page.Dashboard)( - connectors.DashboardPageConnector(state.actionBus).apply + connectors.DashboardPageConnector(state).apply ) .collect[Page.NotFound](pg => pages.errors.NotFoundPage(Routes.homePage, pg.url, state.actionBus) @@ -94,7 +94,7 @@ ) .collectStatic(Page.Directory)( connectors - .DirectoryPageConnector(state.users, state.actionBus) + .DirectoryPageConnector(state) .apply ) div(cls := "h-full", child <-- pageSplitter.$view) diff --git a/app/src/main/scala/mdr/pdb/app/components/AppPage.scala b/app/src/main/scala/mdr/pdb/app/components/AppPage.scala index 130c817..043f50e 100644 --- a/app/src/main/scala/mdr/pdb/app/components/AppPage.scala +++ b/app/src/main/scala/mdr/pdb/app/components/AppPage.scala @@ -9,6 +9,11 @@ import mdr.pdb.UserFunction object AppPage: + trait AppState { + def online: Signal[Boolean] + def actionBus: Observer[Action] + } + // TODO: pages by logged in user val pages: List[Page] = List(Page.Directory, Page.Dashboard) @@ -52,31 +57,34 @@ val $userInfo = $userProfile.signal.map(_.userInfo) type ViewModel = Option[HtmlElement] + def apply( - actionBus: Observer[Action] + state: AppState )($m: Signal[ViewModel], mods: Modifier[HtmlElement]*)(using router: Router[Page] ): HtmlElement = - PageLayout(actionBus)( - $m.combineWith($userInfo, router.$currentPage).map((c, u, cp) => - PageLayout.ViewModel( - NavigationBar.ViewModel( - u, - pages.map(p => - NavigationBar.Link( - () => - PageLink( - p, - actionBus - ), - p == cp - ) + PageLayout(state.actionBus)( + $m.combineWith($userInfo, router.$currentPage, state.online).map( + (c, u, cp, o) => + PageLayout.ViewModel( + NavigationBar.ViewModel( + u, + pages.map(p => + NavigationBar.Link( + () => + PageLink( + p, + state.actionBus + ), + p == cp + ) + ), + userMenu, + logo, + o ), - userMenu, - logo - ), - c - ) + c + ) ), mods ) diff --git a/app/src/main/scala/mdr/pdb/app/components/NavigationBar.scala b/app/src/main/scala/mdr/pdb/app/components/NavigationBar.scala index 4bb8b8d..1ed5b9a 100644 --- a/app/src/main/scala/mdr/pdb/app/components/NavigationBar.scala +++ b/app/src/main/scala/mdr/pdb/app/components/NavigationBar.scala @@ -17,7 +17,8 @@ userInfo: UserInfo, pages: List[Link], userMenu: List[MenuItem], - logo: Logo + logo: Logo, + online: Boolean ) def apply($m: Signal[ViewModel]): HtmlElement = @@ -30,6 +31,14 @@ inline def avatarImage(size: Int = 8) = Avatar($userInfo.map(_.img)).avatarImage(size) + def offlineIcon = button( + tpe := "button", + cls <-- $m.map(m => List("hidden" -> m.online)), + cls("bg-indigo-600 text-indigo-200"), + span(cls := "sr-only", "Server odpojen"), + Icons.outline.`status-offline`() + ) + def notificationButton = button( tpe := "button", cls := List( @@ -44,7 +53,7 @@ "rounded-full", "text-indigo-200" ), - span(cls := "sr-only", "View notifications"), + span(cls := "sr-only", "Zobrazit upozornění"), Icons.outline.bell() ) @@ -132,7 +141,11 @@ ) ) ), - notificationButton.amend(cls := List("flex-shrink-0", "ml-auto")) + div( + cls("flex-shrink-0 ml-auto flex"), + offlineIcon, + notificationButton + ) ), div( cls := "mt-3 px-2 space-y-1", @@ -200,6 +213,7 @@ desktopOnly, div( cls := "ml-4 flex items-center md:ml-6", + offlineIcon, notificationButton, userProfile ) diff --git a/app/src/main/scala/mdr/pdb/app/connectors/DashboardPageConnector.scala b/app/src/main/scala/mdr/pdb/app/connectors/DashboardPageConnector.scala index 7682772..20d426a 100644 --- a/app/src/main/scala/mdr/pdb/app/connectors/DashboardPageConnector.scala +++ b/app/src/main/scala/mdr/pdb/app/connectors/DashboardPageConnector.scala @@ -5,9 +5,12 @@ import mdr.pdb.app.pages.dashboard.DashboardPage import com.raquo.waypoint.Router import mdr.pdb.app.components.AppPage +import state.AppState -class DashboardPageConnector(actionBus: Observer[Action])(using +class DashboardPageConnector(state: AppState)(using router: Router[Page] ): def apply: HtmlElement = - AppPage(actionBus)(Val(Some(DashboardPage.render))) + AppPage(state)( + Val(Some(DashboardPage.render)) + ) diff --git a/app/src/main/scala/mdr/pdb/app/connectors/DetailKriteriaPageConnector.scala b/app/src/main/scala/mdr/pdb/app/connectors/DetailKriteriaPageConnector.scala index 76b9133..9217c19 100644 --- a/app/src/main/scala/mdr/pdb/app/connectors/DetailKriteriaPageConnector.scala +++ b/app/src/main/scala/mdr/pdb/app/connectors/DetailKriteriaPageConnector.scala @@ -12,10 +12,9 @@ import mdr.pdb.ParameterCriteria object DetailKriteriaPageConnector { - trait AppState { + trait AppState extends AppPage.AppState { def details: EventStream[UserInfo] def parameters: EventStream[List[Parameter]] - def actionBus: Observer[Action] } } @@ -47,7 +46,7 @@ ) def apply: HtmlElement = - AppPage(state.actionBus)( + AppPage(state)( $merged.map(_.map(buildModel)) .split(_ => ())((_, s, $s) => DetailKriteriaPage($s)(state.actionBus.contramap { diff --git a/app/src/main/scala/mdr/pdb/app/connectors/DetailPageConnector.scala b/app/src/main/scala/mdr/pdb/app/connectors/DetailPageConnector.scala index df3a6b7..4f0c1e2 100644 --- a/app/src/main/scala/mdr/pdb/app/connectors/DetailPageConnector.scala +++ b/app/src/main/scala/mdr/pdb/app/connectors/DetailPageConnector.scala @@ -13,10 +13,9 @@ import fiftyforms.ui.components.tailwind.Color object DetailPageConnector { - trait AppState { + trait AppState extends AppPage.AppState { def details: EventStream[UserInfo] def parameters: EventStream[List[Parameter]] - def actionBus: Observer[Action] } } @@ -36,7 +35,7 @@ val $params = state.parameters.startWithNone def apply: HtmlElement = - AppPage(state.actionBus)( + AppPage(state)( $data.combineWithFn($params)(_ zip _) .map(_.map(buildModel)) .split(_ => ())((_, _, s) => DetailPage(s)), diff --git a/app/src/main/scala/mdr/pdb/app/connectors/DetailParametruPageConnector.scala b/app/src/main/scala/mdr/pdb/app/connectors/DetailParametruPageConnector.scala index fa7193d..47c2336 100644 --- a/app/src/main/scala/mdr/pdb/app/connectors/DetailParametruPageConnector.scala +++ b/app/src/main/scala/mdr/pdb/app/connectors/DetailParametruPageConnector.scala @@ -11,10 +11,9 @@ import mdr.pdb.app.components.PageLink object DetailParametruPageConnector { - trait AppState { + trait AppState extends AppPage.AppState { def details: EventStream[UserInfo] def parameters: EventStream[List[Parameter]] - def actionBus: Observer[Action] } } @@ -41,7 +40,7 @@ ) def apply: HtmlElement = - AppPage(state.actionBus)( + AppPage(state)( $merged.map(_.map(buildModel)) .split(_ => ())((_, _, s) => DetailParametruPage(s)), $pageChangeSignal --> state.actionBus diff --git a/app/src/main/scala/mdr/pdb/app/connectors/DirectoryPageConnector.scala b/app/src/main/scala/mdr/pdb/app/connectors/DirectoryPageConnector.scala index ab2c045..732aa41 100644 --- a/app/src/main/scala/mdr/pdb/app/connectors/DirectoryPageConnector.scala +++ b/app/src/main/scala/mdr/pdb/app/connectors/DirectoryPageConnector.scala @@ -7,15 +7,18 @@ import mdr.pdb.app.components.PageLink import mdr.pdb.app.components.AppPage -case class DirectoryPageConnector( - $input: EventStream[List[UserInfo]], - actionBus: Observer[Action] -)(using router: Router[Page]): - val $data = $input.startWithNone - val $actionSignal = EventStream.fromValue(FetchDirectory) +object DirectoryPageConnector: + trait AppState extends AppPage.AppState: + def users: EventStream[List[UserInfo]] +class DirectoryPageConnector(state: DirectoryPageConnector.AppState)(using + router: Router[Page] +): def apply: HtmlElement = - AppPage(actionBus)( + val $data = state.users.startWithNone + val $actionSignal = EventStream.fromValue(FetchDirectory) + + AppPage(state)( $data.split(_ => ())((_, _, s) => pages.directory.DirectoryPage( s.map( @@ -23,12 +26,12 @@ _.toUserRow(u => PageLink.container( Page.Detail(Page.Titled(u.personalNumber)), - actionBus + state.actionBus ) ) ) ) ) ), - $actionSignal --> actionBus + $actionSignal --> state.actionBus ) diff --git a/app/src/main/scala/mdr/pdb/app/pages/detail/UpravDukaz.scala b/app/src/main/scala/mdr/pdb/app/pages/detail/UpravDukaz.scala index b58618a..2c23adb 100644 --- a/app/src/main/scala/mdr/pdb/app/pages/detail/UpravDukaz.scala +++ b/app/src/main/scala/mdr/pdb/app/pages/detail/UpravDukaz.scala @@ -13,11 +13,10 @@ type ThisPage = Page.UpravDukazKriteria type PageKey = (OsobniCislo, String, String) - trait State { + trait State extends AppPage.AppState { def details: EventStream[UserInfo] def parameters: EventStream[List[Parameter]] def availableFiles: EventStream[List[File]] - def actionBus: Observer[Action] } def keyOfPage(page: ThisPage): PageKey = @@ -49,7 +48,7 @@ ) def apply: HtmlElement = - AppPage(state.actionBus)( + AppPage(state)( $merged.split(_ => ())((_, s, $s) => PageComponent( $s.map(buildModel), diff --git a/app/src/main/scala/mdr/pdb/app/pages/detail/components/UpravDukazForm.scala b/app/src/main/scala/mdr/pdb/app/pages/detail/components/UpravDukazForm.scala index 28753c9..931dd1a 100644 --- a/app/src/main/scala/mdr/pdb/app/pages/detail/components/UpravDukazForm.scala +++ b/app/src/main/scala/mdr/pdb/app/pages/detail/components/UpravDukazForm.scala @@ -7,8 +7,8 @@ import org.scalajs.dom import com.raquo.laminar.nodes.ReactiveHtmlElement import fiftyforms.services.files.components.tailwind.FilePicker -import mdr.pdb.frontend.AutorizujDukaz -import mdr.pdb.frontend.DocumentRef +import mdr.pdb.api.AutorizujDukaz +import mdr.pdb.api.DocumentRef import fiftyforms.services.files.File object UpravDukazForm: diff --git a/app/src/main/scala/mdr/pdb/app/state/AppState.scala b/app/src/main/scala/mdr/pdb/app/state/AppState.scala index e44b2df..fee56f4 100644 --- a/app/src/main/scala/mdr/pdb/app/state/AppState.scala +++ b/app/src/main/scala/mdr/pdb/app/state/AppState.scala @@ -1,7 +1,8 @@ package mdr.pdb.app package state -import com.raquo.airstream.core.EventStream +import com.raquo.airstream.core.{EventStream, Signal} +import com.raquo.airstream.state.Val import mdr.pdb.{UserInfo, OsobniCislo} import com.raquo.airstream.core.Observer import scala.scalajs.js @@ -17,10 +18,14 @@ import fiftyforms.services.files.File trait AppState - extends connectors.DetailPageConnector.AppState + extends components.AppPage.AppState + with connectors.DirectoryPageConnector.AppState + with connectors.DetailPageConnector.AppState with connectors.DetailParametruPageConnector.AppState with connectors.DetailKriteriaPageConnector.AppState with pages.detail.UpravDukaz.State: + + def online: Signal[Boolean] def users: EventStream[List[UserInfo]] def details: EventStream[UserInfo] def parameters: EventStream[List[Parameter]] @@ -104,6 +109,7 @@ ) } + override def online: Signal[Boolean] = Val(false) override def users: EventStream[List[UserInfo]] = usersStream.debugWithName("users") diff --git a/build.sbt b/build.sbt index 6152097..7f91099 100644 --- a/build.sbt +++ b/build.sbt @@ -14,7 +14,6 @@ .settings( IWDeps.useZIO(Test), IWDeps.tapirCore, - IWDeps.tapirZIO, IWDeps.tapirZIOJson ) @@ -43,7 +42,8 @@ IWDeps.laminextCore, IWDeps.laminextUI, IWDeps.laminextTailwind, - IWDeps.laminextValidationCore + IWDeps.laminextValidationCore, + IWDeps.tapirSttpClient ) .settings( scalaJSLinkerConfig ~= { _.withModuleKind(ModuleKind.ESModule) }, diff --git a/core/src/main/scala/mdr/pdb/api/CustomTapir.scala b/core/src/main/scala/mdr/pdb/api/CustomTapir.scala index 4889939..c36f8f2 100644 --- a/core/src/main/scala/mdr/pdb/api/CustomTapir.scala +++ b/core/src/main/scala/mdr/pdb/api/CustomTapir.scala @@ -1,10 +1,9 @@ package mdr.pdb.api import sttp.tapir.Tapir -import sttp.tapir.ztapir.ZTapir import sttp.tapir.json.zio.TapirJsonZio import sttp.tapir.TapirAliases -trait CustomTapir extends Tapir with ZTapir with TapirJsonZio with TapirAliases +trait CustomTapir extends Tapir with TapirJsonZio with TapirAliases object CustomTapir extends CustomTapir diff --git a/app/src/main/scala/mdr/pdb/app/Main.scala b/app/src/main/scala/mdr/pdb/app/Main.scala index 081b4ee..b9f4fe1 100644 --- a/app/src/main/scala/mdr/pdb/app/Main.scala +++ b/app/src/main/scala/mdr/pdb/app/Main.scala @@ -79,7 +79,7 @@ pages.detail.UpravDukaz.Connector(state)(_).apply ) .collectStatic(Page.Dashboard)( - connectors.DashboardPageConnector(state.actionBus).apply + connectors.DashboardPageConnector(state).apply ) .collect[Page.NotFound](pg => pages.errors.NotFoundPage(Routes.homePage, pg.url, state.actionBus) @@ -94,7 +94,7 @@ ) .collectStatic(Page.Directory)( connectors - .DirectoryPageConnector(state.users, state.actionBus) + .DirectoryPageConnector(state) .apply ) div(cls := "h-full", child <-- pageSplitter.$view) diff --git a/app/src/main/scala/mdr/pdb/app/components/AppPage.scala b/app/src/main/scala/mdr/pdb/app/components/AppPage.scala index 130c817..043f50e 100644 --- a/app/src/main/scala/mdr/pdb/app/components/AppPage.scala +++ b/app/src/main/scala/mdr/pdb/app/components/AppPage.scala @@ -9,6 +9,11 @@ import mdr.pdb.UserFunction object AppPage: + trait AppState { + def online: Signal[Boolean] + def actionBus: Observer[Action] + } + // TODO: pages by logged in user val pages: List[Page] = List(Page.Directory, Page.Dashboard) @@ -52,31 +57,34 @@ val $userInfo = $userProfile.signal.map(_.userInfo) type ViewModel = Option[HtmlElement] + def apply( - actionBus: Observer[Action] + state: AppState )($m: Signal[ViewModel], mods: Modifier[HtmlElement]*)(using router: Router[Page] ): HtmlElement = - PageLayout(actionBus)( - $m.combineWith($userInfo, router.$currentPage).map((c, u, cp) => - PageLayout.ViewModel( - NavigationBar.ViewModel( - u, - pages.map(p => - NavigationBar.Link( - () => - PageLink( - p, - actionBus - ), - p == cp - ) + PageLayout(state.actionBus)( + $m.combineWith($userInfo, router.$currentPage, state.online).map( + (c, u, cp, o) => + PageLayout.ViewModel( + NavigationBar.ViewModel( + u, + pages.map(p => + NavigationBar.Link( + () => + PageLink( + p, + state.actionBus + ), + p == cp + ) + ), + userMenu, + logo, + o ), - userMenu, - logo - ), - c - ) + c + ) ), mods ) diff --git a/app/src/main/scala/mdr/pdb/app/components/NavigationBar.scala b/app/src/main/scala/mdr/pdb/app/components/NavigationBar.scala index 4bb8b8d..1ed5b9a 100644 --- a/app/src/main/scala/mdr/pdb/app/components/NavigationBar.scala +++ b/app/src/main/scala/mdr/pdb/app/components/NavigationBar.scala @@ -17,7 +17,8 @@ userInfo: UserInfo, pages: List[Link], userMenu: List[MenuItem], - logo: Logo + logo: Logo, + online: Boolean ) def apply($m: Signal[ViewModel]): HtmlElement = @@ -30,6 +31,14 @@ inline def avatarImage(size: Int = 8) = Avatar($userInfo.map(_.img)).avatarImage(size) + def offlineIcon = button( + tpe := "button", + cls <-- $m.map(m => List("hidden" -> m.online)), + cls("bg-indigo-600 text-indigo-200"), + span(cls := "sr-only", "Server odpojen"), + Icons.outline.`status-offline`() + ) + def notificationButton = button( tpe := "button", cls := List( @@ -44,7 +53,7 @@ "rounded-full", "text-indigo-200" ), - span(cls := "sr-only", "View notifications"), + span(cls := "sr-only", "Zobrazit upozornění"), Icons.outline.bell() ) @@ -132,7 +141,11 @@ ) ) ), - notificationButton.amend(cls := List("flex-shrink-0", "ml-auto")) + div( + cls("flex-shrink-0 ml-auto flex"), + offlineIcon, + notificationButton + ) ), div( cls := "mt-3 px-2 space-y-1", @@ -200,6 +213,7 @@ desktopOnly, div( cls := "ml-4 flex items-center md:ml-6", + offlineIcon, notificationButton, userProfile ) diff --git a/app/src/main/scala/mdr/pdb/app/connectors/DashboardPageConnector.scala b/app/src/main/scala/mdr/pdb/app/connectors/DashboardPageConnector.scala index 7682772..20d426a 100644 --- a/app/src/main/scala/mdr/pdb/app/connectors/DashboardPageConnector.scala +++ b/app/src/main/scala/mdr/pdb/app/connectors/DashboardPageConnector.scala @@ -5,9 +5,12 @@ import mdr.pdb.app.pages.dashboard.DashboardPage import com.raquo.waypoint.Router import mdr.pdb.app.components.AppPage +import state.AppState -class DashboardPageConnector(actionBus: Observer[Action])(using +class DashboardPageConnector(state: AppState)(using router: Router[Page] ): def apply: HtmlElement = - AppPage(actionBus)(Val(Some(DashboardPage.render))) + AppPage(state)( + Val(Some(DashboardPage.render)) + ) diff --git a/app/src/main/scala/mdr/pdb/app/connectors/DetailKriteriaPageConnector.scala b/app/src/main/scala/mdr/pdb/app/connectors/DetailKriteriaPageConnector.scala index 76b9133..9217c19 100644 --- a/app/src/main/scala/mdr/pdb/app/connectors/DetailKriteriaPageConnector.scala +++ b/app/src/main/scala/mdr/pdb/app/connectors/DetailKriteriaPageConnector.scala @@ -12,10 +12,9 @@ import mdr.pdb.ParameterCriteria object DetailKriteriaPageConnector { - trait AppState { + trait AppState extends AppPage.AppState { def details: EventStream[UserInfo] def parameters: EventStream[List[Parameter]] - def actionBus: Observer[Action] } } @@ -47,7 +46,7 @@ ) def apply: HtmlElement = - AppPage(state.actionBus)( + AppPage(state)( $merged.map(_.map(buildModel)) .split(_ => ())((_, s, $s) => DetailKriteriaPage($s)(state.actionBus.contramap { diff --git a/app/src/main/scala/mdr/pdb/app/connectors/DetailPageConnector.scala b/app/src/main/scala/mdr/pdb/app/connectors/DetailPageConnector.scala index df3a6b7..4f0c1e2 100644 --- a/app/src/main/scala/mdr/pdb/app/connectors/DetailPageConnector.scala +++ b/app/src/main/scala/mdr/pdb/app/connectors/DetailPageConnector.scala @@ -13,10 +13,9 @@ import fiftyforms.ui.components.tailwind.Color object DetailPageConnector { - trait AppState { + trait AppState extends AppPage.AppState { def details: EventStream[UserInfo] def parameters: EventStream[List[Parameter]] - def actionBus: Observer[Action] } } @@ -36,7 +35,7 @@ val $params = state.parameters.startWithNone def apply: HtmlElement = - AppPage(state.actionBus)( + AppPage(state)( $data.combineWithFn($params)(_ zip _) .map(_.map(buildModel)) .split(_ => ())((_, _, s) => DetailPage(s)), diff --git a/app/src/main/scala/mdr/pdb/app/connectors/DetailParametruPageConnector.scala b/app/src/main/scala/mdr/pdb/app/connectors/DetailParametruPageConnector.scala index fa7193d..47c2336 100644 --- a/app/src/main/scala/mdr/pdb/app/connectors/DetailParametruPageConnector.scala +++ b/app/src/main/scala/mdr/pdb/app/connectors/DetailParametruPageConnector.scala @@ -11,10 +11,9 @@ import mdr.pdb.app.components.PageLink object DetailParametruPageConnector { - trait AppState { + trait AppState extends AppPage.AppState { def details: EventStream[UserInfo] def parameters: EventStream[List[Parameter]] - def actionBus: Observer[Action] } } @@ -41,7 +40,7 @@ ) def apply: HtmlElement = - AppPage(state.actionBus)( + AppPage(state)( $merged.map(_.map(buildModel)) .split(_ => ())((_, _, s) => DetailParametruPage(s)), $pageChangeSignal --> state.actionBus diff --git a/app/src/main/scala/mdr/pdb/app/connectors/DirectoryPageConnector.scala b/app/src/main/scala/mdr/pdb/app/connectors/DirectoryPageConnector.scala index ab2c045..732aa41 100644 --- a/app/src/main/scala/mdr/pdb/app/connectors/DirectoryPageConnector.scala +++ b/app/src/main/scala/mdr/pdb/app/connectors/DirectoryPageConnector.scala @@ -7,15 +7,18 @@ import mdr.pdb.app.components.PageLink import mdr.pdb.app.components.AppPage -case class DirectoryPageConnector( - $input: EventStream[List[UserInfo]], - actionBus: Observer[Action] -)(using router: Router[Page]): - val $data = $input.startWithNone - val $actionSignal = EventStream.fromValue(FetchDirectory) +object DirectoryPageConnector: + trait AppState extends AppPage.AppState: + def users: EventStream[List[UserInfo]] +class DirectoryPageConnector(state: DirectoryPageConnector.AppState)(using + router: Router[Page] +): def apply: HtmlElement = - AppPage(actionBus)( + val $data = state.users.startWithNone + val $actionSignal = EventStream.fromValue(FetchDirectory) + + AppPage(state)( $data.split(_ => ())((_, _, s) => pages.directory.DirectoryPage( s.map( @@ -23,12 +26,12 @@ _.toUserRow(u => PageLink.container( Page.Detail(Page.Titled(u.personalNumber)), - actionBus + state.actionBus ) ) ) ) ) ), - $actionSignal --> actionBus + $actionSignal --> state.actionBus ) diff --git a/app/src/main/scala/mdr/pdb/app/pages/detail/UpravDukaz.scala b/app/src/main/scala/mdr/pdb/app/pages/detail/UpravDukaz.scala index b58618a..2c23adb 100644 --- a/app/src/main/scala/mdr/pdb/app/pages/detail/UpravDukaz.scala +++ b/app/src/main/scala/mdr/pdb/app/pages/detail/UpravDukaz.scala @@ -13,11 +13,10 @@ type ThisPage = Page.UpravDukazKriteria type PageKey = (OsobniCislo, String, String) - trait State { + trait State extends AppPage.AppState { def details: EventStream[UserInfo] def parameters: EventStream[List[Parameter]] def availableFiles: EventStream[List[File]] - def actionBus: Observer[Action] } def keyOfPage(page: ThisPage): PageKey = @@ -49,7 +48,7 @@ ) def apply: HtmlElement = - AppPage(state.actionBus)( + AppPage(state)( $merged.split(_ => ())((_, s, $s) => PageComponent( $s.map(buildModel), diff --git a/app/src/main/scala/mdr/pdb/app/pages/detail/components/UpravDukazForm.scala b/app/src/main/scala/mdr/pdb/app/pages/detail/components/UpravDukazForm.scala index 28753c9..931dd1a 100644 --- a/app/src/main/scala/mdr/pdb/app/pages/detail/components/UpravDukazForm.scala +++ b/app/src/main/scala/mdr/pdb/app/pages/detail/components/UpravDukazForm.scala @@ -7,8 +7,8 @@ import org.scalajs.dom import com.raquo.laminar.nodes.ReactiveHtmlElement import fiftyforms.services.files.components.tailwind.FilePicker -import mdr.pdb.frontend.AutorizujDukaz -import mdr.pdb.frontend.DocumentRef +import mdr.pdb.api.AutorizujDukaz +import mdr.pdb.api.DocumentRef import fiftyforms.services.files.File object UpravDukazForm: diff --git a/app/src/main/scala/mdr/pdb/app/state/AppState.scala b/app/src/main/scala/mdr/pdb/app/state/AppState.scala index e44b2df..fee56f4 100644 --- a/app/src/main/scala/mdr/pdb/app/state/AppState.scala +++ b/app/src/main/scala/mdr/pdb/app/state/AppState.scala @@ -1,7 +1,8 @@ package mdr.pdb.app package state -import com.raquo.airstream.core.EventStream +import com.raquo.airstream.core.{EventStream, Signal} +import com.raquo.airstream.state.Val import mdr.pdb.{UserInfo, OsobniCislo} import com.raquo.airstream.core.Observer import scala.scalajs.js @@ -17,10 +18,14 @@ import fiftyforms.services.files.File trait AppState - extends connectors.DetailPageConnector.AppState + extends components.AppPage.AppState + with connectors.DirectoryPageConnector.AppState + with connectors.DetailPageConnector.AppState with connectors.DetailParametruPageConnector.AppState with connectors.DetailKriteriaPageConnector.AppState with pages.detail.UpravDukaz.State: + + def online: Signal[Boolean] def users: EventStream[List[UserInfo]] def details: EventStream[UserInfo] def parameters: EventStream[List[Parameter]] @@ -104,6 +109,7 @@ ) } + override def online: Signal[Boolean] = Val(false) override def users: EventStream[List[UserInfo]] = usersStream.debugWithName("users") diff --git a/build.sbt b/build.sbt index 6152097..7f91099 100644 --- a/build.sbt +++ b/build.sbt @@ -14,7 +14,6 @@ .settings( IWDeps.useZIO(Test), IWDeps.tapirCore, - IWDeps.tapirZIO, IWDeps.tapirZIOJson ) @@ -43,7 +42,8 @@ IWDeps.laminextCore, IWDeps.laminextUI, IWDeps.laminextTailwind, - IWDeps.laminextValidationCore + IWDeps.laminextValidationCore, + IWDeps.tapirSttpClient ) .settings( scalaJSLinkerConfig ~= { _.withModuleKind(ModuleKind.ESModule) }, diff --git a/core/src/main/scala/mdr/pdb/api/CustomTapir.scala b/core/src/main/scala/mdr/pdb/api/CustomTapir.scala index 4889939..c36f8f2 100644 --- a/core/src/main/scala/mdr/pdb/api/CustomTapir.scala +++ b/core/src/main/scala/mdr/pdb/api/CustomTapir.scala @@ -1,10 +1,9 @@ package mdr.pdb.api import sttp.tapir.Tapir -import sttp.tapir.ztapir.ZTapir import sttp.tapir.json.zio.TapirJsonZio import sttp.tapir.TapirAliases -trait CustomTapir extends Tapir with ZTapir with TapirJsonZio with TapirAliases +trait CustomTapir extends Tapir with TapirJsonZio with TapirAliases object CustomTapir extends CustomTapir diff --git a/server/src/main/scala/mdr/pdb/server/CustomTapir.scala b/server/src/main/scala/mdr/pdb/server/CustomTapir.scala index 680b178..2208ac8 100644 --- a/server/src/main/scala/mdr/pdb/server/CustomTapir.scala +++ b/server/src/main/scala/mdr/pdb/server/CustomTapir.scala @@ -9,6 +9,7 @@ trait CustomTapir extends mdr.pdb.api.CustomTapir + with ZTapir with ZHttp4sServerInterpreter[AppEnv] object CustomTapir extends CustomTapir diff --git a/app/src/main/scala/mdr/pdb/app/Main.scala b/app/src/main/scala/mdr/pdb/app/Main.scala index 081b4ee..b9f4fe1 100644 --- a/app/src/main/scala/mdr/pdb/app/Main.scala +++ b/app/src/main/scala/mdr/pdb/app/Main.scala @@ -79,7 +79,7 @@ pages.detail.UpravDukaz.Connector(state)(_).apply ) .collectStatic(Page.Dashboard)( - connectors.DashboardPageConnector(state.actionBus).apply + connectors.DashboardPageConnector(state).apply ) .collect[Page.NotFound](pg => pages.errors.NotFoundPage(Routes.homePage, pg.url, state.actionBus) @@ -94,7 +94,7 @@ ) .collectStatic(Page.Directory)( connectors - .DirectoryPageConnector(state.users, state.actionBus) + .DirectoryPageConnector(state) .apply ) div(cls := "h-full", child <-- pageSplitter.$view) diff --git a/app/src/main/scala/mdr/pdb/app/components/AppPage.scala b/app/src/main/scala/mdr/pdb/app/components/AppPage.scala index 130c817..043f50e 100644 --- a/app/src/main/scala/mdr/pdb/app/components/AppPage.scala +++ b/app/src/main/scala/mdr/pdb/app/components/AppPage.scala @@ -9,6 +9,11 @@ import mdr.pdb.UserFunction object AppPage: + trait AppState { + def online: Signal[Boolean] + def actionBus: Observer[Action] + } + // TODO: pages by logged in user val pages: List[Page] = List(Page.Directory, Page.Dashboard) @@ -52,31 +57,34 @@ val $userInfo = $userProfile.signal.map(_.userInfo) type ViewModel = Option[HtmlElement] + def apply( - actionBus: Observer[Action] + state: AppState )($m: Signal[ViewModel], mods: Modifier[HtmlElement]*)(using router: Router[Page] ): HtmlElement = - PageLayout(actionBus)( - $m.combineWith($userInfo, router.$currentPage).map((c, u, cp) => - PageLayout.ViewModel( - NavigationBar.ViewModel( - u, - pages.map(p => - NavigationBar.Link( - () => - PageLink( - p, - actionBus - ), - p == cp - ) + PageLayout(state.actionBus)( + $m.combineWith($userInfo, router.$currentPage, state.online).map( + (c, u, cp, o) => + PageLayout.ViewModel( + NavigationBar.ViewModel( + u, + pages.map(p => + NavigationBar.Link( + () => + PageLink( + p, + state.actionBus + ), + p == cp + ) + ), + userMenu, + logo, + o ), - userMenu, - logo - ), - c - ) + c + ) ), mods ) diff --git a/app/src/main/scala/mdr/pdb/app/components/NavigationBar.scala b/app/src/main/scala/mdr/pdb/app/components/NavigationBar.scala index 4bb8b8d..1ed5b9a 100644 --- a/app/src/main/scala/mdr/pdb/app/components/NavigationBar.scala +++ b/app/src/main/scala/mdr/pdb/app/components/NavigationBar.scala @@ -17,7 +17,8 @@ userInfo: UserInfo, pages: List[Link], userMenu: List[MenuItem], - logo: Logo + logo: Logo, + online: Boolean ) def apply($m: Signal[ViewModel]): HtmlElement = @@ -30,6 +31,14 @@ inline def avatarImage(size: Int = 8) = Avatar($userInfo.map(_.img)).avatarImage(size) + def offlineIcon = button( + tpe := "button", + cls <-- $m.map(m => List("hidden" -> m.online)), + cls("bg-indigo-600 text-indigo-200"), + span(cls := "sr-only", "Server odpojen"), + Icons.outline.`status-offline`() + ) + def notificationButton = button( tpe := "button", cls := List( @@ -44,7 +53,7 @@ "rounded-full", "text-indigo-200" ), - span(cls := "sr-only", "View notifications"), + span(cls := "sr-only", "Zobrazit upozornění"), Icons.outline.bell() ) @@ -132,7 +141,11 @@ ) ) ), - notificationButton.amend(cls := List("flex-shrink-0", "ml-auto")) + div( + cls("flex-shrink-0 ml-auto flex"), + offlineIcon, + notificationButton + ) ), div( cls := "mt-3 px-2 space-y-1", @@ -200,6 +213,7 @@ desktopOnly, div( cls := "ml-4 flex items-center md:ml-6", + offlineIcon, notificationButton, userProfile ) diff --git a/app/src/main/scala/mdr/pdb/app/connectors/DashboardPageConnector.scala b/app/src/main/scala/mdr/pdb/app/connectors/DashboardPageConnector.scala index 7682772..20d426a 100644 --- a/app/src/main/scala/mdr/pdb/app/connectors/DashboardPageConnector.scala +++ b/app/src/main/scala/mdr/pdb/app/connectors/DashboardPageConnector.scala @@ -5,9 +5,12 @@ import mdr.pdb.app.pages.dashboard.DashboardPage import com.raquo.waypoint.Router import mdr.pdb.app.components.AppPage +import state.AppState -class DashboardPageConnector(actionBus: Observer[Action])(using +class DashboardPageConnector(state: AppState)(using router: Router[Page] ): def apply: HtmlElement = - AppPage(actionBus)(Val(Some(DashboardPage.render))) + AppPage(state)( + Val(Some(DashboardPage.render)) + ) diff --git a/app/src/main/scala/mdr/pdb/app/connectors/DetailKriteriaPageConnector.scala b/app/src/main/scala/mdr/pdb/app/connectors/DetailKriteriaPageConnector.scala index 76b9133..9217c19 100644 --- a/app/src/main/scala/mdr/pdb/app/connectors/DetailKriteriaPageConnector.scala +++ b/app/src/main/scala/mdr/pdb/app/connectors/DetailKriteriaPageConnector.scala @@ -12,10 +12,9 @@ import mdr.pdb.ParameterCriteria object DetailKriteriaPageConnector { - trait AppState { + trait AppState extends AppPage.AppState { def details: EventStream[UserInfo] def parameters: EventStream[List[Parameter]] - def actionBus: Observer[Action] } } @@ -47,7 +46,7 @@ ) def apply: HtmlElement = - AppPage(state.actionBus)( + AppPage(state)( $merged.map(_.map(buildModel)) .split(_ => ())((_, s, $s) => DetailKriteriaPage($s)(state.actionBus.contramap { diff --git a/app/src/main/scala/mdr/pdb/app/connectors/DetailPageConnector.scala b/app/src/main/scala/mdr/pdb/app/connectors/DetailPageConnector.scala index df3a6b7..4f0c1e2 100644 --- a/app/src/main/scala/mdr/pdb/app/connectors/DetailPageConnector.scala +++ b/app/src/main/scala/mdr/pdb/app/connectors/DetailPageConnector.scala @@ -13,10 +13,9 @@ import fiftyforms.ui.components.tailwind.Color object DetailPageConnector { - trait AppState { + trait AppState extends AppPage.AppState { def details: EventStream[UserInfo] def parameters: EventStream[List[Parameter]] - def actionBus: Observer[Action] } } @@ -36,7 +35,7 @@ val $params = state.parameters.startWithNone def apply: HtmlElement = - AppPage(state.actionBus)( + AppPage(state)( $data.combineWithFn($params)(_ zip _) .map(_.map(buildModel)) .split(_ => ())((_, _, s) => DetailPage(s)), diff --git a/app/src/main/scala/mdr/pdb/app/connectors/DetailParametruPageConnector.scala b/app/src/main/scala/mdr/pdb/app/connectors/DetailParametruPageConnector.scala index fa7193d..47c2336 100644 --- a/app/src/main/scala/mdr/pdb/app/connectors/DetailParametruPageConnector.scala +++ b/app/src/main/scala/mdr/pdb/app/connectors/DetailParametruPageConnector.scala @@ -11,10 +11,9 @@ import mdr.pdb.app.components.PageLink object DetailParametruPageConnector { - trait AppState { + trait AppState extends AppPage.AppState { def details: EventStream[UserInfo] def parameters: EventStream[List[Parameter]] - def actionBus: Observer[Action] } } @@ -41,7 +40,7 @@ ) def apply: HtmlElement = - AppPage(state.actionBus)( + AppPage(state)( $merged.map(_.map(buildModel)) .split(_ => ())((_, _, s) => DetailParametruPage(s)), $pageChangeSignal --> state.actionBus diff --git a/app/src/main/scala/mdr/pdb/app/connectors/DirectoryPageConnector.scala b/app/src/main/scala/mdr/pdb/app/connectors/DirectoryPageConnector.scala index ab2c045..732aa41 100644 --- a/app/src/main/scala/mdr/pdb/app/connectors/DirectoryPageConnector.scala +++ b/app/src/main/scala/mdr/pdb/app/connectors/DirectoryPageConnector.scala @@ -7,15 +7,18 @@ import mdr.pdb.app.components.PageLink import mdr.pdb.app.components.AppPage -case class DirectoryPageConnector( - $input: EventStream[List[UserInfo]], - actionBus: Observer[Action] -)(using router: Router[Page]): - val $data = $input.startWithNone - val $actionSignal = EventStream.fromValue(FetchDirectory) +object DirectoryPageConnector: + trait AppState extends AppPage.AppState: + def users: EventStream[List[UserInfo]] +class DirectoryPageConnector(state: DirectoryPageConnector.AppState)(using + router: Router[Page] +): def apply: HtmlElement = - AppPage(actionBus)( + val $data = state.users.startWithNone + val $actionSignal = EventStream.fromValue(FetchDirectory) + + AppPage(state)( $data.split(_ => ())((_, _, s) => pages.directory.DirectoryPage( s.map( @@ -23,12 +26,12 @@ _.toUserRow(u => PageLink.container( Page.Detail(Page.Titled(u.personalNumber)), - actionBus + state.actionBus ) ) ) ) ) ), - $actionSignal --> actionBus + $actionSignal --> state.actionBus ) diff --git a/app/src/main/scala/mdr/pdb/app/pages/detail/UpravDukaz.scala b/app/src/main/scala/mdr/pdb/app/pages/detail/UpravDukaz.scala index b58618a..2c23adb 100644 --- a/app/src/main/scala/mdr/pdb/app/pages/detail/UpravDukaz.scala +++ b/app/src/main/scala/mdr/pdb/app/pages/detail/UpravDukaz.scala @@ -13,11 +13,10 @@ type ThisPage = Page.UpravDukazKriteria type PageKey = (OsobniCislo, String, String) - trait State { + trait State extends AppPage.AppState { def details: EventStream[UserInfo] def parameters: EventStream[List[Parameter]] def availableFiles: EventStream[List[File]] - def actionBus: Observer[Action] } def keyOfPage(page: ThisPage): PageKey = @@ -49,7 +48,7 @@ ) def apply: HtmlElement = - AppPage(state.actionBus)( + AppPage(state)( $merged.split(_ => ())((_, s, $s) => PageComponent( $s.map(buildModel), diff --git a/app/src/main/scala/mdr/pdb/app/pages/detail/components/UpravDukazForm.scala b/app/src/main/scala/mdr/pdb/app/pages/detail/components/UpravDukazForm.scala index 28753c9..931dd1a 100644 --- a/app/src/main/scala/mdr/pdb/app/pages/detail/components/UpravDukazForm.scala +++ b/app/src/main/scala/mdr/pdb/app/pages/detail/components/UpravDukazForm.scala @@ -7,8 +7,8 @@ import org.scalajs.dom import com.raquo.laminar.nodes.ReactiveHtmlElement import fiftyforms.services.files.components.tailwind.FilePicker -import mdr.pdb.frontend.AutorizujDukaz -import mdr.pdb.frontend.DocumentRef +import mdr.pdb.api.AutorizujDukaz +import mdr.pdb.api.DocumentRef import fiftyforms.services.files.File object UpravDukazForm: diff --git a/app/src/main/scala/mdr/pdb/app/state/AppState.scala b/app/src/main/scala/mdr/pdb/app/state/AppState.scala index e44b2df..fee56f4 100644 --- a/app/src/main/scala/mdr/pdb/app/state/AppState.scala +++ b/app/src/main/scala/mdr/pdb/app/state/AppState.scala @@ -1,7 +1,8 @@ package mdr.pdb.app package state -import com.raquo.airstream.core.EventStream +import com.raquo.airstream.core.{EventStream, Signal} +import com.raquo.airstream.state.Val import mdr.pdb.{UserInfo, OsobniCislo} import com.raquo.airstream.core.Observer import scala.scalajs.js @@ -17,10 +18,14 @@ import fiftyforms.services.files.File trait AppState - extends connectors.DetailPageConnector.AppState + extends components.AppPage.AppState + with connectors.DirectoryPageConnector.AppState + with connectors.DetailPageConnector.AppState with connectors.DetailParametruPageConnector.AppState with connectors.DetailKriteriaPageConnector.AppState with pages.detail.UpravDukaz.State: + + def online: Signal[Boolean] def users: EventStream[List[UserInfo]] def details: EventStream[UserInfo] def parameters: EventStream[List[Parameter]] @@ -104,6 +109,7 @@ ) } + override def online: Signal[Boolean] = Val(false) override def users: EventStream[List[UserInfo]] = usersStream.debugWithName("users") diff --git a/build.sbt b/build.sbt index 6152097..7f91099 100644 --- a/build.sbt +++ b/build.sbt @@ -14,7 +14,6 @@ .settings( IWDeps.useZIO(Test), IWDeps.tapirCore, - IWDeps.tapirZIO, IWDeps.tapirZIOJson ) @@ -43,7 +42,8 @@ IWDeps.laminextCore, IWDeps.laminextUI, IWDeps.laminextTailwind, - IWDeps.laminextValidationCore + IWDeps.laminextValidationCore, + IWDeps.tapirSttpClient ) .settings( scalaJSLinkerConfig ~= { _.withModuleKind(ModuleKind.ESModule) }, diff --git a/core/src/main/scala/mdr/pdb/api/CustomTapir.scala b/core/src/main/scala/mdr/pdb/api/CustomTapir.scala index 4889939..c36f8f2 100644 --- a/core/src/main/scala/mdr/pdb/api/CustomTapir.scala +++ b/core/src/main/scala/mdr/pdb/api/CustomTapir.scala @@ -1,10 +1,9 @@ package mdr.pdb.api import sttp.tapir.Tapir -import sttp.tapir.ztapir.ZTapir import sttp.tapir.json.zio.TapirJsonZio import sttp.tapir.TapirAliases -trait CustomTapir extends Tapir with ZTapir with TapirJsonZio with TapirAliases +trait CustomTapir extends Tapir with TapirJsonZio with TapirAliases object CustomTapir extends CustomTapir diff --git a/server/src/main/scala/mdr/pdb/server/CustomTapir.scala b/server/src/main/scala/mdr/pdb/server/CustomTapir.scala index 680b178..2208ac8 100644 --- a/server/src/main/scala/mdr/pdb/server/CustomTapir.scala +++ b/server/src/main/scala/mdr/pdb/server/CustomTapir.scala @@ -9,6 +9,7 @@ trait CustomTapir extends mdr.pdb.api.CustomTapir + with ZTapir with ZHttp4sServerInterpreter[AppEnv] object CustomTapir extends CustomTapir diff --git a/ui/src/main/scala/fiftyforms/ui/components/tailwind/Icons.scala b/ui/src/main/scala/fiftyforms/ui/components/tailwind/Icons.scala index cfba9bb..1d2c774 100644 --- a/ui/src/main/scala/fiftyforms/ui/components/tailwind/Icons.scala +++ b/ui/src/main/scala/fiftyforms/ui/components/tailwind/Icons.scala @@ -91,19 +91,19 @@ ) ) - inline def x(size: Int = defaultSize) = + inline def `status-offline`(size: Int = defaultSize) = svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", + cls := s"w-${size} h-${size}", fill := "none", - viewBox := "0 0 24 24", stroke := "currentColor", + viewBox := "0 0 24 24", + xmlns := "http://www.w3.org/2000/svg", aria.hidden := true, path( strokeLineCap := "round", strokeLineJoin := "round", strokeWidth := "2", - d := "M6 18L18 6M6 6l12 12" + d := "M18.364 5.636a9 9 0 010 12.728m0 0l-2.829-2.829m2.829 2.829L21 21M15.536 8.464a5 5 0 010 7.072m0 0l-2.829-2.829m-4.243 2.829a4.978 4.978 0 01-1.414-2.83m-1.414 5.658a9 9 0 01-2.167-9.238m7.824 2.167a1 1 0 111.414 1.414m-1.414-1.414L3 3m8.293 8.293l1.414 1.414" ) ) @@ -123,6 +123,22 @@ ) ) + inline def x(size: Int = defaultSize) = + svg( + cls := s"h-${size} w-${size}", + xmlns := "http://www.w3.org/2000/svg", + fill := "none", + viewBox := "0 0 24 24", + stroke := "currentColor", + aria.hidden := true, + path( + strokeLineCap := "round", + strokeLineJoin := "round", + strokeWidth := "2", + d := "M6 18L18 6M6 6l12 12" + ) + ) + end outline object solid: