├── .github └── workflows │ ├── ci.yml │ └── docker.yml ├── .gitignore ├── .idea ├── codeStyles │ ├── Project.xml │ └── codeStyleConfig.xml └── vcs.xml ├── .scala-steward.conf ├── LICENSE ├── README.md ├── auth ├── .js │ └── src │ │ ├── main │ │ └── scala │ │ │ └── io │ │ │ └── udash │ │ │ └── auth │ │ │ ├── AuthApplication.scala │ │ │ ├── AuthPresenter.scala │ │ │ └── AuthView.scala │ │ └── test │ │ └── scala │ │ └── io │ │ └── udash │ │ └── auth │ │ ├── AuthApplicationTest.scala │ │ ├── AuthFrontendTestUtils.scala │ │ ├── AuthPresenterTest.scala │ │ └── AuthViewTest.scala └── src │ ├── main │ └── scala │ │ └── io │ │ └── udash │ │ └── auth │ │ ├── AuthRequires.scala │ │ ├── DefaultAuthExceptionCodecRegistry.scala │ │ ├── Permission.scala │ │ ├── PermissionCombinator.scala │ │ ├── PermissionId.scala │ │ ├── UserCtx.scala │ │ └── exceptions.scala │ └── test │ └── scala │ └── io │ └── udash │ └── auth │ ├── AuthRequiresTest.scala │ ├── AuthTestUtils.scala │ ├── PermissionCombinatorTest.scala │ └── PermissionTest.scala ├── benchmarks └── .js │ ├── index.html │ └── src │ └── main │ └── scala │ └── io │ └── udash │ └── benchmarks │ ├── Main.scala │ ├── css │ └── CssStylesApply.scala │ ├── i18n │ └── StaticTranslationBinding.scala │ └── properties │ ├── BenchmarkUtils.scala │ ├── FilteredSeqPropertyListeners.scala │ ├── ModelPropertyListeners.scala │ ├── ModelPropertyWithSeqListeners.scala │ ├── PropertyParameters.scala │ ├── ReversedSeqPropertyListeners.scala │ ├── SinglePropertyListeners.scala │ ├── TransformedSeqPropertyListeners.scala │ └── ZippedSeqPropertyListeners.scala ├── bootstrap4 └── .js │ └── src │ ├── main │ └── scala │ │ └── io │ │ └── udash │ │ └── bootstrap │ │ ├── UdashBootstrap.scala │ │ ├── alert │ │ ├── DismissibleUdashAlert.scala │ │ ├── UdashAlert.scala │ │ └── UdashAlertBase.scala │ │ ├── badge │ │ └── UdashBadge.scala │ │ ├── breadcrumb │ │ └── UdashBreadcrumbs.scala │ │ ├── button │ │ ├── UdashButton.scala │ │ ├── UdashButtonGroup.scala │ │ └── UdashButtonToolbar.scala │ │ ├── card │ │ └── UdashCard.scala │ │ ├── carousel │ │ └── UdashCarousel.scala │ │ ├── collapse │ │ ├── UdashAccordion.scala │ │ └── UdashCollapse.scala │ │ ├── datepicker │ │ └── UdashDatePicker.scala │ │ ├── dropdown │ │ └── UdashDropdown.scala │ │ ├── form │ │ ├── UdashForm.scala │ │ ├── UdashInputGroup.scala │ │ └── Validator.scala │ │ ├── jumbotron │ │ └── UdashJumbotron.scala │ │ ├── list │ │ └── UdashListGroup.scala │ │ ├── modal │ │ └── UdashModal.scala │ │ ├── nav │ │ ├── UdashNav.scala │ │ └── UdashNavbar.scala │ │ ├── package.scala │ │ ├── pagination │ │ └── UdashPagination.scala │ │ ├── progressbar │ │ └── UdashProgressBar.scala │ │ ├── table │ │ └── UdashTable.scala │ │ ├── tooltip │ │ ├── TooltipEvent.scala │ │ ├── TooltipUtils.scala │ │ ├── UdashPopover.scala │ │ └── UdashTooltip.scala │ │ └── utils │ │ ├── BootstrapImplicits.scala │ │ ├── BootstrapStyles.scala │ │ ├── BootstrapTags.scala │ │ ├── UdashBootstrapComponent.scala │ │ └── UdashIcons.scala │ └── test │ ├── scala-2.12 │ └── io │ │ └── udash │ │ └── bootstrap │ │ └── form │ │ └── UdashFormTest.scala │ └── scala │ └── io │ └── udash │ └── bootstrap │ ├── BootstrapImplicitsTest.scala │ ├── alert │ └── UdashAlertTest.scala │ ├── badge │ └── UdashBadgeTest.scala │ ├── breadcrumb │ └── UdashBreadcrumbsTest.scala │ ├── button │ ├── UdashButtonGroupTest.scala │ └── UdashButtonTest.scala │ ├── card │ └── UdashCardTest.scala │ ├── carousel │ └── UdashCarouselTest.scala │ ├── collapse │ ├── UdashAccordionTest.scala │ └── UdashCollapseTest.scala │ ├── datepicker │ └── UdashDatePickerTest.scala │ ├── dropdown │ └── UdashDropdownTest.scala │ ├── form │ └── UdashInputGroupTest.scala │ ├── jumbotron │ └── UdashJumbotronTest.scala │ ├── list │ └── UdashListGroupTest.scala │ ├── modal │ └── UdashModalTest.scala │ ├── nav │ ├── UdashNavTest.scala │ └── UdashNavbarTest.scala │ ├── pagination │ └── UdashPaginationTest.scala │ ├── progressbar │ └── UdashProgressBarTest.scala │ ├── table │ └── UdashTableTest.scala │ ├── tooltip │ ├── PopoverTest.scala │ ├── TooltipTest.scala │ └── TooltipTestUtils.scala │ └── utils │ └── UdashIconsTest.scala ├── build.sbt ├── core ├── .js │ └── src │ │ ├── main │ │ └── scala │ │ │ └── io │ │ │ └── udash │ │ │ ├── Application.scala │ │ │ ├── bindings │ │ │ ├── Bindings.scala │ │ │ ├── inputs │ │ │ │ ├── CheckButtons.scala │ │ │ │ ├── Checkbox.scala │ │ │ │ ├── FileInput.scala │ │ │ │ ├── GroupedButtonsBinding.scala │ │ │ │ ├── Input.scala │ │ │ │ ├── InputBinding.scala │ │ │ │ ├── RadioButtons.scala │ │ │ │ ├── RangeInput.scala │ │ │ │ ├── Select.scala │ │ │ │ ├── SelectBinding.scala │ │ │ │ ├── TextArea.scala │ │ │ │ └── TextInputsModifier.scala │ │ │ └── modifiers │ │ │ │ ├── Binding.scala │ │ │ │ ├── DOMManipulator.scala │ │ │ │ ├── EmptyModifier.scala │ │ │ │ ├── PropertyModifier.scala │ │ │ │ ├── SeqAsValueModifier.scala │ │ │ │ ├── SeqPropertyModifier.scala │ │ │ │ ├── SeqPropertyModifierUtils.scala │ │ │ │ ├── SeqPropertyWithIndexModifier.scala │ │ │ │ ├── SimplePropertyModifier.scala │ │ │ │ ├── ValueModifier.scala │ │ │ │ └── package.scala │ │ │ ├── component │ │ │ ├── Component.scala │ │ │ ├── ComponentId.scala │ │ │ ├── Components.scala │ │ │ └── Listenable.scala │ │ │ ├── core │ │ │ ├── Defaults.scala │ │ │ └── Definitions.scala │ │ │ ├── package.scala │ │ │ ├── routing │ │ │ ├── Routing.scala │ │ │ ├── RoutingEngine.scala │ │ │ ├── RoutingRegistry.scala │ │ │ ├── UrlChangeProvider.scala │ │ │ └── UrlLogging.scala │ │ │ ├── utils │ │ │ ├── FileService.scala │ │ │ └── FileUploader.scala │ │ │ └── view │ │ │ └── ViewRenderer.scala │ │ └── test │ │ └── scala │ │ ├── io │ │ └── udash │ │ │ ├── ApplicationTest.scala │ │ │ ├── bindings │ │ │ ├── QueuedNodeModifierTest.scala │ │ │ ├── TagsBindingTest.scala │ │ │ └── inputs │ │ │ │ ├── CheckButtonsTest.scala │ │ │ │ ├── CheckboxTest.scala │ │ │ │ ├── InputTest.scala │ │ │ │ ├── RadioButtonsTest.scala │ │ │ │ ├── RangeInputTest.scala │ │ │ │ ├── SelectTest.scala │ │ │ │ └── TextAreaTest.scala │ │ │ ├── component │ │ │ ├── ComponentIdTest.scala │ │ │ └── ListenableTest.scala │ │ │ ├── routing │ │ │ ├── RoutingEngineTest.scala │ │ │ ├── RoutingOperatorTest.scala │ │ │ ├── StateTest.scala │ │ │ ├── UrlLoggingTest.scala │ │ │ ├── WindowUrlFragmentChangeProviderTest.scala │ │ │ └── WindowUrlPathChangeProviderTest.scala │ │ │ ├── testing │ │ │ ├── TestRouting.scala │ │ │ ├── TestRoutingRegistry.scala │ │ │ ├── TestState.scala │ │ │ ├── TestUrlChangeProvider.scala │ │ │ ├── TestViewFactory.scala │ │ │ ├── TestViewFactoryRegistry.scala │ │ │ ├── TestViewRenderer.scala │ │ │ └── UdashCoreFrontendTest.scala │ │ │ └── view │ │ │ └── ViewRendererTest.scala │ │ └── manual │ │ └── PropertyErrorManualTest.scala └── src │ ├── main │ └── scala │ │ └── io │ │ └── udash │ │ └── properties │ │ ├── Blank.scala │ │ ├── CallbackSequencer.scala │ │ ├── HasGenCodecAndModelPropertyCreator.scala │ │ ├── HasModelPropertyCreator.scala │ │ ├── ImmutableProperty.scala │ │ ├── IsModelPropertyTemplate.scala │ │ ├── MutableSetRegistration.scala │ │ ├── Properties.scala │ │ ├── PropertyCreator.scala │ │ ├── PropertyCreatorImplicits.scala │ │ ├── model │ │ ├── ModelProperty.scala │ │ ├── ModelPropertyImpl.scala │ │ └── ReadableModelProperty.scala │ │ ├── seq │ │ ├── CombinedReadableSeqProperty.scala │ │ ├── DirectSeqProperty.scala │ │ ├── FilteredSeqProperty.scala │ │ ├── ForwarderReadableSeqProperty.scala │ │ ├── Patch.scala │ │ ├── PropertySeqCombinedReadableSeqProperty.scala │ │ ├── ReadableSeqProperty.scala │ │ ├── ReversedSeqProperty.scala │ │ ├── SeqProperty.scala │ │ ├── SeqPropertyFromSingleValue.scala │ │ ├── TransformedSeqProperty.scala │ │ └── ZippedSeqProperty.scala │ │ └── single │ │ ├── CastableProperty.scala │ │ ├── CombinedProperty.scala │ │ ├── DirectProperty.scala │ │ ├── ForwarderProperty.scala │ │ ├── Property.scala │ │ ├── ReadableProperty.scala │ │ └── TransformedProperty.scala │ └── test │ └── scala │ └── io │ └── udash │ ├── properties │ ├── BlankTest.scala │ ├── CallbackSequencerTest.scala │ ├── HasGenCodecAndModelPropertyCreatorTest.scala │ ├── ImmutablePropertyTest.scala │ ├── ModelPropertyTest.scala │ ├── PropertyCreatorTest.scala │ ├── PropertyTest.scala │ ├── PropertyUsageTest.scala │ ├── SeqPropertyTest.scala │ └── UtilsTest.scala │ └── testing │ ├── AsyncUdashCoreTest.scala │ ├── CoreTestUtils.scala │ └── UdashCoreTest.scala ├── css ├── .js │ └── src │ │ ├── main │ │ └── scala │ │ │ └── io │ │ │ └── udash │ │ │ └── css │ │ │ └── CssView.scala │ │ └── test │ │ └── scala │ │ └── io │ │ └── udash │ │ └── css │ │ └── CssViewTest.scala ├── .jvm │ └── src │ │ ├── main │ │ └── scala │ │ │ └── io │ │ │ └── udash │ │ │ └── css │ │ │ ├── CssFileRenderer.scala │ │ │ └── CssStringRenderer.scala │ │ └── test │ │ └── scala │ │ └── io │ │ └── udash │ │ └── css │ │ ├── CssFileRendererTest.scala │ │ └── CssStringRendererTest.scala └── src │ ├── main │ └── scala │ │ └── io │ │ └── udash │ │ └── css │ │ ├── CssBase.scala │ │ ├── CssStyle.scala │ │ └── CssText.scala │ └── test │ └── scala │ └── io │ └── udash │ └── css │ ├── CssTextTest.scala │ ├── SecondStylesheetExample.scala │ └── StylesheetExample.scala ├── guide ├── backend │ └── src │ │ └── main │ │ ├── resources │ │ ├── demo_translations_en.properties │ │ ├── demo_translations_pl.properties │ │ ├── logback.xml │ │ └── reference.conf │ │ └── scala │ │ └── io │ │ └── udash │ │ └── web │ │ ├── Implicits.scala │ │ ├── Launcher.scala │ │ ├── guide │ │ ├── demos │ │ │ ├── DemosServer.scala │ │ │ ├── activity │ │ │ │ ├── CallLogger.scala │ │ │ │ └── CallServer.scala │ │ │ ├── i18n │ │ │ │ └── TranslationServer.scala │ │ │ └── rpc │ │ │ │ ├── ClientIdServer.scala │ │ │ │ ├── ExceptionsServer.scala │ │ │ │ ├── GenCodecServer.scala │ │ │ │ ├── NotificationsServer.scala │ │ │ │ └── PingServer.scala │ │ ├── markdown │ │ │ └── MarkdownPagesEndpoint.scala │ │ ├── rest │ │ │ └── ExposedRestInterfaces.scala │ │ └── rpc │ │ │ ├── ClientRPC.scala │ │ │ └── ExposedRpcInterfaces.scala │ │ ├── server │ │ └── ApplicationServer.scala │ │ └── styles │ │ └── CssRenderer.scala ├── commons │ └── .js │ │ └── src │ │ └── main │ │ ├── assets │ │ ├── pdf │ │ │ └── origami_crane_printok.pdf │ │ ├── styles │ │ │ └── prism.css │ │ └── svg │ │ │ ├── avsystem.svg │ │ │ ├── based.svg │ │ │ ├── compiled.svg │ │ │ ├── github.svg │ │ │ ├── gitter.svg │ │ │ ├── icon_submenu.svg │ │ │ ├── shared_code.svg │ │ │ ├── stack.svg │ │ │ ├── todomvc.svg │ │ │ └── typesafe.svg │ │ ├── resources │ │ └── prism.js │ │ └── scala │ │ └── io │ │ └── udash │ │ └── web │ │ └── commons │ │ ├── components │ │ ├── CodeBlock.scala │ │ ├── Footer.scala │ │ ├── ForceBootstrap.scala │ │ ├── HeaderButtons.scala │ │ └── HeaderNav.scala │ │ ├── config │ │ └── ExternalUrls.scala │ │ └── views │ │ ├── Component.scala │ │ ├── ImageFactory.scala │ │ └── MarkdownView.scala ├── guide │ └── .js │ │ └── src │ │ ├── main │ │ ├── assets │ │ │ ├── assets.less │ │ │ ├── fonts │ │ │ │ └── roboto │ │ │ │ │ ├── Roboto-Black.ttf │ │ │ │ │ ├── Roboto-BlackItalic.ttf │ │ │ │ │ ├── Roboto-Bold.ttf │ │ │ │ │ ├── Roboto-BoldItalic.ttf │ │ │ │ │ ├── Roboto-Italic.ttf │ │ │ │ │ ├── Roboto-Light.ttf │ │ │ │ │ ├── Roboto-LightItalic.ttf │ │ │ │ │ ├── Roboto-Medium.ttf │ │ │ │ │ ├── Roboto-MediumItalic.ttf │ │ │ │ │ ├── Roboto-Regular.ttf │ │ │ │ │ ├── Roboto-Thin.ttf │ │ │ │ │ └── Roboto-ThinItalic.ttf │ │ │ ├── images │ │ │ │ ├── ext │ │ │ │ │ └── bootstrap │ │ │ │ │ │ └── carousel.jpg │ │ │ │ ├── favicon.ico │ │ │ │ ├── intro_bg.jpg │ │ │ │ ├── intro_bird.png │ │ │ │ ├── quick │ │ │ │ │ └── generator.png │ │ │ │ ├── share │ │ │ │ │ ├── share_facebook.png │ │ │ │ │ ├── share_google.png │ │ │ │ │ └── share_twitter.png │ │ │ │ ├── udash_logo.png │ │ │ │ ├── udash_logo_l.png │ │ │ │ ├── udash_logo_m.png │ │ │ │ └── views │ │ │ │ │ ├── bootstrapping │ │ │ │ │ ├── modules_basic.png │ │ │ │ │ ├── modules_extended.png │ │ │ │ │ └── states.png │ │ │ │ │ ├── frontend │ │ │ │ │ ├── mvp.png │ │ │ │ │ ├── property.png │ │ │ │ │ └── propertyhierarchy.png │ │ │ │ │ └── rest │ │ │ │ │ ├── openapi.svg │ │ │ │ │ └── serialization.svg │ │ │ ├── index.html │ │ │ └── pages │ │ │ │ ├── intro.md │ │ │ │ ├── license.md │ │ │ │ └── rest.md │ │ └── scala │ │ │ └── io │ │ │ └── udash │ │ │ └── web │ │ │ └── guide │ │ │ ├── RoutingRegistryDef.scala │ │ │ ├── RoutingStatesDef.scala │ │ │ ├── StatesToViewFactoryDef.scala │ │ │ ├── components │ │ │ ├── BootstrapUtils.scala │ │ │ ├── GuideMenu.scala │ │ │ └── Header.scala │ │ │ ├── demos │ │ │ ├── AutoDemo.scala │ │ │ ├── DemosClient.scala │ │ │ └── rpc │ │ │ │ ├── NotificationsClient.scala │ │ │ │ └── PingClient.scala │ │ │ ├── init.scala │ │ │ ├── rpc │ │ │ └── RPCService.scala │ │ │ └── views │ │ │ ├── ContentView.scala │ │ │ ├── ErrorView.scala │ │ │ ├── FaqView.scala │ │ │ ├── References.scala │ │ │ ├── RootView.scala │ │ │ ├── Versions.scala │ │ │ ├── ViewContainer.scala │ │ │ ├── bootstrapping │ │ │ ├── AdvancedBootstrappingSbtView.scala │ │ │ ├── BootstrappingBackendView.scala │ │ │ ├── BootstrappingFrontendView.scala │ │ │ ├── BootstrappingIntroView.scala │ │ │ ├── BootstrappingRpcView.scala │ │ │ ├── BootstrappingSbtView.scala │ │ │ └── BootstrappingView.scala │ │ │ ├── ext │ │ │ ├── AuthorizationExtView.scala │ │ │ ├── BootstrapExtView.scala │ │ │ ├── I18NExtView.scala │ │ │ ├── JQueryExtView.scala │ │ │ ├── UserActivityExtView.scala │ │ │ └── demo │ │ │ │ ├── DynamicRemoteTranslationsDemo.scala │ │ │ │ ├── FrontendTranslationsDemo.scala │ │ │ │ ├── JQueryCallbacksDemo.scala │ │ │ │ ├── JQueryEventsDemo.scala │ │ │ │ ├── RemoteTranslationsDemo.scala │ │ │ │ ├── RpcLoggingDemo.scala │ │ │ │ ├── UrlLoggingDemo.scala │ │ │ │ └── bootstrap │ │ │ │ ├── AccordionDemo.scala │ │ │ │ ├── AlertsDemo.scala │ │ │ │ ├── BadgesDemo.scala │ │ │ │ ├── BreadcrumbsDemo.scala │ │ │ │ ├── ButtonDropdownDemo.scala │ │ │ │ ├── ButtonToolbarDemo.scala │ │ │ │ ├── ButtonsDemo.scala │ │ │ │ ├── CardsDemo.scala │ │ │ │ ├── CarouselDemo.scala │ │ │ │ ├── CheckboxButtonsDemo.scala │ │ │ │ ├── DatePickerDemo.scala │ │ │ │ ├── DateRangePickerDemo.scala │ │ │ │ ├── DropdownsDemo.scala │ │ │ │ ├── IconsDemo.scala │ │ │ │ ├── InlineFormDemo.scala │ │ │ │ ├── InputGroupDemo.scala │ │ │ │ ├── JumbotronDemo.scala │ │ │ │ ├── LabelsDemo.scala │ │ │ │ ├── ListGroupDemo.scala │ │ │ │ ├── NavbarDemo.scala │ │ │ │ ├── NavigationDemo.scala │ │ │ │ ├── NavsDemo.scala │ │ │ │ ├── PaginationDemo.scala │ │ │ │ ├── PopoversDemo.scala │ │ │ │ ├── ProgressBarDemo.scala │ │ │ │ ├── RadioButtonsDemo.scala │ │ │ │ ├── ResponsiveEmbedDemo.scala │ │ │ │ ├── SimpleCollapseDemo.scala │ │ │ │ ├── SimpleFormDemo.scala │ │ │ │ ├── SimpleModalDemo.scala │ │ │ │ ├── StaticButtonsGroupDemo.scala │ │ │ │ ├── StaticsDemo.scala │ │ │ │ ├── TableDemo.scala │ │ │ │ ├── ToggleButtonsDemo.scala │ │ │ │ └── TooltipsDemo.scala │ │ │ ├── frontend │ │ │ ├── FrontendBindingsView.scala │ │ │ ├── FrontendFilesView.scala │ │ │ ├── FrontendFormsView.scala │ │ │ ├── FrontendIntroView.scala │ │ │ ├── FrontendMVPView.scala │ │ │ ├── FrontendPropertiesView.scala │ │ │ ├── FrontendRoutingView.scala │ │ │ ├── FrontendTemplatesView.scala │ │ │ ├── FrontendView.scala │ │ │ └── demos │ │ │ │ ├── BindAttributeDemo.scala │ │ │ │ ├── BindDemo.scala │ │ │ │ ├── CheckButtonsDemo.scala │ │ │ │ ├── CheckboxDemo.scala │ │ │ │ ├── DateDemo.scala │ │ │ │ ├── DateTimeLocalDemo.scala │ │ │ │ ├── FileInputDemo.scala │ │ │ │ ├── IntroFormDemo.scala │ │ │ │ ├── MultiSelectDemo.scala │ │ │ │ ├── ProduceDemo.scala │ │ │ │ ├── RadioButtonsDemo.scala │ │ │ │ ├── RepeatDemo.scala │ │ │ │ ├── SelectDemo.scala │ │ │ │ ├── ShowIfDemo.scala │ │ │ │ ├── TextAreaDemo.scala │ │ │ │ ├── TextInputDemo.scala │ │ │ │ └── TimeDemo.scala │ │ │ └── rpc │ │ │ ├── RpcClientServerView.scala │ │ │ ├── RpcInterfacesView.scala │ │ │ ├── RpcIntroView.scala │ │ │ ├── RpcSerializationView.scala │ │ │ ├── RpcServerClientView.scala │ │ │ ├── RpcView.scala │ │ │ └── demos │ │ │ ├── ClientIdDemoComponent.scala │ │ │ ├── ExceptionsDemoComponent.scala │ │ │ ├── GenCodecsDemoComponent.scala │ │ │ ├── NotificationsDemoComponent.scala │ │ │ ├── PingPongCallDemoComponent.scala │ │ │ └── PingPongPushDemoComponent.scala │ │ └── test │ │ └── scala │ │ └── io │ │ └── udash │ │ └── web │ │ └── guide │ │ └── demos │ │ └── AutoDemoTest.scala ├── homepage │ └── .js │ │ └── src │ │ └── main │ │ ├── assets │ │ ├── assets.less │ │ ├── fonts │ │ │ └── roboto │ │ │ │ ├── Roboto-Black.ttf │ │ │ │ ├── Roboto-BlackItalic.ttf │ │ │ │ ├── Roboto-Bold.ttf │ │ │ │ ├── Roboto-BoldItalic.ttf │ │ │ │ ├── Roboto-Italic.ttf │ │ │ │ ├── Roboto-Light.ttf │ │ │ │ ├── Roboto-LightItalic.ttf │ │ │ │ ├── Roboto-Medium.ttf │ │ │ │ ├── Roboto-MediumItalic.ttf │ │ │ │ ├── Roboto-Regular.ttf │ │ │ │ ├── Roboto-Thin.ttf │ │ │ │ └── Roboto-ThinItalic.ttf │ │ ├── images │ │ │ ├── favicon.ico │ │ │ ├── features_compiled.png │ │ │ ├── features_shared.png │ │ │ ├── features_typesafe.png │ │ │ ├── intro_bg.jpg │ │ │ ├── intro_bird.png │ │ │ ├── laptop.png │ │ │ ├── share │ │ │ │ ├── share_facebook.png │ │ │ │ ├── share_google.png │ │ │ │ └── share_twitter.png │ │ │ ├── udash_logo.png │ │ │ ├── udash_logo_l.png │ │ │ └── udash_logo_m.png │ │ └── index.html │ │ └── scala │ │ └── io │ │ └── udash │ │ └── web │ │ └── homepage │ │ ├── RoutingRegistryDef.scala │ │ ├── StatesToViewFactoryDef.scala │ │ ├── components │ │ ├── Buttons.scala │ │ ├── Header.scala │ │ └── demo │ │ │ ├── CodeDemo.scala │ │ │ └── DemoComponent.scala │ │ ├── init.scala │ │ ├── states.scala │ │ └── views │ │ ├── ErrorView.scala │ │ ├── IndexView.scala │ │ └── RootView.scala ├── packager │ └── src │ │ └── main │ │ └── resources │ │ └── application.conf ├── selenium │ └── src │ │ └── test │ │ ├── resources │ │ └── application.conf │ │ └── scala │ │ └── io │ │ └── udash │ │ └── web │ │ ├── SeleniumTest.scala │ │ └── guide │ │ └── demos │ │ ├── ext │ │ ├── I18nDemosTest.scala │ │ └── JQueryDemosTest.scala │ │ ├── frontend │ │ ├── FrontendBindingsTest.scala │ │ ├── FrontendFormsTest.scala │ │ ├── FrontendIntroTest.scala │ │ └── FrontendRoutingTest.scala │ │ └── rpc │ │ ├── RpcBackendTest.scala │ │ ├── RpcFrontendTest.scala │ │ ├── RpcIntroTest.scala │ │ └── RpcSerializationTest.scala └── shared │ └── src │ └── main │ └── scala │ └── io │ └── udash │ └── web │ ├── commons │ └── styles │ │ ├── DefaultStyles.scala │ │ ├── GlobalStyles.scala │ │ ├── attributes │ │ └── Attributes.scala │ │ ├── components │ │ ├── CodeBlockStyles.scala │ │ ├── FooterStyles.scala │ │ ├── HeaderButtonsStyles.scala │ │ ├── HeaderNavStyles.scala │ │ └── MobileMenuStyles.scala │ │ └── utils │ │ ├── CommonStyleUtils.scala │ │ ├── MediaQueries.scala │ │ ├── StyleConstants.scala │ │ └── UdashFonts.scala │ ├── guide │ ├── GuideExceptions.scala │ ├── MainClientRPC.scala │ ├── MainServerRPC.scala │ ├── demos │ │ ├── DemosClientRPC.scala │ │ ├── DemosServerRPC.scala │ │ ├── activity │ │ │ ├── Call.scala │ │ │ └── CallServerRPC.scala │ │ ├── i18n │ │ │ └── Translations.scala │ │ ├── rest │ │ │ ├── MainServerREST.scala │ │ │ └── RestExampleClass.scala │ │ └── rpc │ │ │ ├── ClientIdServerRPC.scala │ │ │ ├── ExceptionsRPC.scala │ │ │ ├── GenCodecServerRPC.scala │ │ │ ├── NotificationsClientRPC.scala │ │ │ ├── NotificationsServerRPC.scala │ │ │ ├── PingClientRPC.scala │ │ │ └── PingServerRPC.scala │ ├── markdown │ │ ├── MarkdownPage.scala │ │ └── MarkdownPageRPC.scala │ └── styles │ │ ├── GuideDefaultStyles.scala │ │ ├── MarkdownStyles.scala │ │ ├── demo │ │ ├── ExampleKeyframes.scala │ │ ├── ExampleMixins.scala │ │ └── ExampleStyles.scala │ │ ├── partials │ │ ├── GuideStyles.scala │ │ ├── HeaderStyles.scala │ │ └── MenuStyles.scala │ │ └── utils │ │ ├── GuideStyleUtils.scala │ │ └── MediaQueries.scala │ └── homepage │ └── styles │ ├── HomepageDefaultStyles.scala │ └── partials │ ├── ButtonsStyle.scala │ ├── DemoStyles.scala │ ├── HeaderStyles.scala │ └── HomepageStyles.scala ├── i18n ├── .js │ └── src │ │ ├── main │ │ └── scala │ │ │ └── io │ │ │ └── udash │ │ │ └── i18n │ │ │ ├── FrontendTranslationProvider.scala │ │ │ ├── LocalTranslationProvider.scala │ │ │ ├── RemoteTranslationProvider.scala │ │ │ ├── Translations.scala │ │ │ ├── bindings │ │ │ ├── AttrTranslationModifier.scala │ │ │ ├── DynamicAttrTranslationBinding.scala │ │ │ ├── DynamicTranslationBinding.scala │ │ │ ├── TranslationModifier.scala │ │ │ └── package.scala │ │ │ └── package.scala │ │ └── test │ │ └── scala │ │ └── io │ │ └── udash │ │ └── i18n │ │ ├── BindingsTest.scala │ │ ├── LocalTranslationProviderTest.scala │ │ └── RemoteTranslationProviderTest.scala ├── .jvm │ └── src │ │ ├── main │ │ └── scala │ │ │ └── io │ │ │ └── udash │ │ │ └── i18n │ │ │ ├── ResourceBundlesTranslationTemplatesProvider.scala │ │ │ ├── TranslationRPCEndpoint.scala │ │ │ └── TranslationTemplatesProvider.scala │ │ └── test │ │ ├── resources │ │ ├── mixed_en.properties │ │ ├── mixed_pl.properties │ │ ├── test2_translations_en.properties │ │ ├── test2_translations_pl.properties │ │ ├── test_translations_en.properties │ │ └── test_translations_pl.properties │ │ └── scala │ │ └── io │ │ └── udash │ │ └── i18n │ │ ├── ResourceBundlesTranslationTemplatesProviderTest.scala │ │ └── TranslationRPCEndpointTest.scala └── src │ ├── main │ └── scala │ │ └── io │ │ └── udash │ │ └── i18n │ │ ├── RemoteTranslationRPC.scala │ │ ├── TranslationKey.scala │ │ ├── TranslationProvider.scala │ │ └── types.scala │ └── test │ └── scala │ └── io │ └── udash │ └── i18n │ ├── TranslationKeyTest.scala │ └── Utils.scala ├── macros └── src │ └── main │ └── scala │ └── io │ └── udash │ ├── css │ └── macros │ │ └── StyleMacros.scala │ └── macros │ ├── AllValuesMacro.scala │ ├── ComponentIdMacro.scala │ ├── PropertyMacros.scala │ ├── RestMacros.scala │ └── TestMacros.scala ├── package-lock.json ├── package.json ├── project ├── Dependencies.scala ├── build.properties └── plugins.sbt ├── rest ├── .js │ └── src │ │ └── main │ │ └── scala │ │ └── io │ │ └── udash │ │ └── rest │ │ └── DefaultSttpBackend.scala ├── .jvm │ └── src │ │ ├── main │ │ └── scala │ │ │ └── io │ │ │ └── udash │ │ │ └── rest │ │ │ ├── DefaultSttpBackend.scala │ │ │ ├── RestServlet.scala │ │ │ └── openapi │ │ │ └── OpenApiServlet.scala │ │ └── test │ │ ├── resources │ │ └── RestTestApi.json │ │ └── scala │ │ └── io │ │ └── udash │ │ └── rest │ │ ├── EndpointsIntegrationTest.scala │ │ ├── ServerTestRESTInterface.scala │ │ ├── ServletBasedRestApiTest.scala │ │ ├── SomeApi.scala │ │ ├── SttpRestCallTest.scala │ │ ├── UsesHttpServer.scala │ │ ├── examples │ │ ├── ClientMain.scala │ │ ├── GenericApi.scala │ │ ├── ServerMain.scala │ │ └── UserApi.scala │ │ └── openapi │ │ └── OpenApiGenerationTest.scala ├── jetty │ └── src │ │ ├── main │ │ └── scala │ │ │ └── io │ │ │ └── udash │ │ │ └── rest │ │ │ └── jetty │ │ │ └── JettyRestClient.scala │ │ └── test │ │ └── scala │ │ └── io │ │ └── udash │ │ └── rest │ │ └── jetty │ │ └── JettyRestCallTest.scala └── src │ ├── main │ └── scala │ │ └── io │ │ └── udash │ │ └── rest │ │ ├── DefaultRestApiCompanion.scala │ │ ├── RestDataCompanion.scala │ │ ├── RestException.scala │ │ ├── SttpRestClient.scala │ │ ├── annotations.scala │ │ ├── companions.scala │ │ ├── implicits.scala │ │ ├── openapi │ │ ├── GeneratedSchemaName.scala │ │ ├── OpenApi.scala │ │ ├── OpenApiMetadata.scala │ │ ├── RestSchema.scala │ │ ├── RestStructure.scala │ │ ├── WhenAbsentInfo.scala │ │ └── adjusters │ │ │ └── Adjuster.scala │ │ ├── raw │ │ ├── AbstractMapping.scala │ │ ├── HttpBody.scala │ │ ├── JsonValue.scala │ │ ├── PlainValue.scala │ │ ├── RawRest.scala │ │ ├── RestMetadata.scala │ │ ├── RestRequest.scala │ │ └── RestResponse.scala │ │ └── util │ │ └── WithHeaders.scala │ └── test │ └── scala │ └── io │ └── udash │ └── rest │ ├── CirceRestApiTest.scala │ ├── CompilationErrorsTest.scala │ ├── PolyRestApi.scala │ ├── PolyRestDataCompanion.scala │ ├── RestApiTest.scala │ ├── RestTestApi.scala │ ├── RestValidationTest.scala │ ├── SomeServerApiImpl.scala │ ├── TestRESTRecord.scala │ ├── WriteOpenApi.scala │ ├── openapi │ ├── RestSchemaTest.scala │ ├── customWa.scala │ └── openapiDependencies.scala │ └── raw │ ├── MappingTest.scala │ ├── RawRestTest.scala │ └── ServerImplApiTest.scala ├── rpc ├── .js │ └── src │ │ ├── main │ │ └── scala │ │ │ └── io │ │ │ └── udash │ │ │ ├── rpc │ │ │ ├── ConnectionStatus.scala │ │ │ ├── DefaultServerRPC.scala │ │ │ ├── RPCFrontend.scala │ │ │ ├── internals │ │ │ │ ├── ExposesClientRPC.scala │ │ │ │ ├── ServerConnector.scala │ │ │ │ └── UsesServerRPC.scala │ │ │ ├── package.scala │ │ │ └── serialization │ │ │ │ └── DefaultExceptionCodecRegistry.scala │ │ │ └── wrappers │ │ │ └── atmosphere │ │ │ └── Atmosphere.scala │ │ └── test │ │ └── scala │ │ └── io │ │ └── udash │ │ └── rpc │ │ ├── NativeRpcMessagesTest.scala │ │ ├── ServerRPCTest.scala │ │ └── internals │ │ └── ExposesClientRPCTest.scala ├── .jvm │ └── src │ │ ├── main │ │ └── scala │ │ │ └── io │ │ │ └── udash │ │ │ └── rpc │ │ │ ├── AtmosphereService.scala │ │ │ ├── DefaultAtmosphereServiceConfig.scala │ │ │ ├── DefaultClientRPC.scala │ │ │ ├── ExposesServerRPC.scala │ │ │ ├── RPCBackend.scala │ │ │ ├── RpcServlet.scala │ │ │ ├── internals │ │ │ ├── BroadcastManager.scala │ │ │ └── UsesClientRPC.scala │ │ │ ├── package.scala │ │ │ ├── serialization │ │ │ └── DefaultExceptionCodecRegistry.scala │ │ │ └── utils │ │ │ ├── CallLogging.scala │ │ │ ├── DefaultAtmosphereFramework.scala │ │ │ ├── FileDownloadServlet.scala │ │ │ └── FileUploadServlet.scala │ │ └── test │ │ └── scala │ │ └── io │ │ └── udash │ │ ├── rpc │ │ ├── ClientRPCTest.scala │ │ ├── DefaultAtmosphereServiceConfigTest.scala │ │ ├── DefaultExceptionCodecRegistryTest.scala │ │ ├── JVMSerializationIntegrationTest.scala │ │ ├── RpcMessagesTest.scala │ │ └── internals │ │ │ ├── AtmosphereServiceTest.scala │ │ │ └── ExposesServerRPCTest.scala │ │ └── testing │ │ └── UdashRpcBackendTest.scala └── src │ ├── main │ └── scala │ │ └── io │ │ └── udash │ │ └── rpc │ │ ├── ExposesLocalRPC.scala │ │ ├── UsesRemoteRPC.scala │ │ ├── companions.scala │ │ ├── rawrpc.scala │ │ ├── serialization │ │ ├── DefaultUdashSerialization.scala │ │ ├── EscapeUtils.scala │ │ └── ExceptionCodecRegistry.scala │ │ └── utils │ │ └── Logged.scala │ └── test │ └── scala │ └── io │ └── udash │ └── rpc │ ├── RpcMessagesTest.scala │ ├── SerializationIntegrationTestBase.scala │ ├── TestRPC.scala │ ├── Utils.scala │ └── types.scala ├── scripts └── authors.scala └── utils ├── .js └── src │ ├── main │ └── scala │ │ └── io │ │ └── udash │ │ ├── logging │ │ └── UdashLogger.scala │ │ └── utils │ │ ├── CrossCollections.scala │ │ └── URLEncoder.scala │ └── test │ └── scala │ └── io │ └── udash │ └── testing │ ├── AsyncUdashSharedTest.scala │ └── UdashFrontendTest.scala ├── .jvm └── src │ ├── main │ └── scala │ │ └── io │ │ └── udash │ │ ├── logging │ │ └── UdashLogger.scala │ │ └── utils │ │ ├── CrossCollections.scala │ │ └── URLEncoder.scala │ └── test │ └── scala │ └── io │ └── udash │ └── testing │ └── AsyncUdashSharedTest.scala └── src ├── main └── scala │ └── io │ └── udash │ ├── logging │ ├── CrossLogger.scala │ └── CrossLogging.scala │ └── utils │ ├── CallbacksHandler.scala │ ├── FilteringUtils.scala │ └── Registration.scala └── test └── scala └── io └── udash ├── testing ├── AsyncUdashSharedTestBase.scala ├── CompilationErrorAssertions.scala └── UdashSharedTest.scala └── utils ├── CallbacksHandlerTest.scala ├── FilteringUtilsTest.scala └── URLEncoderTest.scala /.github/workflows/docker.yml: -------------------------------------------------------------------------------- 1 | name: Create and publish a Docker image 2 | 3 | on: 4 | push: 5 | tags: [ "v[0-9]+*" ] 6 | 7 | env: 8 | REGISTRY: ghcr.io 9 | 10 | jobs: 11 | build-and-push-image: 12 | runs-on: ubuntu-22.04 # https://github.com/actions/runner-images/blob/main/images/ubuntu/Ubuntu2204-Readme.md 13 | # only run on tag push 14 | if: github.event_name == 'push' && (startsWith(github.ref, 'refs/tags/v')) 15 | permissions: 16 | contents: read 17 | packages: write 18 | steps: 19 | - uses: actions/checkout@v4 20 | - uses: docker/login-action@v3 21 | with: 22 | registry: ${{ env.REGISTRY }} 23 | username: udashframework 24 | password: ${{ secrets.GITHUB_TOKEN }} 25 | - uses: actions/setup-java@v4 26 | with: 27 | distribution: temurin 28 | java-version: 17 29 | cache: sbt 30 | - name: Get version 31 | id: get_tag_name 32 | run: echo "VERSION=${GITHUB_REF#refs/tags/v}" >> $GITHUB_OUTPUT 33 | - name: Build and push Docker image 34 | run: > 35 | sbt 36 | 'set ThisBuild/version := "${{ steps.get_tag_name.outputs.VERSION }}"' 37 | 'project guide-packager' 38 | 'set dockerRepository := Some("${{ env.REGISTRY }}")' 39 | 'set dockerUsername := Some("udashframework")' 40 | docker:publish 41 | -------------------------------------------------------------------------------- /.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.scala-steward.conf: -------------------------------------------------------------------------------- 1 | updates.ignore = [ 2 | {groupId = "com.vladsch.flexmark", artifactId = "flexmark-all"}, 3 | ] 4 | updates.pin = [ 5 | {groupId = "ch.qos.logback", version = "1.3."}, 6 | ] 7 | pullRequests.grouping = [ 8 | { 9 | name = "jetty", 10 | title = "Update Jetty dependencies", 11 | filter = [{ group = "org.eclipse.jetty" }, { group = "org.eclipse.jetty*" }] 12 | } 13 | ] 14 | -------------------------------------------------------------------------------- /auth/.js/src/main/scala/io/udash/auth/AuthApplication.scala: -------------------------------------------------------------------------------- 1 | package io.udash.auth 2 | 3 | import io.udash._ 4 | 5 | object AuthApplication { 6 | implicit class ApplicationAuthExt[HierarchyRoot >: Null <: GState[HierarchyRoot]](val application: Application[HierarchyRoot]) extends AnyVal { 7 | /** 8 | * Adds the default listener of authorization failure in routing (redirects to provided state). 9 | * 10 | * @param authFailedRedirectState application will redirect user to this state after auth fail 11 | */ 12 | def withDefaultRoutingFailureListener(authFailedRedirectState: HierarchyRoot): Application[HierarchyRoot] = { 13 | application.onRoutingFailure { 14 | case _: UnauthorizedException | _: UnauthenticatedException if application.currentState != authFailedRedirectState => 15 | application.goTo(authFailedRedirectState) 16 | } 17 | application 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /auth/.js/src/main/scala/io/udash/auth/AuthPresenter.scala: -------------------------------------------------------------------------------- 1 | package io.udash.auth 2 | 3 | import io.udash._ 4 | 5 | /** 6 | * Presenter which check user access in `handleState` method. 7 | * 8 | * @param permission PermissionCombinator verified against provided `userCtx`. 9 | * @param requireAuthenticated If `true`, the presenter requires `userCtx` to don't be Unauthenticated subclass. 10 | */ 11 | abstract class AuthPresenter[S <: State](permission: PermissionCombinator, requireAuthenticated: Boolean = false) 12 | (implicit userCtx: UserCtx) 13 | extends Presenter[S] with AuthRequires { 14 | 15 | override def handleState(state: S): Unit = { 16 | require(permission, requireAuthenticated) 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /auth/.js/src/test/scala/io/udash/auth/AuthFrontendTestUtils.scala: -------------------------------------------------------------------------------- 1 | package io.udash.auth 2 | 3 | import io.udash.State 4 | 5 | trait AuthFrontendTestUtils { 6 | sealed trait TestStates extends State { 7 | override type HierarchyRoot = TestStates 8 | override def parentState: Option[HierarchyRoot] = None 9 | } 10 | case object SomeState extends TestStates 11 | case object SecondState extends TestStates 12 | case object ThirdState extends TestStates 13 | } 14 | -------------------------------------------------------------------------------- /auth/.js/src/test/scala/io/udash/auth/AuthPresenterTest.scala: -------------------------------------------------------------------------------- 1 | package io.udash.auth 2 | 3 | import io.udash.testing.UdashFrontendTest 4 | 5 | class AuthPresenterTest extends UdashFrontendTest with AuthTestUtils with AuthFrontendTestUtils { 6 | import PermissionCombinator.AllowAll 7 | 8 | "AuthPresenter" should { 9 | "throw an exception if user is not authenticated" in { 10 | class SomePresenter extends AuthPresenter[SomeState.type](AllowAll, requireAuthenticated = false)(UnauthenticatedUser) 11 | 12 | val p = new SomePresenter 13 | p.handleState(SomeState) 14 | 15 | class SomePresenter2 extends AuthPresenter[SomeState.type](AllowAll, requireAuthenticated = true)(UnauthenticatedUser) 16 | 17 | val p2 = new SomePresenter2 18 | intercept[UnauthenticatedException] { 19 | p2.handleState(SomeState) 20 | } 21 | } 22 | 23 | "throw an exception if user is not authorized" in { 24 | class SomePresenter extends AuthPresenter[SomeState.type](P1.and(P2.or(P3)))(User(Set(P1, P2))) 25 | 26 | val p = new SomePresenter 27 | p.handleState(SomeState) 28 | 29 | class SomePresenter2 extends AuthPresenter[SomeState.type](P1.and(P2.or(P3)))(User(Set(P1))) 30 | 31 | val p2 = new SomePresenter2 32 | intercept[UnauthorizedException] { 33 | p2.handleState(SomeState) 34 | } 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /auth/src/main/scala/io/udash/auth/AuthRequires.scala: -------------------------------------------------------------------------------- 1 | package io.udash.auth 2 | 3 | trait AuthRequires { 4 | /** Checks if user context has required permissions. */ 5 | def require(permission: PermissionCombinator, requireAuthenticated: Boolean = false)(implicit userCtx: UserCtx): Unit = { 6 | if (requireAuthenticated && !userCtx.isAuthenticated) throw new UnauthenticatedException() 7 | if (!permission.check(userCtx)) throw new UnauthorizedException() 8 | } 9 | 10 | /** Checks if user is authenticated. */ 11 | def requireAuthenticated()(implicit userCtx: UserCtx): Unit = 12 | require(PermissionCombinator.AllowAll, requireAuthenticated = true) 13 | } 14 | 15 | object AuthRequires extends AuthRequires 16 | -------------------------------------------------------------------------------- /auth/src/main/scala/io/udash/auth/DefaultAuthExceptionCodecRegistry.scala: -------------------------------------------------------------------------------- 1 | package io.udash.auth 2 | 3 | import com.avsystem.commons.serialization.GenCodec 4 | import io.udash.rpc.serialization.DefaultExceptionCodecRegistry 5 | 6 | class DefaultAuthExceptionCodecRegistry extends DefaultExceptionCodecRegistry { 7 | register(GenCodec.create(_ => new UnauthenticatedException(), exceptionWriter)) 8 | register(GenCodec.create(_ => new UnauthorizedException(), exceptionWriter)) 9 | } 10 | -------------------------------------------------------------------------------- /auth/src/main/scala/io/udash/auth/Permission.scala: -------------------------------------------------------------------------------- 1 | package io.udash.auth 2 | 3 | /** 4 | * Base class for permissions used with PermissionControl. Permissions are compared by ID. 5 | */ 6 | trait Permission { 7 | def id: PermissionId 8 | 9 | override def equals(other: Any): Boolean = other match { 10 | case that: Permission => id == that.id 11 | case _ => false 12 | } 13 | 14 | override def hashCode(): Int = 15 | id.hashCode() 16 | 17 | override def toString = 18 | s"Permission(${id.value}" 19 | } 20 | 21 | object Permission { 22 | /** Single permission as a combinator resolved implicitly. */ 23 | implicit class Single(private val permission: Permission) extends AnyVal with PermissionCombinator { 24 | override def check(ctx: UserCtx): Boolean = 25 | ctx.has(permission) 26 | 27 | override def toString: String = 28 | permission.toString 29 | } 30 | } -------------------------------------------------------------------------------- /auth/src/main/scala/io/udash/auth/PermissionId.scala: -------------------------------------------------------------------------------- 1 | package io.udash.auth 2 | 3 | import com.avsystem.commons.serialization.{HasGenCodec, transparent} 4 | 5 | @transparent 6 | case class PermissionId(value: String) extends AnyVal 7 | object PermissionId extends HasGenCodec[PermissionId] 8 | -------------------------------------------------------------------------------- /auth/src/main/scala/io/udash/auth/UserCtx.scala: -------------------------------------------------------------------------------- 1 | package io.udash.auth 2 | 3 | trait UserCtx { 4 | def has(permission: Permission): Boolean 5 | def isAuthenticated: Boolean 6 | } 7 | 8 | object UserCtx { 9 | trait Unauthenticated extends UserCtx { 10 | override def has(permission: Permission): Boolean = false 11 | override def isAuthenticated: Boolean = false 12 | } 13 | } -------------------------------------------------------------------------------- /auth/src/main/scala/io/udash/auth/exceptions.scala: -------------------------------------------------------------------------------- 1 | package io.udash.auth 2 | 3 | import com.avsystem.commons.serialization.HasGenCodec 4 | 5 | case class UnauthenticatedException() extends RuntimeException(s"User has to be authenticated to access this content.") 6 | object UnauthenticatedException extends HasGenCodec[UnauthenticatedException] 7 | 8 | case class UnauthorizedException() extends RuntimeException(s"Provided user context does not have access to this content.") 9 | object UnauthorizedException extends HasGenCodec[UnauthorizedException] 10 | -------------------------------------------------------------------------------- /auth/src/test/scala/io/udash/auth/AuthRequiresTest.scala: -------------------------------------------------------------------------------- 1 | package io.udash.auth 2 | 3 | import io.udash.testing.UdashSharedTest 4 | 5 | class AuthRequiresTest extends UdashSharedTest with AuthTestUtils { 6 | import PermissionCombinator.AllowAll 7 | 8 | "AuthRequires utils" should { 9 | "check user's permissions" in { 10 | implicit val user: UserCtx = User(Set(P1, P2)) 11 | 12 | AuthRequires.require(P1.and(P2)) 13 | intercept[UnauthorizedException] { AuthRequires.require(P1.and(P3)) } 14 | intercept[UnauthorizedException] { AuthRequires.require(P2.and(P3)) } 15 | AuthRequires.require(P1.or(P3)) 16 | AuthRequires.require(AllowAll) 17 | } 18 | 19 | "check is user is authenticated" in { 20 | implicit val user: UserCtx = UnauthenticatedUser 21 | 22 | AuthRequires.require(AllowAll) 23 | intercept[UnauthenticatedException] { AuthRequires.require(AllowAll, requireAuthenticated = true) } 24 | intercept[UnauthenticatedException] { AuthRequires.requireAuthenticated() } 25 | intercept[UnauthenticatedException] { AuthRequires.require(P2.and(P3), requireAuthenticated = true) } 26 | intercept[UnauthorizedException] { AuthRequires.require(P2.and(P3)) } 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /auth/src/test/scala/io/udash/auth/AuthTestUtils.scala: -------------------------------------------------------------------------------- 1 | package io.udash.auth 2 | 3 | trait AuthTestUtils { 4 | 5 | class Perm(override val id: PermissionId) extends Permission 6 | 7 | case object P1 extends Perm(PermissionId("1")) 8 | case object P2 extends Perm(PermissionId("2")) 9 | case object P3 extends Perm(PermissionId("3")) 10 | 11 | case class User(perms: Set[Permission]) extends UserCtx { 12 | override def has(permission: Permission): Boolean = 13 | perms.contains(permission) 14 | 15 | override def isAuthenticated: Boolean = true 16 | } 17 | 18 | case class UnauthenticatedUserWithPerms(perms: Set[Permission]) extends UserCtx { 19 | override def has(permission: Permission): Boolean = 20 | perms.contains(permission) 21 | 22 | override def isAuthenticated: Boolean = false 23 | } 24 | 25 | case object UnauthenticatedUser extends UserCtx.Unauthenticated 26 | 27 | } 28 | -------------------------------------------------------------------------------- /benchmarks/.js/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Udash Benchmarks 5 | 6 | 7 |
Loading...
8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /benchmarks/.js/src/main/scala/io/udash/benchmarks/Main.scala: -------------------------------------------------------------------------------- 1 | package io.udash.benchmarks 2 | 3 | import io.udash.benchmarks.css.CssStylesApply 4 | import io.udash.benchmarks.i18n.StaticTranslationBinding 5 | import io.udash.benchmarks.properties._ 6 | import japgolly.scalajs.benchmark.engine.EngineOptions 7 | import japgolly.scalajs.benchmark.gui.BenchmarkGUI 8 | import org.scalajs.dom.document 9 | 10 | object Main { 11 | def main(args: Array[String]): Unit = { 12 | BenchmarkGUI.renderMenu(document.getElementById("body"), engineOptions = EngineOptions.default.copy(iterations = 3))( 13 | SinglePropertyListeners.suite, 14 | ModelPropertyListeners.suite, 15 | ModelPropertyWithSeqListeners.suite, 16 | TransformedSeqPropertyListeners.suite, 17 | FilteredSeqPropertyListeners.suite, 18 | ReversedSeqPropertyListeners.suite, 19 | ZippedSeqPropertyListeners.suite, 20 | 21 | StaticTranslationBinding.suite, 22 | 23 | CssStylesApply.suite, 24 | 25 | PropertyParameters.suite 26 | ) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /benchmarks/.js/src/main/scala/io/udash/benchmarks/css/CssStylesApply.scala: -------------------------------------------------------------------------------- 1 | package io.udash.benchmarks.css 2 | 3 | import io.udash.css.{CssStyleName, CssView} 4 | import japgolly.scalajs.benchmark._ 5 | import japgolly.scalajs.benchmark.gui._ 6 | import scalatags.JsDom.all._ 7 | 8 | object CssStylesApply extends CssView { 9 | 10 | val styles = Benchmark("three styles") { 11 | div( 12 | CssStyleName("style-1"), 13 | CssStyleName("style-2"), 14 | CssStyleName("style-3"), 15 | CssStyleName("style-4"), 16 | CssStyleName("style-5"), 17 | CssStyleName("style-6"), 18 | Seq(CssStyleName("style-12"), CssStyleName("style-13"), CssStyleName("style-14")), 19 | Seq(CssStyleName("style-22"), CssStyleName("style-23"), CssStyleName("style-24")), 20 | Seq(CssStyleName("style-32"), CssStyleName("style-33"), CssStyleName("style-34")), 21 | Seq(CssStyleName("style-42"), CssStyleName("style-43"), CssStyleName("style-44")), 22 | Seq(CssStyleName("style-52"), CssStyleName("style-53"), CssStyleName("style-54")) 23 | ).render 24 | } 25 | 26 | val suite = GuiSuite( 27 | Suite("CssStyle apply")( 28 | styles 29 | ) 30 | ) 31 | } 32 | -------------------------------------------------------------------------------- /benchmarks/.js/src/main/scala/io/udash/benchmarks/i18n/StaticTranslationBinding.scala: -------------------------------------------------------------------------------- 1 | package io.udash.benchmarks.i18n 2 | 3 | import io.udash.i18n._ 4 | import japgolly.scalajs.benchmark._ 5 | import japgolly.scalajs.benchmark.gui._ 6 | 7 | import scala.concurrent.Future 8 | import scalatags.JsDom.all._ 9 | 10 | object StaticTranslationBinding { 11 | 12 | val instantSuccessTranslations = Benchmark("instant success translation") { 13 | div( 14 | (1 to 50).map { _ => 15 | span( 16 | translatedAttr(Future.successful(Translated("Test")), "data-test"), 17 | translated(Future.successful(Translated("Test"))) 18 | ).render 19 | } 20 | ).render 21 | } 22 | 23 | val futureTranslations = Benchmark("future translation") { 24 | import scala.scalajs.concurrent.JSExecutionContext.Implicits.queue 25 | div( 26 | (1 to 50).map { _ => 27 | span( 28 | translatedAttr(Future(Translated("Test")), "data-test"), 29 | translated(Future(Translated("Test"))) 30 | ).render 31 | } 32 | ).render 33 | } 34 | 35 | val suite = GuiSuite( 36 | Suite("StaticTranslations")( 37 | instantSuccessTranslations, 38 | futureTranslations 39 | ) 40 | ) 41 | } 42 | -------------------------------------------------------------------------------- /benchmarks/.js/src/main/scala/io/udash/benchmarks/properties/ReversedSeqPropertyListeners.scala: -------------------------------------------------------------------------------- 1 | package io.udash.benchmarks.properties 2 | 3 | import io.udash._ 4 | import japgolly.scalajs.benchmark._ 5 | import japgolly.scalajs.benchmark.gui._ 6 | 7 | object ReversedSeqPropertyListeners extends BenchmarkUtils { 8 | private val seqSize = 50 9 | private val properties: Seq[(String, () => (SeqProperty[Int], ReadableSeqProperty[Int]))] = Seq( 10 | ("direct property", () => { 11 | val p = SeqProperty(Seq.tabulate(seqSize)(identity)) 12 | (p, p) 13 | }), 14 | ("reversed elements", () => { 15 | val p = SeqProperty(Seq.tabulate(seqSize)(identity)) 16 | val t = p.reversed() 17 | (p, t) 18 | }) 19 | ) 20 | 21 | private val benchmarks = generateGetSetListenBenchmarks[SeqProperty[Int], ReadableSeqProperty[Int]](properties)( 22 | Seq(20), Seq(0.1, 1, 10), Seq(0, 1, 10, 100), 23 | Seq( 24 | ("whole Seq set", (p, i) => p.set(Seq.tabulate(seqSize)(_ + i)), _.get), 25 | ("replace part of Seq", replaceElements, _.get) 26 | ), 27 | Seq(("empty listener", _.listen(_ => ()))) 28 | ) 29 | 30 | val suite = GuiSuite( 31 | Suite("SeqProperty - reverse - set, get & listen")(benchmarks: _*) 32 | ) 33 | } 34 | -------------------------------------------------------------------------------- /benchmarks/.js/src/main/scala/io/udash/benchmarks/properties/ZippedSeqPropertyListeners.scala: -------------------------------------------------------------------------------- 1 | package io.udash.benchmarks.properties 2 | 3 | import io.udash._ 4 | import japgolly.scalajs.benchmark._ 5 | import japgolly.scalajs.benchmark.gui._ 6 | 7 | object ZippedSeqPropertyListeners extends BenchmarkUtils { 8 | private val seqSize = 50 9 | private val properties: Seq[(String, () => (SeqProperty[Int], ReadableSeqProperty[Int]))] = Seq( 10 | ("direct property", () => { 11 | val p = SeqProperty(Seq.tabulate(seqSize)(identity)) 12 | (p, p) 13 | }), 14 | ("zipped elements", () => { 15 | val p = SeqProperty(Seq.tabulate(seqSize)(identity)) 16 | val t = p.zip(p)(_ + _) 17 | (p, t) 18 | }) 19 | ) 20 | 21 | private val benchmarks = generateGetSetListenBenchmarks[SeqProperty[Int], ReadableSeqProperty[Int]](properties)( 22 | Seq(20), Seq(0.1, 1, 10), Seq(0, 1, 10, 100), 23 | Seq( 24 | ("whole Seq set", (p, i) => p.set(Seq.tabulate(seqSize)(_ + i)), _.get), 25 | ("replace part of Seq", replaceElements, _.get) 26 | ), 27 | Seq(("empty listener", _.listen(_ => ()))) 28 | ) 29 | 30 | val suite = GuiSuite( 31 | Suite("SeqProperty - zipped - set, get & listen")(benchmarks: _*) 32 | ) 33 | } 34 | -------------------------------------------------------------------------------- /bootstrap4/.js/src/main/scala/io/udash/bootstrap/UdashBootstrap.scala: -------------------------------------------------------------------------------- 1 | package io.udash.bootstrap 2 | 3 | import io.udash._ 4 | import org.scalajs.dom.Element 5 | import scalatags.JsDom.all._ 6 | 7 | object UdashBootstrap { 8 | final val False: ReadableProperty[Boolean] = false.toProperty 9 | final val True: ReadableProperty[Boolean] = true.toProperty 10 | final val ColorSecondary: ReadableProperty[BootstrapStyles.Color] = BootstrapStyles.Color.Secondary.toProperty 11 | private final val NoneProperty = scala.None.toProperty 12 | def None[A]: ReadableProperty[Option[A]] = NoneProperty 13 | 14 | /** Loads FontAwesome styles. */ 15 | def loadFontAwesome(): Element = 16 | link(rel := "stylesheet", href := "https://use.fontawesome.com/releases/v5.10.1/css/all.css").render 17 | 18 | /** Loads Bootstrap styles. */ 19 | def loadBootstrapStyles(): Element = 20 | link(rel := "stylesheet", href := "https://maxcdn.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css").render 21 | } 22 | -------------------------------------------------------------------------------- /bootstrap4/.js/src/main/scala/io/udash/bootstrap/alert/UdashAlert.scala: -------------------------------------------------------------------------------- 1 | package io.udash.bootstrap 2 | package alert 3 | 4 | import io.udash._ 5 | import io.udash.bindings.modifiers.Binding 6 | import io.udash.bootstrap.utils.BootstrapStyles 7 | import org.scalajs.dom.Element 8 | import scalatags.JsDom.all._ 9 | 10 | final class UdashAlert private[alert]( 11 | alertStyle: ReadableProperty[BootstrapStyles.Color], override val componentId: ComponentId 12 | )(content: Binding.NestedInterceptor => Modifier) extends UdashAlertBase(alertStyle, componentId) { 13 | override val render: Element = 14 | template(content(nestedInterceptor)).render 15 | } 16 | 17 | object UdashAlert extends UdashAlertBaseCompanion[UdashAlert] { 18 | protected def create(alertStyle: ReadableProperty[BootstrapStyles.Color], componentId: ComponentId)( 19 | content: Binding.NestedInterceptor => Modifier 20 | ): UdashAlert = { 21 | new UdashAlert(alertStyle, componentId)(content) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /bootstrap4/.js/src/main/scala/io/udash/bootstrap/package.scala: -------------------------------------------------------------------------------- 1 | package io.udash 2 | 3 | import io.udash.bootstrap.utils.BootstrapImplicits 4 | import io.udash.component.Components 5 | 6 | package object bootstrap extends BootstrapImplicits with Components { 7 | final val BootstrapStyles = io.udash.bootstrap.utils.BootstrapStyles 8 | final val BootstrapTags = io.udash.bootstrap.utils.BootstrapTags 9 | } 10 | -------------------------------------------------------------------------------- /bootstrap4/.js/src/main/scala/io/udash/bootstrap/utils/BootstrapImplicits.scala: -------------------------------------------------------------------------------- 1 | package io.udash.bootstrap.utils 2 | 3 | import io.udash.Url 4 | import io.udash.bindings.modifiers.Binding 5 | import org.scalajs.dom.Element 6 | import scalatags.JsDom 7 | import scalatags.JsDom.GenericAttr 8 | import scalatags.JsDom.all._ 9 | 10 | trait BootstrapImplicits { 11 | implicit val urlAttrValue: AttrValue[Url] = (t: Element, a: JsDom.Attr, v: Url) => new GenericAttr[String].apply(t, a, v.value) 12 | 13 | implicit def withoutNested(modifier: Modifier): Binding.NestedInterceptor => Modifier = _ => modifier 14 | implicit def stringWithoutNested(modifier: String): Binding.NestedInterceptor => Modifier = _ => modifier 15 | } 16 | 17 | object BootstrapImplicits extends BootstrapImplicits -------------------------------------------------------------------------------- /bootstrap4/.js/src/main/scala/io/udash/bootstrap/utils/BootstrapTags.scala: -------------------------------------------------------------------------------- 1 | package io.udash.bootstrap.utils 2 | 3 | object BootstrapTags { 4 | import scalatags.JsDom.all._ 5 | 6 | final val dataBackdrop = data("backdrop") 7 | final val dataBind = data("bind") 8 | final val dataContent = data("content") 9 | final val dataDismiss = data("dismiss") 10 | final val dataKeyboard = data("keyboard") 11 | final val dataLabel = data("label") 12 | final val dataParent = data("parent") 13 | final val dataOriginalTitle = data("original-title") 14 | final val dataRide = data("ride") 15 | final val dataShow = data("show") 16 | final val dataSlide = data("slide") 17 | final val dataSlideTo = data("slide-to") 18 | final val dataTarget = data("target") 19 | final val dataToggle = data("toggle") 20 | } 21 | -------------------------------------------------------------------------------- /bootstrap4/.js/src/main/scala/io/udash/bootstrap/utils/UdashBootstrapComponent.scala: -------------------------------------------------------------------------------- 1 | package io.udash.bootstrap.utils 2 | 3 | import io.udash.bindings.modifiers.Binding 4 | import io.udash.component.Component 5 | import io.udash.css.CssView 6 | import io.udash.wrappers.jquery.* 7 | import org.scalajs.dom.Element 8 | 9 | /** Base trait for Bootstrap components. */ 10 | trait UdashBootstrapComponent extends Component with CssView { 11 | override val render: Element 12 | 13 | protected class JQueryOnBinding(selector: JQuery, event: EventName, callback: JQueryCallback) extends Binding { 14 | selector.on(event, callback) 15 | 16 | override def kill(): Unit = { 17 | super.kill() 18 | selector.off(event, callback) 19 | } 20 | 21 | override def applyTo(t: Element): Unit = () 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /bootstrap4/.js/src/test/scala/io/udash/bootstrap/jumbotron/UdashJumbotronTest.scala: -------------------------------------------------------------------------------- 1 | package io.udash.bootstrap.jumbotron 2 | 3 | import io.udash._ 4 | import io.udash.bootstrap._ 5 | import io.udash.bootstrap.utils.BootstrapStyles 6 | import io.udash.testing.UdashCoreFrontendTest 7 | import scalatags.JsDom.all._ 8 | 9 | class UdashJumbotronTest extends UdashCoreFrontendTest { 10 | 11 | "UdashJumbotron component" should { 12 | "clean up property listeners" in { 13 | val fluid = Property[Boolean](false) 14 | val jumbo = UdashJumbotron(fluid)( 15 | h4("Content") 16 | ) 17 | val el = jumbo.render 18 | el.childNodes.length should be(1) 19 | el.textContent should be("Content") 20 | 21 | el.classList shouldNot contain(BootstrapStyles.Jumbotron.fluid.className) 22 | 23 | fluid.set(true) 24 | el.classList should contain(BootstrapStyles.Jumbotron.fluid.className) 25 | 26 | jumbo.kill() 27 | fluid.listenersCount() should be(0) 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /bootstrap4/.js/src/test/scala/io/udash/bootstrap/tooltip/PopoverTest.scala: -------------------------------------------------------------------------------- 1 | package io.udash.bootstrap.tooltip 2 | 3 | class PopoverTest extends TooltipTestUtils { 4 | "Popover" should tooltipTest(UdashPopover, expectContent = true) 5 | } 6 | -------------------------------------------------------------------------------- /bootstrap4/.js/src/test/scala/io/udash/bootstrap/tooltip/TooltipTest.scala: -------------------------------------------------------------------------------- 1 | package io.udash.bootstrap.tooltip 2 | 3 | class TooltipTest extends TooltipTestUtils { 4 | "Tooltip" should tooltipTest(UdashTooltip, expectContent = false) 5 | } 6 | -------------------------------------------------------------------------------- /core/.js/src/main/scala/io/udash/bindings/inputs/Checkbox.scala: -------------------------------------------------------------------------------- 1 | package io.udash.bindings.inputs 2 | 3 | import io.udash._ 4 | import org.scalajs.dom.Event 5 | import org.scalajs.dom.html.{Input => JSInput} 6 | import scalatags.JsDom.all._ 7 | 8 | /** 9 | * Plain checkbox bidirectionally bound to Property. 10 | * 11 | * For SeqProperty take a look at [[io.udash.bindings.inputs.CheckButtons]] 12 | */ 13 | object Checkbox { 14 | /** 15 | * @param selected Property to bind. 16 | * @param inputModifiers Additional Modifiers, don't use modifiers on type, checked and onchange attributes. 17 | * @return HTML input (checkbox) tag with bound Property and applied modifiers. 18 | */ 19 | def apply(selected: Property[Boolean])(inputModifiers: Modifier*): InputBinding[JSInput] = { 20 | new InputBinding[JSInput] { 21 | private val in = input(inputModifiers, tpe := "checkbox").render 22 | 23 | propertyListeners += selected.listen(in.checked = _, initUpdate = true) 24 | in.onchange = (_: Event) => selected.set(in.checked) 25 | 26 | override def render: JSInput = in 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /core/.js/src/main/scala/io/udash/bindings/inputs/InputBinding.scala: -------------------------------------------------------------------------------- 1 | package io.udash.bindings.inputs 2 | 3 | import io.udash.bindings.modifiers.Binding 4 | import org.scalajs.dom.Element 5 | 6 | trait InputBinding[RenderType <: Element] extends Binding { 7 | def render: RenderType 8 | 9 | override def applyTo(t: Element): Unit = { 10 | t.appendChild(render) 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /core/.js/src/main/scala/io/udash/bindings/inputs/SelectBinding.scala: -------------------------------------------------------------------------------- 1 | package io.udash.bindings.inputs 2 | 3 | import io.udash._ 4 | import org.scalajs.dom.Event 5 | import org.scalajs.dom.html.Select 6 | import scalatags.JsDom.all._ 7 | 8 | private[inputs] class SelectBinding[T]( 9 | options: ReadableSeqProperty[T], label: T => Modifier, labelNoValue: Option[Modifier], selectModifiers: Modifier* 10 | )( 11 | checkedIf: T => ReadableProperty[Boolean], 12 | refreshSelection: Seq[T] => Unit, 13 | onChange: Select => Event => Unit 14 | ) extends InputBinding[Select] { 15 | private val selector = select(selectModifiers)( 16 | produce(options) { opts => 17 | kill() 18 | refreshSelection(opts) 19 | 20 | val empty = labelNoValue.map { l => 21 | option(l, value := "").render 22 | } 23 | 24 | { 25 | empty.iterator ++ opts.iterator.zipWithIndex.map { case (opt, idx) => 26 | val el = option(value := idx.toString, label(opt)).render 27 | 28 | val selected = checkedIf(opt) 29 | propertyListeners += selected.listen(el.selected = _, initUpdate = true) 30 | el 31 | } 32 | }.toSeq 33 | } 34 | ).render 35 | 36 | selector.onchange = onChange(selector) 37 | 38 | override def render: Select = selector 39 | } -------------------------------------------------------------------------------- /core/.js/src/main/scala/io/udash/bindings/modifiers/EmptyModifier.scala: -------------------------------------------------------------------------------- 1 | package io.udash.bindings.modifiers 2 | 3 | import scalatags.generic.Modifier 4 | 5 | private[udash] final class EmptyModifier[T] extends Modifier[T] { 6 | override def applyTo(t: T): Unit = () 7 | } -------------------------------------------------------------------------------- /core/.js/src/main/scala/io/udash/bindings/modifiers/PropertyModifier.scala: -------------------------------------------------------------------------------- 1 | package io.udash.bindings.modifiers 2 | 3 | import io.udash.properties.single.ReadableProperty 4 | import io.udash.utils.Registration 5 | import org.scalajs.dom.Node 6 | 7 | private[bindings] class PropertyModifier[T]( 8 | override val property: ReadableProperty[T], 9 | override val builder: (T, Binding.NestedInterceptor) => Seq[Node], 10 | override val checkNull: Boolean, 11 | override val customElementsReplace: DOMManipulator.ReplaceMethod 12 | ) extends ValueModifier[T] { 13 | 14 | def this(property: ReadableProperty[T], builder: T => Seq[Node], checkNull: Boolean, customElementsReplace: DOMManipulator.ReplaceMethod) = { 15 | this(property, (data: T, _: Binding.NestedInterceptor) => builder(data), checkNull, customElementsReplace) 16 | } 17 | 18 | def listen(callback: T => Unit): Registration = 19 | property.listen(callback) 20 | 21 | } 22 | -------------------------------------------------------------------------------- /core/.js/src/main/scala/io/udash/bindings/modifiers/SeqAsValueModifier.scala: -------------------------------------------------------------------------------- 1 | package io.udash.bindings.modifiers 2 | 3 | import com.avsystem.commons._ 4 | import io.udash.bindings.modifiers.Binding.NestedInterceptor 5 | import io.udash.properties.seq.ReadableSeqProperty 6 | import io.udash.properties.single.ReadableProperty 7 | import io.udash.utils.Registration 8 | import org.scalajs.dom.Node 9 | 10 | private[bindings] final class SeqAsValueModifier[T]( 11 | override val property: ReadableSeqProperty[T, _ <: ReadableProperty[T]], 12 | build: (Seq[T], Binding.NestedInterceptor) => Seq[Node], 13 | override val customElementsReplace: DOMManipulator.ReplaceMethod 14 | ) extends ValueModifier[BSeq[T]] { 15 | 16 | override protected def builder: (BSeq[T], NestedInterceptor) => Seq[Node] = (data, interceptor) => build(data.toSeq, interceptor) 17 | 18 | def this(property: ReadableSeqProperty[T, _ <: ReadableProperty[T]], builder: Seq[T] => Seq[Node], 19 | customElementsReplace: DOMManipulator.ReplaceMethod) = { 20 | this(property, (data: Seq[T], _: Binding.NestedInterceptor) => builder(data), customElementsReplace) 21 | } 22 | 23 | override def listen(callback: BSeq[T] => Unit): Registration = 24 | property.listen(callback) 25 | 26 | override def checkNull: Boolean = false // SeqProperty can not return null from `get` method 27 | 28 | } -------------------------------------------------------------------------------- /core/.js/src/main/scala/io/udash/bindings/modifiers/SeqPropertyModifier.scala: -------------------------------------------------------------------------------- 1 | package io.udash.bindings.modifiers 2 | 3 | import io.udash.properties.seq.ReadableSeqProperty 4 | import io.udash.properties.single.ReadableProperty 5 | import org.scalajs.dom._ 6 | 7 | private[bindings] final class SeqPropertyModifier[T, E <: ReadableProperty[T]]( 8 | override val property: ReadableSeqProperty[T, E], 9 | builder: (E, Binding.NestedInterceptor) => Seq[Node], 10 | override val customElementsReplace: DOMManipulator.ReplaceMethod, 11 | override val customElementsInsert: DOMManipulator.InsertMethod 12 | ) extends SeqPropertyModifierUtils[T, E] { 13 | 14 | def this(property: ReadableSeqProperty[T, E], builder: E => Seq[Node], 15 | customElementsReplace: DOMManipulator.ReplaceMethod, 16 | customElementsInsert: DOMManipulator.InsertMethod) = { 17 | this(property, (d, _) => builder(d), customElementsReplace, customElementsInsert) 18 | } 19 | 20 | protected def build(item: E): Seq[Node] = 21 | builder(item, propertyAwareNestedInterceptor(item)) 22 | } 23 | 24 | 25 | -------------------------------------------------------------------------------- /core/.js/src/main/scala/io/udash/bindings/modifiers/SimplePropertyModifier.scala: -------------------------------------------------------------------------------- 1 | package io.udash.bindings.modifiers 2 | 3 | import io.udash._ 4 | import io.udash.properties.single.ReadableProperty 5 | import org.scalajs.dom 6 | 7 | private[bindings] final class SimplePropertyModifier(property: ReadableProperty[_]) extends PropertyModifier[Any]( 8 | property, 9 | t => dom.document.createTextNode(t.toString), 10 | checkNull = true, 11 | DOMManipulator.DefaultElementReplace 12 | ) 13 | -------------------------------------------------------------------------------- /core/.js/src/main/scala/io/udash/bindings/modifiers/ValueModifier.scala: -------------------------------------------------------------------------------- 1 | package io.udash.bindings.modifiers 2 | 3 | import com.avsystem.commons._ 4 | import io.udash.bindings._ 5 | import io.udash.properties.single.ReadableProperty 6 | import io.udash.utils.Registration 7 | import org.scalajs.dom._ 8 | 9 | private[bindings] trait ValueModifier[T] extends Binding with DOMManipulator { 10 | 11 | import Bindings._ 12 | 13 | protected def property: ReadableProperty[T] 14 | protected def builder: (T, Binding.NestedInterceptor) => Seq[Node] 15 | protected def checkNull: Boolean 16 | protected def listen(callback: T => Unit): Registration 17 | 18 | override def applyTo(t: Element): Unit = { 19 | var elements: Seq[Node] = Seq.empty 20 | 21 | def rebuild(propertyValue: T): Unit = { 22 | killNestedBindings() 23 | 24 | val oldEls = elements 25 | val newEls: Seq[Node] = 26 | builder(propertyValue, nestedInterceptor) 27 | .optIf(!checkNull || propertyValue != null) 28 | .filter(_.nonEmpty) 29 | .getOrElse(emptyStringNode()) 30 | 31 | elements = defragment(newEls) 32 | 33 | replace(t)(oldEls, elements) 34 | } 35 | 36 | propertyListeners.push(listen(rebuild)) 37 | rebuild(property.get) 38 | } 39 | } -------------------------------------------------------------------------------- /core/.js/src/main/scala/io/udash/bindings/modifiers/package.scala: -------------------------------------------------------------------------------- 1 | package io.udash.bindings 2 | 3 | import org.scalajs.dom.Node 4 | 5 | package object modifiers { 6 | implicit class ElementExts(private val el: Node) extends AnyVal { 7 | def replaceChildren(oldChildren: Seq[Node], newChildren: Seq[Node]): Unit = { 8 | if (oldChildren == null || oldChildren.isEmpty) newChildren.foreach(el.appendChild) 9 | else { 10 | oldChildren.iterator.zip(newChildren.iterator).foreach { case (old, fresh) => 11 | el.replaceChild(fresh, old) 12 | } 13 | oldChildren.iterator.drop(newChildren.size).foreach(el.removeChild) 14 | newChildren.iterator.drop(oldChildren.size - 1).sliding(2) 15 | .foreach(s => if (s.size == 2) el.insertBefore(s(1), s(0).nextSibling)) 16 | } 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /core/.js/src/main/scala/io/udash/component/Component.scala: -------------------------------------------------------------------------------- 1 | package io.udash.component 2 | 3 | import io.udash.bindings.modifiers.Binding 4 | import org.scalajs.dom.Element 5 | 6 | /** Base trait for Udash based components. */ 7 | trait Component extends Binding { 8 | /** Component root DOM element ID. */ 9 | val componentId: ComponentId 10 | 11 | /** Creates a component DOM hierarchy. */ 12 | def render: Element 13 | 14 | override def applyTo(t: Element): Unit = 15 | t.appendChild(render) 16 | } -------------------------------------------------------------------------------- /core/.js/src/main/scala/io/udash/component/Components.scala: -------------------------------------------------------------------------------- 1 | package io.udash.component 2 | 3 | trait Components { 4 | type Listenable = io.udash.component.Listenable 5 | type ListenableEvent = io.udash.component.ListenableEvent 6 | } 7 | 8 | object Components extends Components 9 | -------------------------------------------------------------------------------- /core/.js/src/main/scala/io/udash/core/Defaults.scala: -------------------------------------------------------------------------------- 1 | package io.udash.core 2 | 3 | /** 4 | * Creates view with [[io.udash.core.EmptyPresenter]]. Used for static views. 5 | * 6 | * By default, instances of this class are compared by class name to prevent rerendering of static views. 7 | * This behaviour can be opted out of by overriding equals/hashCode. 8 | **/ 9 | abstract class StaticViewFactory[S <: State](viewCreator: () => View) extends ViewFactory[S] { 10 | override def create(): (View, EmptyPresenter.type) = 11 | (viewCreator(), EmptyPresenter) 12 | 13 | override def equals(other: Any): Boolean = other != null && getClass.equals(other.getClass) 14 | override def hashCode(): Int = getClass.hashCode() 15 | } 16 | 17 | /** Ignores state changes. Useful for static views. */ 18 | object EmptyPresenter extends Presenter[State] { 19 | override def handleState(state: State): Unit = () 20 | } -------------------------------------------------------------------------------- /core/.js/src/main/scala/io/udash/routing/Routing.scala: -------------------------------------------------------------------------------- 1 | package io.udash.routing 2 | 3 | trait Routing { 4 | type UrlChangeProvider = io.udash.routing.UrlChangeProvider 5 | type WindowUrlFragmentChangeProvider = io.udash.routing.WindowUrlFragmentChangeProvider 6 | type WindowUrlPathChangeProvider = io.udash.routing.WindowUrlPathChangeProvider 7 | } 8 | 9 | -------------------------------------------------------------------------------- /core/.js/src/main/scala/io/udash/routing/UrlLogging.scala: -------------------------------------------------------------------------------- 1 | package io.udash.routing 2 | 3 | import io.udash._ 4 | import io.udash.logging.CrossLogging 5 | 6 | import scala.util.Try 7 | 8 | /** 9 | * RoutingRegistry mixin simplifying logging app navigation. 10 | */ 11 | trait UrlLogging[S >: Null <: GState[S]] extends CrossLogging { app: Application[S] => 12 | protected def log(url: String, referrer: Option[String]): Unit 13 | 14 | app.onStateChange(event => 15 | Try(log(matchState(event.currentState).value, Try(matchState(event.oldState).value).toOption)) 16 | .failed 17 | .foreach(t => logger.warn("Logging url change failed: {}", t.getMessage))) 18 | } 19 | -------------------------------------------------------------------------------- /core/.js/src/test/scala/io/udash/routing/UrlLoggingTest.scala: -------------------------------------------------------------------------------- 1 | package io.udash.routing 2 | 3 | import io.udash._ 4 | import io.udash.core.Url 5 | import io.udash.testing._ 6 | 7 | import scala.collection.mutable.ListBuffer 8 | 9 | class UrlLoggingTest extends AsyncUdashFrontendTest with TestRouting { 10 | "UrlLogging" should { 11 | "call logging impl on url change" in { 12 | val urlWithRef = ListBuffer.empty[(String, Option[String])] 13 | 14 | new TestViewFactory[TestState]: ViewFactory[_ <: TestState] 15 | 16 | initTestRouting(default = () => new TestViewFactory[TestState]) 17 | val initUrl = Url("/") 18 | val urlProvider: TestUrlChangeProvider = new TestUrlChangeProvider(initUrl) 19 | val app = new Application[TestState](routing, vpRegistry, urlProvider) with UrlLogging[TestState] { 20 | override protected def log(url: String, referrer: Option[String]): Unit = { 21 | urlWithRef += ((url, referrer)) 22 | } 23 | } 24 | app.run(emptyComponent()) 25 | 26 | val urls = Seq("/", "/next", "/abc/1", "/next") 27 | val expected = (urls.head, Some("")) :: urls.sliding(2).map { case Seq(prev, current) => (current, Some(prev)) }.toList 28 | urls.foreach(str => app.goTo(routing.matchUrl(Url(str)))) 29 | retrying(urlWithRef.toList shouldBe expected) 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /core/.js/src/test/scala/io/udash/testing/TestRouting.scala: -------------------------------------------------------------------------------- 1 | package io.udash.testing 2 | 3 | import io.udash._ 4 | import io.udash.routing.RoutingEngine 5 | 6 | trait TestRouting { 7 | 8 | var routing: TestRoutingRegistry = _ 9 | var viewFactory: TestViewFactory[ErrorState.type] = _ 10 | var vpRegistry: TestViewFactoryRegistry = _ 11 | var renderer: TestViewRenderer = _ 12 | private[udash] var routingEngine: RoutingEngine[TestState] = _ 13 | 14 | protected final def initTestRouting(routing: TestRoutingRegistry = new TestRoutingRegistry, 15 | state2vp: Map[TestState, () => ViewFactory[_ <: TestState]] = Map.empty, default: () => ViewFactory[_ <: TestState] = () => viewFactory 16 | ): Unit = { 17 | this.routing = routing 18 | viewFactory = new TestViewFactory[ErrorState.type] 19 | vpRegistry = new TestViewFactoryRegistry(state2vp, default) 20 | renderer = new TestViewRenderer 21 | } 22 | 23 | protected final def initTestRoutingEngine(routing: TestRoutingRegistry = new TestRoutingRegistry, 24 | state2vp: Map[TestState, () => ViewFactory[_ <: TestState]] = Map.empty 25 | ): Unit = { 26 | initTestRouting(routing, state2vp) 27 | routingEngine = new RoutingEngine[TestState](routing, vpRegistry, renderer) 28 | } 29 | } -------------------------------------------------------------------------------- /core/.js/src/test/scala/io/udash/testing/TestState.scala: -------------------------------------------------------------------------------- 1 | package io.udash.testing 2 | 3 | import io.udash._ 4 | 5 | sealed abstract class TestState(val parentState: Option[ContainerTestState]) extends State { 6 | override type HierarchyRoot = TestState 7 | } 8 | sealed abstract class ContainerTestState(parentState: Option[ContainerTestState]) extends TestState(parentState) 9 | sealed abstract class FinalTestState(parentState: Option[ContainerTestState]) extends TestState(parentState) 10 | 11 | case class RootState(sth: Option[Int]) extends ContainerTestState(None) 12 | case class ClassState(arg: String, arg2: Int) extends FinalTestState(Some(RootState(None))) 13 | case object ObjectState extends ContainerTestState(Some(RootState(None))) 14 | case object ThrowExceptionState extends ContainerTestState(Some(RootState(None))) 15 | case object NextObjectState extends FinalTestState(Some(ObjectState)) 16 | case object ErrorState extends FinalTestState(Some(RootState(None))) 17 | -------------------------------------------------------------------------------- /core/.js/src/test/scala/io/udash/testing/TestUrlChangeProvider.scala: -------------------------------------------------------------------------------- 1 | package io.udash.testing 2 | 3 | import io.udash._ 4 | import io.udash.utils.{Registration, SetRegistration} 5 | 6 | import scala.collection.mutable 7 | 8 | class TestUrlChangeProvider(init: Url) extends UrlChangeProvider { 9 | var currUrl: Url = init 10 | var urlsHistory: mutable.ArrayBuffer[Url] = mutable.ArrayBuffer.empty 11 | val changeListeners: mutable.Set[Url => Unit] = mutable.Set.empty 12 | 13 | override def initialize(): Unit = () 14 | 15 | override def changeFragment(url: Url, changeHistory: Boolean): Unit = { 16 | currUrl = url 17 | if (changeHistory || urlsHistory.isEmpty) urlsHistory.append(url) 18 | else urlsHistory.update(urlsHistory.length - 1, url) 19 | changeListeners.foreach(_(url)) 20 | } 21 | 22 | override def changeUrl(url: Url): Unit = changeFragment(url) 23 | 24 | override def currentFragment: Url = currUrl 25 | 26 | override def onFragmentChange(callback: Url => Unit): Registration = { 27 | changeListeners.add(callback) 28 | new SetRegistration(changeListeners, callback) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /core/.js/src/test/scala/io/udash/testing/TestViewFactoryRegistry.scala: -------------------------------------------------------------------------------- 1 | package io.udash.testing 2 | 3 | import io.udash._ 4 | 5 | import scala.collection.mutable 6 | 7 | class TestViewFactoryRegistry(vp: Map[TestState, () => ViewFactory[_ <: TestState]], 8 | default: () => ViewFactory[_ <: TestState]) extends ViewFactoryRegistry[TestState] { 9 | var statesHistory: mutable.ArrayBuffer[TestState] = mutable.ArrayBuffer.empty 10 | 11 | override def matchStateToResolver(state: TestState): ViewFactory[_ <: TestState] = { 12 | if (state == ThrowExceptionState) throw new RuntimeException("ThrowExceptionState") 13 | statesHistory.append(state) 14 | vp.get(state).map(_.apply()).getOrElse(default()) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /core/.js/src/test/scala/io/udash/testing/TestViewRenderer.scala: -------------------------------------------------------------------------------- 1 | package io.udash.testing 2 | 3 | import com.avsystem.commons._ 4 | import io.udash._ 5 | import io.udash.view.ViewRenderer 6 | 7 | class TestViewRenderer extends ViewRenderer(null) { 8 | val views = MArrayBuffer[View]() 9 | var lastSubPathToLeave: List[View] = Nil 10 | var lastPathToAdd: Iterable[View] = Nil 11 | 12 | override def renderView(subPathToLeave: Iterator[View], pathToAdd: Iterable[View]): Unit = { 13 | val subPathList = subPathToLeave.toList 14 | views.clear() 15 | views.appendAll(subPathList) 16 | views.appendAll(pathToAdd) 17 | 18 | lastSubPathToLeave = subPathList 19 | lastPathToAdd = pathToAdd 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /core/.js/src/test/scala/io/udash/testing/UdashCoreFrontendTest.scala: -------------------------------------------------------------------------------- 1 | package io.udash 2 | package testing 3 | 4 | trait UdashCoreFrontendTest extends UdashCoreTest with UdashFrontendTest 5 | trait AsyncUdashCoreFrontendTest extends AsyncUdashCoreTest with AsyncUdashFrontendTest -------------------------------------------------------------------------------- /core/src/main/scala/io/udash/properties/HasGenCodecAndModelPropertyCreator.scala: -------------------------------------------------------------------------------- 1 | package io.udash.properties 2 | 3 | import com.avsystem.commons.meta.MacroInstances 4 | import com.avsystem.commons.serialization.GenCodec 5 | 6 | trait GenCodecAndModelPropertyCreator[T] { 7 | def codec: GenCodec[T] 8 | def modelPropertyCreator: ModelPropertyCreator[T] 9 | } 10 | 11 | abstract class HasGenCodecAndModelPropertyCreator[T](implicit 12 | instances: MacroInstances[Unit, GenCodecAndModelPropertyCreator[T]] 13 | ) { 14 | implicit final lazy val modelPropertyCreator: ModelPropertyCreator[T] = instances((), this).modelPropertyCreator 15 | implicit final lazy val codec: GenCodec[T] = instances((), this).codec 16 | } 17 | -------------------------------------------------------------------------------- /core/src/main/scala/io/udash/properties/HasModelPropertyCreator.scala: -------------------------------------------------------------------------------- 1 | package io.udash.properties 2 | 3 | import com.avsystem.commons.meta.MacroInstances 4 | 5 | abstract class HasModelPropertyCreator[T](implicit instances: MacroInstances[Unit, () => ModelPropertyCreator[T]]) { 6 | implicit final lazy val modelPropertyCreator: ModelPropertyCreator[T] = instances((), this).apply() 7 | } -------------------------------------------------------------------------------- /core/src/main/scala/io/udash/properties/IsModelPropertyTemplate.scala: -------------------------------------------------------------------------------- 1 | package io.udash.properties 2 | 3 | /** 4 | * Evidence that type `T` can be used to create ModelProperty. 5 | * 6 | * There are two valid model bases: 7 | * 20 | */ 21 | class IsModelPropertyTemplate[T] 22 | object IsModelPropertyTemplate { 23 | implicit def checkModelPropertyTemplate[T]: IsModelPropertyTemplate[T] = 24 | macro io.udash.macros.PropertyMacros.checkModelPropertyTemplate[T] 25 | } 26 | 27 | -------------------------------------------------------------------------------- /core/src/main/scala/io/udash/properties/MutableSetRegistration.scala: -------------------------------------------------------------------------------- 1 | package io.udash.properties 2 | 3 | import com.avsystem.commons._ 4 | import io.udash.utils.Registration 5 | 6 | private[udash] class MutableSetRegistration[ElementType]( 7 | s: MSet[ElementType], el: ElementType, 8 | statusChangeListener: Opt[() => Unit] 9 | ) extends Registration { 10 | override def cancel(): Unit = { 11 | s -= el 12 | statusChangeListener.foreach(_.apply()) 13 | } 14 | 15 | override def restart(): Unit = { 16 | s += el 17 | statusChangeListener.foreach(_.apply()) 18 | } 19 | 20 | override def isActive: Boolean = s.contains(el) 21 | } 22 | -------------------------------------------------------------------------------- /core/src/main/scala/io/udash/properties/PropertyCreatorImplicits.scala: -------------------------------------------------------------------------------- 1 | package io.udash.properties 2 | 3 | trait PropertyCreatorImplicits { this: PropertyCreator.type => 4 | implicit final def materializeSingle[T]: PropertyCreator[T] = new SinglePropertyCreator[T] 5 | } -------------------------------------------------------------------------------- /core/src/main/scala/io/udash/properties/seq/Patch.scala: -------------------------------------------------------------------------------- 1 | package io.udash.properties.seq 2 | 3 | import com.avsystem.commons.misc.AbstractCase 4 | import io.udash.properties.single.ReadableProperty 5 | 6 | /** 7 | * Describes changes in SeqProperty structure. 8 | * 9 | * @param idx Index where changes starts. 10 | * @param removed Properties removed from index `idx`. 11 | * @param added Properties added on index `idx`. 12 | * @tparam P Contained properties type. 13 | */ 14 | final case class Patch[+P <: ReadableProperty[_]](idx: Int, removed: Seq[P], added: Seq[P]) extends AbstractCase 15 | -------------------------------------------------------------------------------- /core/src/main/scala/io/udash/properties/single/DirectProperty.scala: -------------------------------------------------------------------------------- 1 | package io.udash.properties.single 2 | 3 | private[properties] final class DirectProperty[A](override protected val parent: ReadableProperty[_]) 4 | extends AbstractProperty[A] with CastableProperty[A] { 5 | 6 | private var value: A = _ 7 | 8 | override def get: A = value 9 | 10 | override def set(t: A, force: Boolean = false): Unit = 11 | if (force || value != t) { 12 | value = t 13 | valueChanged() 14 | } 15 | 16 | override def setInitValue(t: A): Unit = 17 | value = t 18 | 19 | override def touch(): Unit = 20 | valueChanged() 21 | 22 | override def toString: String = s"DirectProperty($value)" 23 | } 24 | -------------------------------------------------------------------------------- /core/src/main/scala/io/udash/properties/single/ForwarderProperty.scala: -------------------------------------------------------------------------------- 1 | package io.udash.properties.single 2 | 3 | private[properties] trait ForwarderReadableProperty[A] extends AbstractReadableProperty[A] { 4 | protected def origin: ReadableProperty[_] 5 | 6 | override protected[properties] def parent: ReadableProperty[_] = null 7 | } 8 | 9 | private[properties] trait ForwarderProperty[A] extends ForwarderReadableProperty[A] with AbstractProperty[A] { 10 | protected def origin: Property[_] 11 | } 12 | -------------------------------------------------------------------------------- /core/src/test/scala/io/udash/properties/HasGenCodecAndModelPropertyCreatorTest.scala: -------------------------------------------------------------------------------- 1 | package io.udash.properties 2 | 3 | import com.avsystem.commons.serialization.json.JsonStringOutput 4 | import io.udash.properties.model.ModelProperty 5 | import io.udash.testing.UdashCoreTest 6 | 7 | class HasGenCodecAndModelPropertyCreatorTest extends UdashCoreTest { 8 | import HasGenCodecAndModelPropertyCreatorTest._ 9 | 10 | "HasGenCodecAndModelPropertyCreator class" should { 11 | "provide valid GenCodec" in { 12 | JsonStringOutput.write(Entity(1, "a", Some(Entity(2, "b", None)))) should be( 13 | """{"i":1,"s":"a","e":{"i":2,"s":"b","e":null}}""" 14 | ) 15 | } 16 | 17 | "provide valid ModelPropertyCreator" in { 18 | val p = ModelProperty(Entity(1, "a", Some(Entity(2, "b", None)))) 19 | 20 | p.subProp(_.i).get should be(1) 21 | p.subProp(_.s).get should be("a") 22 | } 23 | } 24 | } 25 | 26 | object HasGenCodecAndModelPropertyCreatorTest { 27 | case class Entity(i: Int, s: String, e: Option[Entity]) 28 | object Entity extends HasGenCodecAndModelPropertyCreator[Entity] 29 | } 30 | -------------------------------------------------------------------------------- /core/src/test/scala/io/udash/testing/AsyncUdashCoreTest.scala: -------------------------------------------------------------------------------- 1 | package io.udash 2 | package testing 3 | 4 | trait AsyncUdashCoreTest extends AsyncUdashSharedTest with CoreTestUtils -------------------------------------------------------------------------------- /core/src/test/scala/io/udash/testing/CoreTestUtils.scala: -------------------------------------------------------------------------------- 1 | package io.udash.testing 2 | 3 | import io.udash.properties.seq.ReadableSeqProperty 4 | import io.udash.properties.single.ReadableProperty 5 | import org.scalatest.Assertion 6 | import org.scalatest.matchers.should.Matchers 7 | 8 | trait CoreTestUtils extends Matchers { 9 | def ensureNoListeners(seqProperty: ReadableSeqProperty[_, _ <: ReadableProperty[_]]): Assertion = { 10 | seqProperty.listenersCount() should be(0) 11 | seqProperty.structureListenersCount() should be(0) 12 | seqProperty.elemProperties.map(_.listenersCount()).sum should be(0) 13 | } 14 | 15 | def valuesOfType[ReturnType](obj: Any): List[ReturnType] = macro io.udash.macros.AllValuesMacro.ofType[ReturnType] 16 | } 17 | -------------------------------------------------------------------------------- /core/src/test/scala/io/udash/testing/UdashCoreTest.scala: -------------------------------------------------------------------------------- 1 | package io.udash.testing 2 | 3 | trait UdashCoreTest extends UdashSharedTest with CoreTestUtils -------------------------------------------------------------------------------- /css/.jvm/src/main/scala/io/udash/css/CssStringRenderer.scala: -------------------------------------------------------------------------------- 1 | package io.udash.css 2 | 3 | import scalacss.internal.Renderer 4 | 5 | /** Renders provided styles into `String`. Keeps styles order from provided `Seq`. */ 6 | class CssStringRenderer(styles: Seq[CssBase]) { 7 | def render()(implicit renderer: Renderer[String]): String = { 8 | val builder = new StringBuilder 9 | 10 | styles.foreach { style => 11 | builder.append(style.render) 12 | } 13 | 14 | builder.mkString 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /css/src/main/scala/io/udash/css/CssStyle.scala: -------------------------------------------------------------------------------- 1 | package io.udash.css 2 | 3 | import scalacss.internal.{FontFace, StyleS} 4 | 5 | /** Representation of stylesheet elements. In JS it's always `CssStyleName`. */ 6 | sealed trait CssStyle { 7 | def className: String 8 | // Primarily introduced for FontAwesome support, which requires adding two classes, e.g. "fa fa-adjust" 9 | def commonPrefixClass: Option[String] = None 10 | def classNames: Seq[String] = commonPrefixClass.toList :+ className 11 | } 12 | case class CssStyleName(className: String) extends CssStyle 13 | case class CssPrefixedStyleName(prefixClass: String, actualClassSuffix: String) extends CssStyle { 14 | val className = s"$prefixClass-$actualClassSuffix" 15 | override val commonPrefixClass: Option[String] = Some(prefixClass) 16 | } 17 | case class CssStyleNameWithSharedCompanion(companionClass: String, commonPrefix: String, className: String) extends CssStyle { 18 | override val commonPrefixClass: Option[String] = Some(commonPrefix) 19 | override def classNames: Seq[String] = Seq(companionClass, className) 20 | } 21 | case class CssStyleImpl(className: String, impl: StyleS) extends CssStyle 22 | case class CssKeyframes(className: String, steps: Seq[(Double, StyleS)]) extends CssStyle 23 | case class CssFontFace(className: String, font: FontFace[Option[String]]) extends CssStyle 24 | -------------------------------------------------------------------------------- /css/src/main/scala/io/udash/css/CssText.scala: -------------------------------------------------------------------------------- 1 | package io.udash 2 | package css 3 | 4 | import scalatags.Text.all._ 5 | import scalatags.text.Builder 6 | 7 | trait CssText { 8 | implicit def style2TextMod(s: CssStyle): Modifier = new CssText.TextStyleModifier(s) 9 | } 10 | 11 | object CssText extends CssText { 12 | private final class TextStyleModifier(styles: CssStyle*) extends Modifier { 13 | override def applyTo(t: Builder): Unit = 14 | styles.foreach { s => t.appendAttr(cls.name, Builder.GenericAttrValueSource(s.classNames.mkString(" "))) } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /css/src/test/scala/io/udash/css/CssTextTest.scala: -------------------------------------------------------------------------------- 1 | package io.udash 2 | package css 3 | 4 | import io.udash.testing.UdashSharedTest 5 | 6 | class CssTextTest extends UdashSharedTest { 7 | 8 | import CssText._ 9 | import StylesheetExample._ 10 | import scalatags.Text.all._ 11 | 12 | "CssText" should { 13 | "provide tools for rendering styles with Scalatags text nodes" in { 14 | div(test1, test2, indent(2))("Test").render shouldBe 15 | "
Test
" 16 | } 17 | 18 | "separate class names by space" in { 19 | val fontAwesomeStyle = CssStyleNameWithSharedCompanion("far", "fa", "fa-grin") 20 | i(fontAwesomeStyle).render shouldBe "" 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /css/src/test/scala/io/udash/css/SecondStylesheetExample.scala: -------------------------------------------------------------------------------- 1 | package io.udash.css 2 | 3 | 4 | object SecondStylesheetExample extends CssBase { 5 | import dsl._ 6 | 7 | def utils(x: Int): CssStyle = mixin( 8 | margin(x px, auto), 9 | textAlign.left, 10 | cursor.pointer 11 | ) 12 | 13 | val test: CssStyle = style( 14 | utils(12), 15 | 16 | &.hover( 17 | cursor.zoomIn 18 | ), 19 | 20 | media.not.handheld.landscape.maxWidth(640 px)( 21 | width(400 px) 22 | ) 23 | ) 24 | } 25 | -------------------------------------------------------------------------------- /guide/backend/src/main/resources/demo_translations_en.properties: -------------------------------------------------------------------------------- 1 | auth.login.buttonLabel=Sign in 2 | auth.login.retriesLeft={} retries left 3 | auth.login.retriesLeftOne=1 retry left 4 | auth.loginLabel=Username 5 | auth.passwordLabel=Password 6 | auth.register.buttonLabel=Sign up 7 | 8 | server.exception.example=Something went wrong! -------------------------------------------------------------------------------- /guide/backend/src/main/resources/demo_translations_pl.properties: -------------------------------------------------------------------------------- 1 | auth.login.buttonLabel=Zaloguj 2 | auth.login.retriesLeft=Zosta\u0142o kilka pr\u00F3b 3 | auth.login.retriesLeftOne=Zosta\u0142a ostatnia pr\u00F3ba 4 | auth.loginLabel=Nazwa u\u017Cytkownika 5 | auth.passwordLabel=Has\u0142o 6 | auth.register.buttonLabel=Zarejestruj 7 | 8 | server.exception.example=Przet\u0142umaczony b\u0142\u0105d! -------------------------------------------------------------------------------- /guide/backend/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | logs/udash-guide-${bySecond}.log 17 | true 18 | 19 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /guide/backend/src/main/resources/reference.conf: -------------------------------------------------------------------------------- 1 | ui { 2 | server { 3 | port = 8080 4 | homepageResourceBase = "guide/homepage/.js/target/UdashStatics/WebContent/homepage/" 5 | guideResourceBase = "guide/guide/.js/target/UdashStatics/WebContent/guide/" 6 | } 7 | } -------------------------------------------------------------------------------- /guide/backend/src/main/scala/io/udash/web/Implicits.scala: -------------------------------------------------------------------------------- 1 | package io.udash.web 2 | 3 | import java.util.concurrent.Executors 4 | 5 | import scala.concurrent.ExecutionContext 6 | 7 | object Implicits { 8 | implicit val backendExecutionContext: ExecutionContext = ExecutionContext.fromExecutor(Executors.newFixedThreadPool(32)) 9 | } 10 | -------------------------------------------------------------------------------- /guide/backend/src/main/scala/io/udash/web/Launcher.scala: -------------------------------------------------------------------------------- 1 | package io.udash.web 2 | 3 | import com.typesafe.config.ConfigFactory 4 | import com.typesafe.scalalogging.LazyLogging 5 | import io.udash.web.server.ApplicationServer 6 | import monix.execution.Scheduler 7 | 8 | object Launcher extends LazyLogging { 9 | def main(args: Array[String]): Unit = { 10 | val startTime = System.nanoTime 11 | 12 | createApplicationServer().start() 13 | 14 | val duration: Long = (System.nanoTime - startTime) / 1000000000 15 | logger.info(s"Udash Homepage & Dev's Guide started in ${duration}s.") 16 | } 17 | 18 | 19 | private[udash] def createApplicationServer(): ApplicationServer = { 20 | implicit val scheduler: Scheduler = Scheduler.computation() 21 | val serverConfig = ConfigFactory.load().getConfig("ui.server") 22 | new ApplicationServer( 23 | port = serverConfig.getInt("port"), 24 | homepageResourceBase = serverConfig.getString("homepageResourceBase"), 25 | guideResourceBase = serverConfig.getString("guideResourceBase") 26 | ) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /guide/backend/src/main/scala/io/udash/web/guide/demos/DemosServer.scala: -------------------------------------------------------------------------------- 1 | package io.udash.web.guide.demos 2 | 3 | import io.udash.i18n.RemoteTranslationRPC 4 | import io.udash.rpc._ 5 | import io.udash.web.guide.demos.activity.{CallLogger, CallServer, CallServerRPC} 6 | import io.udash.web.guide.demos.i18n.TranslationServer 7 | import io.udash.web.guide.demos.rpc._ 8 | 9 | class DemosServer(callLogger: CallLogger)(implicit clientId: ClientId) extends DemosServerRPC { 10 | override def pingDemo: PingServerRPC = new PingServer 11 | override def clientIdDemo: ClientIdServerRPC = new ClientIdServer 12 | override def notificationsDemo: NotificationsServerRPC = new NotificationsServer 13 | override def gencodecsDemo: GenCodecServerRPC = new GenCodecServer 14 | override def translations: RemoteTranslationRPC = new TranslationServer 15 | override def exceptions: ExceptionsRPC = new ExceptionsServer 16 | 17 | override def call: CallServerRPC = new CallServer(callLogger) 18 | } 19 | -------------------------------------------------------------------------------- /guide/backend/src/main/scala/io/udash/web/guide/demos/activity/CallLogger.scala: -------------------------------------------------------------------------------- 1 | package io.udash.web.guide.demos.activity 2 | 3 | import scala.collection.mutable.ListBuffer 4 | 5 | class CallLogger { 6 | private val _calls = ListBuffer.empty[Call] 7 | 8 | def append(call: Call): Unit = _calls.synchronized { 9 | _calls += call 10 | if (_calls.size > 20) _calls.remove(0, _calls.size-20) 11 | } 12 | 13 | def calls: List[Call] = _calls.synchronized { 14 | _calls.toList 15 | } 16 | } -------------------------------------------------------------------------------- /guide/backend/src/main/scala/io/udash/web/guide/demos/activity/CallServer.scala: -------------------------------------------------------------------------------- 1 | package io.udash.web.guide.demos.activity 2 | 3 | import scala.concurrent.Future 4 | 5 | class CallServer(callLogger: CallLogger) extends CallServerRPC { 6 | override def calls: Future[Seq[Call]] = Future.successful(callLogger.calls) 7 | } 8 | -------------------------------------------------------------------------------- /guide/backend/src/main/scala/io/udash/web/guide/demos/i18n/TranslationServer.scala: -------------------------------------------------------------------------------- 1 | package io.udash.web.guide.demos.i18n 2 | 3 | import io.udash.i18n.{Lang, ResourceBundlesTranslationTemplatesProvider, TranslationRPCEndpoint} 4 | import io.udash.web.Implicits.* 5 | 6 | import java.util as ju 7 | 8 | class TranslationServer extends TranslationRPCEndpoint( 9 | new ResourceBundlesTranslationTemplatesProvider( 10 | TranslationServer.langs 11 | .map(lang => 12 | Lang(lang) -> TranslationServer.bundlesNames.map(name => ju.ResourceBundle.getBundle(name, new ju.Locale.Builder().setLanguage(lang).build())) 13 | ).toMap 14 | ) 15 | ) 16 | 17 | object TranslationServer { 18 | val langs = Seq("en", "pl") 19 | val bundlesNames = Seq("demo_translations") 20 | } -------------------------------------------------------------------------------- /guide/backend/src/main/scala/io/udash/web/guide/demos/rpc/ClientIdServer.scala: -------------------------------------------------------------------------------- 1 | package io.udash.web.guide.demos.rpc 2 | 3 | import io.udash.rpc._ 4 | 5 | import scala.concurrent.Future 6 | 7 | class ClientIdServer(implicit cid: ClientId) extends ClientIdServerRPC { 8 | override def clientId(): Future[String] = { 9 | Future.successful(cid.toString) 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /guide/backend/src/main/scala/io/udash/web/guide/demos/rpc/ExceptionsServer.scala: -------------------------------------------------------------------------------- 1 | package io.udash.web.guide.demos.rpc 2 | 3 | import io.udash.web.guide.GuideExceptions 4 | import io.udash.web.guide.demos.i18n.Translations 5 | 6 | import scala.concurrent.Future 7 | 8 | class ExceptionsServer extends ExceptionsRPC { 9 | override def example(): Future[Unit] = 10 | Future.failed(GuideExceptions.ExampleException("Exception from server")) 11 | 12 | override def exampleWithTranslatableError(): Future[Unit] = 13 | Future.failed(GuideExceptions.TranslatableExampleException(Translations.exceptions.example)) 14 | 15 | override def unknownError(): Future[Unit] = 16 | Future.failed(new RuntimeException("RuntimeException from server")) 17 | } 18 | -------------------------------------------------------------------------------- /guide/backend/src/main/scala/io/udash/web/guide/demos/rpc/PingServer.scala: -------------------------------------------------------------------------------- 1 | package io.udash.web.guide.demos.rpc 2 | 3 | import io.udash.rpc._ 4 | import io.udash.web.guide.rpc.ClientRPC 5 | 6 | import scala.concurrent.Future 7 | 8 | class PingServer(implicit clientId: ClientId) extends PingServerRPC { 9 | import io.udash.web.Implicits._ 10 | 11 | override def ping(id: Int): Unit = { 12 | ClientRPC(clientId).demos().pingDemo().pong(id) 13 | } 14 | 15 | override def fPing(id: Int): Future[Int] = { 16 | Future.successful(id) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /guide/backend/src/main/scala/io/udash/web/guide/rest/ExposedRestInterfaces.scala: -------------------------------------------------------------------------------- 1 | package io.udash.web.guide.rest 2 | 3 | import io.udash.web.guide.demos.rest._ 4 | 5 | import scala.concurrent.Future 6 | 7 | class ExposedRestInterfaces extends MainServerREST { 8 | override def simple(): SimpleServerREST = new SimpleServerREST { 9 | override def string(): Future[String] = Future.successful("OK") 10 | override def cls(): Future[RestExampleClass] = Future.successful(RestExampleClass(42, "Udash", InnerClass(321.123, "REST Support"))) 11 | override def int(): Future[Int] = Future.successful(123) 12 | } 13 | override def echo(): EchoServerREST = new EchoServerREST { 14 | override def withUrlPart(arg: String): Future[String] = Future.successful(s"URL:$arg") 15 | override def withQuery(arg: String): Future[String] = Future.successful(s"Query:$arg") 16 | override def withHeader(arg: String): Future[String] = Future.successful(s"Header:$arg") 17 | override def withBody(arg: String): Future[String] = Future.successful(s"Body:$arg") 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /guide/backend/src/main/scala/io/udash/web/guide/rpc/ClientRPC.scala: -------------------------------------------------------------------------------- 1 | package io.udash.web.guide.rpc 2 | 3 | import io.udash.web.guide.MainClientRPC 4 | import io.udash.rpc._ 5 | 6 | import scala.concurrent.ExecutionContext 7 | 8 | object ClientRPC { 9 | def apply(target: ClientRPCTarget)(implicit ec: ExecutionContext): MainClientRPC = { 10 | new DefaultClientRPC[MainClientRPC](target).get 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /guide/backend/src/main/scala/io/udash/web/guide/rpc/ExposedRpcInterfaces.scala: -------------------------------------------------------------------------------- 1 | package io.udash.web.guide.rpc 2 | 3 | import io.udash.rpc._ 4 | import io.udash.web.guide.MainServerRPC 5 | import io.udash.web.guide.demos.activity.CallLogger 6 | import io.udash.web.guide.demos.{DemosServer, DemosServerRPC} 7 | import io.udash.web.guide.markdown.{MarkdownPageRPC, MarkdownPagesEndpoint} 8 | 9 | class ExposedRpcInterfaces(callLogger: CallLogger, guideResourceBase: String)(implicit clientId: ClientId) extends MainServerRPC { 10 | import io.udash.web.Implicits._ 11 | 12 | override val demos: DemosServerRPC = new DemosServer(callLogger) 13 | override val pages: MarkdownPageRPC = new MarkdownPagesEndpoint(guideResourceBase) 14 | } -------------------------------------------------------------------------------- /guide/commons/.js/src/main/assets/pdf/origami_crane_printok.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UdashFramework/udash-core/bc1969122eaf0e057803c052a0be3861f123d975/guide/commons/.js/src/main/assets/pdf/origami_crane_printok.pdf -------------------------------------------------------------------------------- /guide/commons/.js/src/main/assets/svg/avsystem.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /guide/commons/.js/src/main/assets/svg/based.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /guide/commons/.js/src/main/assets/svg/github.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /guide/commons/.js/src/main/assets/svg/gitter.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /guide/commons/.js/src/main/assets/svg/icon_submenu.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /guide/commons/.js/src/main/assets/svg/shared_code.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 9 | 10 | 11 | 14 | 15 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /guide/commons/.js/src/main/assets/svg/stack.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /guide/commons/.js/src/main/assets/svg/todomvc.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /guide/commons/.js/src/main/scala/io/udash/web/commons/components/ForceBootstrap.scala: -------------------------------------------------------------------------------- 1 | package io.udash 2 | package web.commons.components 3 | 4 | import org.scalajs.dom.html.Div 5 | import scalatags.JsDom 6 | 7 | object ForceBootstrap { 8 | import scalatags.JsDom.all._ 9 | 10 | def apply(modifiers: Modifier*): JsDom.TypedTag[Div] = 11 | div(cls := "bootstrap")( //force Bootstrap styles 12 | modifiers:_* 13 | ) 14 | } 15 | -------------------------------------------------------------------------------- /guide/commons/.js/src/main/scala/io/udash/web/commons/components/HeaderNav.scala: -------------------------------------------------------------------------------- 1 | package io.udash.web.commons.components 2 | 3 | import io.udash.web.commons.styles.components.HeaderNavStyles 4 | import org.scalajs.dom.Element 5 | 6 | trait HeaderNav { 7 | import io.udash.css.CssView._ 8 | import scalatags.JsDom.all._ 9 | import scalatags.JsDom.tags2.nav 10 | 11 | val navStyles: HeaderNavStyles 12 | 13 | case class NavItem(url: String, title: String) 14 | 15 | def navigation(items: NavItem*): Element = 16 | nav(navStyles.headerNav)( 17 | ul(navStyles.headerLinkList)( 18 | items.map(item => 19 | li(navStyles.headerLinkWrapper)( 20 | a(href := item.url, navStyles.headerLink)(item.title) 21 | ) 22 | ) 23 | ) 24 | ).render 25 | } 26 | -------------------------------------------------------------------------------- /guide/commons/.js/src/main/scala/io/udash/web/commons/config/ExternalUrls.scala: -------------------------------------------------------------------------------- 1 | package io.udash.web.commons.config 2 | 3 | object ExternalUrls { 4 | val udashGitter = "https://gitter.im/UdashFramework/udash-core?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge" 5 | val udashGithub = "https://github.com/UdashFramework/" 6 | val udashG8Build = "https://raw.githubusercontent.com/UdashFramework/udash.g8/master/src/main/g8/build.sbt" 7 | val udashG8Plugins = "https://raw.githubusercontent.com/UdashFramework/udash.g8/master/src/main/g8/project/plugins.sbt" 8 | val udashG8Index = "https://raw.githubusercontent.com/UdashFramework/udash.g8/master/src/main/g8/frontend/src/main/assets/index.html" 9 | val udashDemos = "https://github.com/UdashFramework/udash-demos" 10 | val stackoverflow = "http://stackoverflow.com/questions/tagged/udash" 11 | val avsystem = "http://www.avsystem.com/" 12 | val homepage = "http://udash.io/" 13 | val guide = "http://guide.udash.io/" 14 | val scalajs = "https://www.scala-js.org/" 15 | 16 | val releases = "https://github.com/UdashFramework/udash-core/releases" 17 | val license = "http://guide.udash.io/license" 18 | } -------------------------------------------------------------------------------- /guide/commons/.js/src/main/scala/io/udash/web/commons/views/Component.scala: -------------------------------------------------------------------------------- 1 | package io.udash.web.commons.views 2 | 3 | import io.udash.css.CssView 4 | import org.scalajs.dom 5 | import org.scalajs.dom._ 6 | 7 | import scalatags.generic.Modifier 8 | 9 | trait Component extends Modifier[dom.Element] with CssView { 10 | def getTemplate: Modifier[dom.Element] 11 | 12 | def apply(): Modifier[dom.Element] = getTemplate 13 | 14 | override def applyTo(t: Element): Unit = 15 | getTemplate.applyTo(t) 16 | } -------------------------------------------------------------------------------- /guide/guide/.js/src/main/assets/assets.less: -------------------------------------------------------------------------------- 1 | @import (inline) "prism.css"; 2 | .bootstrap { 3 | @import (less) "lib/bootstrap/css/bootstrap.css"; 4 | } 5 | @import (inline) "lib/font-awesome/css/all.min.css"; -------------------------------------------------------------------------------- /guide/guide/.js/src/main/assets/fonts/roboto/Roboto-Black.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UdashFramework/udash-core/bc1969122eaf0e057803c052a0be3861f123d975/guide/guide/.js/src/main/assets/fonts/roboto/Roboto-Black.ttf -------------------------------------------------------------------------------- /guide/guide/.js/src/main/assets/fonts/roboto/Roboto-BlackItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UdashFramework/udash-core/bc1969122eaf0e057803c052a0be3861f123d975/guide/guide/.js/src/main/assets/fonts/roboto/Roboto-BlackItalic.ttf -------------------------------------------------------------------------------- /guide/guide/.js/src/main/assets/fonts/roboto/Roboto-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UdashFramework/udash-core/bc1969122eaf0e057803c052a0be3861f123d975/guide/guide/.js/src/main/assets/fonts/roboto/Roboto-Bold.ttf -------------------------------------------------------------------------------- /guide/guide/.js/src/main/assets/fonts/roboto/Roboto-BoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UdashFramework/udash-core/bc1969122eaf0e057803c052a0be3861f123d975/guide/guide/.js/src/main/assets/fonts/roboto/Roboto-BoldItalic.ttf -------------------------------------------------------------------------------- /guide/guide/.js/src/main/assets/fonts/roboto/Roboto-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UdashFramework/udash-core/bc1969122eaf0e057803c052a0be3861f123d975/guide/guide/.js/src/main/assets/fonts/roboto/Roboto-Italic.ttf -------------------------------------------------------------------------------- /guide/guide/.js/src/main/assets/fonts/roboto/Roboto-Light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UdashFramework/udash-core/bc1969122eaf0e057803c052a0be3861f123d975/guide/guide/.js/src/main/assets/fonts/roboto/Roboto-Light.ttf -------------------------------------------------------------------------------- /guide/guide/.js/src/main/assets/fonts/roboto/Roboto-LightItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UdashFramework/udash-core/bc1969122eaf0e057803c052a0be3861f123d975/guide/guide/.js/src/main/assets/fonts/roboto/Roboto-LightItalic.ttf -------------------------------------------------------------------------------- /guide/guide/.js/src/main/assets/fonts/roboto/Roboto-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UdashFramework/udash-core/bc1969122eaf0e057803c052a0be3861f123d975/guide/guide/.js/src/main/assets/fonts/roboto/Roboto-Medium.ttf -------------------------------------------------------------------------------- /guide/guide/.js/src/main/assets/fonts/roboto/Roboto-MediumItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UdashFramework/udash-core/bc1969122eaf0e057803c052a0be3861f123d975/guide/guide/.js/src/main/assets/fonts/roboto/Roboto-MediumItalic.ttf -------------------------------------------------------------------------------- /guide/guide/.js/src/main/assets/fonts/roboto/Roboto-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UdashFramework/udash-core/bc1969122eaf0e057803c052a0be3861f123d975/guide/guide/.js/src/main/assets/fonts/roboto/Roboto-Regular.ttf -------------------------------------------------------------------------------- /guide/guide/.js/src/main/assets/fonts/roboto/Roboto-Thin.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UdashFramework/udash-core/bc1969122eaf0e057803c052a0be3861f123d975/guide/guide/.js/src/main/assets/fonts/roboto/Roboto-Thin.ttf -------------------------------------------------------------------------------- /guide/guide/.js/src/main/assets/fonts/roboto/Roboto-ThinItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UdashFramework/udash-core/bc1969122eaf0e057803c052a0be3861f123d975/guide/guide/.js/src/main/assets/fonts/roboto/Roboto-ThinItalic.ttf -------------------------------------------------------------------------------- /guide/guide/.js/src/main/assets/images/ext/bootstrap/carousel.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UdashFramework/udash-core/bc1969122eaf0e057803c052a0be3861f123d975/guide/guide/.js/src/main/assets/images/ext/bootstrap/carousel.jpg -------------------------------------------------------------------------------- /guide/guide/.js/src/main/assets/images/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UdashFramework/udash-core/bc1969122eaf0e057803c052a0be3861f123d975/guide/guide/.js/src/main/assets/images/favicon.ico -------------------------------------------------------------------------------- /guide/guide/.js/src/main/assets/images/intro_bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UdashFramework/udash-core/bc1969122eaf0e057803c052a0be3861f123d975/guide/guide/.js/src/main/assets/images/intro_bg.jpg -------------------------------------------------------------------------------- /guide/guide/.js/src/main/assets/images/intro_bird.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UdashFramework/udash-core/bc1969122eaf0e057803c052a0be3861f123d975/guide/guide/.js/src/main/assets/images/intro_bird.png -------------------------------------------------------------------------------- /guide/guide/.js/src/main/assets/images/quick/generator.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UdashFramework/udash-core/bc1969122eaf0e057803c052a0be3861f123d975/guide/guide/.js/src/main/assets/images/quick/generator.png -------------------------------------------------------------------------------- /guide/guide/.js/src/main/assets/images/share/share_facebook.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UdashFramework/udash-core/bc1969122eaf0e057803c052a0be3861f123d975/guide/guide/.js/src/main/assets/images/share/share_facebook.png -------------------------------------------------------------------------------- /guide/guide/.js/src/main/assets/images/share/share_google.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UdashFramework/udash-core/bc1969122eaf0e057803c052a0be3861f123d975/guide/guide/.js/src/main/assets/images/share/share_google.png -------------------------------------------------------------------------------- /guide/guide/.js/src/main/assets/images/share/share_twitter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UdashFramework/udash-core/bc1969122eaf0e057803c052a0be3861f123d975/guide/guide/.js/src/main/assets/images/share/share_twitter.png -------------------------------------------------------------------------------- /guide/guide/.js/src/main/assets/images/udash_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UdashFramework/udash-core/bc1969122eaf0e057803c052a0be3861f123d975/guide/guide/.js/src/main/assets/images/udash_logo.png -------------------------------------------------------------------------------- /guide/guide/.js/src/main/assets/images/udash_logo_l.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UdashFramework/udash-core/bc1969122eaf0e057803c052a0be3861f123d975/guide/guide/.js/src/main/assets/images/udash_logo_l.png -------------------------------------------------------------------------------- /guide/guide/.js/src/main/assets/images/udash_logo_m.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UdashFramework/udash-core/bc1969122eaf0e057803c052a0be3861f123d975/guide/guide/.js/src/main/assets/images/udash_logo_m.png -------------------------------------------------------------------------------- /guide/guide/.js/src/main/assets/images/views/bootstrapping/modules_basic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UdashFramework/udash-core/bc1969122eaf0e057803c052a0be3861f123d975/guide/guide/.js/src/main/assets/images/views/bootstrapping/modules_basic.png -------------------------------------------------------------------------------- /guide/guide/.js/src/main/assets/images/views/bootstrapping/modules_extended.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UdashFramework/udash-core/bc1969122eaf0e057803c052a0be3861f123d975/guide/guide/.js/src/main/assets/images/views/bootstrapping/modules_extended.png -------------------------------------------------------------------------------- /guide/guide/.js/src/main/assets/images/views/bootstrapping/states.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UdashFramework/udash-core/bc1969122eaf0e057803c052a0be3861f123d975/guide/guide/.js/src/main/assets/images/views/bootstrapping/states.png -------------------------------------------------------------------------------- /guide/guide/.js/src/main/assets/images/views/frontend/mvp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UdashFramework/udash-core/bc1969122eaf0e057803c052a0be3861f123d975/guide/guide/.js/src/main/assets/images/views/frontend/mvp.png -------------------------------------------------------------------------------- /guide/guide/.js/src/main/assets/images/views/frontend/property.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UdashFramework/udash-core/bc1969122eaf0e057803c052a0be3861f123d975/guide/guide/.js/src/main/assets/images/views/frontend/property.png -------------------------------------------------------------------------------- /guide/guide/.js/src/main/assets/images/views/frontend/propertyhierarchy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UdashFramework/udash-core/bc1969122eaf0e057803c052a0be3861f123d975/guide/guide/.js/src/main/assets/images/views/frontend/propertyhierarchy.png -------------------------------------------------------------------------------- /guide/guide/.js/src/main/scala/io/udash/web/guide/components/BootstrapUtils.scala: -------------------------------------------------------------------------------- 1 | package io.udash.web.guide.components 2 | 3 | import io.udash.bootstrap.utils.BootstrapStyles 4 | import io.udash.bootstrap.utils.BootstrapStyles.Color 5 | import io.udash.css.CssStyleName 6 | 7 | object BootstrapUtils { 8 | 9 | /** 10 | * Wells component from bootstrap3 is absent in bootstrap4. 11 | * These well-like styles make elements look like the good old bootstrap3 well. 12 | * 13 | * Source: https://getbootstrap.com/docs/3.3/components/#wells 14 | */ 15 | def wellStyles: Seq[CssStyleName] = Seq( 16 | BootstrapStyles.Card.card, 17 | BootstrapStyles.Card.body, 18 | BootstrapStyles.Background.color(Color.Light), 19 | ) 20 | 21 | } 22 | -------------------------------------------------------------------------------- /guide/guide/.js/src/main/scala/io/udash/web/guide/components/Header.scala: -------------------------------------------------------------------------------- 1 | package io.udash.web.guide.components 2 | 3 | import io.udash.web.commons.components.{HeaderButtons, HeaderNav} 4 | import io.udash.web.commons.config.ExternalUrls 5 | import io.udash.web.commons.styles.GlobalStyles 6 | import io.udash.web.commons.styles.components.{HeaderButtonsStyles, HeaderNavStyles} 7 | import io.udash.web.commons.views.Image 8 | import io.udash.web.guide.styles.partials.HeaderStyles 9 | 10 | import scalatags.JsDom.all._ 11 | 12 | object Header extends HeaderButtons with HeaderNav { 13 | import io.udash.css.CssView._ 14 | private lazy val template = header(HeaderStyles.header)( 15 | div(GlobalStyles.body, GlobalStyles.clearfix)( 16 | div(HeaderStyles.headerLeft)( 17 | a(HeaderStyles.headerLogo, href := ExternalUrls.homepage)( 18 | Image("udash_logo_m.png", "Udash Framework", GlobalStyles.block) 19 | )/*, 20 | navigation(Seq( 21 | NavItem(ExternalUrls.guide, "Documentation"), 22 | NavItem(ExternalUrls.releases, "Changelog") 23 | ))*/ 24 | ), 25 | buttons 26 | ) 27 | ) 28 | 29 | def getTemplate: Modifier = template 30 | 31 | override val buttonStyles: HeaderButtonsStyles = HeaderStyles 32 | override val navStyles: HeaderNavStyles = HeaderStyles 33 | } 34 | -------------------------------------------------------------------------------- /guide/guide/.js/src/main/scala/io/udash/web/guide/demos/DemosClient.scala: -------------------------------------------------------------------------------- 1 | package io.udash.web.guide.demos 2 | 3 | import io.udash.web.guide.demos.rpc.{NotificationsClient, NotificationsClientRPC, PingClient, PingClientRPC} 4 | 5 | object DemosClient extends DemosClientRPC { 6 | override def pingDemo(): PingClientRPC = PingClient 7 | override def notificationsDemo(): NotificationsClientRPC = NotificationsClient 8 | } 9 | -------------------------------------------------------------------------------- /guide/guide/.js/src/main/scala/io/udash/web/guide/demos/rpc/NotificationsClient.scala: -------------------------------------------------------------------------------- 1 | package io.udash.web.guide.demos.rpc 2 | 3 | import io.udash.web.guide.Context 4 | 5 | import scala.concurrent.Future 6 | 7 | object NotificationsClient extends NotificationsClientRPC { 8 | 9 | import Context._ 10 | 11 | private val listeners = scala.collection.mutable.ArrayBuffer[String => Any]() 12 | 13 | def registerListener(listener: String => Any): Future[Unit] = { 14 | listeners += listener 15 | if (listeners.size == 1) serverRpc.demos.notificationsDemo.register() 16 | else Future.unit 17 | } 18 | 19 | def unregisterListener(listener: String => Any): Future[Unit] = { 20 | listeners -= listener 21 | if (listeners.isEmpty) serverRpc.demos.notificationsDemo.unregister() 22 | else Future.unit 23 | } 24 | 25 | override def notify(msg: String): Unit = { 26 | listeners.foreach(_ (msg)) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /guide/guide/.js/src/main/scala/io/udash/web/guide/demos/rpc/PingClient.scala: -------------------------------------------------------------------------------- 1 | package io.udash.web.guide.demos.rpc 2 | 3 | object PingClient extends PingClientRPC { 4 | private val pongListeners = scala.collection.mutable.ArrayBuffer[Int => Any]() 5 | 6 | override def pong(id: Int): Unit = { 7 | pongListeners.foreach(l => l(id)) 8 | } 9 | 10 | def registerPongListener(listener: Int => Any) = pongListeners += listener 11 | def unregisterPongListener(listener: Int => Any) = pongListeners -= listener 12 | } 13 | -------------------------------------------------------------------------------- /guide/guide/.js/src/main/scala/io/udash/web/guide/rpc/RPCService.scala: -------------------------------------------------------------------------------- 1 | package io.udash.web.guide.rpc 2 | 3 | import io.udash.web.guide.MainClientRPC 4 | import io.udash.web.guide.demos.{DemosClient, DemosClientRPC} 5 | 6 | class RPCService extends MainClientRPC { 7 | override def demos(): DemosClientRPC = DemosClient 8 | } 9 | -------------------------------------------------------------------------------- /guide/guide/.js/src/main/scala/io/udash/web/guide/views/ContentView.scala: -------------------------------------------------------------------------------- 1 | package io.udash.web.guide.views 2 | 3 | import io.udash.* 4 | import io.udash.web.commons.styles.GlobalStyles 5 | import io.udash.web.guide.ContentState 6 | import io.udash.web.guide.components.GuideMenu 7 | import io.udash.web.guide.styles.partials.GuideStyles 8 | import org.scalajs.dom.Element 9 | import scalatags.JsDom.tags2.* 10 | 11 | object ContentViewFactory extends StaticViewFactory[ContentState.type](() => new ContentView) 12 | 13 | class ContentView extends ViewContainer { 14 | import io.udash.css.CssView._ 15 | 16 | import scalatags.JsDom.all._ 17 | 18 | override protected val child: Element = main(GuideStyles.contentWrapper).render 19 | 20 | private val content = main(GuideStyles.main)( 21 | div(GlobalStyles.body)( 22 | div(GuideStyles.menuWrapper)( 23 | GuideMenu().getTemplate 24 | ), 25 | child 26 | ) 27 | ) 28 | 29 | override def getTemplate: Modifier = content 30 | } -------------------------------------------------------------------------------- /guide/guide/.js/src/main/scala/io/udash/web/guide/views/FaqView.scala: -------------------------------------------------------------------------------- 1 | package io.udash.web.guide.views 2 | 3 | import io.udash._ 4 | import io.udash.web.guide.FaqState 5 | 6 | object FaqViewFactory extends StaticViewFactory[FaqState.type](() => new FaqView) 7 | 8 | class FaqView extends View { 9 | import scalatags.JsDom.all._ 10 | 11 | private val content = div( 12 | h2("FAQ"), 13 | p("TODO") 14 | ) 15 | 16 | override def getTemplate: Modifier = content 17 | } -------------------------------------------------------------------------------- /guide/guide/.js/src/main/scala/io/udash/web/guide/views/References.scala: -------------------------------------------------------------------------------- 1 | package io.udash.web.guide.views 2 | 3 | object References { 4 | val UdashjQueryWrapperRepo = "https://github.com/UdashFramework/scala-js-jquery" 5 | val UdashGuideRepo = "https://github.com/UdashFramework/udash-core/tree/master/guide" 6 | val UdashG8Repo = "https://github.com/UdashFramework/udash.g8" 7 | val UdashFilesDemoRepo = "https://github.com/UdashFramework/udash-demos/tree/master/file-upload" 8 | val AvScalaCommonsGitHub = "https://github.com/AVSystem/scala-commons" 9 | val BootstrapHomepage = "http://getbootstrap.com/" 10 | val JettyHomepage = "http://www.eclipse.org/jetty/" 11 | val MvpPattern = "https://martinfowler.com/eaaDev/uiArchs.html#Model-view-presentermvp" 12 | val ScalaCssHomepage = "https://github.com/japgolly/scalacss" 13 | val ScalaJsHomepage = "http://www.scala-js.org/" 14 | val ScalaHomepage = "http://www.scala-lang.org/" 15 | val ScalatagsHomepage = "https://github.com/lihaoyi/scalatags" 16 | val UpickleHomepage = "https://github.com/lihaoyi/upickle-pprint" 17 | } 18 | -------------------------------------------------------------------------------- /guide/guide/.js/src/main/scala/io/udash/web/guide/views/RootView.scala: -------------------------------------------------------------------------------- 1 | package io.udash.web.guide.views 2 | 3 | import io.udash.* 4 | import io.udash.web.commons.components.Footer 5 | import io.udash.web.guide.RootState 6 | import io.udash.web.guide.components.Header 7 | import org.scalajs.dom.Element 8 | 9 | object RootViewFactory extends StaticViewFactory[RootState.type](() => new RootView) 10 | 11 | class RootView extends ViewContainer { 12 | import scalatags.JsDom.all._ 13 | 14 | override protected val child: Element = div().render 15 | 16 | private val content = div( 17 | Header.getTemplate, 18 | child, 19 | Footer.getTemplate() 20 | ) 21 | 22 | override def getTemplate: Modifier = content 23 | } -------------------------------------------------------------------------------- /guide/guide/.js/src/main/scala/io/udash/web/guide/views/Versions.scala: -------------------------------------------------------------------------------- 1 | package io.udash.web.guide.views 2 | 3 | object Versions { 4 | val udashVersion = "0.8.0" 5 | val udashJQueryVersion = "3.0.2" 6 | } 7 | -------------------------------------------------------------------------------- /guide/guide/.js/src/main/scala/io/udash/web/guide/views/ViewContainer.scala: -------------------------------------------------------------------------------- 1 | package io.udash.web.guide.views 2 | 3 | import io.udash._ 4 | import io.udash.wrappers.jquery.EasingFunction 5 | import org.scalajs.dom._ 6 | 7 | import scala.scalajs.js 8 | 9 | abstract class ViewContainer extends ContainerView { 10 | protected val child: Element 11 | 12 | override def renderChild(view: Option[View]): Unit = { 13 | import io.udash.wrappers.jquery.jQ 14 | val jqChild = jQ(child) 15 | 16 | jqChild 17 | .animate(Map[String, Any]("opacity" -> 0), 150, EasingFunction.swing, 18 | (_: Element) => { 19 | view match { 20 | case Some(view) => 21 | jqChild.children().remove() 22 | view.getTemplate.applyTo(jqChild.toArray.head) 23 | jqChild.animate(Map[String, Any]("opacity" -> 1), 200, EasingFunction.swing, 24 | _ => js.Dynamic.global.Prism.highlightAll() 25 | ) 26 | case None => 27 | jqChild.html(null) 28 | .animate(Map[String, Any]("opacity" -> 1), 200) 29 | } 30 | } 31 | ) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /guide/guide/.js/src/main/scala/io/udash/web/guide/views/bootstrapping/BootstrappingView.scala: -------------------------------------------------------------------------------- 1 | package io.udash.web.guide.views.bootstrapping 2 | 3 | import io.udash.* 4 | import io.udash.web.guide.views.ViewContainer 5 | import io.udash.web.guide.{Context, *} 6 | import org.scalajs.dom.Element 7 | import scalatags.JsDom 8 | 9 | case object BootstrappingViewFactory extends StaticViewFactory[BootstrappingState.type](() => new BootstrappingView) 10 | 11 | class BootstrappingView extends ViewContainer { 12 | import Context._ 13 | import JsDom.all._ 14 | 15 | override protected val child: Element = div().render 16 | 17 | override def getTemplate: Modifier = div( 18 | h1("Application bootstrapping"), 19 | p("In this part of the guide you will read about bootstrapping an Udash application from scratch."), 20 | p( 21 | i("This is an advanced topic, if you want to start development as soon as possible, generate the ", 22 | a(href := IntroState.url)("sample application"), ".") 23 | ), 24 | child 25 | ) 26 | } -------------------------------------------------------------------------------- /guide/guide/.js/src/main/scala/io/udash/web/guide/views/ext/demo/RpcLoggingDemo.scala: -------------------------------------------------------------------------------- 1 | package io.udash.web.guide.views.ext.demo 2 | 3 | import com.avsystem.commons._ 4 | import io.udash._ 5 | import io.udash.bootstrap.utils.BootstrapStyles 6 | import io.udash.bootstrap.utils.BootstrapStyles.Color 7 | import io.udash.web.guide.demos.activity.Call 8 | import io.udash.web.guide.styles.partials.GuideStyles 9 | import org.scalajs.dom 10 | import org.scalajs.dom._ 11 | import scalatags.JsDom.all._ 12 | 13 | object RpcLoggingDemo { 14 | import io.udash.css.CssView._ 15 | def apply(model: ReadableSeqProperty[Call], loadCalls: () => Any): dom.Element = 16 | span(GuideStyles.frame, GuideStyles.useBootstrap)( 17 | button( 18 | id := "call-logging-demo", BootstrapStyles.Button.btn, BootstrapStyles.Button.color(Color.Primary), 19 | onclick :+= ((_: MouseEvent) => loadCalls().thenReturn(true)) 20 | )("Load call list"), 21 | produce(model)(seq => ul(seq.map(call => li(call.toString))).render) 22 | ).render 23 | } 24 | -------------------------------------------------------------------------------- /guide/guide/.js/src/main/scala/io/udash/web/guide/views/ext/demo/bootstrap/BadgesDemo.scala: -------------------------------------------------------------------------------- 1 | package io.udash.web.guide.views.ext.demo.bootstrap 2 | 3 | import io.udash.bootstrap.button.UdashButtonOptions 4 | import io.udash.css.CssView 5 | import io.udash.web.guide.demos.AutoDemo 6 | import io.udash.web.guide.styles.partials.GuideStyles 7 | import scalatags.JsDom.all._ 8 | 9 | object BadgesDemo extends AutoDemo with CssView { 10 | 11 | private val (rendered, source) = { 12 | import io.udash._ 13 | import io.udash.bootstrap.badge.UdashBadge 14 | import io.udash.bootstrap.button.UdashButton 15 | import io.udash.bootstrap.utils.BootstrapStyles._ 16 | import org.scalajs.dom.window 17 | import scalatags.JsDom.all._ 18 | 19 | val counter = Property(0) 20 | window.setInterval(() => counter.set(counter.get + 1), 3000) 21 | 22 | div( 23 | UdashButton( 24 | options = UdashButtonOptions( 25 | color = Color.Primary.opt, 26 | size = Size.Large.opt 27 | ) 28 | )(_ => Seq[Modifier]( 29 | "Button ", 30 | UdashBadge()(nested => nested(bind(counter)) 31 | ).render 32 | )) 33 | ).render 34 | }.withSourceCode 35 | 36 | override protected def demoWithSource(): (Modifier, String) = 37 | (rendered.setup(_.applyTags(GuideStyles.frame)), source) 38 | } 39 | 40 | -------------------------------------------------------------------------------- /guide/guide/.js/src/main/scala/io/udash/web/guide/views/ext/demo/bootstrap/ButtonToolbarDemo.scala: -------------------------------------------------------------------------------- 1 | package io.udash.web.guide.views.ext.demo.bootstrap 2 | 3 | import io.udash.css.CssView 4 | import io.udash.web.guide.demos.AutoDemo 5 | import io.udash.web.guide.styles.partials.GuideStyles 6 | import scalatags.JsDom.all._ 7 | 8 | object ButtonToolbarDemo extends AutoDemo with CssView { 9 | 10 | private val (rendered, source) = { 11 | import io.udash._ 12 | import io.udash.bootstrap.button._ 13 | import io.udash.bootstrap.utils.BootstrapStyles._ 14 | import scalatags.JsDom.all._ 15 | 16 | val groups = SeqProperty[Seq[Int]](Seq[Seq[Int]](1 to 4, 5 to 7, 8 to 8)) 17 | 18 | div( 19 | UdashButtonToolbar.reactive(groups)((p, nested) => { 20 | val group = UdashButtonGroup.reactive( 21 | p.transformToSeq(identity), 22 | size = Some(Size.Large).toProperty[Option[Size]] 23 | ) { 24 | case (element, nested) => 25 | val btn = UdashButton()(_ => nested(bind(element))) 26 | nested(btn) 27 | btn.render 28 | } 29 | nested(group) 30 | group.render 31 | }) 32 | ).render 33 | }.withSourceCode 34 | 35 | override protected def demoWithSource(): (Modifier, String) = 36 | (rendered.setup(_.applyTags(GuideStyles.frame)), source) 37 | } 38 | 39 | -------------------------------------------------------------------------------- /guide/guide/.js/src/main/scala/io/udash/web/guide/views/ext/demo/bootstrap/InlineFormDemo.scala: -------------------------------------------------------------------------------- 1 | package io.udash.web.guide.views.ext.demo.bootstrap 2 | 3 | import io.udash.css.CssView 4 | import io.udash.web.guide.demos.AutoDemo 5 | import io.udash.web.guide.styles.partials.GuideStyles 6 | import scalatags.JsDom.all._ 7 | 8 | object InlineFormDemo extends AutoDemo with CssView { 9 | 10 | private val (rendered, source) = { 11 | import io.udash._ 12 | import io.udash.bootstrap.form.{UdashForm, UdashInputGroup} 13 | import scalatags.JsDom.all._ 14 | 15 | val search = Property.blank[String] 16 | val something = Property.blank[String] 17 | 18 | div( 19 | UdashForm(inline = true)(factory => Seq( 20 | UdashInputGroup()( 21 | UdashInputGroup.prependText("Search: "), 22 | UdashInputGroup.input( 23 | factory.input.textInput(search)().render 24 | ) 25 | ).render, 26 | UdashInputGroup()( 27 | UdashInputGroup.prependText("Something: "), 28 | UdashInputGroup.input( 29 | factory.input.textInput(something)().render 30 | ) 31 | ).render, 32 | )) 33 | ).render 34 | }.withSourceCode 35 | 36 | override protected def demoWithSource(): (Modifier, String) = 37 | (rendered.setup(_.applyTags(GuideStyles.frame)), source) 38 | } 39 | 40 | -------------------------------------------------------------------------------- /guide/guide/.js/src/main/scala/io/udash/web/guide/views/ext/demo/bootstrap/JumbotronDemo.scala: -------------------------------------------------------------------------------- 1 | package io.udash.web.guide.views.ext.demo.bootstrap 2 | 3 | import io.udash.bootstrap.button.UdashButtonOptions 4 | import io.udash.css.CssView 5 | import io.udash.web.guide.demos.AutoDemo 6 | import io.udash.web.guide.styles.partials.GuideStyles 7 | import scalatags.JsDom.all._ 8 | 9 | object JumbotronDemo extends AutoDemo with CssView { 10 | 11 | private val (rendered, source) = { 12 | import io.udash.bootstrap.button.UdashButton 13 | import io.udash.bootstrap.jumbotron.UdashJumbotron 14 | import io.udash.bootstrap.utils.BootstrapStyles._ 15 | import scalatags.JsDom.all._ 16 | 17 | UdashJumbotron()(_ => Seq[Modifier]( 18 | h1("Jumbo poem!"), 19 | p("One component to rule them all, one component to find them, " + 20 | "one component to bring them all and in the darkness bind them." 21 | ), 22 | UdashButton( 23 | options = UdashButtonOptions( 24 | color = Color.Info.opt, 25 | size = Size.Large.opt 26 | ) 27 | )(_ => "Click") 28 | )) 29 | }.withSourceCode 30 | 31 | override protected def demoWithSource(): (Modifier, String) = 32 | (div(GuideStyles.frame)(rendered), source) 33 | } 34 | 35 | -------------------------------------------------------------------------------- /guide/guide/.js/src/main/scala/io/udash/web/guide/views/ext/demo/bootstrap/ResponsiveEmbedDemo.scala: -------------------------------------------------------------------------------- 1 | package io.udash.web.guide.views.ext.demo.bootstrap 2 | 3 | import io.udash.web.guide.demos.AutoDemo 4 | import io.udash.web.guide.styles.partials.GuideStyles 5 | import scalatags.JsDom.all._ 6 | 7 | object ResponsiveEmbedDemo extends AutoDemo { 8 | 9 | private val (rendered, source) = { 10 | import io.udash.bootstrap.utils.BootstrapStyles._ 11 | import io.udash.css.CssView._ 12 | import scalatags.JsDom.all._ 13 | 14 | div( 15 | div( 16 | EmbedResponsive.responsive, 17 | EmbedResponsive.embed16by9, 18 | Spacing.margin(size = SpacingSize.Small) 19 | )( 20 | iframe( 21 | EmbedResponsive.item, 22 | src := "https://www.youtube.com/embed/zpOULjyy-n8?rel=0" 23 | ) 24 | ), 25 | div( 26 | EmbedResponsive.responsive, 27 | EmbedResponsive.embed4by3, 28 | Spacing.margin(size = SpacingSize.Small) 29 | )( 30 | iframe( 31 | EmbedResponsive.item, 32 | src := "https://www.youtube.com/embed/zpOULjyy-n8?rel=0" 33 | ) 34 | ) 35 | ).render 36 | }.withSourceCode 37 | 38 | override protected def demoWithSource(): (Modifier, String) = { 39 | import io.udash.css.CssView._ 40 | (rendered.setup(_.applyTags(GuideStyles.frame)), source) 41 | } 42 | } 43 | 44 | -------------------------------------------------------------------------------- /guide/guide/.js/src/main/scala/io/udash/web/guide/views/ext/demo/bootstrap/StaticButtonsGroupDemo.scala: -------------------------------------------------------------------------------- 1 | package io.udash.web.guide.views.ext.demo.bootstrap 2 | 3 | import io.udash.css.CssView 4 | import io.udash.web.guide.demos.AutoDemo 5 | import io.udash.web.guide.styles.partials.GuideStyles 6 | import scalatags.JsDom.all._ 7 | 8 | object StaticButtonsGroupDemo extends AutoDemo with CssView { 9 | 10 | private val (rendered, source) = { 11 | import io.udash._ 12 | import io.udash.bootstrap._ 13 | import BootstrapStyles.Color 14 | import io.udash.bootstrap.button._ 15 | import scalatags.JsDom.all._ 16 | 17 | div( 18 | UdashButtonGroup(vertical = true.toProperty)( 19 | UdashButton()("Button 1").render, 20 | UdashButton()("Button 2").render, 21 | UdashButton()("Button 3").render 22 | ) 23 | ).render 24 | }.withSourceCode 25 | 26 | override protected def demoWithSource(): (Modifier, String) = 27 | (rendered.setup(_.applyTags(GuideStyles.frame)), source) 28 | } 29 | 30 | -------------------------------------------------------------------------------- /guide/guide/.js/src/main/scala/io/udash/web/guide/views/frontend/FrontendView.scala: -------------------------------------------------------------------------------- 1 | package io.udash.web.guide.views.frontend 2 | 3 | import io.udash.* 4 | import io.udash.web.guide.FrontendState 5 | import io.udash.web.guide.views.ViewContainer 6 | import org.scalajs.dom.Element 7 | import scalatags.JsDom 8 | 9 | case object FrontendViewFactory extends StaticViewFactory[FrontendState.type](() => new FrontendView) 10 | 11 | 12 | class FrontendView extends ViewContainer { 13 | import JsDom.all._ 14 | 15 | override protected val child: Element = div().render 16 | 17 | override def getTemplate: Modifier = div( 18 | h1("Frontend"), 19 | p( 20 | "In this part of the guide you will read about creating a frontend application with Udash. Let's make your ", 21 | "frontend type-safe, elegant and maintainable. " 22 | ), 23 | child 24 | ) 25 | } -------------------------------------------------------------------------------- /guide/guide/.js/src/main/scala/io/udash/web/guide/views/frontend/demos/BindAttributeDemo.scala: -------------------------------------------------------------------------------- 1 | package io.udash.web.guide.views.frontend.demos 2 | 3 | import io.udash.css.CssView 4 | import io.udash.web.guide.demos.AutoDemo 5 | import io.udash.web.guide.styles.partials.GuideStyles 6 | import scalatags.JsDom.all._ 7 | 8 | object BindAttributeDemo extends AutoDemo with CssView { 9 | 10 | private val (rendered, source) = { 11 | import io.udash._ 12 | import org.scalajs.dom.window 13 | import scalatags.JsDom.all._ 14 | 15 | val visible = Property(true) 16 | window.setInterval(() => visible.set(!visible.get), 1000) 17 | 18 | p( 19 | span("Visible: ", bind(visible), " -> "), 20 | span((style := "display: none;").attrIfNot(visible))("Show/hide") 21 | ) 22 | }.withSourceCode 23 | 24 | override protected def demoWithSource(): (Modifier, String) = 25 | ( 26 | div( 27 | id := "bind-attr-demo", 28 | GuideStyles.frame 29 | )(rendered), 30 | source 31 | ) 32 | } 33 | -------------------------------------------------------------------------------- /guide/guide/.js/src/main/scala/io/udash/web/guide/views/frontend/demos/BindDemo.scala: -------------------------------------------------------------------------------- 1 | package io.udash.web.guide.views.frontend.demos 2 | 3 | import io.udash.css.CssView 4 | import io.udash.web.guide.demos.AutoDemo 5 | import io.udash.web.guide.styles.partials.GuideStyles 6 | import scalatags.JsDom.all._ 7 | 8 | object BindDemo extends AutoDemo with CssView { 9 | 10 | private val (rendered, source) = { 11 | import io.udash._ 12 | import org.scalajs.dom.window 13 | import scalatags.JsDom.all._ 14 | 15 | val names = LazyList.continually(LazyList("John", "Amy", "Bryan", "Diana")).flatten.iterator 16 | 17 | val name = Property(names.next()) 18 | window.setInterval(() => name.set(names.next()), 500) 19 | 20 | p("Name: ", bind(name)) 21 | }.withSourceCode 22 | 23 | override protected def demoWithSource(): (Modifier, String) = 24 | ( 25 | div( 26 | id := "bind-demo", 27 | GuideStyles.frame 28 | )(rendered), 29 | source 30 | ) 31 | } 32 | -------------------------------------------------------------------------------- /guide/guide/.js/src/main/scala/io/udash/web/guide/views/frontend/demos/ShowIfDemo.scala: -------------------------------------------------------------------------------- 1 | package io.udash.web.guide.views.frontend.demos 2 | 3 | import io.udash.css.CssView 4 | import io.udash.web.guide.demos.AutoDemo 5 | import io.udash.web.guide.styles.partials.GuideStyles 6 | import scalatags.JsDom.all._ 7 | 8 | object ShowIfDemo extends AutoDemo with CssView { 9 | 10 | private val (rendered, source) = { 11 | import io.udash._ 12 | import org.scalajs.dom.window 13 | import scalatags.JsDom.all._ 14 | 15 | val visible = Property(true) 16 | window.setInterval(() => visible.set(!visible.get), 1000) 17 | 18 | p( 19 | span("Visible: ", bind(visible), " -> "), 20 | showIf(visible)(span("Show/hide").render) 21 | ) 22 | }.withSourceCode 23 | 24 | override protected def demoWithSource(): (Modifier, String) = 25 | (div(id := "show-if-demo", GuideStyles.frame)(rendered), source) 26 | } 27 | -------------------------------------------------------------------------------- /guide/guide/.js/src/main/scala/io/udash/web/guide/views/frontend/demos/TextAreaDemo.scala: -------------------------------------------------------------------------------- 1 | package io.udash.web.guide.views.frontend.demos 2 | 3 | import io.udash.web.guide.demos.AutoDemo 4 | import io.udash.web.guide.styles.partials.GuideStyles 5 | import scalatags.JsDom.all._ 6 | 7 | object TextAreaDemo extends AutoDemo { 8 | 9 | private val (rendered, source) = { 10 | import io.udash._ 11 | import io.udash.bootstrap.utils.BootstrapStyles._ 12 | import io.udash.css.CssView._ 13 | import scalatags.JsDom.all._ 14 | 15 | val text = Property("") 16 | 17 | form(containerFluid)( 18 | div(Grid.row)( 19 | div(Grid.col(4, ResponsiveBreakpoint.Medium))( 20 | TextArea(text)(Form.control) 21 | ), 22 | div(Grid.col(4, ResponsiveBreakpoint.Medium))( 23 | TextArea(text)(Form.control) 24 | ), 25 | div(Grid.col(4, ResponsiveBreakpoint.Medium))( 26 | TextArea(text)(Form.control) 27 | ) 28 | ) 29 | ) 30 | }.withSourceCode 31 | 32 | override protected def demoWithSource(): (Modifier, String) = { 33 | import io.udash.css.CssView._ 34 | (div(id := "text-area-demo", GuideStyles.frame, GuideStyles.useBootstrap)(rendered), source) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /guide/guide/.js/src/main/scala/io/udash/web/guide/views/rpc/RpcView.scala: -------------------------------------------------------------------------------- 1 | package io.udash.web.guide.views.rpc 2 | 3 | import io.udash.* 4 | import io.udash.web.guide.* 5 | import io.udash.web.guide.views.ViewContainer 6 | import org.scalajs.dom.Element 7 | import scalatags.JsDom 8 | 9 | case object RpcViewFactory extends StaticViewFactory[RpcState.type](() => new RpcView) 10 | 11 | class RpcView extends ViewContainer { 12 | import JsDom.all._ 13 | 14 | override protected val child: Element = div().render 15 | 16 | override def getTemplate: Modifier = div( 17 | h1("RPC in Udash"), 18 | p("In this part of the guide you can read about client-server communication in a Udash application."), 19 | child 20 | ) 21 | } -------------------------------------------------------------------------------- /guide/homepage/.js/src/main/assets/assets.less: -------------------------------------------------------------------------------- 1 | @import (inline) "prism.css"; -------------------------------------------------------------------------------- /guide/homepage/.js/src/main/assets/fonts/roboto/Roboto-Black.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UdashFramework/udash-core/bc1969122eaf0e057803c052a0be3861f123d975/guide/homepage/.js/src/main/assets/fonts/roboto/Roboto-Black.ttf -------------------------------------------------------------------------------- /guide/homepage/.js/src/main/assets/fonts/roboto/Roboto-BlackItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UdashFramework/udash-core/bc1969122eaf0e057803c052a0be3861f123d975/guide/homepage/.js/src/main/assets/fonts/roboto/Roboto-BlackItalic.ttf -------------------------------------------------------------------------------- /guide/homepage/.js/src/main/assets/fonts/roboto/Roboto-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UdashFramework/udash-core/bc1969122eaf0e057803c052a0be3861f123d975/guide/homepage/.js/src/main/assets/fonts/roboto/Roboto-Bold.ttf -------------------------------------------------------------------------------- /guide/homepage/.js/src/main/assets/fonts/roboto/Roboto-BoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UdashFramework/udash-core/bc1969122eaf0e057803c052a0be3861f123d975/guide/homepage/.js/src/main/assets/fonts/roboto/Roboto-BoldItalic.ttf -------------------------------------------------------------------------------- /guide/homepage/.js/src/main/assets/fonts/roboto/Roboto-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UdashFramework/udash-core/bc1969122eaf0e057803c052a0be3861f123d975/guide/homepage/.js/src/main/assets/fonts/roboto/Roboto-Italic.ttf -------------------------------------------------------------------------------- /guide/homepage/.js/src/main/assets/fonts/roboto/Roboto-Light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UdashFramework/udash-core/bc1969122eaf0e057803c052a0be3861f123d975/guide/homepage/.js/src/main/assets/fonts/roboto/Roboto-Light.ttf -------------------------------------------------------------------------------- /guide/homepage/.js/src/main/assets/fonts/roboto/Roboto-LightItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UdashFramework/udash-core/bc1969122eaf0e057803c052a0be3861f123d975/guide/homepage/.js/src/main/assets/fonts/roboto/Roboto-LightItalic.ttf -------------------------------------------------------------------------------- /guide/homepage/.js/src/main/assets/fonts/roboto/Roboto-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UdashFramework/udash-core/bc1969122eaf0e057803c052a0be3861f123d975/guide/homepage/.js/src/main/assets/fonts/roboto/Roboto-Medium.ttf -------------------------------------------------------------------------------- /guide/homepage/.js/src/main/assets/fonts/roboto/Roboto-MediumItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UdashFramework/udash-core/bc1969122eaf0e057803c052a0be3861f123d975/guide/homepage/.js/src/main/assets/fonts/roboto/Roboto-MediumItalic.ttf -------------------------------------------------------------------------------- /guide/homepage/.js/src/main/assets/fonts/roboto/Roboto-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UdashFramework/udash-core/bc1969122eaf0e057803c052a0be3861f123d975/guide/homepage/.js/src/main/assets/fonts/roboto/Roboto-Regular.ttf -------------------------------------------------------------------------------- /guide/homepage/.js/src/main/assets/fonts/roboto/Roboto-Thin.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UdashFramework/udash-core/bc1969122eaf0e057803c052a0be3861f123d975/guide/homepage/.js/src/main/assets/fonts/roboto/Roboto-Thin.ttf -------------------------------------------------------------------------------- /guide/homepage/.js/src/main/assets/fonts/roboto/Roboto-ThinItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UdashFramework/udash-core/bc1969122eaf0e057803c052a0be3861f123d975/guide/homepage/.js/src/main/assets/fonts/roboto/Roboto-ThinItalic.ttf -------------------------------------------------------------------------------- /guide/homepage/.js/src/main/assets/images/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UdashFramework/udash-core/bc1969122eaf0e057803c052a0be3861f123d975/guide/homepage/.js/src/main/assets/images/favicon.ico -------------------------------------------------------------------------------- /guide/homepage/.js/src/main/assets/images/features_compiled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UdashFramework/udash-core/bc1969122eaf0e057803c052a0be3861f123d975/guide/homepage/.js/src/main/assets/images/features_compiled.png -------------------------------------------------------------------------------- /guide/homepage/.js/src/main/assets/images/features_shared.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UdashFramework/udash-core/bc1969122eaf0e057803c052a0be3861f123d975/guide/homepage/.js/src/main/assets/images/features_shared.png -------------------------------------------------------------------------------- /guide/homepage/.js/src/main/assets/images/features_typesafe.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UdashFramework/udash-core/bc1969122eaf0e057803c052a0be3861f123d975/guide/homepage/.js/src/main/assets/images/features_typesafe.png -------------------------------------------------------------------------------- /guide/homepage/.js/src/main/assets/images/intro_bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UdashFramework/udash-core/bc1969122eaf0e057803c052a0be3861f123d975/guide/homepage/.js/src/main/assets/images/intro_bg.jpg -------------------------------------------------------------------------------- /guide/homepage/.js/src/main/assets/images/intro_bird.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UdashFramework/udash-core/bc1969122eaf0e057803c052a0be3861f123d975/guide/homepage/.js/src/main/assets/images/intro_bird.png -------------------------------------------------------------------------------- /guide/homepage/.js/src/main/assets/images/laptop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UdashFramework/udash-core/bc1969122eaf0e057803c052a0be3861f123d975/guide/homepage/.js/src/main/assets/images/laptop.png -------------------------------------------------------------------------------- /guide/homepage/.js/src/main/assets/images/share/share_facebook.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UdashFramework/udash-core/bc1969122eaf0e057803c052a0be3861f123d975/guide/homepage/.js/src/main/assets/images/share/share_facebook.png -------------------------------------------------------------------------------- /guide/homepage/.js/src/main/assets/images/share/share_google.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UdashFramework/udash-core/bc1969122eaf0e057803c052a0be3861f123d975/guide/homepage/.js/src/main/assets/images/share/share_google.png -------------------------------------------------------------------------------- /guide/homepage/.js/src/main/assets/images/share/share_twitter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UdashFramework/udash-core/bc1969122eaf0e057803c052a0be3861f123d975/guide/homepage/.js/src/main/assets/images/share/share_twitter.png -------------------------------------------------------------------------------- /guide/homepage/.js/src/main/assets/images/udash_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UdashFramework/udash-core/bc1969122eaf0e057803c052a0be3861f123d975/guide/homepage/.js/src/main/assets/images/udash_logo.png -------------------------------------------------------------------------------- /guide/homepage/.js/src/main/assets/images/udash_logo_l.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UdashFramework/udash-core/bc1969122eaf0e057803c052a0be3861f123d975/guide/homepage/.js/src/main/assets/images/udash_logo_l.png -------------------------------------------------------------------------------- /guide/homepage/.js/src/main/assets/images/udash_logo_m.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UdashFramework/udash-core/bc1969122eaf0e057803c052a0be3861f123d975/guide/homepage/.js/src/main/assets/images/udash_logo_m.png -------------------------------------------------------------------------------- /guide/homepage/.js/src/main/scala/io/udash/web/homepage/RoutingRegistryDef.scala: -------------------------------------------------------------------------------- 1 | package io.udash.web.homepage 2 | 3 | import io.udash._ 4 | 5 | class RoutingRegistryDef extends RoutingRegistry[RoutingState] { 6 | def matchUrl(url: Url): RoutingState = 7 | url2State.applyOrElse("/" + url.value.stripPrefix("/").stripSuffix("/"), (_: String) => ErrorState) 8 | 9 | def matchState(state: RoutingState): Url = 10 | Url(state2Url.apply(state)) 11 | 12 | private val (url2State, state2Url) = bidirectional { 13 | case "/" => HelloState 14 | case "/demo" / "select" => SelectState 15 | } 16 | } -------------------------------------------------------------------------------- /guide/homepage/.js/src/main/scala/io/udash/web/homepage/StatesToViewFactoryDef.scala: -------------------------------------------------------------------------------- 1 | package io.udash.web.homepage 2 | 3 | import io.udash._ 4 | import io.udash.web.homepage.views._ 5 | 6 | final class StatesToViewFactoryDef extends ViewFactoryRegistry[RoutingState] { 7 | 8 | import Context.applicationInstance 9 | 10 | def matchStateToResolver(state: RoutingState): ViewFactory[_ <: RoutingState] = state match { 11 | case RootState => RootViewFactory 12 | case _: IndexState => new IndexViewFactory 13 | case _ => ErrorViewFactory 14 | } 15 | } -------------------------------------------------------------------------------- /guide/homepage/.js/src/main/scala/io/udash/web/homepage/components/Buttons.scala: -------------------------------------------------------------------------------- 1 | package io.udash.web.homepage.components 2 | 3 | import io.udash.web.homepage.styles.partials.ButtonsStyle 4 | 5 | import scalatags.JsDom.all._ 6 | 7 | /** 8 | * Created by malchik on 2016-04-04. 9 | */ 10 | object Buttons { 11 | import io.udash.css.CssView._ 12 | def whiteBorderButton(link: String, label: String, xs: Modifier*): Modifier = 13 | a(href := link, target := "_blank", ButtonsStyle.btnDefault, xs: Modifier)( 14 | div(ButtonsStyle.btnDefaultInner)(label) 15 | ) 16 | 17 | def blackBorderButton(link: String, label: String, xs: Modifier*): Modifier = 18 | a(href := link, target := "_blank", ButtonsStyle.btnDefault, ButtonsStyle.btnDefaultBlack, xs: Modifier)( 19 | div(ButtonsStyle.btnDefaultInner, ButtonsStyle.btnDefaultInnerBlack)(label) 20 | ) 21 | } 22 | -------------------------------------------------------------------------------- /guide/homepage/.js/src/main/scala/io/udash/web/homepage/components/demo/CodeDemo.scala: -------------------------------------------------------------------------------- 1 | package io.udash.web.homepage.components.demo 2 | 3 | import com.avsystem.commons.SharedExtensions._ 4 | import io.udash._ 5 | import scalatags.JsDom.all._ 6 | 7 | trait CodeDemo { 8 | def rendered: Modifier 9 | def source: String 10 | } 11 | 12 | object HelloDemo extends CodeDemo { 13 | val (rendered: Modifier, source) = { 14 | val name = Property.blank[String] 15 | div( 16 | TextInput(name)(), 17 | p("Hello, ", bind(name), "!"), 18 | ) 19 | }.withSourceCode 20 | } 21 | 22 | object SelectDemo extends CodeDemo { 23 | val (rendered: Modifier, source) = { 24 | import com.avsystem.commons.misc.AutoNamedEnum 25 | 26 | sealed trait Fruit extends AutoNamedEnum 27 | case object Apple extends Fruit 28 | case object Banana extends Fruit 29 | case object Orange extends Fruit 30 | 31 | val fruits = Seq(Apple, Banana, Orange) 32 | val favoriteFruits = SeqProperty[Fruit](Banana) 33 | 34 | div( 35 | div(Select(favoriteFruits, fruits.toSeqProperty)(_.name)), 36 | div(produce(favoriteFruits)(_.mkString(",").render)), 37 | ) 38 | }.withSourceCode 39 | } 40 | -------------------------------------------------------------------------------- /guide/homepage/.js/src/main/scala/io/udash/web/homepage/init.scala: -------------------------------------------------------------------------------- 1 | package io.udash.web.homepage 2 | 3 | import io.udash._ 4 | import io.udash.logging.CrossLogging 5 | import io.udash.routing.WindowUrlPathChangeProvider 6 | 7 | import scala.scalajs.js.annotation.JSExport 8 | 9 | object Context { 10 | private val routingRegistry = new RoutingRegistryDef 11 | private val viewFactoriesRegistry = new StatesToViewFactoryDef 12 | 13 | implicit val applicationInstance: Application[RoutingState] = 14 | new Application[RoutingState](routingRegistry, viewFactoriesRegistry, new WindowUrlPathChangeProvider) 15 | } 16 | 17 | object Init extends CrossLogging { 18 | import Context._ 19 | 20 | @JSExport 21 | def main(args: Array[String]): Unit = 22 | applicationInstance.run("#application") 23 | } -------------------------------------------------------------------------------- /guide/homepage/.js/src/main/scala/io/udash/web/homepage/states.scala: -------------------------------------------------------------------------------- 1 | package io.udash.web.homepage 2 | 3 | import com.avsystem.commons.misc.AbstractSealedEnumCompanion 4 | import io.udash._ 5 | import io.udash.web.homepage.components.demo.{CodeDemo, HelloDemo, SelectDemo} 6 | 7 | sealed abstract class RoutingState(val parentState: Option[ContainerRoutingState]) extends State { 8 | override type HierarchyRoot = RoutingState 9 | def url(implicit application: Application[RoutingState]): String = s"${application.matchState(this).value}" 10 | } 11 | sealed abstract class ContainerRoutingState(parentState: Option[ContainerRoutingState]) extends RoutingState(parentState) 12 | 13 | case object RootState extends ContainerRoutingState(None) 14 | 15 | case object ErrorState extends RoutingState(Some(RootState)) 16 | 17 | sealed abstract class IndexState(val name: String, val codeDemo: CodeDemo) extends RoutingState(Some(RootState)) 18 | case object HelloState extends IndexState("Hello, World!", HelloDemo) 19 | case object SelectState extends IndexState("Select", SelectDemo) 20 | object IndexState extends AbstractSealedEnumCompanion[IndexState] { 21 | override val values: Seq[IndexState] = caseObjects 22 | } -------------------------------------------------------------------------------- /guide/homepage/.js/src/main/scala/io/udash/web/homepage/views/RootView.scala: -------------------------------------------------------------------------------- 1 | package io.udash.web.homepage.views 2 | 3 | import com.avsystem.commons._ 4 | import io.udash._ 5 | import io.udash.css.CssView 6 | import io.udash.web.commons.components.Footer 7 | import io.udash.web.commons.styles.GlobalStyles 8 | import io.udash.web.homepage.RootState 9 | import io.udash.web.homepage.components.Header 10 | import io.udash.web.homepage.styles.partials.HomepageStyles 11 | import scalatags.JsDom.tags2._ 12 | 13 | import scala.scalajs.js 14 | 15 | object RootViewFactory extends StaticViewFactory[RootState.type](() => new RootView) 16 | 17 | class RootView extends ContainerView with CssView { 18 | import scalatags.JsDom.all._ 19 | 20 | private val content = div( 21 | Header.getTemplate, 22 | main(GlobalStyles.main)( 23 | childViewContainer 24 | ), 25 | Footer.getTemplate(HomepageStyles.body.opt) 26 | ) 27 | 28 | override def getTemplate: Modifier = content 29 | 30 | override def renderChild(view: Option[View]): Unit = { 31 | super.renderChild(view) 32 | js.Dynamic.global.svg4everybody() 33 | } 34 | } -------------------------------------------------------------------------------- /guide/packager/src/main/resources/application.conf: -------------------------------------------------------------------------------- 1 | ui { 2 | server { 3 | port = 8080 4 | homepageResourceBase = "UdashStatics/WebContent/homepage/" 5 | guideResourceBase = "UdashStatics/WebContent/guide/" 6 | } 7 | } -------------------------------------------------------------------------------- /guide/selenium/src/test/resources/application.conf: -------------------------------------------------------------------------------- 1 | ui.server.port = 8089 -------------------------------------------------------------------------------- /guide/selenium/src/test/scala/io/udash/web/guide/demos/rpc/RpcBackendTest.scala: -------------------------------------------------------------------------------- 1 | package io.udash.web.guide.demos.rpc 2 | 3 | import io.udash.web.SeleniumTest 4 | import org.scalatest.BeforeAndAfterEach 5 | 6 | class RpcBackendTest extends SeleniumTest with BeforeAndAfterEach { 7 | override protected final val url = "/rpc/client-server" 8 | 9 | "RpcBackend view" should { 10 | "receive ClientId in demo" in { 11 | val callDemo = findElementById("client-id-demo") 12 | var response = findElementById("client-id-demo-response") 13 | 14 | callDemo.isEnabled should be(true) 15 | response.getText.equalsIgnoreCase("???") should be(true) 16 | 17 | callDemo.click() 18 | 19 | eventually { 20 | response = findElementById("client-id-demo-response") 21 | response.getText.startsWith("ClientId") should be(true) 22 | callDemo.isEnabled should be(false) 23 | } 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /guide/selenium/src/test/scala/io/udash/web/guide/demos/rpc/RpcIntroTest.scala: -------------------------------------------------------------------------------- 1 | package io.udash.web.guide.demos.rpc 2 | 3 | import io.udash.web.SeleniumTest 4 | import org.openqa.selenium.WebElement 5 | 6 | class RpcIntroTest extends SeleniumTest { 7 | override protected final val url = "/rpc" 8 | 9 | "RpcIntro view" should { 10 | "receive response in call demo" in { 11 | val callDemo = findElementById("ping-pong-call-demo") 12 | buttonTest(callDemo) 13 | } 14 | 15 | "receive response in push demo" in { 16 | val pushDemo = findElementById("ping-pong-push-demo") 17 | buttonTest(pushDemo) 18 | } 19 | } 20 | 21 | def buttonTest(callDemo: WebElement): Unit = { 22 | for (i <- 1 to 3) { 23 | callDemo.click() 24 | eventually { 25 | callDemo.isEnabled should be(true) 26 | callDemo.getText should be(s"Ping($i)") 27 | } 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /guide/selenium/src/test/scala/io/udash/web/guide/demos/rpc/RpcSerializationTest.scala: -------------------------------------------------------------------------------- 1 | package io.udash.web.guide.demos.rpc 2 | 3 | import io.udash.web.SeleniumTest 4 | 5 | class RpcSerializationTest extends SeleniumTest { 6 | override protected final val url = "/rpc/serialization" 7 | 8 | "RpcSerialization view" should { 9 | "receive msg from backend" in { 10 | val callDemo = findElementById("gencodec-demo") 11 | 12 | callDemo.isEnabled should be(true) 13 | callDemo.click() 14 | 15 | eventually { 16 | findElementById("gencodec-demo-int").getText shouldNot be(empty) 17 | findElementById("gencodec-demo-double").getText shouldNot be(empty) 18 | findElementById("gencodec-demo-string").getText shouldNot be(empty) 19 | findElementById("gencodec-demo-seq").getText shouldNot be(empty) 20 | findElementById("gencodec-demo-map").getText shouldNot be(empty) 21 | findElementById("gencodec-demo-caseClass").getText shouldNot be(empty) 22 | findElementById("gencodec-demo-cls-int").getText shouldNot be(empty) 23 | findElementById("gencodec-demo-cls-string").getText shouldNot be(empty) 24 | findElementById("gencodec-demo-cls-var").getText shouldNot be(empty) 25 | findElementById("gencodec-demo-sealedTrait").getText shouldNot be(empty) 26 | } 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /guide/shared/src/main/scala/io/udash/web/commons/styles/attributes/Attributes.scala: -------------------------------------------------------------------------------- 1 | package io.udash.web.commons.styles.attributes 2 | 3 | object Attributes { 4 | /** 5 | * States 6 | */ 7 | val Hidden = "hidden" 8 | val Active = "active" 9 | val Disabled = "disabled" 10 | val Enabled = "enabled" 11 | val Checked = "checked" 12 | val Show = "show" 13 | val Expanded = "expanded" 14 | val State = "state" 15 | val Pinned = "pinned" 16 | 17 | /** 18 | * Generate attribute data-attr 19 | * @param attr name of attribute 20 | * @return attribute data-attr 21 | */ 22 | def data(attr: String) = s"data-$attr" 23 | } 24 | -------------------------------------------------------------------------------- /guide/shared/src/main/scala/io/udash/web/commons/styles/components/CodeBlockStyles.scala: -------------------------------------------------------------------------------- 1 | package io.udash.web.commons.styles.components 2 | 3 | import io.udash.css.{CssBase, CssStyle} 4 | 5 | trait CodeBlockStyles extends CssBase { 6 | val codeWrapper: CssStyle 7 | val codeBlock: CssStyle 8 | } 9 | -------------------------------------------------------------------------------- /guide/shared/src/main/scala/io/udash/web/commons/styles/utils/MediaQueries.scala: -------------------------------------------------------------------------------- 1 | package io.udash.web.commons.styles.utils 2 | 3 | import io.udash.css.{CssBase, CssStyle} 4 | import scalacss.internal.DslBase.ToStyle 5 | 6 | import scala.language.postfixOps 7 | 8 | object MediaQueries extends CssBase { 9 | import dsl._ 10 | 11 | def desktop(properties: ToStyle*): CssStyle = mixin( 12 | media.screen.minWidth(StyleConstants.MediaQueriesBounds.TabletLandscapeMax + 1 px) ( 13 | properties:_* 14 | ) 15 | ) 16 | 17 | def tabletLandscape(properties: ToStyle*): CssStyle = mixin( 18 | media.screen.minWidth(1 px).maxWidth(StyleConstants.MediaQueriesBounds.TabletLandscapeMax px) ( 19 | properties:_* 20 | ) 21 | ) 22 | 23 | def tabletPortrait(properties: ToStyle*): CssStyle = mixin( 24 | media.screen.minWidth(1 px).maxWidth(StyleConstants.MediaQueriesBounds.TabletMax px) ( 25 | properties:_* 26 | ) 27 | ) 28 | 29 | def phone(properties: ToStyle*): CssStyle = mixin( 30 | media.screen.minWidth(1 px).maxWidth(StyleConstants.MediaQueriesBounds.PhoneMax px) ( 31 | properties:_* 32 | ) 33 | ) 34 | } 35 | -------------------------------------------------------------------------------- /guide/shared/src/main/scala/io/udash/web/commons/styles/utils/StyleConstants.scala: -------------------------------------------------------------------------------- 1 | package io.udash.web.commons.styles.utils 2 | 3 | import io.udash.css.CssBase 4 | 5 | object StyleConstants extends CssBase { 6 | import dsl._ 7 | 8 | /** 9 | * SIZES 10 | */ 11 | object Sizes { 12 | val BodyWidth = 1075 13 | val BodyPaddingPx = 30 14 | val MinSiteHeight = 550 15 | val LandingPageHeaderHeight = 150 16 | val HeaderHeight = 80 17 | val GuideHeaderHeightMobile = HeaderHeight * .7 18 | val HeaderHeightPin = 80 19 | val FooterHeight = 120 20 | val MenuWidth = 320 21 | } 22 | 23 | /** 24 | * COLORS 25 | */ 26 | object Colors { 27 | val Red = c"#e30613" 28 | val RedLight = c"#ff2727" 29 | val RedDark = c"#a6031b" 30 | val Grey = c"#898989" 31 | val GreyExtra = c"#ebebeb" 32 | val GreySemi = c"#cfcfd6" 33 | val GreySuperDark = c"#1c1c1e" 34 | val Yellow = c"#ffd600" 35 | } 36 | 37 | /** 38 | * MEDIA QUERIES 39 | */ 40 | object MediaQueriesBounds { 41 | val TabletLandscapeMax = 1074 42 | val TabletMax = 767 43 | val PhoneMax = 480 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /guide/shared/src/main/scala/io/udash/web/guide/GuideExceptions.scala: -------------------------------------------------------------------------------- 1 | package io.udash.web.guide 2 | 3 | import com.avsystem.commons.SharedExtensions._ 4 | import com.avsystem.commons.serialization.GenCodec 5 | import io.udash.i18n.TranslationKey0 6 | import io.udash.rpc.serialization.{DefaultExceptionCodecRegistry, ExceptionCodecRegistry} 7 | 8 | object GuideExceptions { 9 | case class ExampleException(msg: String) extends Exception(msg) 10 | case class TranslatableExampleException(trKey: TranslationKey0) extends Exception 11 | 12 | val registry: ExceptionCodecRegistry = (new DefaultExceptionCodecRegistry).setup { registry => 13 | registry.register(GenCodec.materialize[ExampleException]) 14 | registry.register(GenCodec.materialize[TranslatableExampleException]) 15 | registry 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /guide/shared/src/main/scala/io/udash/web/guide/MainClientRPC.scala: -------------------------------------------------------------------------------- 1 | package io.udash.web.guide 2 | 3 | import io.udash.rpc._ 4 | import io.udash.web.guide.demos.DemosClientRPC 5 | 6 | trait MainClientRPC { 7 | def demos(): DemosClientRPC 8 | } 9 | 10 | object MainClientRPC extends DefaultClientRpcCompanion[MainClientRPC] -------------------------------------------------------------------------------- /guide/shared/src/main/scala/io/udash/web/guide/MainServerRPC.scala: -------------------------------------------------------------------------------- 1 | package io.udash.web.guide 2 | 3 | import io.udash.rpc._ 4 | import io.udash.web.guide.demos.DemosServerRPC 5 | import io.udash.web.guide.markdown.MarkdownPageRPC 6 | 7 | trait MainServerRPC { 8 | def demos: DemosServerRPC 9 | def pages: MarkdownPageRPC 10 | } 11 | 12 | object MainServerRPC extends DefaultServerRpcCompanion[MainServerRPC] -------------------------------------------------------------------------------- /guide/shared/src/main/scala/io/udash/web/guide/demos/DemosClientRPC.scala: -------------------------------------------------------------------------------- 1 | package io.udash.web.guide.demos 2 | 3 | import io.udash.rpc._ 4 | import io.udash.web.guide.demos.rpc.{NotificationsClientRPC, PingClientRPC} 5 | 6 | trait DemosClientRPC { 7 | def pingDemo(): PingClientRPC 8 | def notificationsDemo(): NotificationsClientRPC 9 | } 10 | 11 | object DemosClientRPC extends DefaultClientRpcCompanion[DemosClientRPC] -------------------------------------------------------------------------------- /guide/shared/src/main/scala/io/udash/web/guide/demos/DemosServerRPC.scala: -------------------------------------------------------------------------------- 1 | package io.udash.web.guide.demos 2 | 3 | import io.udash.rpc._ 4 | import io.udash.web.guide.demos.activity.CallServerRPC 5 | import io.udash.web.guide.demos.rpc._ 6 | 7 | trait DemosServerRPC { 8 | import io.udash.i18n._ 9 | 10 | def pingDemo: PingServerRPC 11 | def clientIdDemo: ClientIdServerRPC 12 | def notificationsDemo: NotificationsServerRPC 13 | def gencodecsDemo: GenCodecServerRPC 14 | def translations: RemoteTranslationRPC 15 | def exceptions: ExceptionsRPC 16 | 17 | def call: CallServerRPC 18 | } 19 | 20 | object DemosServerRPC extends DefaultServerRpcCompanion[DemosServerRPC] 21 | -------------------------------------------------------------------------------- /guide/shared/src/main/scala/io/udash/web/guide/demos/activity/Call.scala: -------------------------------------------------------------------------------- 1 | package io.udash.web.guide.demos.activity 2 | 3 | import com.avsystem.commons.serialization.HasGenCodec 4 | 5 | case class Call(rpcName: String, method: String, args: Seq[String]) { 6 | override def toString: String = s"$rpcName.$method args: ${args.mkString("[", ", ", "]")}" 7 | } 8 | object Call extends HasGenCodec[Call] -------------------------------------------------------------------------------- /guide/shared/src/main/scala/io/udash/web/guide/demos/activity/CallServerRPC.scala: -------------------------------------------------------------------------------- 1 | package io.udash.web.guide.demos.activity 2 | 3 | import io.udash.rpc._ 4 | 5 | import scala.concurrent.Future 6 | 7 | trait CallServerRPC { 8 | def calls: Future[Seq[Call]] 9 | } 10 | 11 | object CallServerRPC extends DefaultServerRpcCompanion[CallServerRPC] 12 | -------------------------------------------------------------------------------- /guide/shared/src/main/scala/io/udash/web/guide/demos/i18n/Translations.scala: -------------------------------------------------------------------------------- 1 | package io.udash.web.guide.demos.i18n 2 | 3 | import io.udash.i18n.TranslationKey 4 | 5 | 6 | object Translations { 7 | import TranslationKey._ 8 | 9 | object auth { 10 | val loginLabel = key("auth.loginLabel") 11 | val passwordLabel = key("auth.passwordLabel") 12 | 13 | object login { 14 | val buttonLabel = key("auth.login.buttonLabel") 15 | val retriesLeft = key1[Int]("auth.login.retriesLeft") 16 | val retriesLeftOne = key("auth.login.retriesLeftOne") 17 | } 18 | 19 | object register { 20 | val buttonLabel = key("auth.register.buttonLabel") 21 | } 22 | } 23 | 24 | object exceptions { 25 | val example = key("server.exception.example") 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /guide/shared/src/main/scala/io/udash/web/guide/demos/rest/MainServerREST.scala: -------------------------------------------------------------------------------- 1 | package io.udash.web.guide.demos.rest 2 | 3 | import io.udash.rest._ 4 | 5 | import scala.concurrent.Future 6 | 7 | trait MainServerREST { 8 | def simple(): SimpleServerREST 9 | def echo(): EchoServerREST 10 | } 11 | object MainServerREST extends DefaultRestApiCompanion[MainServerREST] 12 | 13 | trait SimpleServerREST { 14 | @GET def string(): Future[String] 15 | @GET def int(): Future[Int] 16 | @GET def cls(): Future[RestExampleClass] 17 | } 18 | object SimpleServerREST extends DefaultRestApiCompanion[SimpleServerREST] 19 | 20 | trait EchoServerREST { 21 | def withQuery(@Query("param") arg: String): Future[String] 22 | def withHeader(@Header("X-test") arg: String): Future[String] 23 | def withUrlPart(@Path arg: String): Future[String] 24 | def withBody(@Body arg: String): Future[String] 25 | } 26 | object EchoServerREST extends DefaultRestApiCompanion[EchoServerREST] -------------------------------------------------------------------------------- /guide/shared/src/main/scala/io/udash/web/guide/demos/rest/RestExampleClass.scala: -------------------------------------------------------------------------------- 1 | package io.udash.web.guide.demos.rest 2 | 3 | import io.udash.rest.RestDataCompanion 4 | 5 | case class RestExampleClass(i: Int, s: String, inner: InnerClass) 6 | object RestExampleClass extends RestDataCompanion[RestExampleClass] 7 | 8 | case class InnerClass(d: Double, s: String) 9 | object InnerClass extends RestDataCompanion[InnerClass] -------------------------------------------------------------------------------- /guide/shared/src/main/scala/io/udash/web/guide/demos/rpc/ClientIdServerRPC.scala: -------------------------------------------------------------------------------- 1 | package io.udash.web.guide.demos.rpc 2 | 3 | import io.udash.rpc._ 4 | import io.udash.rpc.utils.Logged 5 | 6 | import scala.concurrent.Future 7 | 8 | trait ClientIdServerRPC { 9 | @Logged 10 | def clientId(): Future[String] 11 | } 12 | 13 | object ClientIdServerRPC extends DefaultServerRpcCompanion[ClientIdServerRPC] -------------------------------------------------------------------------------- /guide/shared/src/main/scala/io/udash/web/guide/demos/rpc/ExceptionsRPC.scala: -------------------------------------------------------------------------------- 1 | package io.udash.web.guide.demos.rpc 2 | 3 | import io.udash.rpc._ 4 | 5 | import scala.concurrent.Future 6 | 7 | trait ExceptionsRPC { 8 | def example(): Future[Unit] 9 | def exampleWithTranslatableError(): Future[Unit] 10 | def unknownError(): Future[Unit] 11 | } 12 | 13 | object ExceptionsRPC extends DefaultServerRpcCompanion[ExceptionsRPC] -------------------------------------------------------------------------------- /guide/shared/src/main/scala/io/udash/web/guide/demos/rpc/NotificationsClientRPC.scala: -------------------------------------------------------------------------------- 1 | package io.udash.web.guide.demos.rpc 2 | 3 | import io.udash.rpc._ 4 | 5 | trait NotificationsClientRPC { 6 | def notify(msg: String): Unit 7 | } 8 | 9 | object NotificationsClientRPC extends DefaultClientRpcCompanion[NotificationsClientRPC] -------------------------------------------------------------------------------- /guide/shared/src/main/scala/io/udash/web/guide/demos/rpc/NotificationsServerRPC.scala: -------------------------------------------------------------------------------- 1 | package io.udash.web.guide.demos.rpc 2 | 3 | import io.udash.rpc._ 4 | import io.udash.rpc.utils.Logged 5 | 6 | import scala.concurrent.Future 7 | 8 | trait NotificationsServerRPC { 9 | @Logged 10 | def register(): Future[Unit] 11 | @Logged 12 | def unregister(): Future[Unit] 13 | } 14 | 15 | object NotificationsServerRPC extends DefaultServerRpcCompanion[NotificationsServerRPC] 16 | -------------------------------------------------------------------------------- /guide/shared/src/main/scala/io/udash/web/guide/demos/rpc/PingClientRPC.scala: -------------------------------------------------------------------------------- 1 | package io.udash.web.guide.demos.rpc 2 | 3 | import io.udash.rpc._ 4 | 5 | trait PingClientRPC { 6 | def pong(id: Int): Unit 7 | } 8 | 9 | object PingClientRPC extends DefaultClientRpcCompanion[PingClientRPC] 10 | -------------------------------------------------------------------------------- /guide/shared/src/main/scala/io/udash/web/guide/demos/rpc/PingServerRPC.scala: -------------------------------------------------------------------------------- 1 | package io.udash.web.guide.demos.rpc 2 | 3 | import io.udash.rpc._ 4 | import io.udash.rpc.utils.Logged 5 | 6 | import scala.concurrent.Future 7 | 8 | trait PingServerRPC { 9 | def ping(id: Int): Unit 10 | 11 | @Logged 12 | def fPing(id: Int): Future[Int] 13 | } 14 | 15 | object PingServerRPC extends DefaultServerRpcCompanion[PingServerRPC] 16 | -------------------------------------------------------------------------------- /guide/shared/src/main/scala/io/udash/web/guide/markdown/MarkdownPage.scala: -------------------------------------------------------------------------------- 1 | package io.udash.web.guide.markdown 2 | 3 | import com.avsystem.commons.misc.{AbstractValueEnum, AbstractValueEnumCompanion, EnumCtx} 4 | 5 | final class MarkdownPage(val file: String)(implicit val ctx: EnumCtx) extends AbstractValueEnum 6 | object MarkdownPage extends AbstractValueEnumCompanion[MarkdownPage] { 7 | final val Intro: Value = new MarkdownPage("assets/pages/intro.md") 8 | final val Rest: Value = new MarkdownPage("assets/pages/rest.md") 9 | final val License: Value = new MarkdownPage("assets/pages/license.md") 10 | } 11 | -------------------------------------------------------------------------------- /guide/shared/src/main/scala/io/udash/web/guide/markdown/MarkdownPageRPC.scala: -------------------------------------------------------------------------------- 1 | package io.udash.web.guide.markdown 2 | 3 | import io.udash.rpc.DefaultServerRpcCompanion 4 | 5 | import scala.concurrent.Future 6 | 7 | trait MarkdownPageRPC { 8 | def loadContent(page: MarkdownPage): Future[String] 9 | } 10 | 11 | object MarkdownPageRPC extends DefaultServerRpcCompanion[MarkdownPageRPC] -------------------------------------------------------------------------------- /guide/shared/src/main/scala/io/udash/web/guide/styles/MarkdownStyles.scala: -------------------------------------------------------------------------------- 1 | package io.udash.web.guide.styles 2 | 3 | import io.udash.css.{CssBase, CssStyle} 4 | 5 | import scala.language.postfixOps 6 | 7 | object MarkdownStyles extends CssBase { 8 | import dsl._ 9 | 10 | val markdownPage: CssStyle = style( 11 | unsafeChild("li") ( 12 | position.relative, 13 | paddingLeft(2 rem), 14 | margin(.5 rem, `0`, .5 rem, 4.5 rem), 15 | 16 | &.before( 17 | position.absolute, 18 | left(`0`), 19 | top(`0`), 20 | content.string("•"), 21 | ) 22 | ), 23 | 24 | unsafeChild("iframe")( 25 | marginTop(2.5 rem), 26 | 27 | &.firstChild ( 28 | marginTop(`0`) 29 | ) 30 | ), 31 | 32 | unsafeChild("pre")( 33 | marginTop(2.5 rem), 34 | 35 | &.firstChild ( 36 | marginTop(`0`) 37 | ) 38 | ), 39 | 40 | unsafeChild("code") ( 41 | backgroundColor(c"#f5f2f0"), 42 | fontFamily :=! "Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace" 43 | ), 44 | ) 45 | } 46 | -------------------------------------------------------------------------------- /guide/shared/src/main/scala/io/udash/web/guide/styles/demo/ExampleKeyframes.scala: -------------------------------------------------------------------------------- 1 | package io.udash.web.guide.styles.demo 2 | 3 | import io.udash.css.{CssBase, CssStyle} 4 | 5 | object ExampleKeyframes extends CssBase { 6 | import dsl._ 7 | 8 | val colorPulse: CssStyle = keyframes( 9 | 0d -> keyframe( 10 | color(c"#000000"), 11 | backgroundColor(c"#FFFFFF") 12 | ), 13 | 14 | 50d -> keyframe( 15 | color(c"#FFFFFF"), 16 | backgroundColor(c"#D9534F") 17 | ), 18 | 19 | 100d -> keyframe( 20 | color(c"#000000"), 21 | backgroundColor(c"#FFFFFF") 22 | ) 23 | ) 24 | } 25 | -------------------------------------------------------------------------------- /guide/shared/src/main/scala/io/udash/web/guide/styles/demo/ExampleMixins.scala: -------------------------------------------------------------------------------- 1 | package io.udash.web.guide.styles.demo 2 | 3 | import io.udash.css.{CssBase, CssStyle} 4 | import scalacss.internal.AV 5 | 6 | import scala.concurrent.duration.FiniteDuration 7 | 8 | object ExampleMixins extends CssBase { 9 | import dsl._ 10 | 11 | def animation(keyframes: CssStyle, duration: FiniteDuration, 12 | iterationCount: AV = animationIterationCount.infinite, 13 | easing: AV = animationTimingFunction.easeInOut): CssStyle = mixin( 14 | animationName(keyframes), 15 | iterationCount, 16 | animationDuration(duration), 17 | easing 18 | ) 19 | } 20 | -------------------------------------------------------------------------------- /guide/shared/src/main/scala/io/udash/web/guide/styles/utils/GuideStyleUtils.scala: -------------------------------------------------------------------------------- 1 | package io.udash.web.guide.styles.utils 2 | 3 | import io.udash.css.{CssBase, CssStyle} 4 | import io.udash.web.commons.styles.utils.StyleConstants 5 | import scalacss.internal.Macros.Color 6 | import scalacss.internal.{AV, Attr, Length} 7 | 8 | import scala.concurrent.duration.{DurationInt, FiniteDuration} 9 | import scala.language.postfixOps 10 | 11 | object GuideStyleUtils extends CssBase { 12 | import dsl._ 13 | 14 | val relativeMiddle: CssStyle = mixin( 15 | top(50 %%), 16 | transform := "translateY(-50%)", 17 | position.relative 18 | ) 19 | 20 | def transition(property: Attr = all, duration: FiniteDuration = 250 milliseconds): CssStyle = mixin( 21 | transitionProperty := property.toString(), 22 | transitionDuration(duration), 23 | transitionTimingFunction.easeInOut 24 | ) 25 | 26 | def border(bColor: Color = StyleConstants.Colors.GreyExtra, bWidth: Length[Double] = 1.0 px, bStyle: AV = borderStyle.solid): CssStyle = mixin( 27 | borderWidth(bWidth), 28 | bStyle, 29 | borderColor(bColor) 30 | ) 31 | } 32 | -------------------------------------------------------------------------------- /guide/shared/src/main/scala/io/udash/web/guide/styles/utils/MediaQueries.scala: -------------------------------------------------------------------------------- 1 | package io.udash.web.guide.styles.utils 2 | 3 | import io.udash.css.{CssBase, CssStyle} 4 | import io.udash.web.commons.styles.utils.StyleConstants 5 | import scalacss.internal.DslBase.ToStyle 6 | 7 | import scala.language.postfixOps 8 | 9 | object MediaQueries extends CssBase { 10 | import dsl._ 11 | 12 | def desktop(properties: ToStyle*): CssStyle = mixin( 13 | media.screen.minWidth(StyleConstants.MediaQueriesBounds.TabletLandscapeMax + 1 px)( 14 | properties: _* 15 | ) 16 | ) 17 | 18 | def tabletLandscape(properties: ToStyle*): CssStyle = mixin( 19 | media.screen.minWidth(1 px).maxWidth(StyleConstants.MediaQueriesBounds.TabletLandscapeMax px) ( 20 | properties:_* 21 | ) 22 | ) 23 | 24 | def tabletPortrait(properties: ToStyle*): CssStyle = mixin( 25 | media.screen.minWidth(1 px).maxWidth(StyleConstants.MediaQueriesBounds.TabletMax px) ( 26 | properties:_* 27 | ) 28 | ) 29 | 30 | def phone(properties: ToStyle*): CssStyle = mixin( 31 | media.screen.minWidth(1 px).maxWidth(StyleConstants.MediaQueriesBounds.PhoneMax px) ( 32 | properties:_* 33 | ) 34 | ) 35 | } 36 | -------------------------------------------------------------------------------- /guide/shared/src/main/scala/io/udash/web/homepage/styles/HomepageDefaultStyles.scala: -------------------------------------------------------------------------------- 1 | package io.udash.web.homepage.styles 2 | 3 | import io.udash.css.CssBase 4 | import io.udash.web.commons.styles.DefaultStyles 5 | import io.udash.web.commons.styles.utils.MediaQueries 6 | 7 | import scala.language.postfixOps 8 | 9 | object HomepageDefaultStyles extends CssBase with DefaultStyles { 10 | import dsl._ 11 | 12 | style( 13 | unsafeRoot("body") ( 14 | fontSize(1.0625 rem) 15 | ), 16 | 17 | unsafeRoot("p")( 18 | fontSize(1 rem) 19 | ), 20 | 21 | unsafeRoot("h1") ( 22 | marginBottom(3.125 rem), 23 | 24 | MediaQueries.phone( 25 | paddingTop(3.125 rem), 26 | marginBottom(1.875 rem) 27 | ) 28 | ) 29 | ) 30 | } 31 | -------------------------------------------------------------------------------- /i18n/.js/src/main/scala/io/udash/i18n/FrontendTranslationProvider.scala: -------------------------------------------------------------------------------- 1 | package io.udash.i18n 2 | 3 | import io.udash.logging.CrossLogging 4 | 5 | trait FrontendTranslationProvider extends TranslationProvider with CrossLogging { 6 | protected def handleMixedPlaceholders(template: String): Unit = 7 | logger.warn(s"""Indexed and unindexed placeholders in "$template"!""") 8 | } 9 | -------------------------------------------------------------------------------- /i18n/.js/src/main/scala/io/udash/i18n/LocalTranslationProvider.scala: -------------------------------------------------------------------------------- 1 | package io.udash.i18n 2 | 3 | 4 | import scala.concurrent.Future 5 | import scala.util.Try 6 | 7 | /** 8 | * TranslationProvider dedicated to frontend-only applications. 9 | * 10 | * @param bundles `Bundle`s of translations for each language. 11 | * @param missingTranslationError This text will be used in place of missing translations. 12 | */ 13 | class LocalTranslationProvider(bundles: Map[Lang, Bundle], missingTranslationError: String = "Missing translation") 14 | extends FrontendTranslationProvider { 15 | 16 | def translate(key: String, argv: Any*)(implicit lang: Lang): Future[Translated] = Future.fromTry( 17 | Try(putArgs(bundles(lang).translations.getOrElse(key, missingTranslationError), argv: _*)) 18 | ) 19 | } 20 | -------------------------------------------------------------------------------- /i18n/.js/src/main/scala/io/udash/i18n/bindings/AttrTranslationModifier.scala: -------------------------------------------------------------------------------- 1 | package io.udash.i18n.bindings 2 | 3 | import com.avsystem.commons._ 4 | import io.udash.i18n.Translated 5 | import io.udash.logging.CrossLogging 6 | import org.scalajs.dom.Element 7 | import scalatags.JsDom.Modifier 8 | 9 | import scala.concurrent.Future 10 | import scala.util.{Failure, Success} 11 | 12 | private[i18n] class AttrTranslationModifier(translation: => Future[Translated], attr: String) 13 | extends Modifier with CrossLogging { 14 | 15 | override def applyTo(t: Element): Unit = 16 | translation.onCompleteNow { 17 | case Success(text) => 18 | t.setAttribute(attr, text.string) 19 | case Failure(ex) => 20 | logger.error(ex.getMessage) 21 | } 22 | } -------------------------------------------------------------------------------- /i18n/.js/src/main/scala/io/udash/i18n/bindings/DynamicAttrTranslationBinding.scala: -------------------------------------------------------------------------------- 1 | package io.udash.i18n.bindings 2 | 3 | import io.udash._ 4 | import io.udash.bindings.modifiers.Binding 5 | import io.udash.i18n.{Lang, Translated} 6 | import org.scalajs.dom.Element 7 | 8 | import scala.concurrent.Future 9 | 10 | private[i18n] final class DynamicAttrTranslationBinding(translation: => Future[Translated], attr: String)( 11 | implicit lang: ReadableProperty[Lang]) extends AttrTranslationModifier(translation, attr) with Binding { 12 | override def applyTo(t: Element): Unit = propertyListeners += lang.listen(_ => super.applyTo(t), initUpdate = true) 13 | } -------------------------------------------------------------------------------- /i18n/.js/src/main/scala/io/udash/i18n/bindings/DynamicTranslationBinding.scala: -------------------------------------------------------------------------------- 1 | package io.udash.i18n.bindings 2 | 3 | import com.avsystem.commons._ 4 | import io.udash._ 5 | import io.udash.bindings.modifiers._ 6 | import io.udash.i18n._ 7 | import org.scalajs.dom._ 8 | 9 | import scala.concurrent.Future 10 | 11 | private[i18n] final class DynamicTranslationBinding( 12 | translation: => Future[Translated], 13 | placeholder: Option[Element], 14 | rawHtml: Boolean 15 | )(implicit lang: ReadableProperty[Lang]) extends TranslationModifier(translation, placeholder, rawHtml) with Binding { 16 | override def applyTo(t: Element): Unit = { 17 | var holder: Seq[Node] = t.appendChild(placeholder.getOrElse(emptyStringNode())) 18 | propertyListeners += lang.listen(_ => update(t, holder).foreachNow(holder = _), initUpdate = true) 19 | } 20 | } -------------------------------------------------------------------------------- /i18n/.js/src/main/scala/io/udash/i18n/bindings/TranslationModifier.scala: -------------------------------------------------------------------------------- 1 | package io.udash.i18n.bindings 2 | 3 | import com.avsystem.commons._ 4 | import io.udash.bindings.Bindings 5 | import io.udash.bindings.modifiers._ 6 | import io.udash.i18n._ 7 | import io.udash.logging.CrossLogging 8 | import org.scalajs.dom._ 9 | import scalatags.JsDom.Modifier 10 | 11 | import scala.concurrent.Future 12 | import scala.util.{Failure, Success} 13 | 14 | private[i18n] class TranslationModifier( 15 | translation: => Future[Translated], 16 | placeholder: Option[Element], 17 | rawHtml: Boolean 18 | ) extends Modifier with CrossLogging { 19 | 20 | protected final def update(t: Element, holder: Seq[Node]): Future[Seq[Node]] = { 21 | translation.transformNow { 22 | case Success(Translated(text)) => 23 | val newHolder: Seq[Node] = parseTranslation(rawHtml, text) 24 | t.replaceChildren(holder, newHolder) 25 | Success(newHolder) 26 | case Failure(ex) => 27 | logger.error(ex.getMessage) 28 | Success(holder) 29 | } 30 | } 31 | 32 | override def applyTo(t: Element): Unit = { 33 | val holder: Seq[Node] = Seq(t.appendChild(placeholder.getOrElse(Bindings.emptyStringNode()))) 34 | update(t, holder).discard 35 | } 36 | } -------------------------------------------------------------------------------- /i18n/.js/src/main/scala/io/udash/i18n/bindings/package.scala: -------------------------------------------------------------------------------- 1 | package io.udash.i18n 2 | 3 | import org.scalajs.dom._ 4 | 5 | import scala.scalajs.js.{JavaScriptException, SyntaxError} 6 | import scala.util.{Failure, Success, Try} 7 | 8 | package object bindings { 9 | def parseTranslation(rawHtml: Boolean, text: String): Seq[Node] = 10 | if (rawHtml) Try { 11 | val wrapper = document.createElement("div") 12 | wrapper.innerHTML = text 13 | wrapper.childNodes 14 | } match { 15 | case Success(children) if children.length > 0 => 16 | (0 until children.length).map(children.item) 17 | case Success(_) | Failure(JavaScriptException(_: SyntaxError)) => 18 | Seq(document.createTextNode(text)) 19 | } else Seq(document.createTextNode(text)) 20 | } 21 | -------------------------------------------------------------------------------- /i18n/.js/src/main/scala/io/udash/i18n/package.scala: -------------------------------------------------------------------------------- 1 | package io.udash 2 | 3 | package object i18n extends Translations 4 | -------------------------------------------------------------------------------- /i18n/.jvm/src/main/scala/io/udash/i18n/TranslationRPCEndpoint.scala: -------------------------------------------------------------------------------- 1 | package io.udash.i18n 2 | import scala.concurrent.{ExecutionContext, Future} 3 | 4 | /** Default implementation of `io.udash.i18n.RemoteTranslationRPC`. */ 5 | class TranslationRPCEndpoint(provider: TranslationTemplatesProvider)(implicit ec: ExecutionContext) extends RemoteTranslationRPC { 6 | override def loadTemplateForLang(lang: Lang, key: String): Future[String] = Future { 7 | provider.template(key)(lang) 8 | } 9 | 10 | override def loadTranslationsForLang(lang: Lang, oldHash: BundleHash): Future[Option[Bundle]] = Future { 11 | val hash: BundleHash = provider.langHash(lang) 12 | if (hash == oldHash) None 13 | else Some(Bundle(hash, provider.allTemplates(lang))) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /i18n/.jvm/src/main/scala/io/udash/i18n/TranslationTemplatesProvider.scala: -------------------------------------------------------------------------------- 1 | package io.udash.i18n 2 | 3 | /** Server-side translations provider. */ 4 | trait TranslationTemplatesProvider { 5 | /** Returns translation template for provided `key` and `lang`. */ 6 | def template(key: String)(implicit lang: Lang): String 7 | 8 | /** Returns all translation templates for provided `lang`. */ 9 | def allTemplates(implicit lang: Lang): Map[String, String] 10 | 11 | /** Returns `true` if provided translations `hash` is up to date. */ 12 | def langHash(implicit lang: Lang): BundleHash 13 | 14 | protected def hash(data: Map[String, String]): BundleHash = 15 | BundleHash(new String( 16 | java.security.MessageDigest.getInstance("MD5") 17 | .digest( 18 | data.map { 19 | case (key, value) => key + value 20 | }.mkString.getBytes 21 | ) 22 | )) 23 | } 24 | 25 | -------------------------------------------------------------------------------- /i18n/.jvm/src/test/resources/mixed_en.properties: -------------------------------------------------------------------------------- 1 | mixed.first={} {1} {} -------------------------------------------------------------------------------- /i18n/.jvm/src/test/resources/mixed_pl.properties: -------------------------------------------------------------------------------- 1 | mixed.first={} {1} {} -------------------------------------------------------------------------------- /i18n/.jvm/src/test/resources/test2_translations_en.properties: -------------------------------------------------------------------------------- 1 | test2.key1=Key 1 2 | test2.key2=Key 2 -------------------------------------------------------------------------------- /i18n/.jvm/src/test/resources/test2_translations_pl.properties: -------------------------------------------------------------------------------- 1 | test2.key1=Klucz 1 2 | test2.key2=Klucz 2 -------------------------------------------------------------------------------- /i18n/.jvm/src/test/resources/test_translations_en.properties: -------------------------------------------------------------------------------- 1 | test1.key1=Test Key 1 2 | test1.key2=Test Key 2 -------------------------------------------------------------------------------- /i18n/.jvm/src/test/resources/test_translations_pl.properties: -------------------------------------------------------------------------------- 1 | test1.key1=Klucz testowy 1 2 | test1.key2=Klucz testowy 2 -------------------------------------------------------------------------------- /i18n/src/main/scala/io/udash/i18n/RemoteTranslationRPC.scala: -------------------------------------------------------------------------------- 1 | package io.udash.i18n 2 | 3 | import io.udash.rpc.DefaultServerRpcCompanion 4 | 5 | import scala.concurrent.Future 6 | 7 | /** RPC interface for Udash i18n handling on server-side. */ 8 | trait RemoteTranslationRPC { 9 | /** Returns text to replace translation key. */ 10 | def loadTemplate(key: String)(implicit lang: Lang): Future[String] = 11 | loadTemplateForLang(lang, key) 12 | 13 | /** Returns map of translations and bundle hash. If `oldHash` is not outdated, this Future will contain None. */ 14 | def loadTranslations(oldHash: BundleHash)(implicit lang: Lang): Future[Option[Bundle]] = 15 | loadTranslationsForLang(lang, oldHash) 16 | 17 | /** Returns text to replace translation key. */ 18 | def loadTemplateForLang(lang: Lang, key: String): Future[String] 19 | 20 | /** Returns map of translations and bundle hash. If `oldHash` is not outdated, this Future will contain None. */ 21 | def loadTranslationsForLang(lang: Lang, oldHash: BundleHash): Future[Option[Bundle]] 22 | } 23 | object RemoteTranslationRPC extends DefaultServerRpcCompanion[RemoteTranslationRPC] 24 | -------------------------------------------------------------------------------- /i18n/src/main/scala/io/udash/i18n/types.scala: -------------------------------------------------------------------------------- 1 | package io.udash.i18n 2 | 3 | import com.avsystem.commons.misc.{AbstractCase, CaseMethods} 4 | import com.avsystem.commons.serialization.{HasGenCodec, transparent} 5 | 6 | @transparent final case class Lang(lang: String) extends AnyVal with CaseMethods 7 | object Lang extends HasGenCodec[Lang] 8 | 9 | @transparent final case class BundleHash(hash: String) extends AnyVal with CaseMethods 10 | object BundleHash extends HasGenCodec[BundleHash] 11 | 12 | final case class Bundle(hash: BundleHash, translations: Map[String, String]) extends AbstractCase 13 | object Bundle extends HasGenCodec[Bundle] 14 | 15 | @transparent final case class Translated(string: String) extends AnyVal with CaseMethods 16 | object Translated extends HasGenCodec[Translated] -------------------------------------------------------------------------------- /i18n/src/test/scala/io/udash/i18n/Utils.scala: -------------------------------------------------------------------------------- 1 | package io.udash.i18n 2 | 3 | object Utils { 4 | def getTranslatedString(tr: TranslationKey0)(implicit lang: Lang, provider: TranslationProvider): String = 5 | tr().value.get.get.string 6 | } 7 | -------------------------------------------------------------------------------- /macros/src/main/scala/io/udash/macros/ComponentIdMacro.scala: -------------------------------------------------------------------------------- 1 | package io.udash 2 | package macros 3 | 4 | import scala.reflect.macros.blackbox 5 | 6 | class ComponentIdMacro(val c: blackbox.Context) { 7 | 8 | import c.universe._ 9 | 10 | final def IdObj: Tree = q"io.udash.component.ComponentId" 11 | 12 | def impl(): c.Tree = { 13 | val fqn = Iterator.iterate(c.internal.enclosingOwner)(_.owner).find(_.isClass).get.fullName.replace('.', '-') 14 | q"$IdObj.forName($fqn)" 15 | } 16 | } -------------------------------------------------------------------------------- /macros/src/main/scala/io/udash/macros/RestMacros.scala: -------------------------------------------------------------------------------- 1 | package io.udash.macros 2 | 3 | import com.avsystem.commons.macros.AbstractMacroCommons 4 | 5 | import scala.reflect.macros.blackbox 6 | 7 | class RestMacros(val ctx: blackbox.Context) extends AbstractMacroCommons(ctx) { 8 | 9 | import c.universe._ 10 | 11 | def UdashRestPkg: Tree = q"_root_.io.udash.rest" 12 | 13 | def materializeImplMetadata[Real: c.WeakTypeTag]: Tree = 14 | q"$CommonsPkg.rpc.RpcMetadata.materializeForApi[$UdashRestPkg.raw.RestMetadata, ${weakTypeOf[Real]}]" 15 | 16 | def materializeImplOpenApiMetadata[Real: c.WeakTypeTag]: Tree = 17 | q"$CommonsPkg.rpc.RpcMetadata.materializeForApi[$UdashRestPkg.openapi.OpenApiMetadata, ${weakTypeOf[Real]}]" 18 | } 19 | -------------------------------------------------------------------------------- /macros/src/main/scala/io/udash/macros/TestMacros.scala: -------------------------------------------------------------------------------- 1 | package io.udash 2 | package macros 3 | 4 | import com.avsystem.commons.macros.AbstractMacroCommons 5 | 6 | import scala.reflect.macros.{TypecheckException, blackbox} 7 | 8 | class TestMacros(val ctx: blackbox.Context) extends AbstractMacroCommons(ctx) { 9 | 10 | import c.universe._ 11 | 12 | private def stringLiteral(tree: Tree): String = tree match { 13 | case StringLiteral(str) => str 14 | case Select(StringLiteral(str), TermName("stripMargin")) => str.stripMargin 15 | case _ => abort(s"expected string literal, got $tree") 16 | } 17 | 18 | def typeErrorImpl(code: Tree): Tree = { 19 | val codeTree = c.parse(stringLiteral(code)) 20 | try { 21 | c.typecheck(codeTree) 22 | abort("expected typechecking error, none was raised") 23 | } catch { 24 | case TypecheckException(_, msg) => q"$msg" 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "jsdom": "^24.1.0", 4 | "ws": "^8.17.1" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /project/build.properties: -------------------------------------------------------------------------------- 1 | # suppress inspection "UnusedProperty" 2 | sbt.version=1.11.1 3 | -------------------------------------------------------------------------------- /project/plugins.sbt: -------------------------------------------------------------------------------- 1 | logLevel := Level.Warn 2 | 3 | libraryDependencies += "org.scala-js" %% "scalajs-env-selenium" % "1.1.1" 4 | libraryDependencies += "org.scala-js" %% "scalajs-env-jsdom-nodejs" % "1.1.0" 5 | 6 | addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.19.0") 7 | addSbtPlugin("org.scala-js" % "sbt-jsdependencies" % "1.0.2") 8 | addSbtPlugin("com.timushev.sbt" % "sbt-updates" % "0.6.4") 9 | addSbtPlugin("org.jetbrains.scala" % "sbt-ide-settings" % "1.1.2") 10 | addSbtPlugin("com.github.sbt" % "sbt-less" % "2.0.1") 11 | addSbtPlugin("com.github.sbt" % "sbt-native-packager" % "1.11.1") 12 | addSbtPlugin("com.github.sbt" % "sbt-ci-release" % "1.11.1") 13 | -------------------------------------------------------------------------------- /rest/.js/src/main/scala/io/udash/rest/DefaultSttpBackend.scala: -------------------------------------------------------------------------------- 1 | package io.udash 2 | package rest 3 | 4 | import sttp.client3.{FetchBackend, SttpBackend} 5 | 6 | import scala.concurrent.Future 7 | 8 | object DefaultSttpBackend { 9 | def apply(): SttpBackend[Future, Any] = FetchBackend() 10 | } 11 | -------------------------------------------------------------------------------- /rest/.jvm/src/main/scala/io/udash/rest/DefaultSttpBackend.scala: -------------------------------------------------------------------------------- 1 | package io.udash 2 | package rest 3 | 4 | import sttp.client3.{HttpClientFutureBackend, SttpBackend} 5 | 6 | import scala.concurrent.Future 7 | 8 | object DefaultSttpBackend { 9 | def apply(): SttpBackend[Future, Any] = HttpClientFutureBackend() 10 | } 11 | -------------------------------------------------------------------------------- /rest/.jvm/src/main/scala/io/udash/rest/openapi/OpenApiServlet.scala: -------------------------------------------------------------------------------- 1 | package io.udash 2 | package rest.openapi 3 | 4 | import com.avsystem.commons.OptArg 5 | import com.avsystem.commons.annotation.explicitGenerics 6 | import com.avsystem.commons.serialization.json.JsonStringOutput 7 | import javax.servlet.http.{HttpServlet, HttpServletRequest, HttpServletResponse} 8 | 9 | object OpenApiServlet { 10 | @explicitGenerics def apply[RestApi: OpenApiMetadata]( 11 | info: Info, 12 | components: Components = Components(), 13 | servers: List[Server] = Nil, 14 | security: List[SecurityRequirement] = Nil, 15 | tags: List[Tag] = Nil, 16 | externalDocs: OptArg[ExternalDocumentation] = OptArg.Empty 17 | ): OpenApiServlet = new OpenApiServlet { 18 | protected def render(request: HttpServletRequest): OpenApi = 19 | implicitly[OpenApiMetadata[RestApi]].openapi(info, components, servers, security, tags, externalDocs) 20 | } 21 | } 22 | 23 | abstract class OpenApiServlet extends HttpServlet { 24 | protected def render(request: HttpServletRequest): OpenApi 25 | 26 | override def doGet(req: HttpServletRequest, resp: HttpServletResponse): Unit = { 27 | resp.setContentType("application/json;charset=utf-8") 28 | resp.getWriter.write(JsonStringOutput.writePretty(render(req))) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /rest/.jvm/src/test/scala/io/udash/rest/ServletBasedRestApiTest.scala: -------------------------------------------------------------------------------- 1 | package io.udash 2 | package rest 3 | 4 | import org.eclipse.jetty.ee8.servlet.{ServletContextHandler, ServletHolder} 5 | import org.eclipse.jetty.server.Server 6 | 7 | import scala.concurrent.duration.* 8 | 9 | abstract class ServletBasedRestApiTest extends RestApiTest with UsesHttpServer { 10 | override implicit val patienceConfig: PatienceConfig = PatienceConfig(10.seconds) 11 | 12 | def maxPayloadSize: Int = 1024 * 1024 13 | def serverTimeout: FiniteDuration = 10.seconds 14 | 15 | protected def setupServer(server: Server): Unit = { 16 | val servlet = new RestServlet(serverHandle, serverTimeout, maxPayloadSize) 17 | val holder = new ServletHolder(servlet) 18 | val handler = new ServletContextHandler() 19 | handler.addServlet(holder, "/api/*") 20 | server.setHandler(handler) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /rest/.jvm/src/test/scala/io/udash/rest/SomeApi.scala: -------------------------------------------------------------------------------- 1 | package io.udash 2 | package rest 3 | 4 | import scala.concurrent.Future 5 | 6 | trait SomeApi { 7 | @GET 8 | def hello(who: String): Future[String] 9 | 10 | @POST("hello") 11 | def helloThere(who: String): Future[String] 12 | } 13 | 14 | object SomeApi extends DefaultRestApiCompanion[SomeApi] { 15 | def format(who: String) = s"Hello, $who!" 16 | val poison: String = "poison" 17 | 18 | val impl: SomeApi = new SomeApi { 19 | override def hello(who: String): Future[String] = { 20 | if (who == poison) throw new IllegalArgumentException(poison) 21 | else Future.successful(format(who)) 22 | } 23 | 24 | override def helloThere(who: String): Future[String] = hello(who) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /rest/.jvm/src/test/scala/io/udash/rest/examples/ClientMain.scala: -------------------------------------------------------------------------------- 1 | package io.udash 2 | package rest.examples 3 | 4 | import io.udash.rest.SttpRestClient 5 | import sttp.client3.SttpBackend 6 | 7 | import scala.concurrent.duration._ 8 | import scala.concurrent.{Await, Future} 9 | import scala.util.{Failure, Success} 10 | 11 | object ClientMain { 12 | def main(args: Array[String]): Unit = { 13 | implicit val sttpBackend: SttpBackend[Future, Any] = SttpRestClient.defaultBackend() 14 | val proxy: UserApi = SttpRestClient[UserApi, Future]("http://localhost:9090") 15 | 16 | // make a remote REST call 17 | val result: Future[User] = proxy.createUser("Fred") 18 | 19 | // use whatever execution context is appropriate 20 | import scala.concurrent.ExecutionContext.Implicits.global 21 | 22 | result.onComplete { 23 | case Success(user) => println(s"User $user created") 24 | case Failure(cause) => cause.printStackTrace() 25 | } 26 | 27 | // just wait until future is complete so that main thread doesn't finish prematurely 28 | Await.ready(result, 10.seconds) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /rest/.jvm/src/test/scala/io/udash/rest/examples/GenericApi.scala: -------------------------------------------------------------------------------- 1 | package io.udash 2 | package rest.examples 3 | 4 | import io.udash.rest._ 5 | import io.udash.rest.raw._ 6 | import com.avsystem.commons.serialization.GenCodec 7 | 8 | import scala.concurrent.Future 9 | 10 | trait GenericApi[T] { 11 | def process(value: T): Future[T] 12 | } 13 | object GenericApi { 14 | import DefaultRestImplicits._ 15 | implicit def restAsRawReal[T: GenCodec]: RawRest.AsRawRealRpc[GenericApi[T]] = RawRest.materializeAsRawReal 16 | implicit def restMetadata[T]: RestMetadata[GenericApi[T]] = RestMetadata.materialize 17 | 18 | import openapi._ 19 | implicit def openApiMetadata[T: RestSchema]: OpenApiMetadata[GenericApi[T]] = OpenApiMetadata.materialize 20 | } -------------------------------------------------------------------------------- /rest/.jvm/src/test/scala/io/udash/rest/examples/ServerMain.scala: -------------------------------------------------------------------------------- 1 | package io.udash 2 | package rest.examples 3 | 4 | import io.udash.rest.RestServlet 5 | import monix.execution.Scheduler 6 | import org.eclipse.jetty.server.Server 7 | import org.eclipse.jetty.ee8.servlet.{ServletContextHandler, ServletHolder} 8 | 9 | import scala.concurrent.Future 10 | 11 | class UserApiImpl extends UserApi { 12 | def createUser(name: String): Future[User] = 13 | Future.successful(User(UserId(0), name)) 14 | def getUser(id: UserId): Future[User] = 15 | Future.successful(User(id, s"$id-name")) 16 | } 17 | 18 | object ServerMain { 19 | def main(args: Array[String]): Unit = { 20 | // Scheduler.global is usually not the best choice 21 | // use whatever Scheduler is appropriate in your application, e.g. freshly created Scheduler.computation() 22 | implicit val scheduler: Scheduler = Scheduler.global 23 | 24 | val server = new Server(9090) 25 | val handler = new ServletContextHandler 26 | handler.addServlet(new ServletHolder(RestServlet[UserApi](new UserApiImpl)), "/*") 27 | server.setHandler(handler) 28 | server.start() 29 | server.join() 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /rest/.jvm/src/test/scala/io/udash/rest/openapi/OpenApiGenerationTest.scala: -------------------------------------------------------------------------------- 1 | package io.udash 2 | package rest.openapi 3 | 4 | import com.avsystem.commons.serialization.json.JsonStringOutput 5 | import io.udash.rest.RestTestApi 6 | 7 | import scala.io.Source 8 | import org.scalatest.funsuite.AnyFunSuite 9 | 10 | class OpenApiGenerationTest extends AnyFunSuite { 11 | test("openapi for RestTestApi") { 12 | val openapi = RestTestApi.openapiMetadata.openapi( 13 | Info("Test API", "0.1", description = "Some test REST API"), 14 | servers = List(Server("http://localhost")) 15 | ) 16 | val expected = Source.fromInputStream(getClass.getResourceAsStream("/RestTestApi.json")).getLines().mkString("\n") 17 | val actual = JsonStringOutput.writePretty(openapi) 18 | assert(actual == expected) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /rest/jetty/src/test/scala/io/udash/rest/jetty/JettyRestCallTest.scala: -------------------------------------------------------------------------------- 1 | package io.udash 2 | package rest.jetty 3 | 4 | import io.udash.rest.raw.RawRest.HandleRequest 5 | import io.udash.rest.{RestApiTestScenarios, ServletBasedRestApiTest} 6 | import org.eclipse.jetty.client.HttpClient 7 | 8 | final class JettyRestCallTest extends ServletBasedRestApiTest with RestApiTestScenarios { 9 | /** 10 | * Similar to the default HttpClient, but with a connection timeout 11 | * significantly exceeding the value of the CallTimeout 12 | */ 13 | val client: HttpClient = new HttpClient() { 14 | setMaxConnectionsPerDestination(MaxConnections) 15 | setIdleTimeout(IdleTimout.toMillis) 16 | } 17 | 18 | def clientHandle: HandleRequest = 19 | JettyRestClient.asHandleRequest(client, s"$baseUrl/api", maxPayloadSize) 20 | 21 | override protected def beforeAll(): Unit = { 22 | super.beforeAll() 23 | client.start() 24 | } 25 | 26 | override protected def afterAll(): Unit = { 27 | client.stop() 28 | super.afterAll() 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /rest/src/main/scala/io/udash/rest/RestException.scala: -------------------------------------------------------------------------------- 1 | package io.udash 2 | package rest 3 | 4 | import com.avsystem.commons.rpc.InvalidRpcCall 5 | 6 | class RestException(msg: String, cause: Throwable = null) extends InvalidRpcCall(msg, cause) 7 | 8 | class InvalidRestApiException(msg: String) extends RestException(msg) 9 | -------------------------------------------------------------------------------- /rest/src/main/scala/io/udash/rest/openapi/WhenAbsentInfo.scala: -------------------------------------------------------------------------------- 1 | package io.udash 2 | package rest.openapi 3 | 4 | import com.avsystem.commons._ 5 | import com.avsystem.commons.meta.{TypedMetadata, infer, reifyAnnot} 6 | import com.avsystem.commons.rpc.AsRaw 7 | import com.avsystem.commons.serialization.whenAbsent 8 | import io.udash.rest.raw.JsonValue 9 | 10 | import scala.util.Try 11 | 12 | final case class WhenAbsentInfo[T]( 13 | @reifyAnnot annot: whenAbsent[T], 14 | @infer("for @whenAbsent value: ") asJson: AsRaw[JsonValue, T] 15 | ) extends TypedMetadata[T] { 16 | val fallbackValue: Opt[JsonValue] = 17 | Try(annot.value).fold(_ => Opt.Empty, v => asJson.asRaw(v).opt) 18 | } 19 | -------------------------------------------------------------------------------- /rest/src/main/scala/io/udash/rest/raw/JsonValue.scala: -------------------------------------------------------------------------------- 1 | package io.udash 2 | package rest.raw 3 | 4 | import com.avsystem.commons.serialization.GenCodec 5 | import com.avsystem.commons.serialization.json.RawJson 6 | 7 | /** 8 | * Value used as encoding of [[io.udash.rest.Body Body]] parameters of 9 | * [[io.udash.rest.JsonBody JsonBody]] methods. Wrapped value MUST be a valid JSON. 10 | */ 11 | final case class JsonValue(value: String) extends AnyVal 12 | object JsonValue extends (String => JsonValue) { 13 | implicit val codec: GenCodec[JsonValue] = GenCodec.create( 14 | i => JsonValue(i.readCustom(RawJson).getOrElse(i.readSimple().readString())), 15 | (o, v) => if (!o.writeCustom(RawJson, v.value)) o.writeSimple().writeString(v.value) 16 | ) 17 | 18 | final val Null = JsonValue("null") 19 | } 20 | -------------------------------------------------------------------------------- /rest/src/test/scala/io/udash/rest/PolyRestApi.scala: -------------------------------------------------------------------------------- 1 | package io.udash 2 | package rest 3 | 4 | trait PolyRestApi[F[_]] { 5 | def postThis(thing: String): F[Int] 6 | } 7 | object PolyRestApi extends DefaultPolyRestApiCompanion[PolyRestApi] 8 | -------------------------------------------------------------------------------- /rest/src/test/scala/io/udash/rest/PolyRestDataCompanion.scala: -------------------------------------------------------------------------------- 1 | package io.udash 2 | package rest 3 | 4 | import com.avsystem.commons.meta.MacroInstances 5 | import com.avsystem.commons.serialization.GenCodec 6 | import io.udash.rest.openapi.{RestSchema, RestStructure} 7 | 8 | trait PolyCodecWithStructure[C[_]] { 9 | def codec[T0: GenCodec]: GenCodec[C[T0]] 10 | def structure[T0: RestSchema]: RestStructure[C[T0]] 11 | } 12 | 13 | abstract class PolyRestDataCompanion[C[_]](implicit 14 | instances: MacroInstances[DefaultRestImplicits, PolyCodecWithStructure[C]] 15 | ) extends { 16 | implicit def codec[T: GenCodec]: GenCodec[C[T]] = instances(DefaultRestImplicits, this).codec 17 | implicit def restStructure[T: RestSchema]: RestStructure[C[T]] = instances(DefaultRestImplicits, this).structure 18 | implicit def restSchema[T: RestSchema]: RestSchema[C[T]] = RestSchema.lazySchema(restStructure[T].standaloneSchema) 19 | } 20 | -------------------------------------------------------------------------------- /rest/src/test/scala/io/udash/rest/SomeServerApiImpl.scala: -------------------------------------------------------------------------------- 1 | package io.udash.rest 2 | 3 | import scala.concurrent.Future 4 | 5 | final class SomeServerApiImpl { 6 | @GET 7 | def thingy(param: Int): Future[String] = Future.successful((param - 1).toString) 8 | 9 | val subapi = new SomeServerSubApiImpl 10 | } 11 | object SomeServerApiImpl extends DefaultRestServerApiImplCompanion[SomeServerApiImpl] 12 | 13 | final class SomeServerSubApiImpl { 14 | @POST 15 | def yeet(data: String): Future[String] = Future.successful(s"yeet $data") 16 | } 17 | object SomeServerSubApiImpl extends DefaultRestServerApiImplCompanion[SomeServerSubApiImpl] 18 | -------------------------------------------------------------------------------- /rest/src/test/scala/io/udash/rest/TestRESTRecord.scala: -------------------------------------------------------------------------------- 1 | package io.udash 2 | package rest 3 | 4 | case class TestRESTRecord(id: Option[Int], s: String) 5 | object TestRESTRecord extends RestDataCompanion[TestRESTRecord] -------------------------------------------------------------------------------- /rest/src/test/scala/io/udash/rest/WriteOpenApi.scala: -------------------------------------------------------------------------------- 1 | package io.udash 2 | package rest 3 | 4 | import java.io.FileWriter 5 | 6 | import com.avsystem.commons.serialization.json.{JsonOptions, JsonStringOutput} 7 | import io.udash.rest.openapi.{Info, Server} 8 | 9 | object WriteOpenApi { 10 | def main(args: Array[String]): Unit = { 11 | val openapi = RestTestApi.openapiMetadata.openapi( 12 | Info("Test API", "0.1", description = "Some test REST API"), 13 | servers = List(Server("http://localhost")) 14 | ) 15 | val fw = new FileWriter("/home/ghik/api.js") 16 | fw.write("apiSpec = ") 17 | fw.write(JsonStringOutput.write(openapi, JsonOptions.Pretty)) 18 | fw.close() 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /rest/src/test/scala/io/udash/rest/openapi/customWa.scala: -------------------------------------------------------------------------------- 1 | package io.udash 2 | package rest.openapi 3 | 4 | import com.avsystem.commons.annotation.AnnotationAggregate 5 | import com.avsystem.commons.serialization.whenAbsent 6 | 7 | import scala.annotation.StaticAnnotation 8 | 9 | class customWa[+T](value: => T) extends AnnotationAggregate { 10 | @whenAbsent(value) 11 | final def aggregated: List[StaticAnnotation] = reifyAggregated 12 | } 13 | -------------------------------------------------------------------------------- /rest/src/test/scala/io/udash/rest/raw/MappingTest.scala: -------------------------------------------------------------------------------- 1 | package io.udash 2 | package rest.raw 3 | 4 | import org.scalatest.funsuite.AnyFunSuite 5 | 6 | class MappingTest extends AnyFunSuite { 7 | test("simple") { 8 | assert(Mapping.create("a" -> 1).toMap == Map("a" -> 1)) 9 | } 10 | 11 | test("repeated key") { 12 | assert(Mapping.create("a" -> 1, "a" -> 2).toMap == Map("a" -> 2)) 13 | } 14 | 15 | test("case sensitive") { 16 | assert(Mapping.create("a" -> 1, "A" -> 2).toMap == Map("a" -> 1, "A" -> 2)) 17 | } 18 | 19 | test("append") { 20 | assert(Mapping.create("a" -> 1).append("b", 2) == Mapping.create("a" -> 1, "b" -> 2)) 21 | } 22 | 23 | test("prepend") { 24 | assert(Mapping.create("a" -> 1).prepend("b", 2) == Mapping.create("b" -> 2, "a" -> 1)) 25 | } 26 | 27 | test("case insensitive") { 28 | assert(IMapping.create("a" -> 1, "A" -> 2).toMap == Map("a" -> 2)) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /rpc/.js/src/main/scala/io/udash/rpc/ConnectionStatus.scala: -------------------------------------------------------------------------------- 1 | package io.udash.rpc 2 | 3 | import com.avsystem.commons.misc.{AbstractValueEnum, EnumCtx, ValueEnumCompanion} 4 | 5 | final class ConnectionStatus(implicit enumCtx: EnumCtx) extends AbstractValueEnum 6 | object ConnectionStatus extends ValueEnumCompanion[ConnectionStatus] { 7 | final val Open, Closed: Value = new ConnectionStatus 8 | } 9 | 10 | -------------------------------------------------------------------------------- /rpc/.js/src/main/scala/io/udash/rpc/RPCFrontend.scala: -------------------------------------------------------------------------------- 1 | package io.udash.rpc 2 | 3 | trait RPCFrontend { 4 | type ServerConnector = io.udash.rpc.internals.ServerConnector 5 | type AtmosphereServerConnector = io.udash.rpc.internals.AtmosphereServerConnector 6 | type DefaultAtmosphereServerConnector = io.udash.rpc.internals.DefaultAtmosphereServerConnector 7 | type ExposesClientRPC[ClientRPCType] = io.udash.rpc.internals.ExposesClientRPC[ClientRPCType] 8 | type DefaultExposesClientRPC[ClientRPCType] = io.udash.rpc.internals.DefaultExposesClientRPC[ClientRPCType] 9 | } 10 | -------------------------------------------------------------------------------- /rpc/.js/src/main/scala/io/udash/rpc/internals/ExposesClientRPC.scala: -------------------------------------------------------------------------------- 1 | package io.udash.rpc.internals 2 | 3 | import io.udash.rpc._ 4 | 5 | abstract class ExposesClientRPC[ClientRPCType](protected val localRpc: ClientRPCType) 6 | extends ExposesLocalRPC[ClientRPCType] { 7 | /** 8 | * This allows the RPC implementation to be wrapped in raw RPC which will translate raw calls coming from network 9 | * into calls on actual RPC implementation. 10 | */ 11 | protected def localRpcAsRaw: ClientRawRpc.AsRawRpc[ClientRPCType] 12 | 13 | protected lazy val rawLocalRpc: ClientRawRpc = localRpcAsRaw.asRaw(localRpc) 14 | 15 | /** Handles RPCFires */ 16 | def handleRpcFire(fire: RpcFire): Unit = 17 | rawLocalRpc.handleFire(fire) 18 | } 19 | 20 | class DefaultExposesClientRPC[ClientRPCType](local: ClientRPCType)( 21 | implicit protected val localRpcAsRaw: ClientRawRpc.AsRawRpc[ClientRPCType] 22 | ) extends ExposesClientRPC(local) 23 | -------------------------------------------------------------------------------- /rpc/.js/src/main/scala/io/udash/rpc/package.scala: -------------------------------------------------------------------------------- 1 | package io.udash 2 | 3 | package object rpc extends RPCFrontend 4 | -------------------------------------------------------------------------------- /rpc/.js/src/main/scala/io/udash/rpc/serialization/DefaultExceptionCodecRegistry.scala: -------------------------------------------------------------------------------- 1 | package io.udash.rpc.serialization 2 | 3 | class DefaultExceptionCodecRegistry extends ClassNameBasedECR { 4 | override def name[T <: Throwable](ex: T): String = 5 | throw new NotImplementedError("This method is implemented only in the JVM version.") 6 | } -------------------------------------------------------------------------------- /rpc/.js/src/test/scala/io/udash/rpc/NativeRpcMessagesTest.scala: -------------------------------------------------------------------------------- 1 | package io.udash.rpc 2 | 3 | class NativeRpcMessagesTest extends RpcMessagesTestScenarios { 4 | "RPCMessages default JS serializers" should tests() 5 | "RPCMessages default JS serializers" should hugeTests() 6 | } 7 | -------------------------------------------------------------------------------- /rpc/.jvm/src/main/scala/io/udash/rpc/ExposesServerRPC.scala: -------------------------------------------------------------------------------- 1 | package io.udash.rpc 2 | 3 | import scala.concurrent.Future 4 | 5 | abstract class ExposesServerRPC[ServerRPCType](local: ServerRPCType) extends ExposesLocalRPC[ServerRPCType] { 6 | /** 7 | * This allows the RPC implementation to be wrapped in raw RPC which will translate raw calls coming from network 8 | * into calls on actual RPC implementation. 9 | */ 10 | protected def localRpcAsRaw: ServerRawRpc.AsRawRpc[ServerRPCType] 11 | 12 | protected lazy val rawLocalRpc: ServerRawRpc = 13 | localRpcAsRaw.asRaw(localRpc) 14 | 15 | override protected def localRpc: ServerRPCType = local 16 | 17 | /** Handles RPCCall and returns Future with call result. */ 18 | def handleRpcCall(call: RpcCall): Future[JsonStr] = 19 | rawLocalRpc.handleCall(call) 20 | 21 | /** Handles RPCFire */ 22 | def handleRpcFire(fire: RpcFire): Unit = 23 | rawLocalRpc.handleFire(fire) 24 | } 25 | 26 | class DefaultExposesServerRPC[ServerRPCType](local: ServerRPCType)( 27 | implicit protected val localRpcAsRaw: ServerRawRpc.AsRawRpc[ServerRPCType] 28 | ) extends ExposesServerRPC(local) 29 | -------------------------------------------------------------------------------- /rpc/.jvm/src/main/scala/io/udash/rpc/RPCBackend.scala: -------------------------------------------------------------------------------- 1 | package io.udash.rpc 2 | 3 | trait RPCBackend { 4 | type DefaultAtmosphereFramework = io.udash.rpc.utils.DefaultAtmosphereFramework 5 | type FileDownloadServlet = io.udash.rpc.utils.FileDownloadServlet 6 | type FileUploadServlet = io.udash.rpc.utils.FileUploadServlet 7 | } -------------------------------------------------------------------------------- /rpc/.jvm/src/main/scala/io/udash/rpc/RpcServlet.scala: -------------------------------------------------------------------------------- 1 | package io.udash.rpc 2 | 3 | import javax.servlet.ServletConfig 4 | import javax.servlet.http.{HttpServlet, HttpServletRequest, HttpServletResponse} 5 | import org.atmosphere.cpr._ 6 | 7 | /** 8 | * Servlet for RPC endpoint. 9 | * 10 | * @param framework Instance of initialized AtmosphereFramework, which handle RPC requests. 11 | */ 12 | class RpcServlet(framework: AtmosphereFramework) extends HttpServlet { 13 | override def init(config: ServletConfig): Unit = { 14 | super.init(config) 15 | framework.init(config) 16 | } 17 | 18 | override def doTrace(req: HttpServletRequest, resp: HttpServletResponse): Unit = doPost(req, resp) 19 | 20 | override def doGet(req: HttpServletRequest, resp: HttpServletResponse): Unit = doPost(req, resp) 21 | 22 | override def doPut(req: HttpServletRequest, resp: HttpServletResponse): Unit = doPost(req, resp) 23 | 24 | override def doPost(req: HttpServletRequest, resp: HttpServletResponse): Unit = { 25 | framework.doCometSupport(AtmosphereRequestImpl.wrap(req), AtmosphereResponseImpl.wrap(resp)) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /rpc/.jvm/src/main/scala/io/udash/rpc/internals/UsesClientRPC.scala: -------------------------------------------------------------------------------- 1 | package io.udash.rpc.internals 2 | 3 | import io.udash.rpc._ 4 | 5 | /** 6 | * Base class for server-side components which use some RPC exposed by client-side. 7 | */ 8 | private[rpc] abstract class UsesClientRPC[ClientRPCType] extends UsesRemoteRPC[ClientRPCType] { 9 | /** 10 | * Proxy for remote RPC implementation. Use this to perform RPC calls. 11 | */ 12 | lazy val remoteRpc: ClientRPCType = 13 | remoteRpcAsReal.asReal(new RawRemoteRPC(Nil)) 14 | 15 | /** 16 | * This allows for generation of proxy which translates RPC calls into raw calls that 17 | * can be sent through the network. 18 | */ 19 | protected def remoteRpcAsReal: ClientRawRpc.AsRealRpc[ClientRPCType] 20 | 21 | protected class RawRemoteRPC(getterChain: List[RpcInvocation]) extends ClientRawRpc { 22 | def fire(invocation: RpcInvocation): Unit = 23 | fireRemote(getterChain, invocation) 24 | 25 | def get(invocation: RpcInvocation): ClientRawRpc = 26 | new RawRemoteRPC(invocation :: getterChain) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /rpc/.jvm/src/main/scala/io/udash/rpc/package.scala: -------------------------------------------------------------------------------- 1 | package io.udash 2 | 3 | package object rpc extends RPCBackend 4 | -------------------------------------------------------------------------------- /rpc/.jvm/src/main/scala/io/udash/rpc/serialization/DefaultExceptionCodecRegistry.scala: -------------------------------------------------------------------------------- 1 | package io.udash.rpc.serialization 2 | 3 | class DefaultExceptionCodecRegistry extends ClassNameBasedECR { 4 | override def name[T <: Throwable](ex: T): String = { 5 | import com.avsystem.commons._ 6 | def find(cls: Class[_]): Opt[String] = { 7 | if (cls == null) Opt.Empty 8 | else if (codecs.contains(cls.getName)) Opt(cls.getName) 9 | else { 10 | cls.getInterfaces.iterator 11 | .flatMap(find) 12 | .filter(_.nonEmpty) 13 | .nextOpt 14 | .orElse(find(cls.getSuperclass)) 15 | } 16 | } 17 | find(ex.getClass).getOrElse(ex.getClass.getName) 18 | } 19 | } -------------------------------------------------------------------------------- /rpc/.jvm/src/main/scala/io/udash/rpc/utils/CallLogging.scala: -------------------------------------------------------------------------------- 1 | package io.udash.rpc.utils 2 | 3 | import io.udash.rpc._ 4 | 5 | import scala.concurrent.Future 6 | 7 | /** 8 | * ExposesServerRPC mixin simplifying RPC calls logging. 9 | */ 10 | trait CallLogging[ServerRPCType] extends ExposesServerRPC[ServerRPCType] { 11 | 12 | protected val metadata: ServerRpcMetadata[ServerRPCType] 13 | 14 | protected def logAll: Boolean = false 15 | 16 | def log(rpcName: String, methodName: String, args: Seq[String]): Unit 17 | 18 | private def handleRpcRequest(msg: RpcRequest): Unit = { 19 | val classMetadata = 20 | msg.gettersChain.reverseIterator.foldLeft[ServerRpcMetadata[_]](metadata) { 21 | (metadata, invocation) => metadata.getters(invocation.rpcName).resultMetadata.value 22 | } 23 | 24 | val methodMetadata = classMetadata.methods(msg.invocation.rpcName) 25 | 26 | if (logAll || methodMetadata.logged) 27 | log(classMetadata.name, methodMetadata.name, msg.invocation.args.map(_.json)) 28 | } 29 | 30 | override def handleRpcFire(fire: RpcFire): Unit = { 31 | handleRpcRequest(fire) 32 | super.handleRpcFire(fire) 33 | } 34 | 35 | override def handleRpcCall(call: RpcCall): Future[JsonStr] = { 36 | handleRpcRequest(call) 37 | super.handleRpcCall(call) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /rpc/.jvm/src/main/scala/io/udash/rpc/utils/FileUploadServlet.scala: -------------------------------------------------------------------------------- 1 | package io.udash.rpc.utils 2 | 3 | import java.io.InputStream 4 | import java.nio.file.Paths 5 | import javax.servlet.http.{HttpServlet, HttpServletRequest, HttpServletResponse} 6 | import com.avsystem.commons._ 7 | 8 | /** Template of a servlet handling files upload. It takes files from the request and passes data to the `handleFile` method. 9 | * @param fileFields Names of file inputs in the HTTP request. */ 10 | abstract class FileUploadServlet(fileFields: Set[String]) extends HttpServlet { 11 | /** Uploaded file handler. */ 12 | protected def handleFile(name: String, content: InputStream): Unit 13 | 14 | override protected def doPost(request: HttpServletRequest, response: HttpServletResponse): Unit = { 15 | request.getParts.asScala 16 | .filter(part => fileFields.contains(part.getName)) 17 | .foreach(filePart => { 18 | val fileName = Paths.get(filePart.getSubmittedFileName).getFileName.toString 19 | val fileContent = filePart.getInputStream 20 | handleFile(fileName, fileContent) 21 | fileContent.close() 22 | }) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /rpc/.jvm/src/test/scala/io/udash/rpc/JVMSerializationIntegrationTest.scala: -------------------------------------------------------------------------------- 1 | package io.udash.rpc 2 | 3 | class JVMSerializationIntegrationTest extends SerializationIntegrationTestBase { 4 | "DefaultUdashRPCFramework -> DefaultUdashRPCFramework default serialization" should tests 5 | } -------------------------------------------------------------------------------- /rpc/.jvm/src/test/scala/io/udash/rpc/RpcMessagesTest.scala: -------------------------------------------------------------------------------- 1 | package io.udash.rpc 2 | 3 | class RpcMessagesTest extends RpcMessagesTestScenarios { 4 | "RPCMessages default serializers" should tests() 5 | "RPCMessages default serializers" should hugeTests() 6 | } -------------------------------------------------------------------------------- /rpc/src/main/scala/io/udash/rpc/ExposesLocalRPC.scala: -------------------------------------------------------------------------------- 1 | package io.udash.rpc 2 | 3 | /** 4 | * Base trait for anything that exposes some RPC interface. 5 | */ 6 | trait ExposesLocalRPC[T] { 7 | /** 8 | * Implementation of local RPC interface. Common approach is to implement the local RPC directly and 9 | * return reference to `this` here. 10 | */ 11 | protected def localRpc: T 12 | } 13 | -------------------------------------------------------------------------------- /rpc/src/main/scala/io/udash/rpc/UsesRemoteRPC.scala: -------------------------------------------------------------------------------- 1 | package io.udash.rpc 2 | 3 | /** 4 | * Base trait for anything that uses remote RPC interface. 5 | */ 6 | trait UsesRemoteRPC[T] { 7 | /** 8 | * Sends the raw RPC invocation of method returning `Unit` through network. 9 | */ 10 | protected def fireRemote(getterChain: List[RpcInvocation], invocation: RpcInvocation): Unit 11 | } 12 | -------------------------------------------------------------------------------- /rpc/src/main/scala/io/udash/rpc/serialization/DefaultUdashSerialization.scala: -------------------------------------------------------------------------------- 1 | package io.udash.rpc.serialization 2 | 3 | import com.avsystem.commons.meta.Fallback 4 | import com.avsystem.commons.misc.ImplicitNotFound 5 | import com.avsystem.commons.rpc.{AsRaw, AsReal} 6 | import com.avsystem.commons.serialization.GenCodec 7 | import com.avsystem.commons.serialization.json.{JsonStringInput, JsonStringOutput} 8 | import io.udash.rpc.JsonStr 9 | 10 | import scala.annotation.implicitNotFound 11 | 12 | trait DefaultUdashSerialization { 13 | implicit def genCodecBasedAsReal[T: GenCodec]: Fallback[AsReal[JsonStr, T]] = 14 | Fallback(jsonStr => JsonStringInput.read[T](jsonStr.json)) 15 | 16 | implicit def genCodecBasedAsRaw[T: GenCodec]: Fallback[AsRaw[JsonStr, T]] = 17 | Fallback(value => JsonStr(JsonStringOutput.write[T](value))) 18 | 19 | @implicitNotFound("#{forGenCodec}") 20 | implicit def asRealNotFound[T]( 21 | implicit forGenCodec: ImplicitNotFound[GenCodec[T]] 22 | ): ImplicitNotFound[AsReal[JsonStr, T]] = ImplicitNotFound() 23 | 24 | @implicitNotFound("#{forGenCodec}") 25 | implicit def asRawNotFound[T]( 26 | implicit forGenCodec: ImplicitNotFound[GenCodec[T]] 27 | ): ImplicitNotFound[AsRaw[JsonStr, T]] = ImplicitNotFound() 28 | } 29 | object DefaultUdashSerialization extends DefaultUdashSerialization 30 | -------------------------------------------------------------------------------- /rpc/src/main/scala/io/udash/rpc/utils/Logged.scala: -------------------------------------------------------------------------------- 1 | package io.udash.rpc.utils 2 | 3 | import scala.annotation.StaticAnnotation 4 | 5 | /** 6 | * Marks RPC method for CallLogging 7 | */ 8 | class Logged extends StaticAnnotation 9 | -------------------------------------------------------------------------------- /rpc/src/test/scala/io/udash/rpc/types.scala: -------------------------------------------------------------------------------- 1 | package io.udash.rpc 2 | 3 | import com.avsystem.commons.serialization.HasGenCodec 4 | 5 | case class TestCC(i: Int, l: Long, intAsDouble: Double, b: Boolean, s: String, list: List[Char]) 6 | object TestCC extends HasGenCodec[TestCC] 7 | 8 | case class NestedTestCC(i: Int, t: TestCC, t2: TestCC) 9 | object NestedTestCC extends HasGenCodec[NestedTestCC] 10 | 11 | case class DeepNestedTestCC(n: NestedTestCC, l: DeepNestedTestCC) 12 | object DeepNestedTestCC extends HasGenCodec[DeepNestedTestCC] 13 | 14 | case class CompleteItem(unit: Unit, string: String, specialString: String, char: Char, boolean: Boolean, byte: Byte, short: Short, int: Int, 15 | long: Long, float: Float, double: Double, binary: Array[Byte], list: List[String], 16 | set: Set[String], obj: TestCC, map: Map[String, Int]) 17 | object CompleteItem extends HasGenCodec[CompleteItem] 18 | -------------------------------------------------------------------------------- /utils/.js/src/main/scala/io/udash/utils/CrossCollections.scala: -------------------------------------------------------------------------------- 1 | package io.udash 2 | package utils 3 | 4 | import com.avsystem.commons._ 5 | 6 | import scala.scalajs.js 7 | 8 | object CrossCollections { 9 | 10 | import scala.scalajs.js.JSConverters._ 11 | 12 | def toCrossArray[T](t: Iterable[T]): MBuffer[T] = t.toJSArray 13 | def createArray[T]: MBuffer[T] = js.Array[T]() 14 | def createDictionary[T]: MMap[String, T] = js.Dictionary[T]() 15 | def createMap[K, V]: MMap[K, V] = js.Map.empty[K, V] 16 | def createSet[T]: MSet[T] = js.Set.empty[T] 17 | def copyArray[T](a: MBuffer[T]): MBuffer[T] = a.toJSArray.jsSlice() 18 | def slice[T](a: MBuffer[T], from: Int, to: Int): MBuffer[T] = a.toJSArray.jsSlice(from, to) 19 | def replace[T](a: MBuffer[T], idx: Int, count: Int, items: T*): Unit = replaceSeq(a, idx, count, items) 20 | def replaceSeq[T](a: MBuffer[T], idx: Int, count: Int, items: BSeq[T]): Unit = 21 | a.toJSArray.splice(idx, count, items.toSeq: _*) 22 | } 23 | -------------------------------------------------------------------------------- /utils/.js/src/main/scala/io/udash/utils/URLEncoder.scala: -------------------------------------------------------------------------------- 1 | package io.udash.utils 2 | 3 | import scala.scalajs.js 4 | 5 | object URLEncoder { 6 | def encode(query: String, spaceAsPlus: Boolean): String = { 7 | val res = js.URIUtils.encodeURIComponent(query) 8 | .replace("!", "%21") 9 | .replace("'", "%27") 10 | .replace("(", "%28") 11 | .replace(")", "%29") 12 | .replace("~", "%7E") 13 | 14 | if (spaceAsPlus) res.replace("%20", "+") else res 15 | } 16 | 17 | def decode(query: String, plusAsSpace: Boolean): String = { 18 | val pre = if (plusAsSpace) query.replace("+", "%20") else query 19 | js.URIUtils.decodeURIComponent(pre) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /utils/.js/src/test/scala/io/udash/testing/UdashFrontendTest.scala: -------------------------------------------------------------------------------- 1 | package io.udash.testing 2 | 3 | import com.avsystem.commons._ 4 | import org.scalajs.dom 5 | import org.scalajs.dom.{DOMTokenList, Element} 6 | import org.scalatest.enablers.Containing 7 | 8 | trait FrontendTestUtils { 9 | def emptyComponent(): Element = dom.document.createElement("div") 10 | 11 | implicit val DOMTokensListContains: Containing[DOMTokenList] = new Containing[DOMTokenList] { 12 | override def contains(container: DOMTokenList, element: Any): Boolean = element match { 13 | case s: String => container.contains(s) 14 | case _ => false 15 | } 16 | 17 | override def containsOneOf(container: DOMTokenList, elements: BSeq[Any]): Boolean = elements.exists { 18 | case s: String => container.contains(s) 19 | case _ => false 20 | } 21 | 22 | override def containsNoneOf(container: DOMTokenList, elements: BSeq[Any]): Boolean = elements.forall { 23 | case s: String => container.contains(s) 24 | case _ => false 25 | } 26 | } 27 | } 28 | 29 | trait UdashFrontendTest extends UdashSharedTest with FrontendTestUtils 30 | 31 | trait AsyncUdashFrontendTest extends AsyncUdashSharedTest with FrontendTestUtils -------------------------------------------------------------------------------- /utils/.jvm/src/main/scala/io/udash/logging/UdashLogger.scala: -------------------------------------------------------------------------------- 1 | package io.udash.logging 2 | 3 | import com.typesafe.scalalogging.Logger 4 | import org.slf4j.LoggerFactory 5 | 6 | class UdashLogger(clazz: Class[_]) extends CrossLogger { 7 | private val internalLogger = Logger(LoggerFactory.getLogger(clazz.getName)) 8 | 9 | override def debug(message: String, params: Any*): Unit = 10 | internalLogger.debug(message, params) 11 | 12 | override def debug(message: String, cause: Throwable): Unit = 13 | internalLogger.debug(message, cause) 14 | 15 | override def info(message: String, params: Any*): Unit = 16 | internalLogger.info(message, params) 17 | 18 | override def info(message: String, cause: Throwable): Unit = 19 | internalLogger.info(message, cause) 20 | 21 | override def warn(message: String, params: Any*): Unit = 22 | internalLogger.warn(message, params) 23 | 24 | override def warn(message: String, cause: Throwable): Unit = 25 | internalLogger.warn(message, cause) 26 | 27 | override def error(message: String, params: Any*): Unit = 28 | internalLogger.error(message, params) 29 | 30 | override def error(message: String, cause: Throwable): Unit = 31 | internalLogger.error(message, cause) 32 | } 33 | -------------------------------------------------------------------------------- /utils/.jvm/src/main/scala/io/udash/utils/CrossCollections.scala: -------------------------------------------------------------------------------- 1 | package io.udash 2 | package utils 3 | 4 | import com.avsystem.commons._ 5 | 6 | object CrossCollections { 7 | def toCrossArray[T](t: Iterable[T]): MBuffer[T] = t.to(MArrayBuffer) 8 | def createArray[T]: MBuffer[T] = new MArrayBuffer[T] 9 | def createDictionary[T]: MMap[String, T] = new MHashMap[String, T] 10 | def createMap[K, V]: MMap[K, V] = new MHashMap[K, V] 11 | def createSet[T]: MSet[T] = new MHashSet[T] 12 | def copyArray[T](a: MBuffer[T]): MBuffer[T] = a.to(MArrayBuffer) // creates copy 13 | def slice[T](a: MBuffer[T], from: Int, to: Int): MBuffer[T] = a.slice(from, to) 14 | def replace[T](a: MBuffer[T], idx: Int, count: Int, items: T*): Unit = 15 | replaceSeq(a, idx, count, items) 16 | def replaceSeq[T](a: MBuffer[T], idx: Int, count: Int, items: BSeq[T]): Unit = { 17 | a.remove(idx, count) 18 | a.insertAll(idx, items) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /utils/.jvm/src/main/scala/io/udash/utils/URLEncoder.scala: -------------------------------------------------------------------------------- 1 | package io.udash.utils 2 | 3 | object URLEncoder { 4 | def encode(query: String, spaceAsPlus: Boolean): String = { 5 | val res = java.net.URLEncoder.encode(query, "UTF-8") 6 | if (spaceAsPlus) res else res.replace("+", "%20") 7 | } 8 | 9 | def decode(query: String, plusAsSpace: Boolean): String = { 10 | val pre = if (plusAsSpace) query else query.replace("+", "%2B") 11 | java.net.URLDecoder.decode(pre, "UTF-8") 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /utils/.jvm/src/test/scala/io/udash/testing/AsyncUdashSharedTest.scala: -------------------------------------------------------------------------------- 1 | package io.udash.testing 2 | 3 | import org.scalactic.source.Position 4 | import org.scalatest.concurrent.Eventually 5 | import org.scalatest.concurrent.PatienceConfiguration.{Interval, Timeout} 6 | import org.scalatest.{Assertion, Succeeded} 7 | 8 | import scala.concurrent.{ExecutionContext, Future} 9 | 10 | trait AsyncUdashSharedTest extends AsyncUdashSharedTestBase with Eventually { 11 | override implicit def executionContext: ExecutionContext = ExecutionContext.global 12 | 13 | override def retrying(code: => Any)(implicit patienceConfig: PatienceConfig, pos: Position): Future[Assertion] = { 14 | Future { 15 | eventually(Timeout(patienceConfig.timeout), Interval(patienceConfig.interval))(code) 16 | Succeeded 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /utils/src/main/scala/io/udash/logging/CrossLogger.scala: -------------------------------------------------------------------------------- 1 | package io.udash.logging 2 | 3 | trait CrossLogger { 4 | def debug(message: String, params: Any*): Unit 5 | def debug(message: String, cause: Throwable): Unit 6 | 7 | def info(message: String, params: Any*): Unit 8 | def info(message: String, cause: Throwable): Unit 9 | 10 | def warn(message: String, params: Any*): Unit 11 | def warn(message: String, cause: Throwable): Unit 12 | 13 | def error(message: String, params: Any*): Unit 14 | def error(message: String, cause: Throwable): Unit 15 | } 16 | -------------------------------------------------------------------------------- /utils/src/main/scala/io/udash/logging/CrossLogging.scala: -------------------------------------------------------------------------------- 1 | package io.udash.logging 2 | 3 | trait CrossLogging { 4 | protected def logger: CrossLogger = new UdashLogger(this.getClass) 5 | } 6 | -------------------------------------------------------------------------------- /utils/src/main/scala/io/udash/utils/FilteringUtils.scala: -------------------------------------------------------------------------------- 1 | package io.udash.utils 2 | 3 | import com.avsystem.commons._ 4 | 5 | object FilteringUtils { 6 | /** Finds the longest prefix with equal elements. */ 7 | def findEqPrefix[T](newPath: Iterator[T], previousPath: Iterator[T]): Iterator[T] = 8 | newPath.zip(previousPath).takeWhile { case (h1, h2) => h1 == h2 }.map(_._1) 9 | 10 | /** Finds @newPath suffix which is different than in @previousPath. */ 11 | def findDiffSuffix[T](newPath: Iterator[T], previousPath: Iterator[T]): Iterator[T] = 12 | newPath.map(_.opt).zipAll(previousPath.map(_.opt), Opt.Empty, Opt.Empty).dropWhile { case (h1, h2) => 13 | h1 == h2 14 | }.flatMap(_._1) 15 | } 16 | -------------------------------------------------------------------------------- /utils/src/main/scala/io/udash/utils/Registration.scala: -------------------------------------------------------------------------------- 1 | package io.udash.utils 2 | 3 | import scala.collection.mutable 4 | 5 | /** Should be returned from every callback registration method in Udash. */ 6 | trait Registration { 7 | /** Removes registered callback */ 8 | def cancel(): Unit 9 | 10 | /** Registers callback again. */ 11 | def restart(): Unit 12 | 13 | /** Returns `true`, if callback is active. */ 14 | def isActive: Boolean 15 | } 16 | 17 | private[udash] class SetRegistration[ElementType](s: mutable.Set[ElementType], el: ElementType) extends Registration { 18 | override def cancel(): Unit = s.synchronized { s -= el } 19 | override def restart(): Unit = s.synchronized { s += el } 20 | override def isActive: Boolean = s.synchronized { s.contains(el) } 21 | } -------------------------------------------------------------------------------- /utils/src/test/scala/io/udash/testing/AsyncUdashSharedTestBase.scala: -------------------------------------------------------------------------------- 1 | package io.udash.testing 2 | 3 | import org.scalactic.source.Position 4 | import org.scalatest.{Assertion, BeforeAndAfterAll} 5 | import org.scalatest.concurrent.PatienceConfiguration 6 | import org.scalatest.time.{Millis, Span} 7 | 8 | import scala.concurrent.Future 9 | import org.scalatest.matchers.should.Matchers 10 | import org.scalatest.wordspec.AsyncWordSpec 11 | 12 | trait AsyncUdashSharedTestBase extends AsyncWordSpec with Matchers with BeforeAndAfterAll with PatienceConfiguration { 13 | case class RetryingTimeout() extends Exception 14 | 15 | override implicit val patienceConfig: PatienceConfig = PatienceConfig(scaled(Span(5000, Millis)), scaled(Span(100, Millis))) 16 | 17 | def retrying(code: => Any)(implicit patienceConfig: PatienceConfig, pos: Position): Future[Assertion] 18 | } 19 | -------------------------------------------------------------------------------- /utils/src/test/scala/io/udash/testing/CompilationErrorAssertions.scala: -------------------------------------------------------------------------------- 1 | package io.udash 2 | package testing 3 | 4 | import org.scalatest.Assertions 5 | 6 | trait CompilationErrorAssertions extends Assertions { 7 | def typeErrorFor(code: String): String = macro io.udash.macros.TestMacros.typeErrorImpl 8 | } 9 | -------------------------------------------------------------------------------- /utils/src/test/scala/io/udash/testing/UdashSharedTest.scala: -------------------------------------------------------------------------------- 1 | package io.udash.testing 2 | 3 | import org.scalatest.BeforeAndAfterAll 4 | import org.scalatest.matchers.should.Matchers 5 | import org.scalatest.wordspec.AnyWordSpec 6 | 7 | trait UdashSharedTest extends AnyWordSpec with Matchers with BeforeAndAfterAll { 8 | } 9 | -------------------------------------------------------------------------------- /utils/src/test/scala/io/udash/utils/URLEncoderTest.scala: -------------------------------------------------------------------------------- 1 | package io.udash 2 | package utils 3 | 4 | import io.udash.testing.UdashSharedTest 5 | 6 | class URLEncoderTest extends UdashSharedTest { 7 | 8 | "URLEncoder" should { 9 | "encode and decode data in the same way for both JVM and JS" in { 10 | val data = "a b »~!@#$%^&*()_+=-`{}[]:\";'<>?/.,♦" 11 | 12 | URLEncoder.encode(data, spaceAsPlus = true) should 13 | be("a+b+%C2%BB%7E%21%40%23%24%25%5E%26*%28%29_%2B%3D-%60%7B%7D%5B%5D%3A%22%3B%27%3C%3E%3F%2F.%2C%E2%99%A6") 14 | URLEncoder.decode(URLEncoder.encode(data, spaceAsPlus = true), plusAsSpace = true) should be(data) 15 | 16 | URLEncoder.encode(data, spaceAsPlus = false) should 17 | be("a%20b%20%C2%BB%7E%21%40%23%24%25%5E%26*%28%29_%2B%3D-%60%7B%7D%5B%5D%3A%22%3B%27%3C%3E%3F%2F.%2C%E2%99%A6") 18 | URLEncoder.decode(URLEncoder.encode(data, spaceAsPlus = false), plusAsSpace = false) should be(data) 19 | } 20 | } 21 | 22 | } 23 | --------------------------------------------------------------------------------