From eca4a374647cbe6ae4ff60a8a093233ad257bb25 Mon Sep 17 00:00:00 2001 From: Manel Martos Date: Sun, 10 Mar 2024 19:08:09 +0100 Subject: [PATCH 1/7] Support Kotlin/Wasm --- .../cards/common/build.gradle.kts | 9 + .../promoter/common/build.gradle.kts | 9 + .../puzzle15/common/build.gradle.kts | 9 + .../puzzle15/web/build.gradle.kts | 8 + .../experimental/puzzle15/web/main.js.kt | 50 ++++ .../web/src/wasmJsMain/resources/index.html | 15 + .../web/src/wasmJsMain/resources/styles.css | 12 + .../test-drive/common/build.gradle.kts | 9 + .../backstack/common/build.gradle.kts | 9 + .../spotlight/common/build.gradle.kts | 9 + appyx-interactions/common/build.gradle.kts | 8 + .../com/bumble/appyx/interactions/platform.kt | 2 +- .../appyx/interactions/SystemClock.wasmJs.kt | 13 + .../com/bumble/appyx/interactions/UUID.kt | 13 + .../com/bumble/appyx/interactions/platform.kt | 3 + appyx-navigation/common/build.gradle.kts | 8 + .../integration/BrowserViewportWindow.kt | 82 ++++++ .../integration/MainIntegrationPoint.kt | 16 ++ .../navigation/integration/WebNodeHost.kt | 65 +++++ .../platform/OnBackPressedCallback.kt | 59 ++++ .../platform/OnBackPressedDispatcher.kt | 69 +++++ .../platform/PlatformBackHandler.wasmJs.kt | 56 ++++ .../platform/PlatformLifecycleRegistry.kt | 91 +++++++ demos/appyx-interactions/web/build.gradle.kts | 8 + .../com/bumble/appyx/interactions/main.js.kt | 78 ++++++ .../web/src/wasmJsMain/resources/index.html | 15 + .../web/src/wasmJsMain/resources/styles.css | 12 + .../appyx-navigation/common/build.gradle.kts | 9 + .../demos/navigation/platform/PlatformName.kt | 3 + .../navigation/ui/EmbeddableResourceImage.kt | 24 ++ demos/appyx-navigation/web/build.gradle.kts | 33 ++- .../com/bumble/appyx/navigation/Main.kt | 138 ++++++++++ .../bumble/appyx/navigation/WebTypography.kt | 44 +++ .../web/src/wasmJsMain/resources/index.html | 15 + .../web/src/wasmJsMain/resources/styles.css | 12 + demos/common/build.gradle.kts | 8 + demos/image-loader/common/build.gradle.kts | 8 + .../appyx/imageloader/ImageBitmap.wasmJs.kt | 9 + .../backstack/fader/web/build.gradle.kts | 9 + .../backstack/fader/BackStackFaderSample.kt | 51 ++++ .../appyx/demos/backstack/fader/main.js.kt | 47 ++++ .../web/src/wasmJsMain/resources/index.html | 15 + .../web/src/wasmJsMain/resources/styles.css | 19 ++ .../backstack/parallax/web/build.gradle.kts | 9 + .../parallax/BackStackParallaxSample.kt | 51 ++++ .../appyx/demos/backstack/parallax/main.js.kt | 44 +++ .../web/src/wasmJsMain/resources/index.html | 15 + .../web/src/wasmJsMain/resources/styles.css | 19 ++ .../backstack/slider/web/build.gradle.kts | 9 + .../backstack/slider/BackStackSliderSample.kt | 52 ++++ .../appyx/demos/backstack/slider/main.js.kt | 47 ++++ .../web/src/wasmJsMain/resources/index.html | 15 + .../web/src/wasmJsMain/resources/styles.css | 19 ++ .../backstack/stack3d/web/build.gradle.kts | 9 + .../backstack/stack3d/BackStack3DSample.kt | 51 ++++ .../appyx/demos/backstack/stack3d/main.js.kt | 47 ++++ .../web/src/wasmJsMain/resources/index.html | 15 + .../web/src/wasmJsMain/resources/styles.css | 19 ++ .../appyx-components/common/build.gradle.kts | 9 + .../datingcards/web/build.gradle.kts | 9 + .../datingcards/DatingCardsSample.kt | 49 ++++ .../demos/experimental/datingcards/main.js.kt | 47 ++++ .../web/src/wasmJsMain/resources/index.html | 15 + .../web/src/wasmJsMain/resources/styles.css | 19 ++ .../puzzle15/web/build.gradle.kts | 9 + .../demos/experimental/puzzle15/main.js.kt | 50 ++++ .../web/src/wasmJsMain/resources/index.html | 15 + .../web/src/wasmJsMain/resources/styles.css | 19 ++ .../spotlight/fader/web/build.gradle.kts | 9 + .../spotlight/fader/SpotlightFaderSample.kt | 65 +++++ .../appyx/demos/spotlight/fader/main.js.kt | 47 ++++ .../web/src/wasmJsMain/resources/index.html | 15 + .../web/src/wasmJsMain/resources/styles.css | 19 ++ .../spotlight/slider/web/build.gradle.kts | 9 + .../spotlight/slider/SpotlightSliderSample.kt | 51 ++++ .../appyx/demos/spotlight/slider/main.js.kt | 47 ++++ .../web/src/wasmJsMain/resources/index.html | 15 + .../web/src/wasmJsMain/resources/styles.css | 19 ++ .../sliderrotation/web/build.gradle.kts | 9 + .../SpotlightSliderRotationSample.kt | 53 ++++ .../demos/spotlight/sliderrotation/main.js.kt | 47 ++++ .../web/src/wasmJsMain/resources/index.html | 15 + .../web/src/wasmJsMain/resources/styles.css | 19 ++ .../sliderscale/web/build.gradle.kts | 9 + .../sliderscale/SpotlightSliderScaleSample.kt | 52 ++++ .../demos/spotlight/sliderscale/main.js.kt | 47 ++++ .../web/src/wasmJsMain/resources/index.html | 15 + .../web/src/wasmJsMain/resources/styles.css | 19 ++ .../spotlight/stack3d/web/build.gradle.kts | 9 + .../stack3d/SpotlightStack3DSample.kt | 72 +++++ .../appyx/demos/spotlight/stack3d/main.js.kt | 47 ++++ .../web/src/wasmJsMain/resources/index.html | 15 + .../web/src/wasmJsMain/resources/styles.css | 19 ++ .../dragprediction/web/build.gradle.kts | 9 + .../demos/dragprediction/DragPrediction.kt | 230 ++++++++++++++++ .../DragPredictionVisualisation.kt | 136 +++++++++ .../demos/dragprediction/TargetUiState.kt | 18 ++ .../appyx/demos/dragprediction/main.js.kt | 57 ++++ .../web/src/wasmJsMain/resources/index.html | 15 + .../web/src/wasmJsMain/resources/styles.css | 19 ++ .../incompletedrag/web/build.gradle.kts | 9 + .../demos/incompletedrag/IncompleteDrag.kt | 194 +++++++++++++ .../IncompleteDragVisualisation.kt | 154 +++++++++++ .../demos/incompletedrag/TargetUiState.kt | 18 ++ .../appyx/demos/incompletedrag/main.js.kt | 57 ++++ .../web/src/wasmJsMain/resources/index.html | 15 + .../web/src/wasmJsMain/resources/styles.css | 19 ++ .../observemp/web/build.gradle.kts | 9 + .../ObserveMotionPropertiesSample.kt | 137 ++++++++++ .../bumble/appyx/demos/observemp/main.js.kt | 47 ++++ .../web/src/wasmJsMain/resources/index.html | 15 + .../web/src/wasmJsMain/resources/styles.css | 19 ++ .../interactions/sample1/web/build.gradle.kts | 9 + .../com/bumble/appyx/demos/sample1/Sample1.kt | 189 +++++++++++++ .../demos/sample1/Sample1Visualisation.kt | 148 ++++++++++ .../appyx/demos/sample1/TargetUiState.kt | 16 ++ .../com/bumble/appyx/demos/sample1/main.js.kt | 57 ++++ .../web/src/wasmJsMain/resources/index.html | 15 + .../web/src/wasmJsMain/resources/styles.css | 19 ++ .../interactions/sample2/web/build.gradle.kts | 9 + .../com/bumble/appyx/demos/sample2/Sample2.kt | 190 +++++++++++++ .../demos/sample2/Sample2Visualisation.kt | 154 +++++++++++ .../appyx/demos/sample2/TargetUiState.kt | 18 ++ .../com/bumble/appyx/demos/sample2/main.js.kt | 57 ++++ .../web/src/wasmJsMain/resources/index.html | 15 + .../web/src/wasmJsMain/resources/styles.css | 19 ++ .../interactions/sample3/web/build.gradle.kts | 9 + .../com/bumble/appyx/demos/sample3/Sample3.kt | 257 ++++++++++++++++++ .../demos/sample3/Sample3Visualisation.kt | 148 ++++++++++ .../appyx/demos/sample3/TargetUiState.kt | 16 ++ .../com/bumble/appyx/demos/sample3/main.js.kt | 57 ++++ .../web/src/wasmJsMain/resources/index.html | 15 + .../web/src/wasmJsMain/resources/styles.css | 19 ++ demos/mkdocs/common/build.gradle.kts | 8 + .../com/bumble/appyx/demos/AppyxSample.kt | 65 +++++ .../common/build.gradle.kts | 9 + .../web/build.gradle.kts | 8 + .../appyx/demos/sandbox/navigation/Main.kt | 94 +++++++ .../web/src/wasmJsMain/resources/index.html | 15 + .../web/src/wasmJsMain/resources/styles.css | 12 + gradle.properties | 1 + gradle/libs.versions.toml | 2 +- .../MultiplatformConventionPlugin.kt | 1 + utils/customisations/build.gradle.kts | 8 + .../NodeCustomisationDirectoryImpl.wasmJs.kt | 33 +++ utils/material3/build.gradle.kts | 9 +- utils/multiplatform/build.gradle.kts | 8 + .../utils/multiplatform/AppyxLogger.wasmJs.kt | 50 ++++ .../appyx/utils/multiplatform/BuildFlags.kt | 6 + .../appyx/utils/multiplatform/Parcelable.kt | 8 + 150 files changed, 5530 insertions(+), 6 deletions(-) create mode 100644 appyx-components/experimental/puzzle15/web/src/wasmJsMain/kotlin/com/bumble/appyx/experimental/puzzle15/web/main.js.kt create mode 100644 appyx-components/experimental/puzzle15/web/src/wasmJsMain/resources/index.html create mode 100644 appyx-components/experimental/puzzle15/web/src/wasmJsMain/resources/styles.css create mode 100644 appyx-interactions/common/src/wasmJsMain/kotlin/com/bumble/appyx/interactions/SystemClock.wasmJs.kt create mode 100644 appyx-interactions/common/src/wasmJsMain/kotlin/com/bumble/appyx/interactions/UUID.kt create mode 100644 appyx-interactions/common/src/wasmJsMain/kotlin/com/bumble/appyx/interactions/platform.kt create mode 100644 appyx-navigation/common/src/wasmJsMain/kotlin/com/bumble/appyx/navigation/integration/BrowserViewportWindow.kt create mode 100644 appyx-navigation/common/src/wasmJsMain/kotlin/com/bumble/appyx/navigation/integration/MainIntegrationPoint.kt create mode 100644 appyx-navigation/common/src/wasmJsMain/kotlin/com/bumble/appyx/navigation/integration/WebNodeHost.kt create mode 100644 appyx-navigation/common/src/wasmJsMain/kotlin/com/bumble/appyx/navigation/platform/OnBackPressedCallback.kt create mode 100644 appyx-navigation/common/src/wasmJsMain/kotlin/com/bumble/appyx/navigation/platform/OnBackPressedDispatcher.kt create mode 100644 appyx-navigation/common/src/wasmJsMain/kotlin/com/bumble/appyx/navigation/platform/PlatformBackHandler.wasmJs.kt create mode 100644 appyx-navigation/common/src/wasmJsMain/kotlin/com/bumble/appyx/navigation/platform/PlatformLifecycleRegistry.kt create mode 100644 demos/appyx-interactions/web/src/wasmJsMain/kotlin/com/bumble/appyx/interactions/main.js.kt create mode 100644 demos/appyx-interactions/web/src/wasmJsMain/resources/index.html create mode 100644 demos/appyx-interactions/web/src/wasmJsMain/resources/styles.css create mode 100644 demos/appyx-navigation/common/src/wasmJsMain/kotlin/com/bumble/appyx/demos/navigation/platform/PlatformName.kt create mode 100644 demos/appyx-navigation/common/src/wasmJsMain/kotlin/com/bumble/appyx/demos/navigation/ui/EmbeddableResourceImage.kt create mode 100644 demos/appyx-navigation/web/src/wasmJsMain/kotlin/com/bumble/appyx/navigation/Main.kt create mode 100644 demos/appyx-navigation/web/src/wasmJsMain/kotlin/com/bumble/appyx/navigation/WebTypography.kt create mode 100644 demos/appyx-navigation/web/src/wasmJsMain/resources/index.html create mode 100644 demos/appyx-navigation/web/src/wasmJsMain/resources/styles.css create mode 100644 demos/image-loader/common/src/wasmJsMain/kotlin/com/bumble/appyx/imageloader/ImageBitmap.wasmJs.kt create mode 100644 demos/mkdocs/appyx-components/backstack/fader/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/backstack/fader/BackStackFaderSample.kt create mode 100644 demos/mkdocs/appyx-components/backstack/fader/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/backstack/fader/main.js.kt create mode 100644 demos/mkdocs/appyx-components/backstack/fader/web/src/wasmJsMain/resources/index.html create mode 100644 demos/mkdocs/appyx-components/backstack/fader/web/src/wasmJsMain/resources/styles.css create mode 100644 demos/mkdocs/appyx-components/backstack/parallax/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/backstack/parallax/BackStackParallaxSample.kt create mode 100644 demos/mkdocs/appyx-components/backstack/parallax/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/backstack/parallax/main.js.kt create mode 100644 demos/mkdocs/appyx-components/backstack/parallax/web/src/wasmJsMain/resources/index.html create mode 100644 demos/mkdocs/appyx-components/backstack/parallax/web/src/wasmJsMain/resources/styles.css create mode 100644 demos/mkdocs/appyx-components/backstack/slider/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/backstack/slider/BackStackSliderSample.kt create mode 100644 demos/mkdocs/appyx-components/backstack/slider/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/backstack/slider/main.js.kt create mode 100644 demos/mkdocs/appyx-components/backstack/slider/web/src/wasmJsMain/resources/index.html create mode 100644 demos/mkdocs/appyx-components/backstack/slider/web/src/wasmJsMain/resources/styles.css create mode 100644 demos/mkdocs/appyx-components/backstack/stack3d/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/backstack/stack3d/BackStack3DSample.kt create mode 100644 demos/mkdocs/appyx-components/backstack/stack3d/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/backstack/stack3d/main.js.kt create mode 100644 demos/mkdocs/appyx-components/backstack/stack3d/web/src/wasmJsMain/resources/index.html create mode 100644 demos/mkdocs/appyx-components/backstack/stack3d/web/src/wasmJsMain/resources/styles.css create mode 100644 demos/mkdocs/appyx-components/experimental/datingcards/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/experimental/datingcards/DatingCardsSample.kt create mode 100644 demos/mkdocs/appyx-components/experimental/datingcards/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/experimental/datingcards/main.js.kt create mode 100644 demos/mkdocs/appyx-components/experimental/datingcards/web/src/wasmJsMain/resources/index.html create mode 100644 demos/mkdocs/appyx-components/experimental/datingcards/web/src/wasmJsMain/resources/styles.css create mode 100644 demos/mkdocs/appyx-components/experimental/puzzle15/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/experimental/puzzle15/main.js.kt create mode 100644 demos/mkdocs/appyx-components/experimental/puzzle15/web/src/wasmJsMain/resources/index.html create mode 100644 demos/mkdocs/appyx-components/experimental/puzzle15/web/src/wasmJsMain/resources/styles.css create mode 100644 demos/mkdocs/appyx-components/spotlight/fader/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/spotlight/fader/SpotlightFaderSample.kt create mode 100644 demos/mkdocs/appyx-components/spotlight/fader/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/spotlight/fader/main.js.kt create mode 100644 demos/mkdocs/appyx-components/spotlight/fader/web/src/wasmJsMain/resources/index.html create mode 100644 demos/mkdocs/appyx-components/spotlight/fader/web/src/wasmJsMain/resources/styles.css create mode 100644 demos/mkdocs/appyx-components/spotlight/slider/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/spotlight/slider/SpotlightSliderSample.kt create mode 100644 demos/mkdocs/appyx-components/spotlight/slider/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/spotlight/slider/main.js.kt create mode 100644 demos/mkdocs/appyx-components/spotlight/slider/web/src/wasmJsMain/resources/index.html create mode 100644 demos/mkdocs/appyx-components/spotlight/slider/web/src/wasmJsMain/resources/styles.css create mode 100644 demos/mkdocs/appyx-components/spotlight/sliderrotation/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/spotlight/sliderrotation/SpotlightSliderRotationSample.kt create mode 100644 demos/mkdocs/appyx-components/spotlight/sliderrotation/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/spotlight/sliderrotation/main.js.kt create mode 100644 demos/mkdocs/appyx-components/spotlight/sliderrotation/web/src/wasmJsMain/resources/index.html create mode 100644 demos/mkdocs/appyx-components/spotlight/sliderrotation/web/src/wasmJsMain/resources/styles.css create mode 100644 demos/mkdocs/appyx-components/spotlight/sliderscale/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/spotlight/sliderscale/SpotlightSliderScaleSample.kt create mode 100644 demos/mkdocs/appyx-components/spotlight/sliderscale/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/spotlight/sliderscale/main.js.kt create mode 100644 demos/mkdocs/appyx-components/spotlight/sliderscale/web/src/wasmJsMain/resources/index.html create mode 100644 demos/mkdocs/appyx-components/spotlight/sliderscale/web/src/wasmJsMain/resources/styles.css create mode 100644 demos/mkdocs/appyx-components/spotlight/stack3d/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/spotlight/stack3d/SpotlightStack3DSample.kt create mode 100644 demos/mkdocs/appyx-components/spotlight/stack3d/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/spotlight/stack3d/main.js.kt create mode 100644 demos/mkdocs/appyx-components/spotlight/stack3d/web/src/wasmJsMain/resources/index.html create mode 100644 demos/mkdocs/appyx-components/spotlight/stack3d/web/src/wasmJsMain/resources/styles.css create mode 100644 demos/mkdocs/appyx-interactions/gestures/dragprediction/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/dragprediction/DragPrediction.kt create mode 100644 demos/mkdocs/appyx-interactions/gestures/dragprediction/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/dragprediction/DragPredictionVisualisation.kt create mode 100644 demos/mkdocs/appyx-interactions/gestures/dragprediction/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/dragprediction/TargetUiState.kt create mode 100644 demos/mkdocs/appyx-interactions/gestures/dragprediction/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/dragprediction/main.js.kt create mode 100644 demos/mkdocs/appyx-interactions/gestures/dragprediction/web/src/wasmJsMain/resources/index.html create mode 100644 demos/mkdocs/appyx-interactions/gestures/dragprediction/web/src/wasmJsMain/resources/styles.css create mode 100644 demos/mkdocs/appyx-interactions/gestures/incompletedrag/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/incompletedrag/IncompleteDrag.kt create mode 100644 demos/mkdocs/appyx-interactions/gestures/incompletedrag/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/incompletedrag/IncompleteDragVisualisation.kt create mode 100644 demos/mkdocs/appyx-interactions/gestures/incompletedrag/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/incompletedrag/TargetUiState.kt create mode 100644 demos/mkdocs/appyx-interactions/gestures/incompletedrag/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/incompletedrag/main.js.kt create mode 100644 demos/mkdocs/appyx-interactions/gestures/incompletedrag/web/src/wasmJsMain/resources/index.html create mode 100644 demos/mkdocs/appyx-interactions/gestures/incompletedrag/web/src/wasmJsMain/resources/styles.css create mode 100644 demos/mkdocs/appyx-interactions/interactions/observemp/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/observemp/ObserveMotionPropertiesSample.kt create mode 100644 demos/mkdocs/appyx-interactions/interactions/observemp/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/observemp/main.js.kt create mode 100644 demos/mkdocs/appyx-interactions/interactions/observemp/web/src/wasmJsMain/resources/index.html create mode 100644 demos/mkdocs/appyx-interactions/interactions/observemp/web/src/wasmJsMain/resources/styles.css create mode 100644 demos/mkdocs/appyx-interactions/interactions/sample1/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/sample1/Sample1.kt create mode 100644 demos/mkdocs/appyx-interactions/interactions/sample1/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/sample1/Sample1Visualisation.kt create mode 100644 demos/mkdocs/appyx-interactions/interactions/sample1/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/sample1/TargetUiState.kt create mode 100644 demos/mkdocs/appyx-interactions/interactions/sample1/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/sample1/main.js.kt create mode 100644 demos/mkdocs/appyx-interactions/interactions/sample1/web/src/wasmJsMain/resources/index.html create mode 100644 demos/mkdocs/appyx-interactions/interactions/sample1/web/src/wasmJsMain/resources/styles.css create mode 100644 demos/mkdocs/appyx-interactions/interactions/sample2/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/sample2/Sample2.kt create mode 100644 demos/mkdocs/appyx-interactions/interactions/sample2/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/sample2/Sample2Visualisation.kt create mode 100644 demos/mkdocs/appyx-interactions/interactions/sample2/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/sample2/TargetUiState.kt create mode 100644 demos/mkdocs/appyx-interactions/interactions/sample2/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/sample2/main.js.kt create mode 100644 demos/mkdocs/appyx-interactions/interactions/sample2/web/src/wasmJsMain/resources/index.html create mode 100644 demos/mkdocs/appyx-interactions/interactions/sample2/web/src/wasmJsMain/resources/styles.css create mode 100644 demos/mkdocs/appyx-interactions/interactions/sample3/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/sample3/Sample3.kt create mode 100644 demos/mkdocs/appyx-interactions/interactions/sample3/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/sample3/Sample3Visualisation.kt create mode 100644 demos/mkdocs/appyx-interactions/interactions/sample3/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/sample3/TargetUiState.kt create mode 100644 demos/mkdocs/appyx-interactions/interactions/sample3/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/sample3/main.js.kt create mode 100644 demos/mkdocs/appyx-interactions/interactions/sample3/web/src/wasmJsMain/resources/index.html create mode 100644 demos/mkdocs/appyx-interactions/interactions/sample3/web/src/wasmJsMain/resources/styles.css create mode 100644 demos/mkdocs/common/src/wasmJsMain/kotlin/com/bumble/appyx/demos/AppyxSample.kt create mode 100644 demos/sandbox-appyx-navigation/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/sandbox/navigation/Main.kt create mode 100644 demos/sandbox-appyx-navigation/web/src/wasmJsMain/resources/index.html create mode 100644 demos/sandbox-appyx-navigation/web/src/wasmJsMain/resources/styles.css create mode 100644 utils/customisations/src/wasmJsMain/kotlin/com/bumble/appyx/utils/customisations/NodeCustomisationDirectoryImpl.wasmJs.kt create mode 100644 utils/multiplatform/src/wasmJsMain/kotlin/com/bumble/appyx/utils/multiplatform/AppyxLogger.wasmJs.kt create mode 100644 utils/multiplatform/src/wasmJsMain/kotlin/com/bumble/appyx/utils/multiplatform/BuildFlags.kt create mode 100644 utils/multiplatform/src/wasmJsMain/kotlin/com/bumble/appyx/utils/multiplatform/Parcelable.kt diff --git a/appyx-components/experimental/cards/common/build.gradle.kts b/appyx-components/experimental/cards/common/build.gradle.kts index 054ebae60..d5711023d 100644 --- a/appyx-components/experimental/cards/common/build.gradle.kts +++ b/appyx-components/experimental/cards/common/build.gradle.kts @@ -1,3 +1,5 @@ +import org.jetbrains.kotlin.gradle.targets.js.dsl.ExperimentalWasmDsl + plugins { id("com.bumble.appyx.multiplatform") id("org.jetbrains.compose") @@ -25,6 +27,12 @@ kotlin { moduleName = "appyx-components-experimental-cards-commons" browser() } + @OptIn(ExperimentalWasmDsl::class) + wasmJs { + // Adding moduleName as a workaround for this issue: https://youtrack.jetbrains.com/issue/KT-51942 + moduleName = "appyx-components-experimental-cards-commons" + browser() + } iosX64() iosArm64() iosSimulatorArm64() @@ -61,6 +69,7 @@ dependencies { add("kspAndroid", project(":ksp:appyx-processor")) add("kspDesktop", project(":ksp:appyx-processor")) add("kspJs", project(":ksp:appyx-processor")) + add("kspWasmJs", project(":ksp:appyx-processor")) add("kspIosArm64", project(":ksp:appyx-processor")) add("kspIosX64", project(":ksp:appyx-processor")) add("kspIosSimulatorArm64", project(":ksp:appyx-processor")) diff --git a/appyx-components/experimental/promoter/common/build.gradle.kts b/appyx-components/experimental/promoter/common/build.gradle.kts index 10f2e7458..748523bd9 100644 --- a/appyx-components/experimental/promoter/common/build.gradle.kts +++ b/appyx-components/experimental/promoter/common/build.gradle.kts @@ -1,3 +1,5 @@ +import org.jetbrains.kotlin.gradle.targets.js.dsl.ExperimentalWasmDsl + plugins { id("com.bumble.appyx.multiplatform") id("org.jetbrains.compose") @@ -25,6 +27,12 @@ kotlin { moduleName = "appyx-components-experimental-promoter-commons" browser() } + @OptIn(ExperimentalWasmDsl::class) + wasmJs { + // Adding moduleName as a workaround for this issue: https://youtrack.jetbrains.com/issue/KT-51942 + moduleName = "appyx-components-experimental-promoter-commons" + browser() + } iosX64() iosArm64() iosSimulatorArm64() @@ -62,6 +70,7 @@ dependencies { add("kspAndroid", project(":ksp:appyx-processor")) add("kspDesktop", project(":ksp:appyx-processor")) add("kspJs", project(":ksp:appyx-processor")) + add("kspWasmJs", project(":ksp:appyx-processor")) add("kspIosArm64", project(":ksp:appyx-processor")) add("kspIosX64", project(":ksp:appyx-processor")) add("kspIosSimulatorArm64", project(":ksp:appyx-processor")) diff --git a/appyx-components/experimental/puzzle15/common/build.gradle.kts b/appyx-components/experimental/puzzle15/common/build.gradle.kts index 6b484b3bc..664c2d9ef 100644 --- a/appyx-components/experimental/puzzle15/common/build.gradle.kts +++ b/appyx-components/experimental/puzzle15/common/build.gradle.kts @@ -1,3 +1,5 @@ +import org.jetbrains.kotlin.gradle.targets.js.dsl.ExperimentalWasmDsl + plugins { id("com.bumble.appyx.multiplatform") id("org.jetbrains.compose") @@ -25,6 +27,12 @@ kotlin { moduleName = "appyx-components-experimental-puzzle15-commons" browser() } + @OptIn(ExperimentalWasmDsl::class) + wasmJs { + // Adding moduleName as a workaround for this issue: https://youtrack.jetbrains.com/issue/KT-51942 + moduleName = "appyx-components-experimental-puzzle15-commons" + browser() + } sourceSets { val commonMain by getting { dependencies { @@ -49,4 +57,5 @@ dependencies { add("kspAndroid", project(":ksp:appyx-processor")) add("kspDesktop", project(":ksp:appyx-processor")) add("kspJs", project(":ksp:appyx-processor")) + add("kspWasmJs", project(":ksp:appyx-processor")) } diff --git a/appyx-components/experimental/puzzle15/web/build.gradle.kts b/appyx-components/experimental/puzzle15/web/build.gradle.kts index 332d30812..2dda95172 100644 --- a/appyx-components/experimental/puzzle15/web/build.gradle.kts +++ b/appyx-components/experimental/puzzle15/web/build.gradle.kts @@ -1,3 +1,5 @@ +import org.jetbrains.kotlin.gradle.targets.js.dsl.ExperimentalWasmDsl + plugins { id("com.bumble.appyx.multiplatform") id("org.jetbrains.compose") @@ -9,6 +11,12 @@ kotlin { browser() binaries.executable() } + @OptIn(ExperimentalWasmDsl::class) + wasmJs { + moduleName = "appyx-components-experimental-puzzle15-web" + browser() + binaries.executable() + } sourceSets { val commonMain by getting { dependencies { diff --git a/appyx-components/experimental/puzzle15/web/src/wasmJsMain/kotlin/com/bumble/appyx/experimental/puzzle15/web/main.js.kt b/appyx-components/experimental/puzzle15/web/src/wasmJsMain/kotlin/com/bumble/appyx/experimental/puzzle15/web/main.js.kt new file mode 100644 index 000000000..be5c03358 --- /dev/null +++ b/appyx-components/experimental/puzzle15/web/src/wasmJsMain/kotlin/com/bumble/appyx/experimental/puzzle15/web/main.js.kt @@ -0,0 +1,50 @@ +package com.bumble.appyx.experimental.puzzle15.web + +import androidx.compose.foundation.background +import androidx.compose.foundation.focusable +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.material.Surface +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.ExperimentalComposeUiApi +import androidx.compose.ui.Modifier +import androidx.compose.ui.focus.FocusRequester +import androidx.compose.ui.focus.focusRequester +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.layout.onSizeChanged +import androidx.compose.ui.unit.IntSize +import androidx.compose.ui.window.CanvasBasedWindow +import com.bumble.appyx.components.experimental.puzzle15.ui.Puzzle15Ui +import org.jetbrains.skiko.wasm.onWasmReady + +@OptIn(ExperimentalComposeUiApi::class) +fun main() { + onWasmReady { + CanvasBasedWindow("Puzzle15") { + val requester = remember { FocusRequester() } + var size by remember { mutableStateOf(IntSize.Zero) } + Surface( + modifier = Modifier + .fillMaxSize() + .onSizeChanged { size = it } + ) { + Puzzle15Ui( + screenWidthPx = size.width, + screenHeightPx = size.height, + modifier = Modifier + .fillMaxSize() + .background(Color.Black) + .focusRequester(requester) + .focusable(), + ) + } + + LaunchedEffect(Unit) { + requester.requestFocus() + } + } + } +} diff --git a/appyx-components/experimental/puzzle15/web/src/wasmJsMain/resources/index.html b/appyx-components/experimental/puzzle15/web/src/wasmJsMain/resources/index.html new file mode 100644 index 000000000..5d3cb39cc --- /dev/null +++ b/appyx-components/experimental/puzzle15/web/src/wasmJsMain/resources/index.html @@ -0,0 +1,15 @@ + + + + + Puzzle15 + + + + +
+ +
+ + + diff --git a/appyx-components/experimental/puzzle15/web/src/wasmJsMain/resources/styles.css b/appyx-components/experimental/puzzle15/web/src/wasmJsMain/resources/styles.css new file mode 100644 index 000000000..8655f2e76 --- /dev/null +++ b/appyx-components/experimental/puzzle15/web/src/wasmJsMain/resources/styles.css @@ -0,0 +1,12 @@ +#root { + width: 100%; + height: 100vh; +} + +body { + margin: 0; +} + +#root > .compose-web-column > div { + position: relative; +} diff --git a/appyx-components/internal/test-drive/common/build.gradle.kts b/appyx-components/internal/test-drive/common/build.gradle.kts index 926510668..998874b3b 100644 --- a/appyx-components/internal/test-drive/common/build.gradle.kts +++ b/appyx-components/internal/test-drive/common/build.gradle.kts @@ -1,3 +1,5 @@ +import org.jetbrains.kotlin.gradle.targets.js.dsl.ExperimentalWasmDsl + plugins { id("com.bumble.appyx.multiplatform") id("org.jetbrains.compose") @@ -24,6 +26,12 @@ kotlin { moduleName = "appyx-components-internal-testdrive-common" browser() } + @OptIn(ExperimentalWasmDsl::class) + wasmJs { + // Adding moduleName as a workaround for this issue: https://youtrack.jetbrains.com/issue/KT-51942 + moduleName = "appyx-components-internal-testdrive-common" + browser() + } sourceSets { val commonMain by getting { dependencies { @@ -49,4 +57,5 @@ dependencies { add("kspAndroid", project(":ksp:appyx-processor")) add("kspDesktop", project(":ksp:appyx-processor")) add("kspJs", project(":ksp:appyx-processor")) + add("kspWasmJs", project(":ksp:appyx-processor")) } diff --git a/appyx-components/standard/backstack/common/build.gradle.kts b/appyx-components/standard/backstack/common/build.gradle.kts index ae4cd61cd..4c04366bd 100644 --- a/appyx-components/standard/backstack/common/build.gradle.kts +++ b/appyx-components/standard/backstack/common/build.gradle.kts @@ -1,3 +1,5 @@ +import org.jetbrains.kotlin.gradle.targets.js.dsl.ExperimentalWasmDsl + plugins { id("com.bumble.appyx.multiplatform") id("org.jetbrains.compose") @@ -25,6 +27,12 @@ kotlin { moduleName = "appyx-components-stable-backstack-commons" browser() } + @OptIn(ExperimentalWasmDsl::class) + wasmJs { + // Adding moduleName as a workaround for this issue: https://youtrack.jetbrains.com/issue/KT-51942 + moduleName = "appyx-components-stable-backstack-commons" + browser() + } iosX64() iosArm64() @@ -65,6 +73,7 @@ dependencies { add("kspAndroid", project(":ksp:appyx-processor")) add("kspDesktop", project(":ksp:appyx-processor")) add("kspJs", project(":ksp:appyx-processor")) + add("kspWasmJs", project(":ksp:appyx-processor")) add("kspIosArm64", project(":ksp:appyx-processor")) add("kspIosX64", project(":ksp:appyx-processor")) add("kspIosSimulatorArm64", project(":ksp:appyx-processor")) diff --git a/appyx-components/standard/spotlight/common/build.gradle.kts b/appyx-components/standard/spotlight/common/build.gradle.kts index 2b56f8952..b28b77529 100644 --- a/appyx-components/standard/spotlight/common/build.gradle.kts +++ b/appyx-components/standard/spotlight/common/build.gradle.kts @@ -1,3 +1,5 @@ +import org.jetbrains.kotlin.gradle.targets.js.dsl.ExperimentalWasmDsl + plugins { id("com.bumble.appyx.multiplatform") id("org.jetbrains.compose") @@ -25,6 +27,12 @@ kotlin { moduleName = "appyx-components-stable-spotlight-commons" browser() } + @OptIn(ExperimentalWasmDsl::class) + wasmJs { + // Adding moduleName as a workaround for this issue: https://youtrack.jetbrains.com/issue/KT-51942 + moduleName = "appyx-components-stable-spotlight-commons" + browser() + } iosX64() iosArm64() @@ -68,6 +76,7 @@ dependencies { add("kspAndroid", project(":ksp:appyx-processor")) add("kspDesktop", project(":ksp:appyx-processor")) add("kspJs", project(":ksp:appyx-processor")) + add("kspWasmJs", project(":ksp:appyx-processor")) add("kspIosArm64", project(":ksp:appyx-processor")) add("kspIosX64", project(":ksp:appyx-processor")) add("kspIosSimulatorArm64", project(":ksp:appyx-processor")) diff --git a/appyx-interactions/common/build.gradle.kts b/appyx-interactions/common/build.gradle.kts index 72ee1489e..5d569ba43 100644 --- a/appyx-interactions/common/build.gradle.kts +++ b/appyx-interactions/common/build.gradle.kts @@ -1,3 +1,5 @@ +import org.jetbrains.kotlin.gradle.targets.js.dsl.ExperimentalWasmDsl + plugins { id("com.bumble.appyx.multiplatform") kotlin("plugin.serialization") @@ -25,6 +27,12 @@ kotlin { moduleName = "appyx-interactions-common" browser() } + @OptIn(ExperimentalWasmDsl::class) + wasmJs { + // Adding moduleName as a workaround for this issue: https://youtrack.jetbrains.com/issue/KT-51942 + moduleName = "appyx-interactions-common" + browser() + } iosX64() iosArm64() diff --git a/appyx-interactions/common/src/jsMain/kotlin/com/bumble/appyx/interactions/platform.kt b/appyx-interactions/common/src/jsMain/kotlin/com/bumble/appyx/interactions/platform.kt index b66bfb4e3..ba9d2071b 100644 --- a/appyx-interactions/common/src/jsMain/kotlin/com/bumble/appyx/interactions/platform.kt +++ b/appyx-interactions/common/src/jsMain/kotlin/com/bumble/appyx/interactions/platform.kt @@ -1,3 +1,3 @@ package com.bumble.appyx.interactions -actual fun getPlatformName(): String = "Web" +actual fun getPlatformName(): String = "Kotlin/JS" diff --git a/appyx-interactions/common/src/wasmJsMain/kotlin/com/bumble/appyx/interactions/SystemClock.wasmJs.kt b/appyx-interactions/common/src/wasmJsMain/kotlin/com/bumble/appyx/interactions/SystemClock.wasmJs.kt new file mode 100644 index 000000000..ba43a8c19 --- /dev/null +++ b/appyx-interactions/common/src/wasmJsMain/kotlin/com/bumble/appyx/interactions/SystemClock.wasmJs.kt @@ -0,0 +1,13 @@ +package com.bumble.appyx.interactions + +import kotlinx.browser.window +import kotlin.math.roundToLong + +actual object SystemClock { + + actual fun nanoTime(): Long = + (window.performance.now() * MillisToNanos).roundToLong() + + private const val MillisToNanos = 1_000_000L + +} \ No newline at end of file diff --git a/appyx-interactions/common/src/wasmJsMain/kotlin/com/bumble/appyx/interactions/UUID.kt b/appyx-interactions/common/src/wasmJsMain/kotlin/com/bumble/appyx/interactions/UUID.kt new file mode 100644 index 000000000..e3ae0ca8b --- /dev/null +++ b/appyx-interactions/common/src/wasmJsMain/kotlin/com/bumble/appyx/interactions/UUID.kt @@ -0,0 +1,13 @@ +package com.bumble.appyx.interactions + +@JsModule("uuid") +private external object Uuid { + fun v4(): String +} + +actual object UUID { + + actual fun randomUUID(): String = + Uuid.v4() + +} diff --git a/appyx-interactions/common/src/wasmJsMain/kotlin/com/bumble/appyx/interactions/platform.kt b/appyx-interactions/common/src/wasmJsMain/kotlin/com/bumble/appyx/interactions/platform.kt new file mode 100644 index 000000000..a155aefee --- /dev/null +++ b/appyx-interactions/common/src/wasmJsMain/kotlin/com/bumble/appyx/interactions/platform.kt @@ -0,0 +1,3 @@ +package com.bumble.appyx.interactions + +actual fun getPlatformName(): String = "Kotlin/Wasm" diff --git a/appyx-navigation/common/build.gradle.kts b/appyx-navigation/common/build.gradle.kts index 11c1cc48c..7bdc432fd 100644 --- a/appyx-navigation/common/build.gradle.kts +++ b/appyx-navigation/common/build.gradle.kts @@ -1,3 +1,5 @@ +import org.jetbrains.kotlin.gradle.targets.js.dsl.ExperimentalWasmDsl + plugins { id("com.bumble.appyx.multiplatform") id("org.jetbrains.compose") @@ -9,6 +11,7 @@ appyx { androidNamespace.set("com.bumble.appyx.navigation") } +@OptIn(ExperimentalWasmDsl::class) kotlin { androidTarget { publishLibraryVariants("release") @@ -23,6 +26,11 @@ kotlin { moduleName = "appyx-navigation-common" browser() } + wasmJs { + // Adding moduleName as a workaround for this issue: https://youtrack.jetbrains.com/issue/KT-51942 + moduleName = "appyx-navigation-common" + browser() + } iosX64() iosArm64() diff --git a/appyx-navigation/common/src/wasmJsMain/kotlin/com/bumble/appyx/navigation/integration/BrowserViewportWindow.kt b/appyx-navigation/common/src/wasmJsMain/kotlin/com/bumble/appyx/navigation/integration/BrowserViewportWindow.kt new file mode 100644 index 000000000..90f94b819 --- /dev/null +++ b/appyx-navigation/common/src/wasmJsMain/kotlin/com/bumble/appyx/navigation/integration/BrowserViewportWindow.kt @@ -0,0 +1,82 @@ +// From Slack by OliverO +// See: https://kotlinlang.slack.com/archives/C01F2HV7868/p1660083429206369?thread_ts=1660083398.571449&cid=C01F2HV7868 +// adapted with scaling fix from https://github.com/OliverO2/compose-counting-grid/blob/master/src/frontendJsMain/kotlin/BrowserViewportWindow.kt + +@file:Suppress( + "INVISIBLE_MEMBER", + "INVISIBLE_REFERENCE", + "EXPOSED_PARAMETER_TYPE" +) // WORKAROUND: ComposeWindow and ComposeLayer are internal + +package com.bumble.appyx.navigation.integration + +import androidx.compose.runtime.Composable +import androidx.compose.ui.window.ComposeWindow +import kotlinx.browser.document +import kotlinx.browser.window +import org.w3c.dom.HTMLCanvasElement +import org.w3c.dom.HTMLStyleElement +import org.w3c.dom.HTMLTitleElement + +private const val CANVAS_ELEMENT_ID = "ComposeTarget" // Hardwired into ComposeWindow + +/** + * A Skiko/Canvas-based top-level window using the browser's entire viewport. Supports resizing. + */ +@Composable +@Suppress("FunctionNaming") +fun BrowserViewportWindow( + title: String = "Untitled", + content: @Composable () -> Unit +) { + val htmlHeadElement = document.head!! + htmlHeadElement.appendChild( + (document.createElement("style") as HTMLStyleElement).apply { + type = "text/css" + appendChild( + document.createTextNode( + """ + html, body { + overflow: hidden; + margin: 0 !important; + padding: 0 !important; + } + + #$CANVAS_ELEMENT_ID { + outline: none; + } + """.trimIndent() + ) + ) + } + ) + + fun HTMLCanvasElement.fillViewportSize() { + setAttribute("width", "${window.innerWidth}") + setAttribute("height", "${window.innerHeight}") + } + + val canvas = (document.getElementById(CANVAS_ELEMENT_ID) as HTMLCanvasElement).apply { + fillViewportSize() + } + + ComposeWindow(canvasId = "Appyx", content = content).apply { + window.addEventListener("resize", { + canvas.fillViewportSize() + layer.layer.attachTo(canvas) + layer.layer.needRedraw() + val scale = layer.layer.contentScale + layer.setSize( + (canvas.width / scale * density.density).toInt(), + (canvas.height / scale * density.density).toInt() + ) + }) + + // WORKAROUND: ComposeWindow does not implement `setTitle(title)` + val htmlTitleElement = ( + htmlHeadElement.getElementsByTagName("title").item(0) + ?: document.createElement("title").also { htmlHeadElement.appendChild(it) } + ) as HTMLTitleElement + htmlTitleElement.textContent = title + } +} diff --git a/appyx-navigation/common/src/wasmJsMain/kotlin/com/bumble/appyx/navigation/integration/MainIntegrationPoint.kt b/appyx-navigation/common/src/wasmJsMain/kotlin/com/bumble/appyx/navigation/integration/MainIntegrationPoint.kt new file mode 100644 index 000000000..2adb723bb --- /dev/null +++ b/appyx-navigation/common/src/wasmJsMain/kotlin/com/bumble/appyx/navigation/integration/MainIntegrationPoint.kt @@ -0,0 +1,16 @@ +package com.bumble.appyx.navigation.integration + + +class MainIntegrationPoint : IntegrationPoint() { + override val isChangingConfigurations: Boolean + get() = false + + @Suppress("EmptyFunctionBlock") + override fun onRootFinished() { + } + + @Suppress("EmptyFunctionBlock") + override fun handleUpNavigation() { + + } +} diff --git a/appyx-navigation/common/src/wasmJsMain/kotlin/com/bumble/appyx/navigation/integration/WebNodeHost.kt b/appyx-navigation/common/src/wasmJsMain/kotlin/com/bumble/appyx/navigation/integration/WebNodeHost.kt new file mode 100644 index 000000000..b2b2b30d4 --- /dev/null +++ b/appyx-navigation/common/src/wasmJsMain/kotlin/com/bumble/appyx/navigation/integration/WebNodeHost.kt @@ -0,0 +1,65 @@ +package com.bumble.appyx.navigation.integration + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.Modifier +import com.bumble.appyx.navigation.node.Node +import com.bumble.appyx.navigation.platform.LocalOnBackPressedDispatcherOwner +import com.bumble.appyx.navigation.platform.OnBackPressedDispatcher +import com.bumble.appyx.navigation.platform.OnBackPressedDispatcherOwner +import com.bumble.appyx.navigation.platform.PlatformLifecycleRegistry +import com.bumble.appyx.utils.customisations.NodeCustomisationDirectory +import com.bumble.appyx.utils.customisations.NodeCustomisationDirectoryImpl +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.launch + +/** + * Composable function to host [Node<*>]. + * + * This convenience wrapper provides an [OnBackPressedDispatcherOwner] hooked up to the + * [.onBackPressedEvents] flow to simplify implementing the global "go back" functionality + * that is a common concept in the Appyx framework. + */ +@Suppress("ComposableParamOrder") +@Composable +fun > WebNodeHost( + screenSize: ScreenSize, + onBackPressedEvents: Flow, + modifier: Modifier = Modifier, + integrationPoint: IntegrationPoint = remember { MainIntegrationPoint() }, + customisations: NodeCustomisationDirectory = remember { NodeCustomisationDirectoryImpl() }, + factory: NodeFactory +) { + val platformLifecycleRegistry = remember { + PlatformLifecycleRegistry() + } + val onBackPressedDispatcherOwner = remember { + object : OnBackPressedDispatcherOwner { + override val onBackPressedDispatcher: OnBackPressedDispatcher = + OnBackPressedDispatcher { integrationPoint.handleUpNavigation() } + } + } + + val scope = rememberCoroutineScope() + LaunchedEffect(onBackPressedEvents) { + scope.launch { + onBackPressedEvents.collect { + onBackPressedDispatcherOwner.onBackPressedDispatcher.onBackPressed() + } + } + } + + CompositionLocalProvider(LocalOnBackPressedDispatcherOwner provides onBackPressedDispatcherOwner) { + NodeHost( + lifecycle = platformLifecycleRegistry, + integrationPoint = integrationPoint, + modifier = modifier, + customisations = customisations, + screenSize = screenSize, + factory = factory, + ) + } +} diff --git a/appyx-navigation/common/src/wasmJsMain/kotlin/com/bumble/appyx/navigation/platform/OnBackPressedCallback.kt b/appyx-navigation/common/src/wasmJsMain/kotlin/com/bumble/appyx/navigation/platform/OnBackPressedCallback.kt new file mode 100644 index 000000000..a568631a5 --- /dev/null +++ b/appyx-navigation/common/src/wasmJsMain/kotlin/com/bumble/appyx/navigation/platform/OnBackPressedCallback.kt @@ -0,0 +1,59 @@ +package com.bumble.appyx.navigation.platform + +interface Cancellable { + /** + * Cancel the subscription. This call should be idempotent, making it safe to + * call multiple times. + */ + fun cancel() +} + +/** + * Create a [OnBackPressedCallback]. + * + * @param isEnabled The default enabled state for this callback. + * @see .setEnabled + */ +abstract class OnBackPressedCallback( + /** + * Set the enabled state of the callback. Only when this callback + * is enabled will it receive callbacks to [.handleOnBackPressed]. + * + * @param isEnabled whether the callback should be considered enabled + */ + var isEnabled: Boolean +) { + /** + * Checks whether this callback should be considered enabled. Only when this callback + * is enabled will it receive callbacks to [.handleOnBackPressed]. + * + * @return Whether this callback should be considered enabled. + */ + private val cancellables: MutableList = mutableListOf() + + /** + * Removes this callback from any [OnBackPressedDispatcher] it is currently + * added to. + */ + fun remove() { + for (cancellable in cancellables) { + cancellable.cancel() + } + } + + /** + * Callback for handling the [OnBackPressedDispatcher.onBackPressed] event. + */ + abstract fun handleOnBackPressed() + fun addCancellable(cancellable: Cancellable) { + run { + cancellables.add(cancellable) + } + } + + fun removeCancellable(cancellable: Cancellable) { + run { + cancellables.remove(cancellable) + } + } +} diff --git a/appyx-navigation/common/src/wasmJsMain/kotlin/com/bumble/appyx/navigation/platform/OnBackPressedDispatcher.kt b/appyx-navigation/common/src/wasmJsMain/kotlin/com/bumble/appyx/navigation/platform/OnBackPressedDispatcher.kt new file mode 100644 index 000000000..0bf083749 --- /dev/null +++ b/appyx-navigation/common/src/wasmJsMain/kotlin/com/bumble/appyx/navigation/platform/OnBackPressedDispatcher.kt @@ -0,0 +1,69 @@ +package com.bumble.appyx.navigation.platform + +import androidx.compose.ui.util.fastForEachReversed + +/** + * Adapted from Android's OnBackPressedDispatcher. + * + * Create a new OnBackPressedDispatcher that dispatches [.onBackPressed] events + * to one or more [OnBackPressedCallback] instances. + * + * @param fallbackOnBackPressed The Runnable that should be triggered if + * [.onBackPressed] is called when no [OnBackPressedCallback] have been registered. + */ +class OnBackPressedDispatcher(private val fallbackOnBackPressed: (() -> Unit)? = null) { + val onBackPressedCallbacks: ArrayDeque = + ArrayDeque() + + /** + * Internal implementation of [.addCallback] that gives + * access to the [Cancellable] that specifically removes this callback from + * the dispatcher without relying on [OnBackPressedCallback.remove] which + * is what external developers should be using. + * + * @param onBackPressedCallback The callback to add + * @return a [Cancellable] which can be used to [cancel][Cancellable.cancel] + * the callback and remove it from the set of OnBackPressedCallbacks. + */ + fun addCancellableCallback(onBackPressedCallback: OnBackPressedCallback): Cancellable { + onBackPressedCallbacks.add(onBackPressedCallback) + val cancellable = OnBackPressedCancellable(onBackPressedCallback) + onBackPressedCallback.addCancellable(cancellable) + return cancellable + } + + /** + * Trigger a call to the currently added [callbacks][OnBackPressedCallback] in reverse + * order in which they were added. Only if the most recently added callback is not + * [enabled][OnBackPressedCallback.isEnabled] + * will any previously added callback be called. + * + * + * If [.hasEnabledCallbacks] is `false` when this method is called, the + * fallback Runnable set by [the constructor][.OnBackPressedDispatcher] + * will be triggered. + */ + fun onBackPressed() { + onBackPressedCallbacks.fastForEachReversed { callback -> + if (callback.isEnabled) { + callback.handleOnBackPressed() + return + } + } + fallbackOnBackPressed?.invoke() + } + + private inner class OnBackPressedCancellable(onBackPressedCallback: OnBackPressedCallback) : + Cancellable { + private val onBackPressedCallback: OnBackPressedCallback + + init { + this.onBackPressedCallback = onBackPressedCallback + } + + override fun cancel() { + onBackPressedCallbacks.remove(onBackPressedCallback) + onBackPressedCallback.removeCancellable(this) + } + } +} diff --git a/appyx-navigation/common/src/wasmJsMain/kotlin/com/bumble/appyx/navigation/platform/PlatformBackHandler.wasmJs.kt b/appyx-navigation/common/src/wasmJsMain/kotlin/com/bumble/appyx/navigation/platform/PlatformBackHandler.wasmJs.kt new file mode 100644 index 000000000..46113be27 --- /dev/null +++ b/appyx-navigation/common/src/wasmJsMain/kotlin/com/bumble/appyx/navigation/platform/PlatformBackHandler.wasmJs.kt @@ -0,0 +1,56 @@ +package com.bumble.appyx.navigation.platform + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect +import androidx.compose.runtime.ProvidableCompositionLocal +import androidx.compose.runtime.SideEffect +import androidx.compose.runtime.compositionLocalOf +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberUpdatedState + +@Suppress("CompositionLocalAllowlist") +val LocalOnBackPressedDispatcherOwner: ProvidableCompositionLocal = + compositionLocalOf { + object : OnBackPressedDispatcherOwner { + override val onBackPressedDispatcher: OnBackPressedDispatcher + get() = OnBackPressedDispatcher(null) + } + } + +interface OnBackPressedDispatcherOwner { + val onBackPressedDispatcher: OnBackPressedDispatcher +} + +@Composable +actual fun PlatformBackHandler( + enabled: Boolean, + onBack: () -> Unit +) { + // Safely update the current `onBack` lambda when a new one is provided + val currentOnBack by rememberUpdatedState(onBack) + // Remember in Composition a back callback that calls the `onBack` lambda + val backCallback = remember { + object : OnBackPressedCallback(enabled) { + override fun handleOnBackPressed() { + currentOnBack() + } + } + } + // On every successful composition, update the callback with the `enabled` value + SideEffect { + backCallback.isEnabled = enabled + } + + // register for back events only whilst present in the composition + val backDispatcher = checkNotNull(LocalOnBackPressedDispatcherOwner.current) { + "No OnBackPressedDispatcherOwner was provided via LocalOnBackPressedDispatcherOwner" + }.onBackPressedDispatcher + DisposableEffect(backDispatcher) { + val cancellable = backDispatcher.addCancellableCallback(backCallback) + + onDispose { + cancellable.cancel() + } + } +} diff --git a/appyx-navigation/common/src/wasmJsMain/kotlin/com/bumble/appyx/navigation/platform/PlatformLifecycleRegistry.kt b/appyx-navigation/common/src/wasmJsMain/kotlin/com/bumble/appyx/navigation/platform/PlatformLifecycleRegistry.kt new file mode 100644 index 000000000..518ca5afa --- /dev/null +++ b/appyx-navigation/common/src/wasmJsMain/kotlin/com/bumble/appyx/navigation/platform/PlatformLifecycleRegistry.kt @@ -0,0 +1,91 @@ +package com.bumble.appyx.navigation.platform + +import com.bumble.appyx.navigation.lifecycle.CommonLifecycleOwner +import com.bumble.appyx.navigation.lifecycle.DefaultPlatformLifecycleObserver +import com.bumble.appyx.navigation.lifecycle.Lifecycle +import com.bumble.appyx.navigation.lifecycle.PlatformLifecycleEventObserver +import com.bumble.appyx.navigation.lifecycle.PlatformLifecycleObserver +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.MainScope +import kotlinx.coroutines.cancel +import kotlinx.coroutines.isActive + +actual class PlatformLifecycleRegistry : Lifecycle { + + private val managedDefaultLifecycleObservers: MutableList = + ArrayList() + private val managedLifecycleEventObservers: MutableList = + ArrayList() + + private var _currentState: Lifecycle.State = Lifecycle.State.INITIALIZED + override var currentState: Lifecycle.State + get() = _currentState + set(value) { + when (value) { + Lifecycle.State.INITIALIZED -> Unit + Lifecycle.State.CREATED -> { + managedDefaultLifecycleObservers.forEach { it.onCreate() } + managedLifecycleEventObservers.forEach { + it.onStateChanged( + value, + Lifecycle.Event.ON_CREATE + ) + } + } + Lifecycle.State.STARTED -> { + managedDefaultLifecycleObservers.forEach { it.onStart() } + managedLifecycleEventObservers.forEach { + it.onStateChanged( + value, + Lifecycle.Event.ON_START + ) + } + } + Lifecycle.State.RESUMED -> { + managedDefaultLifecycleObservers.forEach { it.onResume() } + managedLifecycleEventObservers.forEach { + it.onStateChanged( + value, + Lifecycle.Event.ON_RESUME + ) + } + } + Lifecycle.State.DESTROYED -> { + managedDefaultLifecycleObservers.forEach { it.onDestroy() } + managedLifecycleEventObservers.forEach { + it.onStateChanged( + value, + Lifecycle.Event.ON_DESTROY + ) + } + if (coroutineScope.isActive) coroutineScope.cancel("lifecycle was destroyed") + } + } + _currentState = value + } + + override val coroutineScope: CoroutineScope by lazy { MainScope() } + + override fun addObserver(observer: PlatformLifecycleObserver) { + when (observer) { + is DefaultPlatformLifecycleObserver -> managedDefaultLifecycleObservers.add(observer) + is PlatformLifecycleEventObserver -> managedLifecycleEventObservers.add(observer) + } + } + + override fun removeObserver(observer: PlatformLifecycleObserver) { + when (observer) { + is DefaultPlatformLifecycleObserver -> managedDefaultLifecycleObservers.remove(observer) + is PlatformLifecycleEventObserver -> managedLifecycleEventObservers.remove(observer) + } + } + + actual fun setCurrentState(state: Lifecycle.State) { + currentState = state + } + + actual companion object { + actual fun create(owner: CommonLifecycleOwner): PlatformLifecycleRegistry = + PlatformLifecycleRegistry() + } +} diff --git a/demos/appyx-interactions/web/build.gradle.kts b/demos/appyx-interactions/web/build.gradle.kts index ba3d15648..e2dd99fe0 100644 --- a/demos/appyx-interactions/web/build.gradle.kts +++ b/demos/appyx-interactions/web/build.gradle.kts @@ -1,3 +1,5 @@ +import org.jetbrains.kotlin.gradle.targets.js.dsl.ExperimentalWasmDsl + plugins { id("com.bumble.appyx.multiplatform") id("org.jetbrains.compose") @@ -9,6 +11,12 @@ kotlin { browser() binaries.executable() } + @OptIn(ExperimentalWasmDsl::class) + wasmJs { + moduleName = "appyx-demos-interactions-web" + browser() + binaries.executable() + } sourceSets { val commonMain by getting { dependencies { diff --git a/demos/appyx-interactions/web/src/wasmJsMain/kotlin/com/bumble/appyx/interactions/main.js.kt b/demos/appyx-interactions/web/src/wasmJsMain/kotlin/com/bumble/appyx/interactions/main.js.kt new file mode 100644 index 000000000..53b130781 --- /dev/null +++ b/demos/appyx-interactions/web/src/wasmJsMain/kotlin/com/bumble/appyx/interactions/main.js.kt @@ -0,0 +1,78 @@ +package com.bumble.appyx.interactions + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.material.Surface +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.ExperimentalComposeUiApi +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.layout.onSizeChanged +import androidx.compose.ui.unit.IntSize +import androidx.compose.ui.window.CanvasBasedWindow +import com.bumble.appyx.components.internal.testdrive.TestDriveExperiment +import com.bumble.appyx.components.internal.testdrive.ui.md_amber_500 +import com.bumble.appyx.components.internal.testdrive.ui.md_blue_500 +import com.bumble.appyx.components.internal.testdrive.ui.md_blue_grey_500 +import com.bumble.appyx.components.internal.testdrive.ui.md_cyan_500 +import com.bumble.appyx.components.internal.testdrive.ui.md_grey_500 +import com.bumble.appyx.components.internal.testdrive.ui.md_indigo_500 +import com.bumble.appyx.components.internal.testdrive.ui.md_light_blue_500 +import com.bumble.appyx.components.internal.testdrive.ui.md_light_green_500 +import com.bumble.appyx.components.internal.testdrive.ui.md_lime_500 +import com.bumble.appyx.components.internal.testdrive.ui.md_pink_500 +import com.bumble.appyx.components.internal.testdrive.ui.md_teal_500 +import org.jetbrains.skiko.wasm.onWasmReady + +val manatee = Color(0xFF8D99AE) +val silver_sand = Color(0xFFBDC6D1) +val sizzling_red = Color(0xFFF05D5E) +val atomic_tangerine = Color(0xFFF0965D) + +val colors = listOf( + manatee, + sizzling_red, + atomic_tangerine, + silver_sand, + md_pink_500, + md_indigo_500, + md_blue_500, + md_light_blue_500, + md_cyan_500, + md_teal_500, + md_light_green_500, + md_lime_500, + md_amber_500, + md_grey_500, + md_blue_grey_500 +) + +enum class InteractionTarget { + Child1 +} + +@OptIn(ExperimentalComposeUiApi::class) +fun main() { + onWasmReady { + CanvasBasedWindow("Appyx") { + var size by remember { mutableStateOf(IntSize.Zero) } + Surface( + modifier = Modifier + .fillMaxSize() + .onSizeChanged { size = it } + ) { + if (size != IntSize.Zero) { + TestDriveExperiment( + screenWidthPx = size.width, + screenHeightPx = size.height, + element = InteractionTarget.Child1, + modifier = Modifier.fillMaxSize().background(Color.Black), + ) + } + } + } + } +} diff --git a/demos/appyx-interactions/web/src/wasmJsMain/resources/index.html b/demos/appyx-interactions/web/src/wasmJsMain/resources/index.html new file mode 100644 index 000000000..834b74027 --- /dev/null +++ b/demos/appyx-interactions/web/src/wasmJsMain/resources/index.html @@ -0,0 +1,15 @@ + + + + + Appyx Interactions + + + + +
+ +
+ + + diff --git a/demos/appyx-interactions/web/src/wasmJsMain/resources/styles.css b/demos/appyx-interactions/web/src/wasmJsMain/resources/styles.css new file mode 100644 index 000000000..8655f2e76 --- /dev/null +++ b/demos/appyx-interactions/web/src/wasmJsMain/resources/styles.css @@ -0,0 +1,12 @@ +#root { + width: 100%; + height: 100vh; +} + +body { + margin: 0; +} + +#root > .compose-web-column > div { + position: relative; +} diff --git a/demos/appyx-navigation/common/build.gradle.kts b/demos/appyx-navigation/common/build.gradle.kts index 85c316443..d02b27822 100644 --- a/demos/appyx-navigation/common/build.gradle.kts +++ b/demos/appyx-navigation/common/build.gradle.kts @@ -1,3 +1,5 @@ +import org.jetbrains.kotlin.gradle.targets.js.dsl.ExperimentalWasmDsl + plugins { id("com.bumble.appyx.multiplatform") id("org.jetbrains.compose") @@ -24,6 +26,12 @@ kotlin { moduleName = "demo-appyx-navigation-common" browser() } + @OptIn(ExperimentalWasmDsl::class) + wasmJs { + // Adding moduleName as a workaround for this issue: https://youtrack.jetbrains.com/issue/KT-51942 + moduleName = "demo-appyx-navigation-common" + browser() + } iosX64() iosArm64() iosSimulatorArm64() @@ -95,6 +103,7 @@ dependencies { add("kspAndroid", project(":ksp:appyx-processor")) add("kspDesktop", project(":ksp:appyx-processor")) add("kspJs", project(":ksp:appyx-processor")) + add("kspWasmJs", project(":ksp:appyx-processor")) add("kspIosArm64", project(":ksp:appyx-processor")) add("kspIosX64", project(":ksp:appyx-processor")) add("kspIosSimulatorArm64", project(":ksp:appyx-processor")) diff --git a/demos/appyx-navigation/common/src/wasmJsMain/kotlin/com/bumble/appyx/demos/navigation/platform/PlatformName.kt b/demos/appyx-navigation/common/src/wasmJsMain/kotlin/com/bumble/appyx/demos/navigation/platform/PlatformName.kt new file mode 100644 index 000000000..ff4c7405d --- /dev/null +++ b/demos/appyx-navigation/common/src/wasmJsMain/kotlin/com/bumble/appyx/demos/navigation/platform/PlatformName.kt @@ -0,0 +1,3 @@ +package com.bumble.appyx.demos.navigation.platform + +actual fun getPlatformName(): String = "Kotlin/Wasm" diff --git a/demos/appyx-navigation/common/src/wasmJsMain/kotlin/com/bumble/appyx/demos/navigation/ui/EmbeddableResourceImage.kt b/demos/appyx-navigation/common/src/wasmJsMain/kotlin/com/bumble/appyx/demos/navigation/ui/EmbeddableResourceImage.kt new file mode 100644 index 000000000..32c3edfc2 --- /dev/null +++ b/demos/appyx-navigation/common/src/wasmJsMain/kotlin/com/bumble/appyx/demos/navigation/ui/EmbeddableResourceImage.kt @@ -0,0 +1,24 @@ +package com.bumble.appyx.demos.navigation.ui + +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.layout.ContentScale +import com.bumble.appyx.imageloader.ResourceImage + +private const val EMBED_URL_PATH = "appyx/samples/documentation-appyx-navigation/" + +@Composable +actual fun EmbeddableResourceImage( + path: String, + contentDescription: String, + contentScale: ContentScale, + modifier: Modifier +) { + ResourceImage( + path = EMBED_URL_PATH + path, + fallbackUrl = path, + contentDescription = contentDescription, + contentScale = contentScale, + modifier = modifier, + ) +} diff --git a/demos/appyx-navigation/web/build.gradle.kts b/demos/appyx-navigation/web/build.gradle.kts index d4fa3ec95..9c5166260 100644 --- a/demos/appyx-navigation/web/build.gradle.kts +++ b/demos/appyx-navigation/web/build.gradle.kts @@ -1,3 +1,5 @@ +import org.jetbrains.kotlin.gradle.targets.js.dsl.ExperimentalWasmDsl + plugins { id("com.bumble.appyx.multiplatform") id("org.jetbrains.compose") @@ -10,6 +12,12 @@ kotlin { browser() binaries.executable() } + @OptIn(ExperimentalWasmDsl::class) + wasmJs { + moduleName = "appyx-demos-navigation-web" + browser() + binaries.executable() + } sourceSets { val commonMain by getting { dependencies { @@ -36,9 +44,10 @@ compose.experimental { dependencies { add("kspCommonMainMetadata", project(":ksp:appyx-processor")) add("kspJs", project(":ksp:appyx-processor")) + add("kspWasmJs", project(":ksp:appyx-processor")) } -tasks.register("copyResources") { +tasks.register("jsCopyResources") { // Dirs containing files we want to copy from("../common/src/commonMain/resources") @@ -49,9 +58,27 @@ tasks.register("copyResources") { } tasks.named("jsBrowserProductionExecutableDistributeResources") { - dependsOn("copyResources") + dependsOn("jsCopyResources") } tasks.named("jsMainClasses") { - dependsOn("copyResources") + dependsOn("jsCopyResources") +} + +tasks.register("wasmJsCopyResources") { + // Dirs containing files we want to copy + from("../common/src/commonMain/resources") + + // Output for web resources + into("$buildDir/processedResources/wasmJs/main") + + include("**/*") +} + +tasks.named("wasmJsBrowserProductionExecutableDistributeResources") { + dependsOn("wasmJsCopyResources") } + +tasks.named("wasmJsMainClasses") { + dependsOn("wasmJsCopyResources") +} \ No newline at end of file diff --git a/demos/appyx-navigation/web/src/wasmJsMain/kotlin/com/bumble/appyx/navigation/Main.kt b/demos/appyx-navigation/web/src/wasmJsMain/kotlin/com/bumble/appyx/navigation/Main.kt new file mode 100644 index 000000000..e43f5ab47 --- /dev/null +++ b/demos/appyx-navigation/web/src/wasmJsMain/kotlin/com/bumble/appyx/navigation/Main.kt @@ -0,0 +1,138 @@ +package com.bumble.appyx.navigation + +import androidx.compose.foundation.border +import androidx.compose.foundation.focusable +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.aspectRatio +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface +import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.ExperimentalComposeUiApi +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.focus.FocusRequester +import androidx.compose.ui.focus.focusRequester +import androidx.compose.ui.focus.onFocusChanged +import androidx.compose.ui.input.key.Key +import androidx.compose.ui.input.key.KeyEvent +import androidx.compose.ui.input.key.KeyEventType +import androidx.compose.ui.input.key.key +import androidx.compose.ui.input.key.onKeyEvent +import androidx.compose.ui.input.key.type +import androidx.compose.ui.layout.onSizeChanged +import androidx.compose.ui.unit.dp +import androidx.compose.ui.window.CanvasBasedWindow +import com.bumble.appyx.demos.appyxSample +import com.bumble.appyx.demos.common.color_primary +import com.bumble.appyx.demos.navigation.navigator.LocalNavigator +import com.bumble.appyx.demos.navigation.navigator.Navigator +import com.bumble.appyx.demos.navigation.node.root.RootNode +import com.bumble.appyx.demos.navigation.ui.AppyxSampleAppTheme +import com.bumble.appyx.navigation.integration.ScreenSize +import com.bumble.appyx.navigation.integration.WebNodeHost +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.flow.receiveAsFlow +import kotlinx.coroutines.launch + +@OptIn(ExperimentalComposeUiApi::class) +fun main() { + val events: Channel = Channel() + val navigator = Navigator() + appyxSample { + CanvasBasedWindow("Appyx navigation demo") { + CakeApp(events, navigator) + } + } +} + +private val containerShape = RoundedCornerShape(8) + +@Composable +private fun CakeApp(events: Channel, navigator: Navigator) { + AppyxSampleAppTheme(darkTheme = true, themeTypography = webTypography) { + val requester = remember { FocusRequester() } + var hasFocus by remember { mutableStateOf(false) } + + var screenSize by remember { mutableStateOf(ScreenSize(0.dp, 0.dp)) } + val eventScope = remember { CoroutineScope(SupervisorJob() + Dispatchers.Main) } + + Surface( + modifier = Modifier + .fillMaxSize() + .onSizeChanged { screenSize = ScreenSize(it.width.dp, it.height.dp) } + .onKeyEvent { + onKeyEvent(it, events, eventScope) + } + .focusRequester(requester) + .focusable() + .onFocusChanged { hasFocus = it.hasFocus }, + color = MaterialTheme.colorScheme.background, + ) { + CompositionLocalProvider(LocalNavigator provides navigator) { + BlackContainer { + WebNodeHost( + screenSize = screenSize, + onBackPressedEvents = events.receiveAsFlow(), + ) { nodeContext -> + RootNode( + nodeContext = nodeContext, + plugins = listOf(navigator) + ) + } + } + } + } + + if (!hasFocus) { + LaunchedEffect(Unit) { + requester.requestFocus() + } + } + } +} + +@Composable +private fun BlackContainer(content: @Composable () -> Unit) { + Box( + modifier = Modifier + .fillMaxSize() + .padding(16.dp), + contentAlignment = Alignment.Center + ) { + Box( + modifier = Modifier + .aspectRatio(0.56f) + .border(4.dp, color_primary, containerShape) + .clip(containerShape) + ) { + content() + } + } +} + +private fun onKeyEvent( + keyEvent: KeyEvent, + events: Channel, + coroutineScope: CoroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.Main), +): Boolean = + when { + keyEvent.type == KeyEventType.KeyUp && keyEvent.key == Key.Backspace -> { + coroutineScope.launch { events.send(Unit) } + true + } + + else -> false + } diff --git a/demos/appyx-navigation/web/src/wasmJsMain/kotlin/com/bumble/appyx/navigation/WebTypography.kt b/demos/appyx-navigation/web/src/wasmJsMain/kotlin/com/bumble/appyx/navigation/WebTypography.kt new file mode 100644 index 000000000..833b55cac --- /dev/null +++ b/demos/appyx-navigation/web/src/wasmJsMain/kotlin/com/bumble/appyx/navigation/WebTypography.kt @@ -0,0 +1,44 @@ +package com.bumble.appyx.navigation + +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.unit.sp +import com.bumble.appyx.demos.navigation.ui.typography + +internal val webTypography = typography.copy( + bodySmall = typography.bodySmall.copy( + fontSize = 8.sp, + fontFamily = FontFamily.SansSerif, + ), + bodyMedium = typography.bodyMedium.copy( + fontSize = 10.sp, + fontFamily = FontFamily.SansSerif, + ), + bodyLarge = typography.bodyLarge.copy( + fontSize = 12.sp, + fontFamily = FontFamily.SansSerif, + ), + titleSmall = typography.titleSmall.copy( + fontSize = 8.sp, + fontFamily = FontFamily.SansSerif, + ), + titleMedium = typography.titleMedium.copy( + fontSize = 10.sp, + fontFamily = FontFamily.SansSerif, + ), + titleLarge = typography.titleLarge.copy( + fontSize = 12.sp, + fontFamily = FontFamily.SansSerif, + ), + headlineSmall = typography.headlineSmall.copy( + fontSize = 14.sp, + fontFamily = FontFamily.SansSerif, + ), + headlineMedium = typography.headlineMedium.copy( + fontSize = 16.sp, + fontFamily = FontFamily.SansSerif, + ), + headlineLarge = typography.headlineLarge.copy( + fontSize = 18.sp, + fontFamily = FontFamily.SansSerif, + ), +) diff --git a/demos/appyx-navigation/web/src/wasmJsMain/resources/index.html b/demos/appyx-navigation/web/src/wasmJsMain/resources/index.html new file mode 100644 index 000000000..07c3ca9bc --- /dev/null +++ b/demos/appyx-navigation/web/src/wasmJsMain/resources/index.html @@ -0,0 +1,15 @@ + + + + + Navigation Demo + + + + +
+ +
+ + + diff --git a/demos/appyx-navigation/web/src/wasmJsMain/resources/styles.css b/demos/appyx-navigation/web/src/wasmJsMain/resources/styles.css new file mode 100644 index 000000000..8655f2e76 --- /dev/null +++ b/demos/appyx-navigation/web/src/wasmJsMain/resources/styles.css @@ -0,0 +1,12 @@ +#root { + width: 100%; + height: 100vh; +} + +body { + margin: 0; +} + +#root > .compose-web-column > div { + position: relative; +} diff --git a/demos/common/build.gradle.kts b/demos/common/build.gradle.kts index abcbffc86..eb6bab564 100644 --- a/demos/common/build.gradle.kts +++ b/demos/common/build.gradle.kts @@ -1,3 +1,5 @@ +import org.jetbrains.kotlin.gradle.targets.js.dsl.ExperimentalWasmDsl + plugins { id("com.bumble.appyx.multiplatform") id("org.jetbrains.compose") @@ -23,6 +25,12 @@ kotlin { moduleName = "appyx-demos-commons" browser() } + @OptIn(ExperimentalWasmDsl::class) + wasmJs { + // Adding moduleName as a workaround for this issue: https://youtrack.jetbrains.com/issue/KT-51942 + moduleName = "appyx-demos-commons" + browser() + } iosX64() iosArm64() iosSimulatorArm64() diff --git a/demos/image-loader/common/build.gradle.kts b/demos/image-loader/common/build.gradle.kts index b8d70eb27..fef202c5c 100644 --- a/demos/image-loader/common/build.gradle.kts +++ b/demos/image-loader/common/build.gradle.kts @@ -1,3 +1,5 @@ +import org.jetbrains.kotlin.gradle.targets.js.dsl.ExperimentalWasmDsl + plugins { id("com.bumble.appyx.multiplatform") id("org.jetbrains.compose") @@ -22,6 +24,12 @@ kotlin { moduleName = "appyx-navigation-imageloader" browser() } + @OptIn(ExperimentalWasmDsl::class) + wasmJs { + // Adding moduleName as a workaround for this issue: https://youtrack.jetbrains.com/issue/KT-51942 + moduleName = "appyx-navigation-imageloader" + browser() + } iosX64() iosArm64() diff --git a/demos/image-loader/common/src/wasmJsMain/kotlin/com/bumble/appyx/imageloader/ImageBitmap.wasmJs.kt b/demos/image-loader/common/src/wasmJsMain/kotlin/com/bumble/appyx/imageloader/ImageBitmap.wasmJs.kt new file mode 100644 index 000000000..b614c08dd --- /dev/null +++ b/demos/image-loader/common/src/wasmJsMain/kotlin/com/bumble/appyx/imageloader/ImageBitmap.wasmJs.kt @@ -0,0 +1,9 @@ +package com.bumble.appyx.imageloader + +import androidx.compose.ui.graphics.ImageBitmap +import androidx.compose.ui.graphics.toComposeImageBitmap + +actual fun ByteArray.toImageBitmap(): ImageBitmap { + // TODO This doesn't work. Fix this for web + return org.jetbrains.skia.Image.makeFromEncoded(this).toComposeImageBitmap() +} diff --git a/demos/mkdocs/appyx-components/backstack/fader/web/build.gradle.kts b/demos/mkdocs/appyx-components/backstack/fader/web/build.gradle.kts index 82e3a6a71..52f832bf1 100644 --- a/demos/mkdocs/appyx-components/backstack/fader/web/build.gradle.kts +++ b/demos/mkdocs/appyx-components/backstack/fader/web/build.gradle.kts @@ -1,3 +1,5 @@ +import org.jetbrains.kotlin.gradle.targets.js.dsl.ExperimentalWasmDsl + plugins { id("com.bumble.appyx.multiplatform") id("org.jetbrains.compose") @@ -10,6 +12,12 @@ kotlin { browser() binaries.executable() } + @OptIn(ExperimentalWasmDsl::class) + wasmJs { + moduleName = "appyx-demos-backstack-fader-web" + browser() + binaries.executable() + } sourceSets { val commonMain by getting { dependencies { @@ -36,4 +44,5 @@ compose.experimental { dependencies { add("kspCommonMainMetadata", project(":ksp:appyx-processor")) add("kspJs", project(":ksp:appyx-processor")) + add("kspWasmJs", project(":ksp:appyx-processor")) } diff --git a/demos/mkdocs/appyx-components/backstack/fader/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/backstack/fader/BackStackFaderSample.kt b/demos/mkdocs/appyx-components/backstack/fader/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/backstack/fader/BackStackFaderSample.kt new file mode 100644 index 000000000..9dec56871 --- /dev/null +++ b/demos/mkdocs/appyx-components/backstack/fader/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/backstack/fader/BackStackFaderSample.kt @@ -0,0 +1,51 @@ +package com.bumble.appyx.demos.backstack.fader + +import androidx.compose.animation.core.Spring +import androidx.compose.animation.core.spring +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.Modifier +import com.bumble.appyx.components.backstack.BackStack +import com.bumble.appyx.components.backstack.BackStackModel +import com.bumble.appyx.components.backstack.operation.pop +import com.bumble.appyx.components.backstack.operation.push +import com.bumble.appyx.components.backstack.ui.fader.BackStackFader +import com.bumble.appyx.demos.common.AppyxWebSample +import com.bumble.appyx.demos.common.ChildSize +import com.bumble.appyx.demos.common.InteractionTarget +import com.bumble.appyx.interactions.gesture.GestureFactory + +@Composable +fun BackStackFaderSample( + screenWidthPx: Int, + screenHeightPx: Int, + modifier: Modifier = Modifier, +) { + val coroutineScope = rememberCoroutineScope() + val model = remember { + BackStackModel( + initialTarget = InteractionTarget.Element(), + savedStateMap = null + ) + } + val backStack = BackStack( + scope = coroutineScope, + model = model, + visualisation = { BackStackFader(it) }, + gestureFactory = { GestureFactory.Noop() }, + animationSpec = spring(stiffness = Spring.StiffnessVeryLow * 2), + ) + val actions = mapOf( + "Pop" to { backStack.pop() }, + "Push" to { backStack.push(InteractionTarget.Element()) } + ) + AppyxWebSample( + screenWidthPx = screenWidthPx, + screenHeightPx = screenHeightPx, + appyxComponent = backStack, + actions = actions, + childSize = ChildSize.MAX, + modifier = modifier, + ) +} diff --git a/demos/mkdocs/appyx-components/backstack/fader/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/backstack/fader/main.js.kt b/demos/mkdocs/appyx-components/backstack/fader/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/backstack/fader/main.js.kt new file mode 100644 index 000000000..15e0c3fe8 --- /dev/null +++ b/demos/mkdocs/appyx-components/backstack/fader/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/backstack/fader/main.js.kt @@ -0,0 +1,47 @@ +package com.bumble.appyx.demos.backstack.fader + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.material.Surface +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.ExperimentalComposeUiApi +import androidx.compose.ui.Modifier +import androidx.compose.ui.layout.onSizeChanged +import androidx.compose.ui.unit.IntSize +import androidx.compose.ui.unit.dp +import androidx.compose.ui.window.CanvasBasedWindow +import com.bumble.appyx.demos.appyxSample +import com.bumble.appyx.demos.common.color_dark + + +@OptIn(ExperimentalComposeUiApi::class) +fun main() { + appyxSample { + CanvasBasedWindow("Appyx") { + var size by remember { mutableStateOf(IntSize.Zero) } + Surface( + modifier = Modifier + .fillMaxSize() + .onSizeChanged { size = it } + ) { + if (size != IntSize.Zero) { + BackStackFaderSample( + screenWidthPx = size.width, + screenHeightPx = size.height, + modifier = Modifier + .fillMaxSize() + .background(color_dark) + .padding( + horizontal = 16.dp, + vertical = 16.dp + ) + ) + } + } + } + } +} diff --git a/demos/mkdocs/appyx-components/backstack/fader/web/src/wasmJsMain/resources/index.html b/demos/mkdocs/appyx-components/backstack/fader/web/src/wasmJsMain/resources/index.html new file mode 100644 index 000000000..cb31659d7 --- /dev/null +++ b/demos/mkdocs/appyx-components/backstack/fader/web/src/wasmJsMain/resources/index.html @@ -0,0 +1,15 @@ + + + + + Appyx Interactions + + + + +
+ +
+ + + diff --git a/demos/mkdocs/appyx-components/backstack/fader/web/src/wasmJsMain/resources/styles.css b/demos/mkdocs/appyx-components/backstack/fader/web/src/wasmJsMain/resources/styles.css new file mode 100644 index 000000000..f8b13d234 --- /dev/null +++ b/demos/mkdocs/appyx-components/backstack/fader/web/src/wasmJsMain/resources/styles.css @@ -0,0 +1,19 @@ +#root { + width: 100%; + height: 100vh; +} + +body { + margin: 0; +} + +#root > .compose-web-column > div { + position: relative; +} + +@media (max-width: 511px) { + #ComposeTarget { + scale: 0.5; + transform-origin: 0 0; + } +} diff --git a/demos/mkdocs/appyx-components/backstack/parallax/web/build.gradle.kts b/demos/mkdocs/appyx-components/backstack/parallax/web/build.gradle.kts index 5f407941d..a5e71544e 100644 --- a/demos/mkdocs/appyx-components/backstack/parallax/web/build.gradle.kts +++ b/demos/mkdocs/appyx-components/backstack/parallax/web/build.gradle.kts @@ -1,3 +1,5 @@ +import org.jetbrains.kotlin.gradle.targets.js.dsl.ExperimentalWasmDsl + plugins { id("com.bumble.appyx.multiplatform") id("org.jetbrains.compose") @@ -10,6 +12,12 @@ kotlin { browser() binaries.executable() } + @OptIn(ExperimentalWasmDsl::class) + wasmJs { + moduleName = "appyx-demos-backstack-parallax-web" + browser() + binaries.executable() + } sourceSets { val commonMain by getting { dependencies { @@ -36,4 +44,5 @@ compose.experimental { dependencies { add("kspCommonMainMetadata", project(":ksp:appyx-processor")) add("kspJs", project(":ksp:appyx-processor")) + add("kspWasmJs", project(":ksp:appyx-processor")) } diff --git a/demos/mkdocs/appyx-components/backstack/parallax/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/backstack/parallax/BackStackParallaxSample.kt b/demos/mkdocs/appyx-components/backstack/parallax/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/backstack/parallax/BackStackParallaxSample.kt new file mode 100644 index 000000000..16b53dc53 --- /dev/null +++ b/demos/mkdocs/appyx-components/backstack/parallax/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/backstack/parallax/BackStackParallaxSample.kt @@ -0,0 +1,51 @@ +package com.bumble.appyx.demos.backstack.parallax + +import androidx.compose.animation.core.Spring.StiffnessVeryLow +import androidx.compose.animation.core.spring +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.Modifier +import com.bumble.appyx.components.backstack.BackStack +import com.bumble.appyx.components.backstack.BackStackModel +import com.bumble.appyx.components.backstack.operation.pop +import com.bumble.appyx.components.backstack.operation.push +import com.bumble.appyx.components.backstack.ui.parallax.BackStackParallax +import com.bumble.appyx.demos.common.AppyxWebSample +import com.bumble.appyx.demos.common.ChildSize +import com.bumble.appyx.demos.common.InteractionTarget + +@Composable +fun BackStackParallaxSample( + screenWidthPx: Int, + screenHeightPx: Int, + modifier: Modifier = Modifier, +) { + val coroutineScope = rememberCoroutineScope() + val model = remember { + BackStackModel( + initialTargets = List(5) { InteractionTarget.Element() }, + savedStateMap = null, + ) + } + val backStack = + BackStack( + scope = coroutineScope, + model = model, + visualisation = { BackStackParallax(it) }, + gestureFactory = { BackStackParallax.Gestures(it) }, + animationSpec = spring(stiffness = StiffnessVeryLow), + ) + val actions = mapOf( + "Pop" to { backStack.pop() }, + "Push" to { backStack.push(InteractionTarget.Element()) } + ) + AppyxWebSample( + screenWidthPx = screenWidthPx, + screenHeightPx = screenHeightPx, + appyxComponent = backStack, + actions = actions, + childSize = ChildSize.MAX, + modifier = modifier, + ) +} diff --git a/demos/mkdocs/appyx-components/backstack/parallax/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/backstack/parallax/main.js.kt b/demos/mkdocs/appyx-components/backstack/parallax/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/backstack/parallax/main.js.kt new file mode 100644 index 000000000..810e353c1 --- /dev/null +++ b/demos/mkdocs/appyx-components/backstack/parallax/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/backstack/parallax/main.js.kt @@ -0,0 +1,44 @@ +package com.bumble.appyx.demos.backstack.parallax + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.material.Surface +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.ExperimentalComposeUiApi +import androidx.compose.ui.Modifier +import androidx.compose.ui.layout.onSizeChanged +import androidx.compose.ui.unit.IntSize +import androidx.compose.ui.unit.dp +import androidx.compose.ui.window.CanvasBasedWindow +import com.bumble.appyx.demos.appyxSample +import com.bumble.appyx.demos.common.color_dark + + +@OptIn(ExperimentalComposeUiApi::class) +fun main() { + appyxSample { + CanvasBasedWindow("Appyx") { + var size by remember { mutableStateOf(IntSize.Zero) } + Surface( + modifier = Modifier + .fillMaxSize() + .onSizeChanged { size = it } + ) { + if (size != IntSize.Zero) { + BackStackParallaxSample( + screenWidthPx = size.width, + screenHeightPx = size.height, + modifier = Modifier + .fillMaxSize() + .background(color_dark) + .padding(vertical = 16.dp) + ) + } + } + } + } +} diff --git a/demos/mkdocs/appyx-components/backstack/parallax/web/src/wasmJsMain/resources/index.html b/demos/mkdocs/appyx-components/backstack/parallax/web/src/wasmJsMain/resources/index.html new file mode 100644 index 000000000..cb31659d7 --- /dev/null +++ b/demos/mkdocs/appyx-components/backstack/parallax/web/src/wasmJsMain/resources/index.html @@ -0,0 +1,15 @@ + + + + + Appyx Interactions + + + + +
+ +
+ + + diff --git a/demos/mkdocs/appyx-components/backstack/parallax/web/src/wasmJsMain/resources/styles.css b/demos/mkdocs/appyx-components/backstack/parallax/web/src/wasmJsMain/resources/styles.css new file mode 100644 index 000000000..f8b13d234 --- /dev/null +++ b/demos/mkdocs/appyx-components/backstack/parallax/web/src/wasmJsMain/resources/styles.css @@ -0,0 +1,19 @@ +#root { + width: 100%; + height: 100vh; +} + +body { + margin: 0; +} + +#root > .compose-web-column > div { + position: relative; +} + +@media (max-width: 511px) { + #ComposeTarget { + scale: 0.5; + transform-origin: 0 0; + } +} diff --git a/demos/mkdocs/appyx-components/backstack/slider/web/build.gradle.kts b/demos/mkdocs/appyx-components/backstack/slider/web/build.gradle.kts index d48bc8e78..78b1a43f6 100644 --- a/demos/mkdocs/appyx-components/backstack/slider/web/build.gradle.kts +++ b/demos/mkdocs/appyx-components/backstack/slider/web/build.gradle.kts @@ -1,3 +1,5 @@ +import org.jetbrains.kotlin.gradle.targets.js.dsl.ExperimentalWasmDsl + plugins { id("com.bumble.appyx.multiplatform") id("org.jetbrains.compose") @@ -10,6 +12,12 @@ kotlin { browser() binaries.executable() } + @OptIn(ExperimentalWasmDsl::class) + wasmJs { + moduleName = "appyx-demos-backstack-slider-web" + browser() + binaries.executable() + } sourceSets { val commonMain by getting { dependencies { @@ -36,4 +44,5 @@ compose.experimental { dependencies { add("kspCommonMainMetadata", project(":ksp:appyx-processor")) add("kspJs", project(":ksp:appyx-processor")) + add("kspWasmJs", project(":ksp:appyx-processor")) } diff --git a/demos/mkdocs/appyx-components/backstack/slider/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/backstack/slider/BackStackSliderSample.kt b/demos/mkdocs/appyx-components/backstack/slider/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/backstack/slider/BackStackSliderSample.kt new file mode 100644 index 000000000..22c4b1571 --- /dev/null +++ b/demos/mkdocs/appyx-components/backstack/slider/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/backstack/slider/BackStackSliderSample.kt @@ -0,0 +1,52 @@ +package com.bumble.appyx.demos.backstack.slider + +import androidx.compose.animation.core.Spring +import androidx.compose.animation.core.spring +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.Modifier +import com.bumble.appyx.components.backstack.BackStack +import com.bumble.appyx.components.backstack.BackStackModel +import com.bumble.appyx.components.backstack.operation.pop +import com.bumble.appyx.components.backstack.operation.push +import com.bumble.appyx.components.backstack.ui.slider.BackStackSlider +import com.bumble.appyx.demos.common.AppyxWebSample +import com.bumble.appyx.demos.common.ChildSize +import com.bumble.appyx.demos.common.InteractionTarget +import com.bumble.appyx.interactions.gesture.GestureFactory + +@Composable +fun BackStackSliderSample( + screenWidthPx: Int, + screenHeightPx: Int, + modifier: Modifier = Modifier, +) { + val coroutineScope = rememberCoroutineScope() + val model = remember { + BackStackModel( + initialTarget = InteractionTarget.Element(), + savedStateMap = null + ) + } + val backStack = + BackStack( + scope = coroutineScope, + model = model, + visualisation = { BackStackSlider(it) }, + gestureFactory = { GestureFactory.Noop() }, + animationSpec = spring(stiffness = Spring.StiffnessVeryLow), + ) + val actions = mapOf( + "Pop" to { backStack.pop() }, + "Push" to { backStack.push(InteractionTarget.Element()) } + ) + AppyxWebSample( + screenWidthPx = screenWidthPx, + screenHeightPx = screenHeightPx, + appyxComponent = backStack, + actions = actions, + childSize = ChildSize.MAX, + modifier = modifier, + ) +} diff --git a/demos/mkdocs/appyx-components/backstack/slider/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/backstack/slider/main.js.kt b/demos/mkdocs/appyx-components/backstack/slider/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/backstack/slider/main.js.kt new file mode 100644 index 000000000..997896eca --- /dev/null +++ b/demos/mkdocs/appyx-components/backstack/slider/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/backstack/slider/main.js.kt @@ -0,0 +1,47 @@ +package com.bumble.appyx.demos.backstack.slider + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.material.Surface +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.ExperimentalComposeUiApi +import androidx.compose.ui.Modifier +import androidx.compose.ui.layout.onSizeChanged +import androidx.compose.ui.unit.IntSize +import androidx.compose.ui.unit.dp +import androidx.compose.ui.window.CanvasBasedWindow +import com.bumble.appyx.demos.appyxSample +import com.bumble.appyx.demos.common.color_dark + + +@OptIn(ExperimentalComposeUiApi::class) +fun main() { + appyxSample { + CanvasBasedWindow("Appyx") { + var size by remember { mutableStateOf(IntSize.Zero) } + Surface( + modifier = Modifier + .fillMaxSize() + .onSizeChanged { size = it } + ) { + if (size != IntSize.Zero) { + BackStackSliderSample( + screenWidthPx = size.width, + screenHeightPx = size.height, + modifier = Modifier + .fillMaxSize() + .background(color_dark) + .padding( + horizontal = 16.dp, + vertical = 16.dp + ) + ) + } + } + } + } +} diff --git a/demos/mkdocs/appyx-components/backstack/slider/web/src/wasmJsMain/resources/index.html b/demos/mkdocs/appyx-components/backstack/slider/web/src/wasmJsMain/resources/index.html new file mode 100644 index 000000000..cb31659d7 --- /dev/null +++ b/demos/mkdocs/appyx-components/backstack/slider/web/src/wasmJsMain/resources/index.html @@ -0,0 +1,15 @@ + + + + + Appyx Interactions + + + + +
+ +
+ + + diff --git a/demos/mkdocs/appyx-components/backstack/slider/web/src/wasmJsMain/resources/styles.css b/demos/mkdocs/appyx-components/backstack/slider/web/src/wasmJsMain/resources/styles.css new file mode 100644 index 000000000..f8b13d234 --- /dev/null +++ b/demos/mkdocs/appyx-components/backstack/slider/web/src/wasmJsMain/resources/styles.css @@ -0,0 +1,19 @@ +#root { + width: 100%; + height: 100vh; +} + +body { + margin: 0; +} + +#root > .compose-web-column > div { + position: relative; +} + +@media (max-width: 511px) { + #ComposeTarget { + scale: 0.5; + transform-origin: 0 0; + } +} diff --git a/demos/mkdocs/appyx-components/backstack/stack3d/web/build.gradle.kts b/demos/mkdocs/appyx-components/backstack/stack3d/web/build.gradle.kts index fc9946580..bfa526112 100644 --- a/demos/mkdocs/appyx-components/backstack/stack3d/web/build.gradle.kts +++ b/demos/mkdocs/appyx-components/backstack/stack3d/web/build.gradle.kts @@ -1,3 +1,5 @@ +import org.jetbrains.kotlin.gradle.targets.js.dsl.ExperimentalWasmDsl + plugins { id("com.bumble.appyx.multiplatform") id("org.jetbrains.compose") @@ -10,6 +12,12 @@ kotlin { browser() binaries.executable() } + @OptIn(ExperimentalWasmDsl::class) + wasmJs { + moduleName = "appyx-demos-backstack-stack3d-web" + browser() + binaries.executable() + } sourceSets { val commonMain by getting { dependencies { @@ -36,4 +44,5 @@ compose.experimental { dependencies { add("kspCommonMainMetadata", project(":ksp:appyx-processor")) add("kspJs", project(":ksp:appyx-processor")) + add("kspWasmJs", project(":ksp:appyx-processor")) } diff --git a/demos/mkdocs/appyx-components/backstack/stack3d/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/backstack/stack3d/BackStack3DSample.kt b/demos/mkdocs/appyx-components/backstack/stack3d/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/backstack/stack3d/BackStack3DSample.kt new file mode 100644 index 000000000..9c31d253a --- /dev/null +++ b/demos/mkdocs/appyx-components/backstack/stack3d/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/backstack/stack3d/BackStack3DSample.kt @@ -0,0 +1,51 @@ +package com.bumble.appyx.demos.backstack.stack3d + +import androidx.compose.animation.core.Spring +import androidx.compose.animation.core.spring +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.Modifier +import com.bumble.appyx.components.backstack.BackStack +import com.bumble.appyx.components.backstack.BackStackModel +import com.bumble.appyx.components.backstack.operation.pop +import com.bumble.appyx.components.backstack.operation.push +import com.bumble.appyx.components.backstack.ui.stack3d.BackStack3D +import com.bumble.appyx.demos.common.AppyxWebSample +import com.bumble.appyx.demos.common.ChildSize +import com.bumble.appyx.demos.common.InteractionTarget + +@Composable +fun BackStack3DSample( + screenWidthPx: Int, + screenHeightPx: Int, + modifier: Modifier = Modifier, +) { + val coroutineScope = rememberCoroutineScope() + val model = remember { + BackStackModel( + initialTarget = InteractionTarget.Element(), + savedStateMap = null + ) + } + val backStack = + BackStack( + scope = coroutineScope, + model = model, + visualisation = { BackStack3D(it) }, + gestureFactory = { BackStack3D.Gestures(it) }, + animationSpec = spring(stiffness = Spring.StiffnessVeryLow * 2), + ) + val actions = mapOf( + "Pop" to { backStack.pop() }, + "Push" to { backStack.push(InteractionTarget.Element()) } + ) + AppyxWebSample( + screenWidthPx = screenWidthPx, + screenHeightPx = screenHeightPx, + appyxComponent = backStack, + actions = actions, + childSize = ChildSize.MEDIUM, + modifier = modifier, + ) +} diff --git a/demos/mkdocs/appyx-components/backstack/stack3d/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/backstack/stack3d/main.js.kt b/demos/mkdocs/appyx-components/backstack/stack3d/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/backstack/stack3d/main.js.kt new file mode 100644 index 000000000..840b348dc --- /dev/null +++ b/demos/mkdocs/appyx-components/backstack/stack3d/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/backstack/stack3d/main.js.kt @@ -0,0 +1,47 @@ +package com.bumble.appyx.demos.backstack.stack3d + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.material.Surface +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.ExperimentalComposeUiApi +import androidx.compose.ui.Modifier +import androidx.compose.ui.layout.onSizeChanged +import androidx.compose.ui.unit.IntSize +import androidx.compose.ui.unit.dp +import androidx.compose.ui.window.CanvasBasedWindow +import com.bumble.appyx.demos.appyxSample +import com.bumble.appyx.demos.common.color_dark + + +@OptIn(ExperimentalComposeUiApi::class) +fun main() { + appyxSample { + CanvasBasedWindow("Appyx") { + var size by remember { mutableStateOf(IntSize.Zero) } + Surface( + modifier = Modifier + .fillMaxSize() + .onSizeChanged { size = it } + ) { + if (size != IntSize.Zero) { + BackStack3DSample( + screenWidthPx = size.width, + screenHeightPx = size.height, + modifier = Modifier + .fillMaxSize() + .background(color_dark) + .padding( + horizontal = 16.dp, + vertical = 16.dp + ) + ) + } + } + } + } +} diff --git a/demos/mkdocs/appyx-components/backstack/stack3d/web/src/wasmJsMain/resources/index.html b/demos/mkdocs/appyx-components/backstack/stack3d/web/src/wasmJsMain/resources/index.html new file mode 100644 index 000000000..cb31659d7 --- /dev/null +++ b/demos/mkdocs/appyx-components/backstack/stack3d/web/src/wasmJsMain/resources/index.html @@ -0,0 +1,15 @@ + + + + + Appyx Interactions + + + + +
+ +
+ + + diff --git a/demos/mkdocs/appyx-components/backstack/stack3d/web/src/wasmJsMain/resources/styles.css b/demos/mkdocs/appyx-components/backstack/stack3d/web/src/wasmJsMain/resources/styles.css new file mode 100644 index 000000000..f8b13d234 --- /dev/null +++ b/demos/mkdocs/appyx-components/backstack/stack3d/web/src/wasmJsMain/resources/styles.css @@ -0,0 +1,19 @@ +#root { + width: 100%; + height: 100vh; +} + +body { + margin: 0; +} + +#root > .compose-web-column > div { + position: relative; +} + +@media (max-width: 511px) { + #ComposeTarget { + scale: 0.5; + transform-origin: 0 0; + } +} diff --git a/demos/mkdocs/appyx-components/common/build.gradle.kts b/demos/mkdocs/appyx-components/common/build.gradle.kts index 6c4418afe..1044b6bf2 100644 --- a/demos/mkdocs/appyx-components/common/build.gradle.kts +++ b/demos/mkdocs/appyx-components/common/build.gradle.kts @@ -1,3 +1,5 @@ +import org.jetbrains.kotlin.gradle.targets.js.dsl.ExperimentalWasmDsl + plugins { id("com.bumble.appyx.multiplatform") id("org.jetbrains.compose") @@ -10,6 +12,12 @@ kotlin { browser() binaries.executable() } + @OptIn(ExperimentalWasmDsl::class) + wasmJs { + moduleName = "demos-mkdocs-appyx-components-common" + browser() + binaries.executable() + } sourceSets { val commonMain by getting { dependencies { @@ -29,4 +37,5 @@ compose.experimental { dependencies { add("kspCommonMainMetadata", project(":ksp:appyx-processor")) add("kspJs", project(":ksp:appyx-processor")) + add("kspWasmJs", project(":ksp:appyx-processor")) } diff --git a/demos/mkdocs/appyx-components/experimental/datingcards/web/build.gradle.kts b/demos/mkdocs/appyx-components/experimental/datingcards/web/build.gradle.kts index 3de9b88f3..1b7b0d658 100644 --- a/demos/mkdocs/appyx-components/experimental/datingcards/web/build.gradle.kts +++ b/demos/mkdocs/appyx-components/experimental/datingcards/web/build.gradle.kts @@ -1,3 +1,5 @@ +import org.jetbrains.kotlin.gradle.targets.js.dsl.ExperimentalWasmDsl + plugins { id("com.bumble.appyx.multiplatform") id("org.jetbrains.compose") @@ -10,6 +12,12 @@ kotlin { browser() binaries.executable() } + @OptIn(ExperimentalWasmDsl::class) + wasmJs { + moduleName = "appyx-demos-experimental-datingcards-web" + browser() + binaries.executable() + } sourceSets { val commonMain by getting { dependencies { @@ -36,4 +44,5 @@ compose.experimental { dependencies { add("kspCommonMainMetadata", project(":ksp:appyx-processor")) add("kspJs", project(":ksp:appyx-processor")) + add("kspWasmJs", project(":ksp:appyx-processor")) } diff --git a/demos/mkdocs/appyx-components/experimental/datingcards/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/experimental/datingcards/DatingCardsSample.kt b/demos/mkdocs/appyx-components/experimental/datingcards/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/experimental/datingcards/DatingCardsSample.kt new file mode 100644 index 000000000..a2532ecb5 --- /dev/null +++ b/demos/mkdocs/appyx-components/experimental/datingcards/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/experimental/datingcards/DatingCardsSample.kt @@ -0,0 +1,49 @@ +package com.bumble.appyx.demos.experimental.datingcards + +import androidx.compose.animation.core.AnimationSpec +import androidx.compose.animation.core.Spring +import androidx.compose.animation.core.spring +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import com.bumble.appyx.components.experimental.cards.Cards +import com.bumble.appyx.components.experimental.cards.CardsModel +import com.bumble.appyx.components.experimental.cards.operation.like +import com.bumble.appyx.components.experimental.cards.operation.pass +import com.bumble.appyx.components.experimental.cards.ui.CardsVisualisation +import com.bumble.appyx.demos.common.AppyxWebSample +import com.bumble.appyx.demos.common.ChildSize +import com.bumble.appyx.demos.common.InteractionTarget + +@Composable +fun DatingCardsSample( + screenWidthPx: Int, + screenHeightPx: Int, + modifier: Modifier = Modifier, +) { + val cards = remember { + Cards( + model = CardsModel( + initialItems = List(15) { InteractionTarget.Element(it) }, + savedStateMap = null + ), + visualisation = { CardsVisualisation(it) }, + gestureFactory = { CardsVisualisation.Gestures(it) }, + animateSettle = true + ) + } + + val animationSpec: AnimationSpec = spring(stiffness = Spring.StiffnessVeryLow * 2) + val actions = mapOf( + "Pass" to { cards.pass(animationSpec = animationSpec) }, + "Like" to { cards.like(animationSpec = animationSpec) }, + ) + AppyxWebSample( + screenWidthPx = screenWidthPx, + screenHeightPx = screenHeightPx, + appyxComponent = cards, + actions = actions, + childSize = ChildSize.MAX, + modifier = modifier, + ) +} diff --git a/demos/mkdocs/appyx-components/experimental/datingcards/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/experimental/datingcards/main.js.kt b/demos/mkdocs/appyx-components/experimental/datingcards/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/experimental/datingcards/main.js.kt new file mode 100644 index 000000000..41c268dee --- /dev/null +++ b/demos/mkdocs/appyx-components/experimental/datingcards/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/experimental/datingcards/main.js.kt @@ -0,0 +1,47 @@ +package com.bumble.appyx.demos.experimental.datingcards + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.material.Surface +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.ExperimentalComposeUiApi +import androidx.compose.ui.Modifier +import androidx.compose.ui.layout.onSizeChanged +import androidx.compose.ui.unit.IntSize +import androidx.compose.ui.unit.dp +import androidx.compose.ui.window.CanvasBasedWindow +import com.bumble.appyx.demos.appyxSample +import com.bumble.appyx.demos.common.color_dark + + +@OptIn(ExperimentalComposeUiApi::class) +fun main() { + appyxSample { + CanvasBasedWindow("Appyx") { + var size by remember { mutableStateOf(IntSize.Zero) } + Surface( + modifier = Modifier + .fillMaxSize() + .onSizeChanged { size = it } + ) { + if (size != IntSize.Zero) { + DatingCardsSample( + screenWidthPx = size.width, + screenHeightPx = size.height, + modifier = Modifier + .fillMaxSize() + .background(color_dark) + .padding( + horizontal = 16.dp, + vertical = 16.dp + ) + ) + } + } + } + } +} diff --git a/demos/mkdocs/appyx-components/experimental/datingcards/web/src/wasmJsMain/resources/index.html b/demos/mkdocs/appyx-components/experimental/datingcards/web/src/wasmJsMain/resources/index.html new file mode 100644 index 000000000..cb31659d7 --- /dev/null +++ b/demos/mkdocs/appyx-components/experimental/datingcards/web/src/wasmJsMain/resources/index.html @@ -0,0 +1,15 @@ + + + + + Appyx Interactions + + + + +
+ +
+ + + diff --git a/demos/mkdocs/appyx-components/experimental/datingcards/web/src/wasmJsMain/resources/styles.css b/demos/mkdocs/appyx-components/experimental/datingcards/web/src/wasmJsMain/resources/styles.css new file mode 100644 index 000000000..f8b13d234 --- /dev/null +++ b/demos/mkdocs/appyx-components/experimental/datingcards/web/src/wasmJsMain/resources/styles.css @@ -0,0 +1,19 @@ +#root { + width: 100%; + height: 100vh; +} + +body { + margin: 0; +} + +#root > .compose-web-column > div { + position: relative; +} + +@media (max-width: 511px) { + #ComposeTarget { + scale: 0.5; + transform-origin: 0 0; + } +} diff --git a/demos/mkdocs/appyx-components/experimental/puzzle15/web/build.gradle.kts b/demos/mkdocs/appyx-components/experimental/puzzle15/web/build.gradle.kts index 40d079ae9..d1ae761de 100644 --- a/demos/mkdocs/appyx-components/experimental/puzzle15/web/build.gradle.kts +++ b/demos/mkdocs/appyx-components/experimental/puzzle15/web/build.gradle.kts @@ -1,3 +1,5 @@ +import org.jetbrains.kotlin.gradle.targets.js.dsl.ExperimentalWasmDsl + plugins { id("com.bumble.appyx.multiplatform") id("org.jetbrains.compose") @@ -10,6 +12,12 @@ kotlin { browser() binaries.executable() } + @OptIn(ExperimentalWasmDsl::class) + wasmJs { + moduleName = "appyx-demos-experimental-puzzle15-web" + browser() + binaries.executable() + } sourceSets { val commonMain by getting { dependencies { @@ -36,4 +44,5 @@ compose.experimental { dependencies { add("kspCommonMainMetadata", project(":ksp:appyx-processor")) add("kspJs", project(":ksp:appyx-processor")) + add("kspWasmJs", project(":ksp:appyx-processor")) } diff --git a/demos/mkdocs/appyx-components/experimental/puzzle15/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/experimental/puzzle15/main.js.kt b/demos/mkdocs/appyx-components/experimental/puzzle15/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/experimental/puzzle15/main.js.kt new file mode 100644 index 000000000..ab71d5098 --- /dev/null +++ b/demos/mkdocs/appyx-components/experimental/puzzle15/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/experimental/puzzle15/main.js.kt @@ -0,0 +1,50 @@ +package com.bumble.appyx.demos.experimental.puzzle15 + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.material.Surface +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.ExperimentalComposeUiApi +import androidx.compose.ui.Modifier +import androidx.compose.ui.layout.onSizeChanged +import androidx.compose.ui.unit.IntSize +import androidx.compose.ui.unit.dp +import androidx.compose.ui.window.CanvasBasedWindow +import com.bumble.appyx.components.experimental.puzzle15.ui.Puzzle15Ui +import com.bumble.appyx.demos.appyxSample +import com.bumble.appyx.demos.common.color_dark +import com.bumble.appyx.demos.common.color_primary + + +@OptIn(ExperimentalComposeUiApi::class) +fun main() { + appyxSample { + CanvasBasedWindow("Appyx") { + var size by remember { mutableStateOf(IntSize.Zero) } + Surface( + modifier = Modifier + .fillMaxSize() + .onSizeChanged { size = it } + ) { + if (size != IntSize.Zero) { + Puzzle15Ui( + screenWidthPx = size.width, + screenHeightPx = size.height, + accentColor = color_primary, + modifier = Modifier + .fillMaxSize() + .background(color_dark) + .padding( + horizontal = 16.dp, + vertical = 16.dp + ) + ) + } + } + } + } +} diff --git a/demos/mkdocs/appyx-components/experimental/puzzle15/web/src/wasmJsMain/resources/index.html b/demos/mkdocs/appyx-components/experimental/puzzle15/web/src/wasmJsMain/resources/index.html new file mode 100644 index 000000000..cb31659d7 --- /dev/null +++ b/demos/mkdocs/appyx-components/experimental/puzzle15/web/src/wasmJsMain/resources/index.html @@ -0,0 +1,15 @@ + + + + + Appyx Interactions + + + + +
+ +
+ + + diff --git a/demos/mkdocs/appyx-components/experimental/puzzle15/web/src/wasmJsMain/resources/styles.css b/demos/mkdocs/appyx-components/experimental/puzzle15/web/src/wasmJsMain/resources/styles.css new file mode 100644 index 000000000..f8b13d234 --- /dev/null +++ b/demos/mkdocs/appyx-components/experimental/puzzle15/web/src/wasmJsMain/resources/styles.css @@ -0,0 +1,19 @@ +#root { + width: 100%; + height: 100vh; +} + +body { + margin: 0; +} + +#root > .compose-web-column > div { + position: relative; +} + +@media (max-width: 511px) { + #ComposeTarget { + scale: 0.5; + transform-origin: 0 0; + } +} diff --git a/demos/mkdocs/appyx-components/spotlight/fader/web/build.gradle.kts b/demos/mkdocs/appyx-components/spotlight/fader/web/build.gradle.kts index 89894921e..591ff0435 100644 --- a/demos/mkdocs/appyx-components/spotlight/fader/web/build.gradle.kts +++ b/demos/mkdocs/appyx-components/spotlight/fader/web/build.gradle.kts @@ -1,3 +1,5 @@ +import org.jetbrains.kotlin.gradle.targets.js.dsl.ExperimentalWasmDsl + plugins { id("com.bumble.appyx.multiplatform") id("org.jetbrains.compose") @@ -10,6 +12,12 @@ kotlin { browser() binaries.executable() } + @OptIn(ExperimentalWasmDsl::class) + wasmJs { + moduleName = "appyx-components-spotlight-fader-web" + browser() + binaries.executable() + } sourceSets { val commonMain by getting { dependencies { @@ -36,4 +44,5 @@ compose.experimental { dependencies { add("kspCommonMainMetadata", project(":ksp:appyx-processor")) add("kspJs", project(":ksp:appyx-processor")) + add("kspWasmJs", project(":ksp:appyx-processor")) } diff --git a/demos/mkdocs/appyx-components/spotlight/fader/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/spotlight/fader/SpotlightFaderSample.kt b/demos/mkdocs/appyx-components/spotlight/fader/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/spotlight/fader/SpotlightFaderSample.kt new file mode 100644 index 000000000..1f1866f45 --- /dev/null +++ b/demos/mkdocs/appyx-components/spotlight/fader/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/spotlight/fader/SpotlightFaderSample.kt @@ -0,0 +1,65 @@ +package com.bumble.appyx.demos.spotlight.fader + +import androidx.compose.animation.core.AnimationSpec +import androidx.compose.animation.core.Spring +import androidx.compose.animation.core.spring +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.Modifier +import com.bumble.appyx.components.spotlight.Spotlight +import com.bumble.appyx.components.spotlight.SpotlightModel +import com.bumble.appyx.components.spotlight.operation.next +import com.bumble.appyx.components.spotlight.operation.previous +import com.bumble.appyx.components.spotlight.ui.fader.SpotlightFader +import com.bumble.appyx.components.spotlight.ui.slider.SpotlightSlider +import com.bumble.appyx.demos.common.AppyxWebSample +import com.bumble.appyx.demos.common.ChildSize +import com.bumble.appyx.demos.common.InteractionTarget +import com.bumble.appyx.interactions.model.transition.Operation + +@Composable +fun SpotlightFaderSample( + screenWidthPx: Int, + screenHeightPx: Int, + modifier: Modifier = Modifier, +) { + val coroutineScope = rememberCoroutineScope() + val model = remember { + SpotlightModel( + items = List(7) { InteractionTarget.Element(it) }, + initialActiveIndex = 0f, + savedStateMap = null + ) + } + val spotlight = + Spotlight( + scope = coroutineScope, + model = model, + visualisation = { SpotlightFader(it) }, + gestureFactory = { SpotlightSlider.Gestures(it) }, + ) + val animationSpec: AnimationSpec = spring(stiffness = Spring.StiffnessVeryLow * 2) + val actions = mapOf( + "Prev" to { + spotlight.previous( + mode = Operation.Mode.KEYFRAME, + animationSpec = animationSpec, + ) + }, + "Next" to { + spotlight.next( + mode = Operation.Mode.KEYFRAME, + animationSpec = animationSpec, + ) + }, + ) + AppyxWebSample( + screenWidthPx = screenWidthPx, + screenHeightPx = screenHeightPx, + appyxComponent = spotlight, + actions = actions, + childSize = ChildSize.MAX, + modifier = modifier, + ) +} diff --git a/demos/mkdocs/appyx-components/spotlight/fader/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/spotlight/fader/main.js.kt b/demos/mkdocs/appyx-components/spotlight/fader/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/spotlight/fader/main.js.kt new file mode 100644 index 000000000..2c7277c71 --- /dev/null +++ b/demos/mkdocs/appyx-components/spotlight/fader/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/spotlight/fader/main.js.kt @@ -0,0 +1,47 @@ +package com.bumble.appyx.demos.spotlight.fader + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.material.Surface +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.ExperimentalComposeUiApi +import androidx.compose.ui.Modifier +import androidx.compose.ui.layout.onSizeChanged +import androidx.compose.ui.unit.IntSize +import androidx.compose.ui.unit.dp +import androidx.compose.ui.window.CanvasBasedWindow +import com.bumble.appyx.demos.appyxSample +import com.bumble.appyx.demos.common.color_dark + + +@OptIn(ExperimentalComposeUiApi::class) +fun main() { + appyxSample { + CanvasBasedWindow("Appyx") { + var size by remember { mutableStateOf(IntSize.Zero) } + Surface( + modifier = Modifier + .fillMaxSize() + .onSizeChanged { size = it } + ) { + if (size != IntSize.Zero) { + SpotlightFaderSample( + screenWidthPx = size.width, + screenHeightPx = size.height, + modifier = Modifier + .fillMaxSize() + .background(color_dark) + .padding( + horizontal = 60.dp, + vertical = 16.dp + ) + ) + } + } + } + } +} diff --git a/demos/mkdocs/appyx-components/spotlight/fader/web/src/wasmJsMain/resources/index.html b/demos/mkdocs/appyx-components/spotlight/fader/web/src/wasmJsMain/resources/index.html new file mode 100644 index 000000000..cb31659d7 --- /dev/null +++ b/demos/mkdocs/appyx-components/spotlight/fader/web/src/wasmJsMain/resources/index.html @@ -0,0 +1,15 @@ + + + + + Appyx Interactions + + + + +
+ +
+ + + diff --git a/demos/mkdocs/appyx-components/spotlight/fader/web/src/wasmJsMain/resources/styles.css b/demos/mkdocs/appyx-components/spotlight/fader/web/src/wasmJsMain/resources/styles.css new file mode 100644 index 000000000..f8b13d234 --- /dev/null +++ b/demos/mkdocs/appyx-components/spotlight/fader/web/src/wasmJsMain/resources/styles.css @@ -0,0 +1,19 @@ +#root { + width: 100%; + height: 100vh; +} + +body { + margin: 0; +} + +#root > .compose-web-column > div { + position: relative; +} + +@media (max-width: 511px) { + #ComposeTarget { + scale: 0.5; + transform-origin: 0 0; + } +} diff --git a/demos/mkdocs/appyx-components/spotlight/slider/web/build.gradle.kts b/demos/mkdocs/appyx-components/spotlight/slider/web/build.gradle.kts index 0626d9ca4..c24879ef6 100644 --- a/demos/mkdocs/appyx-components/spotlight/slider/web/build.gradle.kts +++ b/demos/mkdocs/appyx-components/spotlight/slider/web/build.gradle.kts @@ -1,3 +1,5 @@ +import org.jetbrains.kotlin.gradle.targets.js.dsl.ExperimentalWasmDsl + plugins { id("com.bumble.appyx.multiplatform") id("org.jetbrains.compose") @@ -10,6 +12,12 @@ kotlin { browser() binaries.executable() } + @OptIn(ExperimentalWasmDsl::class) + wasmJs { + moduleName = "appyx-components-spotlight-slider-web" + browser() + binaries.executable() + } sourceSets { val commonMain by getting { dependencies { @@ -36,4 +44,5 @@ compose.experimental { dependencies { add("kspCommonMainMetadata", project(":ksp:appyx-processor")) add("kspJs", project(":ksp:appyx-processor")) + add("kspWasmJs", project(":ksp:appyx-processor")) } diff --git a/demos/mkdocs/appyx-components/spotlight/slider/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/spotlight/slider/SpotlightSliderSample.kt b/demos/mkdocs/appyx-components/spotlight/slider/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/spotlight/slider/SpotlightSliderSample.kt new file mode 100644 index 000000000..2ad4a672b --- /dev/null +++ b/demos/mkdocs/appyx-components/spotlight/slider/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/spotlight/slider/SpotlightSliderSample.kt @@ -0,0 +1,51 @@ +package com.bumble.appyx.demos.spotlight.slider + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.Modifier +import com.bumble.appyx.components.spotlight.Spotlight +import com.bumble.appyx.components.spotlight.SpotlightModel +import com.bumble.appyx.components.spotlight.operation.first +import com.bumble.appyx.components.spotlight.operation.last +import com.bumble.appyx.components.spotlight.operation.next +import com.bumble.appyx.components.spotlight.operation.previous +import com.bumble.appyx.components.spotlight.ui.slider.SpotlightSlider +import com.bumble.appyx.demos.common.AppyxWebSample +import com.bumble.appyx.demos.common.InteractionTarget + +@Composable +fun SpotlightSliderSample( + screenWidthPx: Int, + screenHeightPx: Int, + modifier: Modifier = Modifier, +) { + val coroutineScope = rememberCoroutineScope() + val model = remember { + SpotlightModel( + items = List(7) { InteractionTarget.Element(it) }, + initialActiveIndex = 1f, + savedStateMap = null + ) + } + val spotlight = + Spotlight( + scope = coroutineScope, + model = model, + visualisation = { SpotlightSlider(it, model.currentState) }, + gestureFactory = { SpotlightSlider.Gestures(it) } + ) + val actions = mapOf( + "First" to { spotlight.first() }, + "Prev" to { spotlight.previous() }, + "Next" to { spotlight.next() }, + "Last" to { spotlight.last() }, + ) + AppyxWebSample( + screenWidthPx = screenWidthPx, + screenHeightPx = screenHeightPx, + appyxComponent = spotlight, + actions = actions, + modifier = modifier, + ) +} diff --git a/demos/mkdocs/appyx-components/spotlight/slider/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/spotlight/slider/main.js.kt b/demos/mkdocs/appyx-components/spotlight/slider/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/spotlight/slider/main.js.kt new file mode 100644 index 000000000..eb8f4b292 --- /dev/null +++ b/demos/mkdocs/appyx-components/spotlight/slider/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/spotlight/slider/main.js.kt @@ -0,0 +1,47 @@ +package com.bumble.appyx.demos.spotlight.slider + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.material.Surface +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.ExperimentalComposeUiApi +import androidx.compose.ui.Modifier +import androidx.compose.ui.layout.onSizeChanged +import androidx.compose.ui.unit.IntSize +import androidx.compose.ui.unit.dp +import androidx.compose.ui.window.CanvasBasedWindow +import com.bumble.appyx.demos.appyxSample +import com.bumble.appyx.demos.common.color_dark + + +@OptIn(ExperimentalComposeUiApi::class) +fun main() { + appyxSample { + CanvasBasedWindow("Appyx") { + var size by remember { mutableStateOf(IntSize.Zero) } + Surface( + modifier = Modifier + .fillMaxSize() + .onSizeChanged { size = it } + ) { + if (size != IntSize.Zero) { + SpotlightSliderSample( + screenWidthPx = size.width, + screenHeightPx = size.height, + modifier = Modifier + .fillMaxSize() + .background(color_dark) + .padding( + horizontal = 60.dp, + vertical = 16.dp + ) + ) + } + } + } + } +} diff --git a/demos/mkdocs/appyx-components/spotlight/slider/web/src/wasmJsMain/resources/index.html b/demos/mkdocs/appyx-components/spotlight/slider/web/src/wasmJsMain/resources/index.html new file mode 100644 index 000000000..cb31659d7 --- /dev/null +++ b/demos/mkdocs/appyx-components/spotlight/slider/web/src/wasmJsMain/resources/index.html @@ -0,0 +1,15 @@ + + + + + Appyx Interactions + + + + +
+ +
+ + + diff --git a/demos/mkdocs/appyx-components/spotlight/slider/web/src/wasmJsMain/resources/styles.css b/demos/mkdocs/appyx-components/spotlight/slider/web/src/wasmJsMain/resources/styles.css new file mode 100644 index 000000000..f8b13d234 --- /dev/null +++ b/demos/mkdocs/appyx-components/spotlight/slider/web/src/wasmJsMain/resources/styles.css @@ -0,0 +1,19 @@ +#root { + width: 100%; + height: 100vh; +} + +body { + margin: 0; +} + +#root > .compose-web-column > div { + position: relative; +} + +@media (max-width: 511px) { + #ComposeTarget { + scale: 0.5; + transform-origin: 0 0; + } +} diff --git a/demos/mkdocs/appyx-components/spotlight/sliderrotation/web/build.gradle.kts b/demos/mkdocs/appyx-components/spotlight/sliderrotation/web/build.gradle.kts index b08345ee8..b2f0e1476 100644 --- a/demos/mkdocs/appyx-components/spotlight/sliderrotation/web/build.gradle.kts +++ b/demos/mkdocs/appyx-components/spotlight/sliderrotation/web/build.gradle.kts @@ -1,3 +1,5 @@ +import org.jetbrains.kotlin.gradle.targets.js.dsl.ExperimentalWasmDsl + plugins { id("com.bumble.appyx.multiplatform") id("org.jetbrains.compose") @@ -10,6 +12,12 @@ kotlin { browser() binaries.executable() } + @OptIn(ExperimentalWasmDsl::class) + wasmJs { + moduleName = "appyx-components-spotlight-slider-rotation-web" + browser() + binaries.executable() + } sourceSets { val commonMain by getting { dependencies { @@ -36,4 +44,5 @@ compose.experimental { dependencies { add("kspCommonMainMetadata", project(":ksp:appyx-processor")) add("kspJs", project(":ksp:appyx-processor")) + add("kspWasmJs", project(":ksp:appyx-processor")) } diff --git a/demos/mkdocs/appyx-components/spotlight/sliderrotation/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/spotlight/sliderrotation/SpotlightSliderRotationSample.kt b/demos/mkdocs/appyx-components/spotlight/sliderrotation/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/spotlight/sliderrotation/SpotlightSliderRotationSample.kt new file mode 100644 index 000000000..2fcb17043 --- /dev/null +++ b/demos/mkdocs/appyx-components/spotlight/sliderrotation/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/spotlight/sliderrotation/SpotlightSliderRotationSample.kt @@ -0,0 +1,53 @@ +package com.bumble.appyx.demos.spotlight.sliderrotation + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.Modifier +import com.bumble.appyx.components.spotlight.Spotlight +import com.bumble.appyx.components.spotlight.SpotlightModel +import com.bumble.appyx.components.spotlight.operation.first +import com.bumble.appyx.components.spotlight.operation.last +import com.bumble.appyx.components.spotlight.operation.next +import com.bumble.appyx.components.spotlight.operation.previous +import com.bumble.appyx.components.spotlight.ui.slider.SpotlightSlider +import com.bumble.appyx.components.spotlight.ui.sliderrotation.SpotlightSliderRotation +import com.bumble.appyx.demos.common.AppyxWebSample +import com.bumble.appyx.demos.common.InteractionTarget + +@Composable +fun SpotlightSliderRotationSample( + screenWidthPx: Int, + screenHeightPx: Int, + modifier: Modifier = Modifier, +) { + val coroutineScope = rememberCoroutineScope() + val model = remember { + SpotlightModel( + items = List(7) { InteractionTarget.Element(it) }, + initialActiveIndex = 1f, + savedStateMap = null + ) + } + val spotlight = + Spotlight( + scope = coroutineScope, + model = model, + visualisation = { SpotlightSliderRotation(it, model.currentState) }, + gestureFactory = { SpotlightSlider.Gestures(it) } + ) + val actions = mapOf( + "First" to { spotlight.first() }, + "Prev" to { spotlight.previous() }, + "Next" to { spotlight.next() }, + "Last" to { spotlight.last() }, + + ) + AppyxWebSample( + screenWidthPx = screenWidthPx, + screenHeightPx = screenHeightPx, + appyxComponent = spotlight, + actions = actions, + modifier = modifier, + ) +} diff --git a/demos/mkdocs/appyx-components/spotlight/sliderrotation/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/spotlight/sliderrotation/main.js.kt b/demos/mkdocs/appyx-components/spotlight/sliderrotation/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/spotlight/sliderrotation/main.js.kt new file mode 100644 index 000000000..6999e1c8d --- /dev/null +++ b/demos/mkdocs/appyx-components/spotlight/sliderrotation/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/spotlight/sliderrotation/main.js.kt @@ -0,0 +1,47 @@ +package com.bumble.appyx.demos.spotlight.sliderrotation + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.material.Surface +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.ExperimentalComposeUiApi +import androidx.compose.ui.Modifier +import androidx.compose.ui.layout.onSizeChanged +import androidx.compose.ui.unit.IntSize +import androidx.compose.ui.unit.dp +import androidx.compose.ui.window.CanvasBasedWindow +import com.bumble.appyx.demos.appyxSample +import com.bumble.appyx.demos.common.color_dark + + +@OptIn(ExperimentalComposeUiApi::class) +fun main() { + appyxSample { + CanvasBasedWindow("Appyx") { + var size by remember { mutableStateOf(IntSize.Zero) } + Surface( + modifier = Modifier + .fillMaxSize() + .onSizeChanged { size = it } + ) { + if (size != IntSize.Zero) { + SpotlightSliderRotationSample( + screenWidthPx = size.width, + screenHeightPx = size.height, + modifier = Modifier + .fillMaxSize() + .background(color_dark) + .padding( + horizontal = 85.dp, + vertical = 16.dp + ) + ) + } + } + } + } +} diff --git a/demos/mkdocs/appyx-components/spotlight/sliderrotation/web/src/wasmJsMain/resources/index.html b/demos/mkdocs/appyx-components/spotlight/sliderrotation/web/src/wasmJsMain/resources/index.html new file mode 100644 index 000000000..cb31659d7 --- /dev/null +++ b/demos/mkdocs/appyx-components/spotlight/sliderrotation/web/src/wasmJsMain/resources/index.html @@ -0,0 +1,15 @@ + + + + + Appyx Interactions + + + + +
+ +
+ + + diff --git a/demos/mkdocs/appyx-components/spotlight/sliderrotation/web/src/wasmJsMain/resources/styles.css b/demos/mkdocs/appyx-components/spotlight/sliderrotation/web/src/wasmJsMain/resources/styles.css new file mode 100644 index 000000000..f8b13d234 --- /dev/null +++ b/demos/mkdocs/appyx-components/spotlight/sliderrotation/web/src/wasmJsMain/resources/styles.css @@ -0,0 +1,19 @@ +#root { + width: 100%; + height: 100vh; +} + +body { + margin: 0; +} + +#root > .compose-web-column > div { + position: relative; +} + +@media (max-width: 511px) { + #ComposeTarget { + scale: 0.5; + transform-origin: 0 0; + } +} diff --git a/demos/mkdocs/appyx-components/spotlight/sliderscale/web/build.gradle.kts b/demos/mkdocs/appyx-components/spotlight/sliderscale/web/build.gradle.kts index c58816d23..85efadf01 100644 --- a/demos/mkdocs/appyx-components/spotlight/sliderscale/web/build.gradle.kts +++ b/demos/mkdocs/appyx-components/spotlight/sliderscale/web/build.gradle.kts @@ -1,3 +1,5 @@ +import org.jetbrains.kotlin.gradle.targets.js.dsl.ExperimentalWasmDsl + plugins { id("com.bumble.appyx.multiplatform") id("org.jetbrains.compose") @@ -10,6 +12,12 @@ kotlin { browser() binaries.executable() } + @OptIn(ExperimentalWasmDsl::class) + wasmJs { + moduleName = "appyx-components-spotlight-slider-scale-web" + browser() + binaries.executable() + } sourceSets { val commonMain by getting { dependencies { @@ -36,4 +44,5 @@ compose.experimental { dependencies { add("kspCommonMainMetadata", project(":ksp:appyx-processor")) add("kspJs", project(":ksp:appyx-processor")) + add("kspWasmJs", project(":ksp:appyx-processor")) } diff --git a/demos/mkdocs/appyx-components/spotlight/sliderscale/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/spotlight/sliderscale/SpotlightSliderScaleSample.kt b/demos/mkdocs/appyx-components/spotlight/sliderscale/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/spotlight/sliderscale/SpotlightSliderScaleSample.kt new file mode 100644 index 000000000..8c1e8baba --- /dev/null +++ b/demos/mkdocs/appyx-components/spotlight/sliderscale/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/spotlight/sliderscale/SpotlightSliderScaleSample.kt @@ -0,0 +1,52 @@ +package com.bumble.appyx.demos.spotlight.sliderscale + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.Modifier +import com.bumble.appyx.components.spotlight.Spotlight +import com.bumble.appyx.components.spotlight.SpotlightModel +import com.bumble.appyx.components.spotlight.operation.first +import com.bumble.appyx.components.spotlight.operation.last +import com.bumble.appyx.components.spotlight.operation.next +import com.bumble.appyx.components.spotlight.operation.previous +import com.bumble.appyx.components.spotlight.ui.slider.SpotlightSlider +import com.bumble.appyx.components.spotlight.ui.sliderscale.SpotlightSliderScale +import com.bumble.appyx.demos.common.AppyxWebSample +import com.bumble.appyx.demos.common.InteractionTarget + +@Composable +fun SpotlightSliderScaleSample( + screenWidthPx: Int, + screenHeightPx: Int, + modifier: Modifier = Modifier, +) { + val coroutineScope = rememberCoroutineScope() + val model = remember { + SpotlightModel( + items = List(7) { InteractionTarget.Element(it) }, + initialActiveIndex = 1f, + savedStateMap = null + ) + } + val spotlight = + Spotlight( + scope = coroutineScope, + model = model, + visualisation = { SpotlightSliderScale(it, model.currentState) }, + gestureFactory = { SpotlightSlider.Gestures(it) } + ) + val actions = mapOf( + "First" to { spotlight.first() }, + "Prev" to { spotlight.previous() }, + "Next" to { spotlight.next() }, + "Last" to { spotlight.last() }, + ) + AppyxWebSample( + screenWidthPx = screenWidthPx, + screenHeightPx = screenHeightPx, + appyxComponent = spotlight, + actions = actions, + modifier = modifier, + ) +} diff --git a/demos/mkdocs/appyx-components/spotlight/sliderscale/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/spotlight/sliderscale/main.js.kt b/demos/mkdocs/appyx-components/spotlight/sliderscale/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/spotlight/sliderscale/main.js.kt new file mode 100644 index 000000000..72ed61307 --- /dev/null +++ b/demos/mkdocs/appyx-components/spotlight/sliderscale/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/spotlight/sliderscale/main.js.kt @@ -0,0 +1,47 @@ +package com.bumble.appyx.demos.spotlight.sliderscale + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.material.Surface +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.ExperimentalComposeUiApi +import androidx.compose.ui.Modifier +import androidx.compose.ui.layout.onSizeChanged +import androidx.compose.ui.unit.IntSize +import androidx.compose.ui.unit.dp +import androidx.compose.ui.window.CanvasBasedWindow +import com.bumble.appyx.demos.appyxSample +import com.bumble.appyx.demos.common.color_dark + + +@OptIn(ExperimentalComposeUiApi::class) +fun main() { + appyxSample { + CanvasBasedWindow("Appyx") { + var size by remember { mutableStateOf(IntSize.Zero) } + Surface( + modifier = Modifier + .fillMaxSize() + .onSizeChanged { size = it } + ) { + if (size != IntSize.Zero) { + SpotlightSliderScaleSample( + screenWidthPx = size.width, + screenHeightPx = size.height, + modifier = Modifier + .fillMaxSize() + .background(color_dark) + .padding( + horizontal = 60.dp, + vertical = 16.dp + ) + ) + } + } + } + } +} diff --git a/demos/mkdocs/appyx-components/spotlight/sliderscale/web/src/wasmJsMain/resources/index.html b/demos/mkdocs/appyx-components/spotlight/sliderscale/web/src/wasmJsMain/resources/index.html new file mode 100644 index 000000000..cb31659d7 --- /dev/null +++ b/demos/mkdocs/appyx-components/spotlight/sliderscale/web/src/wasmJsMain/resources/index.html @@ -0,0 +1,15 @@ + + + + + Appyx Interactions + + + + +
+ +
+ + + diff --git a/demos/mkdocs/appyx-components/spotlight/sliderscale/web/src/wasmJsMain/resources/styles.css b/demos/mkdocs/appyx-components/spotlight/sliderscale/web/src/wasmJsMain/resources/styles.css new file mode 100644 index 000000000..f8b13d234 --- /dev/null +++ b/demos/mkdocs/appyx-components/spotlight/sliderscale/web/src/wasmJsMain/resources/styles.css @@ -0,0 +1,19 @@ +#root { + width: 100%; + height: 100vh; +} + +body { + margin: 0; +} + +#root > .compose-web-column > div { + position: relative; +} + +@media (max-width: 511px) { + #ComposeTarget { + scale: 0.5; + transform-origin: 0 0; + } +} diff --git a/demos/mkdocs/appyx-components/spotlight/stack3d/web/build.gradle.kts b/demos/mkdocs/appyx-components/spotlight/stack3d/web/build.gradle.kts index 27eaa27ad..9073ed4ca 100644 --- a/demos/mkdocs/appyx-components/spotlight/stack3d/web/build.gradle.kts +++ b/demos/mkdocs/appyx-components/spotlight/stack3d/web/build.gradle.kts @@ -1,3 +1,5 @@ +import org.jetbrains.kotlin.gradle.targets.js.dsl.ExperimentalWasmDsl + plugins { id("com.bumble.appyx.multiplatform") id("org.jetbrains.compose") @@ -10,6 +12,12 @@ kotlin { browser() binaries.executable() } + @OptIn(ExperimentalWasmDsl::class) + wasmJs { + moduleName = "appyx-components-spotlight-stack3d-web" + browser() + binaries.executable() + } sourceSets { val commonMain by getting { dependencies { @@ -36,4 +44,5 @@ compose.experimental { dependencies { add("kspCommonMainMetadata", project(":ksp:appyx-processor")) add("kspJs", project(":ksp:appyx-processor")) + add("kspWasmJs", project(":ksp:appyx-processor")) } diff --git a/demos/mkdocs/appyx-components/spotlight/stack3d/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/spotlight/stack3d/SpotlightStack3DSample.kt b/demos/mkdocs/appyx-components/spotlight/stack3d/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/spotlight/stack3d/SpotlightStack3DSample.kt new file mode 100644 index 000000000..12279a8ce --- /dev/null +++ b/demos/mkdocs/appyx-components/spotlight/stack3d/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/spotlight/stack3d/SpotlightStack3DSample.kt @@ -0,0 +1,72 @@ +package com.bumble.appyx.demos.spotlight.stack3d + +import androidx.compose.animation.core.AnimationSpec +import androidx.compose.animation.core.Spring +import androidx.compose.animation.core.spring +import androidx.compose.foundation.gestures.Orientation +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.Modifier +import com.bumble.appyx.components.spotlight.Spotlight +import com.bumble.appyx.components.spotlight.SpotlightModel +import com.bumble.appyx.components.spotlight.operation.next +import com.bumble.appyx.components.spotlight.operation.previous +import com.bumble.appyx.components.spotlight.ui.slider.SpotlightSlider +import com.bumble.appyx.components.spotlight.ui.stack3d.SpotlightStack3D +import com.bumble.appyx.demos.common.AppyxWebSample +import com.bumble.appyx.demos.common.ChildSize +import com.bumble.appyx.demos.common.InteractionTarget +import com.bumble.appyx.interactions.model.transition.Operation + +@Composable +fun SpotlightStack3DSample( + screenWidthPx: Int, + screenHeightPx: Int, + modifier: Modifier = Modifier, +) { + val coroutineScope = rememberCoroutineScope() + val model = remember { + SpotlightModel( + items = List(7) { InteractionTarget.Element(it) }, + initialActiveIndex = 0f, + savedStateMap = null + ) + } + val spotlight = + Spotlight( + scope = coroutineScope, + model = model, + visualisation = { SpotlightStack3D(it, model.currentState) }, + gestureFactory = { + SpotlightSlider.Gestures( + transitionBounds = it, + orientation = Orientation.Vertical, + reverseOrientation = true, + ) + }, + ) + val animationSpec: AnimationSpec = spring(stiffness = Spring.StiffnessVeryLow * 2) + val actions = mapOf( + "Prev" to { + spotlight.previous( + mode = Operation.Mode.KEYFRAME, + animationSpec = animationSpec, + ) + }, + "Next" to { + spotlight.next( + mode = Operation.Mode.KEYFRAME, + animationSpec = animationSpec, + ) + }, + ) + AppyxWebSample( + screenWidthPx = screenWidthPx, + screenHeightPx = screenHeightPx, + appyxComponent = spotlight, + actions = actions, + childSize = ChildSize.MEDIUM, + modifier = modifier, + ) +} diff --git a/demos/mkdocs/appyx-components/spotlight/stack3d/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/spotlight/stack3d/main.js.kt b/demos/mkdocs/appyx-components/spotlight/stack3d/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/spotlight/stack3d/main.js.kt new file mode 100644 index 000000000..39f40cc69 --- /dev/null +++ b/demos/mkdocs/appyx-components/spotlight/stack3d/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/spotlight/stack3d/main.js.kt @@ -0,0 +1,47 @@ +package com.bumble.appyx.demos.spotlight.stack3d + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.material.Surface +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.ExperimentalComposeUiApi +import androidx.compose.ui.Modifier +import androidx.compose.ui.layout.onSizeChanged +import androidx.compose.ui.unit.IntSize +import androidx.compose.ui.unit.dp +import androidx.compose.ui.window.CanvasBasedWindow +import com.bumble.appyx.demos.appyxSample +import com.bumble.appyx.demos.common.color_dark + + +@OptIn(ExperimentalComposeUiApi::class) +fun main() { + appyxSample { + CanvasBasedWindow("Appyx") { + var size by remember { mutableStateOf(IntSize.Zero) } + Surface( + modifier = Modifier + .fillMaxSize() + .onSizeChanged { size = it } + ) { + if (size != IntSize.Zero) { + SpotlightStack3DSample( + screenWidthPx = size.width, + screenHeightPx = size.height, + modifier = Modifier + .fillMaxSize() + .background(color_dark) + .padding( + horizontal = 60.dp, + vertical = 16.dp + ) + ) + } + } + } + } +} diff --git a/demos/mkdocs/appyx-components/spotlight/stack3d/web/src/wasmJsMain/resources/index.html b/demos/mkdocs/appyx-components/spotlight/stack3d/web/src/wasmJsMain/resources/index.html new file mode 100644 index 000000000..cb31659d7 --- /dev/null +++ b/demos/mkdocs/appyx-components/spotlight/stack3d/web/src/wasmJsMain/resources/index.html @@ -0,0 +1,15 @@ + + + + + Appyx Interactions + + + + +
+ +
+ + + diff --git a/demos/mkdocs/appyx-components/spotlight/stack3d/web/src/wasmJsMain/resources/styles.css b/demos/mkdocs/appyx-components/spotlight/stack3d/web/src/wasmJsMain/resources/styles.css new file mode 100644 index 000000000..f8b13d234 --- /dev/null +++ b/demos/mkdocs/appyx-components/spotlight/stack3d/web/src/wasmJsMain/resources/styles.css @@ -0,0 +1,19 @@ +#root { + width: 100%; + height: 100vh; +} + +body { + margin: 0; +} + +#root > .compose-web-column > div { + position: relative; +} + +@media (max-width: 511px) { + #ComposeTarget { + scale: 0.5; + transform-origin: 0 0; + } +} diff --git a/demos/mkdocs/appyx-interactions/gestures/dragprediction/web/build.gradle.kts b/demos/mkdocs/appyx-interactions/gestures/dragprediction/web/build.gradle.kts index c8c76e0b4..2a746be35 100644 --- a/demos/mkdocs/appyx-interactions/gestures/dragprediction/web/build.gradle.kts +++ b/demos/mkdocs/appyx-interactions/gestures/dragprediction/web/build.gradle.kts @@ -1,3 +1,5 @@ +import org.jetbrains.kotlin.gradle.targets.js.dsl.ExperimentalWasmDsl + plugins { id("com.bumble.appyx.multiplatform") id("org.jetbrains.compose") @@ -10,6 +12,12 @@ kotlin { browser() binaries.executable() } + @OptIn(ExperimentalWasmDsl::class) + wasmJs { + moduleName = "appyx-interactions-gestures-dragpredication-web" + browser() + binaries.executable() + } sourceSets { val commonMain by getting { dependencies { @@ -36,4 +44,5 @@ compose.experimental { dependencies { add("kspCommonMainMetadata", project(":ksp:appyx-processor")) add("kspJs", project(":ksp:appyx-processor")) + add("kspWasmJs", project(":ksp:appyx-processor")) } diff --git a/demos/mkdocs/appyx-interactions/gestures/dragprediction/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/dragprediction/DragPrediction.kt b/demos/mkdocs/appyx-interactions/gestures/dragprediction/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/dragprediction/DragPrediction.kt new file mode 100644 index 000000000..563c27997 --- /dev/null +++ b/demos/mkdocs/appyx-interactions/gestures/dragprediction/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/dragprediction/DragPrediction.kt @@ -0,0 +1,230 @@ +@file:Suppress("MatchingDeclarationName") + +package com.bumble.appyx.demos.dragprediction + +import androidx.compose.animation.animateColorAsState +import androidx.compose.animation.core.Spring +import androidx.compose.animation.core.spring +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.offset +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.Alignment +import androidx.compose.ui.Alignment.Companion.CenterHorizontally +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.alpha +import androidx.compose.ui.draw.rotate +import androidx.compose.ui.draw.scale +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.LinearGradientShader +import androidx.compose.ui.graphics.ShaderBrush +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.compose.ui.zIndex +import com.bumble.appyx.components.internal.testdrive.TestDrive +import com.bumble.appyx.components.internal.testdrive.TestDriveModel +import com.bumble.appyx.components.internal.testdrive.TestDriveModel.State +import com.bumble.appyx.components.internal.testdrive.TestDriveModel.State.ElementState +import com.bumble.appyx.components.internal.testdrive.TestDriveModel.State.ElementState.A +import com.bumble.appyx.components.internal.testdrive.TestDriveModel.State.ElementState.B +import com.bumble.appyx.components.internal.testdrive.TestDriveModel.State.ElementState.C +import com.bumble.appyx.components.internal.testdrive.TestDriveModel.State.ElementState.D +import com.bumble.appyx.demos.dragprediction.DragPredictionVisualisation.Companion.toTargetUiState +import com.bumble.appyx.demos.dragprediction.InteractionTarget.Child1 +import com.bumble.appyx.interactions.composable.AppyxInteractionsContainer +import com.bumble.appyx.interactions.model.transition.Keyframes +import com.bumble.appyx.interactions.model.transition.Update +import com.bumble.appyx.interactions.gesture.GestureSettleConfig +import com.bumble.appyx.interactions.ui.helper.AppyxComponentSetup + +enum class InteractionTarget { + Child1 +} + +@Composable +fun DragPrediction( + screenWidthPx: Int, + screenHeightPx: Int, + modifier: Modifier = Modifier, +) { + val coroutineScope = rememberCoroutineScope() + val model = remember { TestDriveModel(Child1, null) } + val testDrive = remember { + TestDrive( + scope = coroutineScope, + model = model, + progressAnimationSpec = spring(visibilityThreshold = 0.001f), + visualisation = { DragPredictionVisualisation(it) }, + gestureFactory = { DragPredictionVisualisation.Gestures(it) }, + gestureSettleConfig = GestureSettleConfig(0.25f) + ) + } + + AppyxComponentSetup(testDrive) + + val output = model.output.collectAsState().value + val currentTarget: androidx.compose.runtime.State?> = + when (output) { + is Keyframes -> output.currentSegmentTargetStateFlow.collectAsState(null) + is Update -> remember(output) { mutableStateOf(output.currentTargetState) } + } + + @Suppress("UnusedPrivateMember") + val index = when (output) { + is Keyframes -> output.currentIndex + is Update -> null + } + + Box( + modifier = modifier, + ) { + Background( + screenWidthPx = screenWidthPx, + screenHeightPx = screenHeightPx, + currentTarget = currentTarget.value + ) + Box( + modifier = Modifier.padding(24.dp, 24.dp) + ) { + Target(elementState = B, alpha = 0.15f) + Target(elementState = C, alpha = 0.15f) + Target(elementState = D, alpha = 0.15f) + Target(elementState = currentTarget.value?.elementState, alpha = 0.65f) + ModelUi( + screenWidthPx = screenWidthPx, + screenHeightPx = screenHeightPx, + testDrive = testDrive, + model = model + ) + Controls( + testDrive = testDrive + ) + } + } +} + +@Composable +fun Background( + screenWidthPx: Int, + screenHeightPx: Int, + currentTarget: TestDriveModel.State?, + modifier: Modifier = Modifier.fillMaxSize() +) { + val backgroundColor1 = animateColorAsState( + when (currentTarget?.elementState) { + A -> color_neutral1 + B -> color_neutral2 + C -> color_neutral3 + D -> color_neutral4 + null -> color_bright + }, + animationSpec = spring(stiffness = Spring.StiffnessVeryLow) + ) + + Box( + modifier + .zIndex(0f) + .background( + ShaderBrush( + LinearGradientShader( + from = Offset.Zero, + to = Offset(screenWidthPx.toFloat(), screenHeightPx.toFloat()), + colors = listOf(color_bright, backgroundColor1.value) + ) + ) + ) + ) +} + +@Composable +fun Target( + elementState: ElementState?, + alpha: Float, + modifier: Modifier = Modifier, +) { + val targetUiState = elementState?.toTargetUiState() + targetUiState?.let { + Box( + modifier = modifier + .size(60.dp) + .offset( + targetUiState.positionOffset.value.offset.x, + targetUiState.positionOffset.value.offset.y + ) + .scale(targetUiState.scale.value) + .rotate(targetUiState.rotationZ.value) + .alpha(alpha) + .background( + color = targetUiState.backgroundColor.value, + shape = RoundedCornerShape(targetUiState.roundedCorners.value) + ) + ) { + Text( + modifier = Modifier.align(Alignment.Center), + text = elementState.name, + fontSize = 24.sp, + color = Color.White + ) + } + } +} + +@Composable +fun ModelUi( + screenWidthPx: Int, + screenHeightPx: Int, + testDrive: TestDrive, + model: TestDriveModel, + modifier: Modifier = Modifier.fillMaxSize() +) { + AppyxInteractionsContainer( + appyxComponent = testDrive, + screenWidthPx = screenWidthPx, + screenHeightPx = screenHeightPx, + modifier = modifier + ) { + Box( + modifier = Modifier.size(60.dp) + ) { + Text( + modifier = Modifier.align(Alignment.Center), + text = model.output.collectAsState().value.currentTargetState.elementState.name, + fontSize = 24.sp, + color = Color.White + ) + } + } +} + +@Suppress("UnusedPrivateMember") +@Composable +private fun Controls( + testDrive: TestDrive, + modifier: Modifier = Modifier.fillMaxSize() +) { + Column( + modifier = modifier.zIndex(1f), + verticalArrangement = Arrangement.Bottom, + horizontalAlignment = CenterHorizontally + ) { + Row { + Spacer(Modifier.width(16.dp)) + } + + } +} diff --git a/demos/mkdocs/appyx-interactions/gestures/dragprediction/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/dragprediction/DragPredictionVisualisation.kt b/demos/mkdocs/appyx-interactions/gestures/dragprediction/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/dragprediction/DragPredictionVisualisation.kt new file mode 100644 index 000000000..66caff8c7 --- /dev/null +++ b/demos/mkdocs/appyx-interactions/gestures/dragprediction/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/dragprediction/DragPredictionVisualisation.kt @@ -0,0 +1,136 @@ +package com.bumble.appyx.demos.dragprediction + +import androidx.compose.animation.core.SpringSpec +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.graphics.TransformOrigin +import androidx.compose.ui.unit.Density +import androidx.compose.ui.unit.DpOffset +import androidx.compose.ui.unit.dp +import com.bumble.appyx.components.internal.testdrive.TestDriveModel +import com.bumble.appyx.components.internal.testdrive.TestDriveModel.State.ElementState.A +import com.bumble.appyx.components.internal.testdrive.TestDriveModel.State.ElementState.B +import com.bumble.appyx.components.internal.testdrive.TestDriveModel.State.ElementState.C +import com.bumble.appyx.components.internal.testdrive.TestDriveModel.State.ElementState.D +import com.bumble.appyx.components.internal.testdrive.operation.MoveTo +import com.bumble.appyx.interactions.ui.context.TransitionBounds +import com.bumble.appyx.interactions.ui.context.UiContext +import com.bumble.appyx.interactions.gesture.Drag.Direction8.DOWN +import com.bumble.appyx.interactions.gesture.Drag.Direction8.DOWNRIGHT +import com.bumble.appyx.interactions.gesture.Drag.Direction8.LEFT +import com.bumble.appyx.interactions.gesture.Drag.Direction8.RIGHT +import com.bumble.appyx.interactions.gesture.Drag.Direction8.UP +import com.bumble.appyx.interactions.gesture.Drag.Direction8.UPLEFT +import com.bumble.appyx.interactions.gesture.Gesture +import com.bumble.appyx.interactions.gesture.GestureFactory +import com.bumble.appyx.interactions.gesture.dragDirection8 +import com.bumble.appyx.interactions.ui.DefaultAnimationSpec +import com.bumble.appyx.interactions.ui.property.impl.BackgroundColor +import com.bumble.appyx.interactions.ui.property.impl.RotationZ +import com.bumble.appyx.interactions.ui.property.impl.Scale +import com.bumble.appyx.interactions.ui.property.impl.position.PositionOffset +import com.bumble.appyx.interactions.ui.state.MatchedTargetUiState +import com.bumble.appyx.transitionmodel.BaseVisualisation +import com.bumble.appyx.utils.multiplatform.AppyxLogger + +class DragPredictionVisualisation( + uiContext: UiContext, + uiAnimationSpec: SpringSpec = DefaultAnimationSpec +) : BaseVisualisation, TargetUiState, MutableUiState>( + uiContext = uiContext, + defaultAnimationSpec = uiAnimationSpec, +) { + override fun TestDriveModel.State.toUiTargets(): + List> = + listOf( + MatchedTargetUiState(element, elementState.toTargetUiState()).also { + AppyxLogger.d("TestDrive", "Matched $elementState -> UiState: ${it.targetUiState}") + } + ) + + companion object { + fun TestDriveModel.State.ElementState.toTargetUiState(): TargetUiState = + when (this) { + A -> topLeftCorner + B -> topRightCorner + C -> BottomRightCorner + D -> bottomLeftCorner + } + + private val topLeftCorner = TargetUiState( + positionOffset = PositionOffset.Target(DpOffset(0.dp, 0.dp)), + scale = Scale.Target(1f), + backgroundColor = BackgroundColor.Target(color_primary) + ) + + private val topRightCorner = TargetUiState( + positionOffset = PositionOffset.Target(DpOffset(180.dp, 30.dp)), + scale = Scale.Target(2f, TransformOrigin(0f, 0f)), + backgroundColor = BackgroundColor.Target(color_dark) + ) + + private val BottomRightCorner = TargetUiState( + positionOffset = PositionOffset.Target(DpOffset(180.dp, 180.dp)), + scale = Scale.Target(2f, TransformOrigin(0f, 0f)), + rotationZ = RotationZ.Target(90f), + backgroundColor = BackgroundColor.Target(color_secondary) + ) + + private val bottomLeftCorner = TargetUiState( + positionOffset = PositionOffset.Target(DpOffset(30.dp, 180.dp)), + scale = Scale.Target(2f, TransformOrigin(0f, 0f)), + rotationZ = RotationZ.Target(180f), + backgroundColor = BackgroundColor.Target(color_tertiary) + ) + } + + override fun mutableUiStateFor( + uiContext: UiContext, + targetUiState: TargetUiState + ): MutableUiState = + targetUiState.toMutableUiState(uiContext) + + + @Suppress("UnusedPrivateMember") + class Gestures( + transitionBounds: TransitionBounds, + ) : GestureFactory> { + private val maxX = topRightCorner.positionOffset.value.offset.x - topLeftCorner.positionOffset.value.offset.x + private val maxY = bottomLeftCorner.positionOffset.value.offset.y - topLeftCorner.positionOffset.value.offset.y + + @Suppress("ComplexMethod") + override fun createGesture( + state: TestDriveModel.State, + delta: Offset, + density: Density + ): Gesture> { + val maxX = with(density) { maxX.toPx() } + val maxY = with(density) { maxY.toPx() } + + val direction = dragDirection8(delta) + return when (state.elementState) { + A -> when (direction) { + RIGHT -> Gesture(MoveTo(B), Offset(maxX, 0f)) + DOWNRIGHT -> Gesture(MoveTo(C), Offset(maxX, maxY)) + DOWN -> Gesture(MoveTo(D), Offset(0f, maxY)) + else -> Gesture.Noop() + } + + B -> when (direction) { + LEFT -> Gesture(MoveTo(A), Offset(-maxX, 0f)) + else -> Gesture.Noop() + } + + C -> when (direction) { + UPLEFT -> Gesture(MoveTo(A), Offset(-maxX, -maxY)) + else -> Gesture.Noop() + } + + D -> when (direction) { + UP -> Gesture(MoveTo(A), Offset(0f, -maxY)) + else -> Gesture.Noop() + } + } + } + } +} + diff --git a/demos/mkdocs/appyx-interactions/gestures/dragprediction/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/dragprediction/TargetUiState.kt b/demos/mkdocs/appyx-interactions/gestures/dragprediction/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/dragprediction/TargetUiState.kt new file mode 100644 index 000000000..e79f03f3a --- /dev/null +++ b/demos/mkdocs/appyx-interactions/gestures/dragprediction/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/dragprediction/TargetUiState.kt @@ -0,0 +1,18 @@ +package com.bumble.appyx.demos.dragprediction + +import com.bumble.appyx.interactions.ui.property.impl.BackgroundColor +import com.bumble.appyx.interactions.ui.property.impl.RotationZ +import com.bumble.appyx.interactions.ui.property.impl.RoundedCorners +import com.bumble.appyx.interactions.ui.property.impl.Scale +import com.bumble.appyx.interactions.ui.property.impl.position.PositionOffset +import com.bumble.appyx.interactions.ui.state.MutableUiStateSpecs + +@Suppress("unused") +@MutableUiStateSpecs +class TargetUiState( + val positionOffset: PositionOffset.Target, + val scale: Scale.Target, + val rotationZ: RotationZ.Target = RotationZ.Target(0f), + val roundedCorners: RoundedCorners.Target = RoundedCorners.Target(10), + val backgroundColor: BackgroundColor.Target, +) diff --git a/demos/mkdocs/appyx-interactions/gestures/dragprediction/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/dragprediction/main.js.kt b/demos/mkdocs/appyx-interactions/gestures/dragprediction/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/dragprediction/main.js.kt new file mode 100644 index 000000000..8cb6ad058 --- /dev/null +++ b/demos/mkdocs/appyx-interactions/gestures/dragprediction/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/dragprediction/main.js.kt @@ -0,0 +1,57 @@ +package com.bumble.appyx.demos.dragprediction + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.material.Surface +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.ExperimentalComposeUiApi +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.layout.onSizeChanged +import androidx.compose.ui.unit.IntSize +import androidx.compose.ui.unit.dp +import androidx.compose.ui.window.CanvasBasedWindow +import com.bumble.appyx.demos.appyxSample + +val color_bright = Color(0xFFFFFFFF) +val color_dark = Color(0xFF353535) +val color_primary = Color(0xFFFFC629) +val color_secondary = Color(0xFFFE9763) +val color_tertiary = Color(0xFF855353) +val color_neutral1 = Color(0xFFD2D7DF) +val color_neutral2 = Color(0xFF8A897C) +val color_neutral3 = Color(0xFFD9E8ED) +val color_neutral4 = Color(0xFFBEA489) + + +@OptIn(ExperimentalComposeUiApi::class) +fun main() { + appyxSample { + CanvasBasedWindow("Appyx") { + var size by remember { mutableStateOf(IntSize.Zero) } + Surface( + modifier = Modifier + .fillMaxSize() + .onSizeChanged { size = it } + ) { + if (size != IntSize.Zero) { + DragPrediction( + screenWidthPx = size.width, + screenHeightPx = size.height, + modifier = Modifier + .fillMaxSize() + .background(color_dark) + .padding( + horizontal = 16.dp, + vertical = 16.dp + ) + ) + } + } + } + } +} diff --git a/demos/mkdocs/appyx-interactions/gestures/dragprediction/web/src/wasmJsMain/resources/index.html b/demos/mkdocs/appyx-interactions/gestures/dragprediction/web/src/wasmJsMain/resources/index.html new file mode 100644 index 000000000..cb31659d7 --- /dev/null +++ b/demos/mkdocs/appyx-interactions/gestures/dragprediction/web/src/wasmJsMain/resources/index.html @@ -0,0 +1,15 @@ + + + + + Appyx Interactions + + + + +
+ +
+ + + diff --git a/demos/mkdocs/appyx-interactions/gestures/dragprediction/web/src/wasmJsMain/resources/styles.css b/demos/mkdocs/appyx-interactions/gestures/dragprediction/web/src/wasmJsMain/resources/styles.css new file mode 100644 index 000000000..f8b13d234 --- /dev/null +++ b/demos/mkdocs/appyx-interactions/gestures/dragprediction/web/src/wasmJsMain/resources/styles.css @@ -0,0 +1,19 @@ +#root { + width: 100%; + height: 100vh; +} + +body { + margin: 0; +} + +#root > .compose-web-column > div { + position: relative; +} + +@media (max-width: 511px) { + #ComposeTarget { + scale: 0.5; + transform-origin: 0 0; + } +} diff --git a/demos/mkdocs/appyx-interactions/gestures/incompletedrag/web/build.gradle.kts b/demos/mkdocs/appyx-interactions/gestures/incompletedrag/web/build.gradle.kts index 984bcaf61..1ecb6dc20 100644 --- a/demos/mkdocs/appyx-interactions/gestures/incompletedrag/web/build.gradle.kts +++ b/demos/mkdocs/appyx-interactions/gestures/incompletedrag/web/build.gradle.kts @@ -1,3 +1,5 @@ +import org.jetbrains.kotlin.gradle.targets.js.dsl.ExperimentalWasmDsl + plugins { id("com.bumble.appyx.multiplatform") id("org.jetbrains.compose") @@ -10,6 +12,12 @@ kotlin { browser() binaries.executable() } + @OptIn(ExperimentalWasmDsl::class) + wasmJs { + moduleName = "appyx-interactions-gestures-incompletedrag-web" + browser() + binaries.executable() + } sourceSets { val commonMain by getting { dependencies { @@ -35,4 +43,5 @@ compose.experimental { dependencies { add("kspCommonMainMetadata", project(":ksp:appyx-processor")) add("kspJs", project(":ksp:appyx-processor")) + add("kspWasmJs", project(":ksp:appyx-processor")) } diff --git a/demos/mkdocs/appyx-interactions/gestures/incompletedrag/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/incompletedrag/IncompleteDrag.kt b/demos/mkdocs/appyx-interactions/gestures/incompletedrag/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/incompletedrag/IncompleteDrag.kt new file mode 100644 index 000000000..064e9b164 --- /dev/null +++ b/demos/mkdocs/appyx-interactions/gestures/incompletedrag/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/incompletedrag/IncompleteDrag.kt @@ -0,0 +1,194 @@ +@file:Suppress("MatchingDeclarationName") + +package com.bumble.appyx.demos.incompletedrag + +import androidx.compose.animation.animateColorAsState +import androidx.compose.animation.core.Spring +import androidx.compose.animation.core.spring +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.Alignment +import androidx.compose.ui.Alignment.Companion.CenterHorizontally +import androidx.compose.ui.Modifier +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.LinearGradientShader +import androidx.compose.ui.graphics.ShaderBrush +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.compose.ui.zIndex +import com.bumble.appyx.components.internal.testdrive.TestDrive +import com.bumble.appyx.components.internal.testdrive.TestDriveModel +import com.bumble.appyx.components.internal.testdrive.TestDriveModel.State.ElementState.A +import com.bumble.appyx.components.internal.testdrive.TestDriveModel.State.ElementState.B +import com.bumble.appyx.components.internal.testdrive.TestDriveModel.State.ElementState.C +import com.bumble.appyx.components.internal.testdrive.TestDriveModel.State.ElementState.D +import com.bumble.appyx.components.internal.testdrive.operation.next +import com.bumble.appyx.demos.incompletedrag.InteractionTarget.Child1 +import com.bumble.appyx.interactions.composable.AppyxInteractionsContainer +import com.bumble.appyx.interactions.gesture.GestureReferencePoint +import com.bumble.appyx.interactions.model.transition.Operation.Mode.IMMEDIATE +import com.bumble.appyx.interactions.gesture.GestureSettleConfig +import com.bumble.appyx.interactions.ui.helper.AppyxComponentSetup + +enum class InteractionTarget { + Child1 +} + +@Composable +fun IncompleteDrag( + screenWidthPx: Int, + screenHeightPx: Int, + modifier: Modifier = Modifier, +) { + val coroutineScope = rememberCoroutineScope() + val model = remember { TestDriveModel(Child1, null) } + val testDrive = remember { + TestDrive( + scope = coroutineScope, + model = model, + visualisation = { IncompleteDragVisualisation(it) }, + gestureFactory = { IncompleteDragVisualisation.Gestures(it) }, + gestureSettleConfig = GestureSettleConfig(0.15f) + ) + } + + AppyxComponentSetup(testDrive) + + Box( + modifier = modifier, + ) { + Background( + screenWidthPx = screenWidthPx, + screenHeightPx = screenHeightPx, + model = model + ) + Box( + modifier = Modifier.padding(24.dp, 24.dp) + ) { + ModelUi( + screenWidthPx = screenWidthPx, + screenHeightPx = screenHeightPx, + testDrive = testDrive, + model = model + ) + Controls( + testDrive = testDrive + ) + } + } +} + +@Composable +fun Background( + screenWidthPx: Int, + screenHeightPx: Int, + model: TestDriveModel, + modifier: Modifier = Modifier.fillMaxSize() +) { + val output = model.output.collectAsState() + val currentTarget = output.value.currentTargetState.elementState + val backgroundColor1 = animateColorAsState( + when (currentTarget) { + A -> color_neutral1 + B -> color_neutral2 + C -> color_neutral3 + D -> color_neutral4 + } + ) + + Box( + modifier + .zIndex(0f) + .background( + ShaderBrush( + LinearGradientShader( + from = Offset.Zero, + to = Offset(screenWidthPx.toFloat(), screenHeightPx.toFloat()), + colors = listOf(color_bright, backgroundColor1.value) + ) + ) + ) + ) +} + +@Composable +fun ModelUi( + screenWidthPx: Int, + screenHeightPx: Int, + testDrive: TestDrive, + model: TestDriveModel, + modifier: Modifier = Modifier.fillMaxSize() +) { + val output = model.output.collectAsState() + val currentTarget = output.value.currentTargetState.elementState + val backgroundColor1 = animateColorAsState( + when (currentTarget) { + A -> color_neutral1 + B -> color_neutral2 + C -> color_neutral3 + D -> color_neutral4 + } + ) + + AppyxInteractionsContainer( + appyxComponent = testDrive, + screenWidthPx = screenWidthPx, + screenHeightPx = screenHeightPx, + gestureRelativeTo = GestureReferencePoint.Element, + modifier = modifier + ) + { + Box( + modifier = Modifier + .size(60.dp) + ) { + Text( + modifier = Modifier.align(Alignment.Center), + text = model.output.collectAsState().value.currentTargetState.elementState.name, + fontSize = 24.sp, + color = Color.White + ) + } + } +} + +@Composable +private fun Controls( + testDrive: TestDrive, + modifier: Modifier = Modifier.fillMaxSize() +) { + Column( + modifier = modifier.zIndex(1f), + verticalArrangement = Arrangement.Bottom, + horizontalAlignment = CenterHorizontally + ) { + Box( + modifier = Modifier + .background(color_primary, shape = RoundedCornerShape(4.dp)) + .clickable { + testDrive.next( + mode = IMMEDIATE, animationSpec = spring( + stiffness = Spring.StiffnessLow, + dampingRatio = Spring.DampingRatioLowBouncy, + ) + ) + } + .padding(horizontal = 18.dp, vertical = 9.dp) + ) { + Text("Next") + } + } +} diff --git a/demos/mkdocs/appyx-interactions/gestures/incompletedrag/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/incompletedrag/IncompleteDragVisualisation.kt b/demos/mkdocs/appyx-interactions/gestures/incompletedrag/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/incompletedrag/IncompleteDragVisualisation.kt new file mode 100644 index 000000000..0716497d1 --- /dev/null +++ b/demos/mkdocs/appyx-interactions/gestures/incompletedrag/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/incompletedrag/IncompleteDragVisualisation.kt @@ -0,0 +1,154 @@ +package com.bumble.appyx.demos.incompletedrag + +import androidx.compose.animation.core.SpringSpec +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.unit.Density +import androidx.compose.ui.unit.DpOffset +import androidx.compose.ui.unit.dp +import com.bumble.appyx.components.internal.testdrive.TestDriveModel +import com.bumble.appyx.components.internal.testdrive.TestDriveModel.State.ElementState.A +import com.bumble.appyx.components.internal.testdrive.TestDriveModel.State.ElementState.B +import com.bumble.appyx.components.internal.testdrive.TestDriveModel.State.ElementState.C +import com.bumble.appyx.components.internal.testdrive.TestDriveModel.State.ElementState.D +import com.bumble.appyx.components.internal.testdrive.operation.MoveTo +import com.bumble.appyx.interactions.ui.context.TransitionBounds +import com.bumble.appyx.interactions.ui.context.UiContext +import com.bumble.appyx.interactions.gesture.Drag.Direction8.DOWN +import com.bumble.appyx.interactions.gesture.Drag.Direction8.DOWNLEFT +import com.bumble.appyx.interactions.gesture.Drag.Direction8.DOWNRIGHT +import com.bumble.appyx.interactions.gesture.Drag.Direction8.LEFT +import com.bumble.appyx.interactions.gesture.Drag.Direction8.RIGHT +import com.bumble.appyx.interactions.gesture.Drag.Direction8.UP +import com.bumble.appyx.interactions.gesture.Drag.Direction8.UPLEFT +import com.bumble.appyx.interactions.gesture.Drag.Direction8.UPRIGHT +import com.bumble.appyx.interactions.gesture.Gesture +import com.bumble.appyx.interactions.gesture.GestureFactory +import com.bumble.appyx.interactions.gesture.dragDirection8 +import com.bumble.appyx.interactions.ui.DefaultAnimationSpec +import com.bumble.appyx.interactions.ui.property.impl.BackgroundColor +import com.bumble.appyx.interactions.ui.property.impl.RotationZ +import com.bumble.appyx.interactions.ui.property.impl.position.BiasAlignment.InsideAlignment.Companion.BottomEnd +import com.bumble.appyx.interactions.ui.property.impl.position.BiasAlignment.InsideAlignment.Companion.BottomStart +import com.bumble.appyx.interactions.ui.property.impl.position.BiasAlignment.InsideAlignment.Companion.TopEnd +import com.bumble.appyx.interactions.ui.property.impl.position.BiasAlignment.InsideAlignment.Companion.TopStart +import com.bumble.appyx.interactions.ui.property.impl.position.PositionAlignment +import com.bumble.appyx.interactions.ui.property.impl.position.PositionOffset +import com.bumble.appyx.interactions.ui.state.MatchedTargetUiState +import com.bumble.appyx.transitionmodel.BaseVisualisation +import com.bumble.appyx.utils.multiplatform.AppyxLogger + +class IncompleteDragVisualisation( + uiContext: UiContext, + uiAnimationSpec: SpringSpec = DefaultAnimationSpec +) : BaseVisualisation, TargetUiState, MutableUiState>( + uiContext = uiContext, + defaultAnimationSpec = uiAnimationSpec, +) { + override fun TestDriveModel.State.toUiTargets(): + List> = + listOf( + MatchedTargetUiState(element, elementState.toTargetUiState()).also { + AppyxLogger.d("TestDrive", "Matched $elementState -> UiState: ${it.targetUiState}") + } + ) + + companion object { + val bottomOffset = DpOffset(0.dp, (-50).dp) + + fun TestDriveModel.State.ElementState.toTargetUiState(): TargetUiState = + when (this) { + A -> topLeftCorner + B -> topRightCorner + C -> bottomRightCorner + D -> bottomLeftCorner + } + + private val topLeftCorner = TargetUiState( + positionAlignment = PositionAlignment.Target(TopStart), + positionOffset = PositionOffset.Target(DpOffset.Zero), + rotationZ = RotationZ.Target(0f), + backgroundColor = BackgroundColor.Target(color_primary) + ) + + private val topRightCorner = TargetUiState( + positionAlignment = PositionAlignment.Target(TopEnd), + positionOffset = PositionOffset.Target(DpOffset.Zero), + rotationZ = RotationZ.Target(180f), + backgroundColor = BackgroundColor.Target(color_dark) + ) + + private val bottomRightCorner = TargetUiState( + positionAlignment = PositionAlignment.Target(BottomEnd), + positionOffset = PositionOffset.Target(bottomOffset), + rotationZ = RotationZ.Target(270f), + backgroundColor = BackgroundColor.Target(color_secondary) + ) + + private val bottomLeftCorner = TargetUiState( + positionAlignment = PositionAlignment.Target(BottomStart), + positionOffset = PositionOffset.Target(bottomOffset), + rotationZ = RotationZ.Target(540f), + backgroundColor = BackgroundColor.Target(color_tertiary) + ) + } + + override fun mutableUiStateFor( + uiContext: UiContext, + targetUiState: TargetUiState + ): MutableUiState = + targetUiState.toMutableUiState(uiContext) + + + class Gestures( + private val transitionBounds: TransitionBounds, + ) : GestureFactory> { + + @Suppress("ComplexMethod") + override fun createGesture( + state: TestDriveModel.State, + delta: Offset, + density: Density + ): Gesture> { + // FIXME 60.dp is the assumed element size, connect it to real value + // TODO automate this whole calculation based on .onPlaced centers of targetUiStates + val maxX = with(density) { + (transitionBounds.widthDp - 60.dp).toPx() + } + val maxY = with(density) { + (transitionBounds.heightDp + bottomOffset.y - 60.dp).toPx() + } + + val direction = dragDirection8(delta) + return when (state.elementState) { + A -> when (direction) { + RIGHT -> Gesture(MoveTo(B), Offset(maxX, 0f)) + DOWNRIGHT -> Gesture(MoveTo(C), Offset(maxX, maxY)) + DOWN -> Gesture(MoveTo(D), Offset(0f, maxY)) + else -> Gesture.Noop() + } + + B -> when (direction) { + DOWN -> Gesture(MoveTo(C), Offset(0f, maxY)) + DOWNLEFT -> Gesture(MoveTo(D), Offset(-maxX, maxY)) + LEFT -> Gesture(MoveTo(A), Offset(-maxX, 0f)) + else -> Gesture.Noop() + } + + C -> when (direction) { + LEFT -> Gesture(MoveTo(D), Offset(-maxX, 0f)) + UPLEFT -> Gesture(MoveTo(A), Offset(-maxX, -maxY)) + UP -> Gesture(MoveTo(B), Offset(0f, -maxY)) + else -> Gesture.Noop() + } + + D -> when (direction) { + UP -> Gesture(MoveTo(A), Offset(0f, -maxY)) + UPRIGHT -> Gesture(MoveTo(B), Offset(maxX, -maxY)) + RIGHT -> Gesture(MoveTo(C), Offset(maxX, 0f)) + else -> Gesture.Noop() + } + } + } + } +} + diff --git a/demos/mkdocs/appyx-interactions/gestures/incompletedrag/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/incompletedrag/TargetUiState.kt b/demos/mkdocs/appyx-interactions/gestures/incompletedrag/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/incompletedrag/TargetUiState.kt new file mode 100644 index 000000000..421759948 --- /dev/null +++ b/demos/mkdocs/appyx-interactions/gestures/incompletedrag/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/incompletedrag/TargetUiState.kt @@ -0,0 +1,18 @@ +package com.bumble.appyx.demos.incompletedrag + +import com.bumble.appyx.interactions.ui.property.impl.BackgroundColor +import com.bumble.appyx.interactions.ui.property.impl.RotationZ +import com.bumble.appyx.interactions.ui.property.impl.RoundedCorners +import com.bumble.appyx.interactions.ui.property.impl.position.PositionAlignment +import com.bumble.appyx.interactions.ui.property.impl.position.PositionOffset +import com.bumble.appyx.interactions.ui.state.MutableUiStateSpecs + +@Suppress("unused") +@MutableUiStateSpecs +class TargetUiState( + val positionAlignment: PositionAlignment.Target, + val positionOffset: PositionOffset.Target, + val rotationZ: RotationZ.Target, + val roundedCorners: RoundedCorners.Target = RoundedCorners.Target(10), + val backgroundColor: BackgroundColor.Target, +) diff --git a/demos/mkdocs/appyx-interactions/gestures/incompletedrag/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/incompletedrag/main.js.kt b/demos/mkdocs/appyx-interactions/gestures/incompletedrag/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/incompletedrag/main.js.kt new file mode 100644 index 000000000..421193d13 --- /dev/null +++ b/demos/mkdocs/appyx-interactions/gestures/incompletedrag/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/incompletedrag/main.js.kt @@ -0,0 +1,57 @@ +package com.bumble.appyx.demos.incompletedrag + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.material.Surface +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.ExperimentalComposeUiApi +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.layout.onSizeChanged +import androidx.compose.ui.unit.IntSize +import androidx.compose.ui.unit.dp +import androidx.compose.ui.window.CanvasBasedWindow +import com.bumble.appyx.demos.appyxSample + +val color_bright = Color(0xFFFFFFFF) +val color_dark = Color(0xFF353535) +val color_primary = Color(0xFFFFC629) +val color_secondary = Color(0xFFFE9763) +val color_tertiary = Color(0xFF855353) +val color_neutral1 = Color(0xFFD2D7DF) +val color_neutral2 = Color(0xFF8A897C) +val color_neutral3 = Color(0xFFD9E8ED) +val color_neutral4 = Color(0xFFBEA489) + + +@OptIn(ExperimentalComposeUiApi::class) +fun main() { + appyxSample { + CanvasBasedWindow("Appyx") { + var size by remember { mutableStateOf(IntSize.Zero) } + Surface( + modifier = Modifier + .fillMaxSize() + .onSizeChanged { size = it } + ) { + if (size != IntSize.Zero) { + IncompleteDrag( + screenWidthPx = size.width, + screenHeightPx = size.height, + modifier = Modifier + .fillMaxSize() + .background(color_dark) + .padding( + horizontal = 16.dp, + vertical = 16.dp + ) + ) + } + } + } + } +} diff --git a/demos/mkdocs/appyx-interactions/gestures/incompletedrag/web/src/wasmJsMain/resources/index.html b/demos/mkdocs/appyx-interactions/gestures/incompletedrag/web/src/wasmJsMain/resources/index.html new file mode 100644 index 000000000..cb31659d7 --- /dev/null +++ b/demos/mkdocs/appyx-interactions/gestures/incompletedrag/web/src/wasmJsMain/resources/index.html @@ -0,0 +1,15 @@ + + + + + Appyx Interactions + + + + +
+ +
+ + + diff --git a/demos/mkdocs/appyx-interactions/gestures/incompletedrag/web/src/wasmJsMain/resources/styles.css b/demos/mkdocs/appyx-interactions/gestures/incompletedrag/web/src/wasmJsMain/resources/styles.css new file mode 100644 index 000000000..f8b13d234 --- /dev/null +++ b/demos/mkdocs/appyx-interactions/gestures/incompletedrag/web/src/wasmJsMain/resources/styles.css @@ -0,0 +1,19 @@ +#root { + width: 100%; + height: 100vh; +} + +body { + margin: 0; +} + +#root > .compose-web-column > div { + position: relative; +} + +@media (max-width: 511px) { + #ComposeTarget { + scale: 0.5; + transform-origin: 0 0; + } +} diff --git a/demos/mkdocs/appyx-interactions/interactions/observemp/web/build.gradle.kts b/demos/mkdocs/appyx-interactions/interactions/observemp/web/build.gradle.kts index b34f2c892..365bb4bc0 100644 --- a/demos/mkdocs/appyx-interactions/interactions/observemp/web/build.gradle.kts +++ b/demos/mkdocs/appyx-interactions/interactions/observemp/web/build.gradle.kts @@ -1,3 +1,5 @@ +import org.jetbrains.kotlin.gradle.targets.js.dsl.ExperimentalWasmDsl + plugins { id("com.bumble.appyx.multiplatform") id("org.jetbrains.compose") @@ -10,6 +12,12 @@ kotlin { browser() binaries.executable() } + @OptIn(ExperimentalWasmDsl::class) + wasmJs { + moduleName = "appyx-interactions-observemp-web" + browser() + binaries.executable() + } sourceSets { val commonMain by getting { dependencies { @@ -31,4 +39,5 @@ compose.experimental { dependencies { add("kspCommonMainMetadata", project(":ksp:appyx-processor")) add("kspJs", project(":ksp:appyx-processor")) + add("kspWasmJs", project(":ksp:appyx-processor")) } diff --git a/demos/mkdocs/appyx-interactions/interactions/observemp/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/observemp/ObserveMotionPropertiesSample.kt b/demos/mkdocs/appyx-interactions/interactions/observemp/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/observemp/ObserveMotionPropertiesSample.kt new file mode 100644 index 000000000..3223c42a8 --- /dev/null +++ b/demos/mkdocs/appyx-interactions/interactions/observemp/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/observemp/ObserveMotionPropertiesSample.kt @@ -0,0 +1,137 @@ +package com.bumble.appyx.demos.observemp + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.bumble.appyx.components.spotlight.Spotlight +import com.bumble.appyx.components.spotlight.SpotlightModel +import com.bumble.appyx.components.spotlight.operation.first +import com.bumble.appyx.components.spotlight.operation.last +import com.bumble.appyx.components.spotlight.operation.next +import com.bumble.appyx.components.spotlight.operation.previous +import com.bumble.appyx.components.spotlight.ui.slider.SpotlightSlider +import com.bumble.appyx.components.spotlight.ui.sliderrotation.SpotlightSliderRotation +import com.bumble.appyx.demos.common.AppyxWebSample +import com.bumble.appyx.demos.common.InteractionTarget +import com.bumble.appyx.demos.common.colors +import com.bumble.appyx.interactions.model.Element +import com.bumble.appyx.interactions.ui.property.impl.RotationY +import com.bumble.appyx.interactions.ui.property.impl.position.PositionAlignment +import com.bumble.appyx.interactions.ui.property.motionPropertyRenderValue +import kotlin.math.roundToInt + +@Composable +fun ObserveMotionPropertiesSample( + screenWidthPx: Int, + screenHeightPx: Int, + modifier: Modifier = Modifier, +) { + val coroutineScope = rememberCoroutineScope() + val model = remember { + SpotlightModel( + items = List(7) { InteractionTarget.Element(it) }, + initialActiveIndex = 1f, + savedStateMap = null + ) + } + val spotlight = remember { + Spotlight( + scope = coroutineScope, + model = model, + visualisation = { SpotlightSliderRotation(it, model.currentState) }, + gestureFactory = { SpotlightSlider.Gestures(it) } + ) + } + val actions = mapOf( + "First" to { spotlight.first() }, + "Prev" to { spotlight.previous() }, + "Next" to { spotlight.next() }, + "Last" to { spotlight.last() }, + + ) + AppyxWebSample( + screenWidthPx = screenWidthPx, + screenHeightPx = screenHeightPx, + appyxComponent = spotlight, + actions = actions, + modifier = modifier, + ) { + ModalUi(it, false) + } +} + +@Composable +fun ModalUi( + element: Element, + isChildMaxSize: Boolean, + modifier: Modifier = Modifier +) { + Box( + modifier = modifier + .fillMaxSize(if (isChildMaxSize) 1f else 0.9f) + .background( + color = when (val target = element.interactionTarget) { + is com.bumble.appyx.demos.common.InteractionTarget.Element -> colors.getOrElse( + target.idx % colors.size + ) { Color.Cyan } + + else -> { + Color.Cyan + } + }, + shape = RoundedCornerShape(if (isChildMaxSize) 0 else 8) + ) + ) { + Column( + modifier = Modifier.align(Alignment.Center), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(8.dp) + ) { + Text( + text = element.interactionTarget.toString(), + fontSize = 12.sp, + color = Color.White + ) + val alignment = + motionPropertyRenderValue() + if (alignment != null) { + val offsetPercentage = roundFloatToTwoDecimals( + alignment.outsideAlignment.horizontalBias * 100 + ) + + Text( + text = "Offset:\n$offsetPercentage%", + fontSize = 12.sp, + textAlign = TextAlign.Center, + color = Color.White + ) + } + val rotationY = motionPropertyRenderValue() + if (rotationY != null) { + Text( + text = "Rotation:\n${roundFloatToTwoDecimals(rotationY)}°", + fontSize = 12.sp, + textAlign = TextAlign.Center, + color = Color.White + ) + } + } + } +} + +private fun roundFloatToTwoDecimals(float: Float): Double { + return (float * 100.0).roundToInt() / 100.0 +} diff --git a/demos/mkdocs/appyx-interactions/interactions/observemp/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/observemp/main.js.kt b/demos/mkdocs/appyx-interactions/interactions/observemp/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/observemp/main.js.kt new file mode 100644 index 000000000..2028b19b8 --- /dev/null +++ b/demos/mkdocs/appyx-interactions/interactions/observemp/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/observemp/main.js.kt @@ -0,0 +1,47 @@ +package com.bumble.appyx.demos.observemp + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.material.Surface +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.ExperimentalComposeUiApi +import androidx.compose.ui.Modifier +import androidx.compose.ui.layout.onSizeChanged +import androidx.compose.ui.unit.IntSize +import androidx.compose.ui.unit.dp +import androidx.compose.ui.window.CanvasBasedWindow +import com.bumble.appyx.demos.common.color_dark +import org.jetbrains.skiko.wasm.onWasmReady + + +@OptIn(ExperimentalComposeUiApi::class) +fun main() { + onWasmReady { + CanvasBasedWindow("Appyx") { + var size by remember { mutableStateOf(IntSize.Zero) } + Surface( + modifier = Modifier + .fillMaxSize() + .onSizeChanged { size = it } + ) { + if (size != IntSize.Zero) { + ObserveMotionPropertiesSample( + screenWidthPx = size.width, + screenHeightPx = size.height, + modifier = Modifier + .fillMaxSize() + .background(color_dark) + .padding( + horizontal = 85.dp, + vertical = 16.dp + ) + ) + } + } + } + } +} diff --git a/demos/mkdocs/appyx-interactions/interactions/observemp/web/src/wasmJsMain/resources/index.html b/demos/mkdocs/appyx-interactions/interactions/observemp/web/src/wasmJsMain/resources/index.html new file mode 100644 index 000000000..5ca3e007b --- /dev/null +++ b/demos/mkdocs/appyx-interactions/interactions/observemp/web/src/wasmJsMain/resources/index.html @@ -0,0 +1,15 @@ + + + + + Appyx Interactions + + + + +
+ +
+ + + diff --git a/demos/mkdocs/appyx-interactions/interactions/observemp/web/src/wasmJsMain/resources/styles.css b/demos/mkdocs/appyx-interactions/interactions/observemp/web/src/wasmJsMain/resources/styles.css new file mode 100644 index 000000000..f8b13d234 --- /dev/null +++ b/demos/mkdocs/appyx-interactions/interactions/observemp/web/src/wasmJsMain/resources/styles.css @@ -0,0 +1,19 @@ +#root { + width: 100%; + height: 100vh; +} + +body { + margin: 0; +} + +#root > .compose-web-column > div { + position: relative; +} + +@media (max-width: 511px) { + #ComposeTarget { + scale: 0.5; + transform-origin: 0 0; + } +} diff --git a/demos/mkdocs/appyx-interactions/interactions/sample1/web/build.gradle.kts b/demos/mkdocs/appyx-interactions/interactions/sample1/web/build.gradle.kts index ee1fbb5ef..08d56896f 100644 --- a/demos/mkdocs/appyx-interactions/interactions/sample1/web/build.gradle.kts +++ b/demos/mkdocs/appyx-interactions/interactions/sample1/web/build.gradle.kts @@ -1,3 +1,5 @@ +import org.jetbrains.kotlin.gradle.targets.js.dsl.ExperimentalWasmDsl + plugins { id("com.bumble.appyx.multiplatform") id("org.jetbrains.compose") @@ -10,6 +12,12 @@ kotlin { browser() binaries.executable() } + @OptIn(ExperimentalWasmDsl::class) + wasmJs { + moduleName = "appyx-interactions-sample1-web" + browser() + binaries.executable() + } sourceSets { val commonMain by getting { dependencies { @@ -36,4 +44,5 @@ compose.experimental { dependencies { add("kspCommonMainMetadata", project(":ksp:appyx-processor")) add("kspJs", project(":ksp:appyx-processor")) + add("kspWasmJs", project(":ksp:appyx-processor")) } diff --git a/demos/mkdocs/appyx-interactions/interactions/sample1/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/sample1/Sample1.kt b/demos/mkdocs/appyx-interactions/interactions/sample1/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/sample1/Sample1.kt new file mode 100644 index 000000000..b6db13809 --- /dev/null +++ b/demos/mkdocs/appyx-interactions/interactions/sample1/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/sample1/Sample1.kt @@ -0,0 +1,189 @@ +@file:Suppress("MatchingDeclarationName") +package com.bumble.appyx.demos.sample1 + +import androidx.compose.animation.animateColorAsState +import androidx.compose.animation.core.Spring +import androidx.compose.animation.core.spring +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.Alignment +import androidx.compose.ui.Alignment.Companion.CenterHorizontally +import androidx.compose.ui.Modifier +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.LinearGradientShader +import androidx.compose.ui.graphics.ShaderBrush +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.compose.ui.zIndex +import com.bumble.appyx.components.internal.testdrive.TestDrive +import com.bumble.appyx.components.internal.testdrive.TestDriveModel +import com.bumble.appyx.components.internal.testdrive.TestDriveModel.State.ElementState.A +import com.bumble.appyx.components.internal.testdrive.TestDriveModel.State.ElementState.B +import com.bumble.appyx.components.internal.testdrive.TestDriveModel.State.ElementState.C +import com.bumble.appyx.components.internal.testdrive.TestDriveModel.State.ElementState.D +import com.bumble.appyx.components.internal.testdrive.operation.next +import com.bumble.appyx.demos.sample1.InteractionTarget.Child1 +import com.bumble.appyx.interactions.composable.AppyxInteractionsContainer +import com.bumble.appyx.interactions.gesture.GestureReferencePoint +import com.bumble.appyx.interactions.model.transition.Operation.Mode.IMMEDIATE +import com.bumble.appyx.interactions.ui.helper.AppyxComponentSetup + +enum class InteractionTarget { + Child1 +} + +@Composable +fun Sample1( + screenWidthPx: Int, + screenHeightPx: Int, + modifier: Modifier = Modifier, +) { + val coroutineScope = rememberCoroutineScope() + val model = remember { TestDriveModel(Child1, null) } + val testDrive = remember { + TestDrive( + scope = coroutineScope, + model = model, + visualisation = { Sample1Visualisation(it) }, + gestureFactory = { Sample1Visualisation.Gestures(it) } + ) + } + + AppyxComponentSetup(testDrive) + + Box( + modifier = modifier, + ) { + Background( + screenWidthPx = screenWidthPx, + screenHeightPx = screenHeightPx, + model = model + ) + Box( + modifier = Modifier.padding(24.dp, 24.dp) + ) { + ModelUi( + screenWidthPx = screenWidthPx, + screenHeightPx = screenHeightPx, + testDrive = testDrive, + model = model + ) + Controls( + testDrive = testDrive + ) + } + } +} + +@Composable +fun Background( + screenWidthPx: Int, + screenHeightPx: Int, + model: TestDriveModel, + modifier: Modifier = Modifier.fillMaxSize() +) { + val output = model.output.collectAsState() + val currentTarget = output.value.currentTargetState.elementState + val backgroundColor1 = animateColorAsState( + when (currentTarget) { + A -> color_neutral1 + B -> color_neutral2 + C -> color_neutral3 + D -> color_neutral4 + } + ) + + Box( + modifier + .zIndex(0f) + .background( + ShaderBrush( + LinearGradientShader( + from = Offset.Zero, + to = Offset(screenWidthPx.toFloat(), screenHeightPx.toFloat()), + colors = listOf(color_bright, backgroundColor1.value) + ) + ) + ) + ) +} + +@Composable +fun ModelUi( + screenWidthPx: Int, + screenHeightPx: Int, + testDrive: TestDrive, + model: TestDriveModel, + modifier: Modifier = Modifier.fillMaxSize() +) { + val output = model.output.collectAsState() + val currentTarget = output.value.currentTargetState.elementState + val backgroundColor1 = animateColorAsState( + when (currentTarget) { + A -> color_neutral1 + B -> color_neutral2 + C -> color_neutral3 + D -> color_neutral4 + } + ) + + AppyxInteractionsContainer( + appyxComponent = testDrive, + screenWidthPx = screenWidthPx, + screenHeightPx = screenHeightPx, + gestureRelativeTo = GestureReferencePoint.Element + ) { + Box( + modifier = Modifier + .size(60.dp) + ) { + Text( + modifier = Modifier.align(Alignment.Center), + text = model.output.collectAsState().value.currentTargetState.elementState.name, + fontSize = 24.sp, + color = Color.White + ) + } + } +} + +@Composable +private fun Controls( + testDrive: TestDrive, + modifier: Modifier = Modifier.fillMaxSize() +) { + Column( + modifier = modifier.zIndex(1f), + verticalArrangement = Arrangement.Bottom, + horizontalAlignment = CenterHorizontally + ) { + Box( + modifier = Modifier + .background(color_primary, shape = RoundedCornerShape(4.dp)) + .clickable { + testDrive.next( + mode = IMMEDIATE, animationSpec = spring( + stiffness = Spring.StiffnessMedium, + dampingRatio = Spring.DampingRatioLowBouncy, + ) + ) + } + .padding(horizontal = 18.dp, vertical = 9.dp) + ) { + Text("Next") + } + } +} diff --git a/demos/mkdocs/appyx-interactions/interactions/sample1/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/sample1/Sample1Visualisation.kt b/demos/mkdocs/appyx-interactions/interactions/sample1/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/sample1/Sample1Visualisation.kt new file mode 100644 index 000000000..8fd3e1764 --- /dev/null +++ b/demos/mkdocs/appyx-interactions/interactions/sample1/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/sample1/Sample1Visualisation.kt @@ -0,0 +1,148 @@ +package com.bumble.appyx.demos.sample1 + +import androidx.compose.animation.core.SpringSpec +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.unit.Density +import androidx.compose.ui.unit.DpOffset +import androidx.compose.ui.unit.dp +import com.bumble.appyx.components.internal.testdrive.TestDriveModel +import com.bumble.appyx.components.internal.testdrive.TestDriveModel.State.ElementState.A +import com.bumble.appyx.components.internal.testdrive.TestDriveModel.State.ElementState.B +import com.bumble.appyx.components.internal.testdrive.TestDriveModel.State.ElementState.C +import com.bumble.appyx.components.internal.testdrive.TestDriveModel.State.ElementState.D +import com.bumble.appyx.components.internal.testdrive.operation.MoveTo +import com.bumble.appyx.interactions.ui.context.TransitionBounds +import com.bumble.appyx.interactions.ui.context.UiContext +import com.bumble.appyx.interactions.gesture.Drag.Direction8.DOWN +import com.bumble.appyx.interactions.gesture.Drag.Direction8.DOWNLEFT +import com.bumble.appyx.interactions.gesture.Drag.Direction8.DOWNRIGHT +import com.bumble.appyx.interactions.gesture.Drag.Direction8.LEFT +import com.bumble.appyx.interactions.gesture.Drag.Direction8.RIGHT +import com.bumble.appyx.interactions.gesture.Drag.Direction8.UP +import com.bumble.appyx.interactions.gesture.Drag.Direction8.UPLEFT +import com.bumble.appyx.interactions.gesture.Drag.Direction8.UPRIGHT +import com.bumble.appyx.interactions.gesture.Gesture +import com.bumble.appyx.interactions.gesture.GestureFactory +import com.bumble.appyx.interactions.gesture.dragDirection8 +import com.bumble.appyx.interactions.ui.DefaultAnimationSpec +import com.bumble.appyx.interactions.ui.property.impl.BackgroundColor +import com.bumble.appyx.interactions.ui.property.impl.position.BiasAlignment.InsideAlignment +import com.bumble.appyx.interactions.ui.property.impl.position.BiasAlignment.InsideAlignment.Companion.BottomEnd +import com.bumble.appyx.interactions.ui.property.impl.position.BiasAlignment.InsideAlignment.Companion.TopEnd +import com.bumble.appyx.interactions.ui.property.impl.position.BiasAlignment.InsideAlignment.Companion.TopStart +import com.bumble.appyx.interactions.ui.property.impl.position.PositionAlignment +import com.bumble.appyx.interactions.ui.property.impl.position.PositionOffset +import com.bumble.appyx.interactions.ui.state.MatchedTargetUiState +import com.bumble.appyx.transitionmodel.BaseVisualisation +import com.bumble.appyx.utils.multiplatform.AppyxLogger + +class Sample1Visualisation( + uiContext: UiContext, + uiAnimationSpec: SpringSpec = DefaultAnimationSpec +) : BaseVisualisation, TargetUiState, MutableUiState>( + uiContext = uiContext, + defaultAnimationSpec = uiAnimationSpec, +) { + override fun TestDriveModel.State.toUiTargets(): + List> = + listOf( + MatchedTargetUiState(element, elementState.toTargetUiState()).also { + AppyxLogger.d("TestDrive", "Matched $elementState -> UiState: ${it.targetUiState}") + } + ) + + companion object { + val bottomOffset = DpOffset(0.dp, (-50).dp) + + fun TestDriveModel.State.ElementState.toTargetUiState(): TargetUiState = + when (this) { + A -> topLeftCorner + B -> topRightCorner + C -> bottomRightCorner + D -> bottomLeftCorner + } + + private val topLeftCorner = TargetUiState( + positionAlignment = PositionAlignment.Target(TopStart), + positionOffset = PositionOffset.Target(DpOffset.Zero), + backgroundColor = BackgroundColor.Target(color_primary) + ) + + private val topRightCorner = TargetUiState( + positionAlignment = PositionAlignment.Target(TopEnd), + positionOffset = PositionOffset.Target(DpOffset.Zero), + backgroundColor = BackgroundColor.Target(color_dark) + ) + + private val bottomRightCorner = TargetUiState( + positionAlignment = PositionAlignment.Target(BottomEnd), + positionOffset = PositionOffset.Target(bottomOffset), + backgroundColor = BackgroundColor.Target(color_secondary) + ) + + private val bottomLeftCorner = TargetUiState( + positionAlignment = PositionAlignment.Target(InsideAlignment.BottomStart), + positionOffset = PositionOffset.Target(bottomOffset), + backgroundColor = BackgroundColor.Target(color_tertiary) + ) + } + + override fun mutableUiStateFor( + uiContext: UiContext, + targetUiState: TargetUiState + ): MutableUiState = + targetUiState.toMutableUiState(uiContext) + + class Gestures( + private val transitionBounds: TransitionBounds, + ) : GestureFactory> { + + @Suppress("ComplexMethod") + override fun createGesture( + state: TestDriveModel.State, + delta: Offset, + density: Density + ): Gesture> { + // FIXME quick fix – 60.dp is the assumed element size, connect it to real value + // TODO properly – automate this whole calculation based on .onPlaced centers of targetUiStates + val maxX = with(density) { + (transitionBounds.widthDp - 60.dp).toPx() + } + val maxY = with(density) { + (transitionBounds.heightDp + bottomOffset.y - 60.dp).toPx() + } + + val direction = dragDirection8(delta) + return when (state.elementState) { + A -> when (direction) { + RIGHT -> Gesture(MoveTo(B), Offset(maxX, 0f)) + DOWNRIGHT -> Gesture(MoveTo(C), Offset(maxX, maxY)) + DOWN -> Gesture(MoveTo(D), Offset(0f, maxY)) + else -> Gesture.Noop() + } + + B -> when (direction) { + DOWN -> Gesture(MoveTo(C), Offset(0f, maxY)) + DOWNLEFT -> Gesture(MoveTo(D), Offset(-maxX, maxY)) + LEFT -> Gesture(MoveTo(A), Offset(-maxX, 0f)) + else -> Gesture.Noop() + } + + C -> when (direction) { + LEFT -> Gesture(MoveTo(D), Offset(-maxX, 0f)) + UPLEFT -> Gesture(MoveTo(A), Offset(-maxX, -maxY)) + UP -> Gesture(MoveTo(B), Offset(0f, -maxY)) + else -> Gesture.Noop() + } + + D -> when (direction) { + UP -> Gesture(MoveTo(A), Offset(0f, -maxY)) + UPRIGHT -> Gesture(MoveTo(B), Offset(maxX, -maxY)) + RIGHT -> Gesture(MoveTo(C), Offset(maxX, 0f)) + else -> Gesture.Noop() + } + } + } + } +} + diff --git a/demos/mkdocs/appyx-interactions/interactions/sample1/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/sample1/TargetUiState.kt b/demos/mkdocs/appyx-interactions/interactions/sample1/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/sample1/TargetUiState.kt new file mode 100644 index 000000000..4312ea1b6 --- /dev/null +++ b/demos/mkdocs/appyx-interactions/interactions/sample1/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/sample1/TargetUiState.kt @@ -0,0 +1,16 @@ +package com.bumble.appyx.demos.sample1 + +import com.bumble.appyx.interactions.ui.property.impl.BackgroundColor +import com.bumble.appyx.interactions.ui.property.impl.RoundedCorners +import com.bumble.appyx.interactions.ui.property.impl.position.PositionAlignment +import com.bumble.appyx.interactions.ui.property.impl.position.PositionOffset +import com.bumble.appyx.interactions.ui.state.MutableUiStateSpecs + +@Suppress("unused") +@MutableUiStateSpecs +class TargetUiState( + val positionAlignment: PositionAlignment.Target, + val positionOffset: PositionOffset.Target, + val roundedCorners: RoundedCorners.Target = RoundedCorners.Target(10), + val backgroundColor: BackgroundColor.Target, +) diff --git a/demos/mkdocs/appyx-interactions/interactions/sample1/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/sample1/main.js.kt b/demos/mkdocs/appyx-interactions/interactions/sample1/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/sample1/main.js.kt new file mode 100644 index 000000000..ecd275657 --- /dev/null +++ b/demos/mkdocs/appyx-interactions/interactions/sample1/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/sample1/main.js.kt @@ -0,0 +1,57 @@ +package com.bumble.appyx.demos.sample1 + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.material.Surface +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.ExperimentalComposeUiApi +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.layout.onSizeChanged +import androidx.compose.ui.unit.IntSize +import androidx.compose.ui.unit.dp +import androidx.compose.ui.window.CanvasBasedWindow +import com.bumble.appyx.demos.appyxSample + +val color_bright = Color(0xFFFFFFFF) +val color_dark = Color(0xFF353535) +val color_primary = Color(0xFFFFC629) +val color_secondary = Color(0xFFFE9763) +val color_tertiary = Color(0xFF855353) +val color_neutral1 = Color(0xFFD2D7DF) +val color_neutral2 = Color(0xFF8A897C) +val color_neutral3 = Color(0xFFD9E8ED) +val color_neutral4 = Color(0xFFBEA489) + + +@OptIn(ExperimentalComposeUiApi::class) +fun main() { + appyxSample { + CanvasBasedWindow("Appyx") { + var size by remember { mutableStateOf(IntSize.Zero) } + Surface( + modifier = Modifier + .fillMaxSize() + .onSizeChanged { size = it } + ) { + if (size != IntSize.Zero) { + Sample1( + screenWidthPx = size.width, + screenHeightPx = size.height, + modifier = Modifier + .fillMaxSize() + .background(color_dark) + .padding( + horizontal = 16.dp, + vertical = 16.dp + ) + ) + } + } + } + } +} diff --git a/demos/mkdocs/appyx-interactions/interactions/sample1/web/src/wasmJsMain/resources/index.html b/demos/mkdocs/appyx-interactions/interactions/sample1/web/src/wasmJsMain/resources/index.html new file mode 100644 index 000000000..5ca3e007b --- /dev/null +++ b/demos/mkdocs/appyx-interactions/interactions/sample1/web/src/wasmJsMain/resources/index.html @@ -0,0 +1,15 @@ + + + + + Appyx Interactions + + + + +
+ +
+ + + diff --git a/demos/mkdocs/appyx-interactions/interactions/sample1/web/src/wasmJsMain/resources/styles.css b/demos/mkdocs/appyx-interactions/interactions/sample1/web/src/wasmJsMain/resources/styles.css new file mode 100644 index 000000000..f8b13d234 --- /dev/null +++ b/demos/mkdocs/appyx-interactions/interactions/sample1/web/src/wasmJsMain/resources/styles.css @@ -0,0 +1,19 @@ +#root { + width: 100%; + height: 100vh; +} + +body { + margin: 0; +} + +#root > .compose-web-column > div { + position: relative; +} + +@media (max-width: 511px) { + #ComposeTarget { + scale: 0.5; + transform-origin: 0 0; + } +} diff --git a/demos/mkdocs/appyx-interactions/interactions/sample2/web/build.gradle.kts b/demos/mkdocs/appyx-interactions/interactions/sample2/web/build.gradle.kts index 755a322db..114aa1d4c 100644 --- a/demos/mkdocs/appyx-interactions/interactions/sample2/web/build.gradle.kts +++ b/demos/mkdocs/appyx-interactions/interactions/sample2/web/build.gradle.kts @@ -1,3 +1,5 @@ +import org.jetbrains.kotlin.gradle.targets.js.dsl.ExperimentalWasmDsl + plugins { id("com.bumble.appyx.multiplatform") id("org.jetbrains.compose") @@ -10,6 +12,12 @@ kotlin { browser() binaries.executable() } + @OptIn(ExperimentalWasmDsl::class) + wasmJs { + moduleName = "appyx-interactions-sample2-web" + browser() + binaries.executable() + } sourceSets { val commonMain by getting { dependencies { @@ -36,4 +44,5 @@ compose.experimental { dependencies { add("kspCommonMainMetadata", project(":ksp:appyx-processor")) add("kspJs", project(":ksp:appyx-processor")) + add("kspWasmJs", project(":ksp:appyx-processor")) } diff --git a/demos/mkdocs/appyx-interactions/interactions/sample2/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/sample2/Sample2.kt b/demos/mkdocs/appyx-interactions/interactions/sample2/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/sample2/Sample2.kt new file mode 100644 index 000000000..be07cd535 --- /dev/null +++ b/demos/mkdocs/appyx-interactions/interactions/sample2/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/sample2/Sample2.kt @@ -0,0 +1,190 @@ +@file:Suppress("MatchingDeclarationName") + +package com.bumble.appyx.demos.sample2 + +import androidx.compose.animation.animateColorAsState +import androidx.compose.animation.core.Spring +import androidx.compose.animation.core.spring +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.Alignment +import androidx.compose.ui.Alignment.Companion.CenterHorizontally +import androidx.compose.ui.Modifier +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.LinearGradientShader +import androidx.compose.ui.graphics.ShaderBrush +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.compose.ui.zIndex +import com.bumble.appyx.components.internal.testdrive.TestDrive +import com.bumble.appyx.components.internal.testdrive.TestDriveModel +import com.bumble.appyx.components.internal.testdrive.TestDriveModel.State.ElementState.A +import com.bumble.appyx.components.internal.testdrive.TestDriveModel.State.ElementState.B +import com.bumble.appyx.components.internal.testdrive.TestDriveModel.State.ElementState.C +import com.bumble.appyx.components.internal.testdrive.TestDriveModel.State.ElementState.D +import com.bumble.appyx.components.internal.testdrive.operation.next +import com.bumble.appyx.demos.sample2.InteractionTarget.Child1 +import com.bumble.appyx.interactions.composable.AppyxInteractionsContainer +import com.bumble.appyx.interactions.gesture.GestureReferencePoint +import com.bumble.appyx.interactions.model.transition.Operation.Mode.IMMEDIATE +import com.bumble.appyx.interactions.ui.helper.AppyxComponentSetup + +enum class InteractionTarget { + Child1 +} + +@Composable +fun Sample2( + screenWidthPx: Int, + screenHeightPx: Int, + modifier: Modifier = Modifier, +) { + val coroutineScope = rememberCoroutineScope() + val model = remember { TestDriveModel(Child1, null) } + val testDrive = remember { + TestDrive( + scope = coroutineScope, + model = model, + visualisation = { Sample2Visualisation(it) }, + gestureFactory = { Sample2Visualisation.Gestures(it) } + ) + } + + AppyxComponentSetup(testDrive) + + Box( + modifier = modifier, + ) { + Background( + screenWidthPx = screenWidthPx, + screenHeightPx = screenHeightPx, + model = model + ) + Box( + modifier = Modifier.padding(24.dp, 24.dp) + ) { + ModelUi( + screenWidthPx = screenWidthPx, + screenHeightPx = screenHeightPx, + testDrive = testDrive, + model = model + ) + Controls( + testDrive = testDrive + ) + } + } +} + +@Composable +fun Background( + screenWidthPx: Int, + screenHeightPx: Int, + model: TestDriveModel, + modifier: Modifier = Modifier.fillMaxSize() +) { + val output = model.output.collectAsState() + val currentTarget = output.value.currentTargetState.elementState + val backgroundColor1 = animateColorAsState( + when (currentTarget) { + A -> color_neutral1 + B -> color_neutral2 + C -> color_neutral3 + D -> color_neutral4 + } + ) + + Box( + modifier + .zIndex(0f) + .background( + ShaderBrush( + LinearGradientShader( + from = Offset.Zero, + to = Offset(screenWidthPx.toFloat(), screenHeightPx.toFloat()), + colors = listOf(color_bright, backgroundColor1.value) + ) + ) + ) + ) +} + +@Composable +fun ModelUi( + screenWidthPx: Int, + screenHeightPx: Int, + testDrive: TestDrive, + model: TestDriveModel, + modifier: Modifier = Modifier.fillMaxSize() +) { + val output = model.output.collectAsState() + val currentTarget = output.value.currentTargetState.elementState + val backgroundColor1 = animateColorAsState( + when (currentTarget) { + A -> color_neutral1 + B -> color_neutral2 + C -> color_neutral3 + D -> color_neutral4 + } + ) + + AppyxInteractionsContainer( + appyxComponent = testDrive, + screenWidthPx = screenWidthPx, + screenHeightPx = screenHeightPx, + gestureRelativeTo = GestureReferencePoint.Element + ) { + Box( + modifier = Modifier + .size(60.dp) + ) { + Text( + modifier = Modifier.align(Alignment.Center), + text = model.output.collectAsState().value.currentTargetState.elementState.name, + fontSize = 24.sp, + color = Color.White + ) + } + } +} + +@Composable +private fun Controls( + testDrive: TestDrive, + modifier: Modifier = Modifier.fillMaxSize() +) { + Column( + modifier = modifier.zIndex(1f), + verticalArrangement = Arrangement.Bottom, + horizontalAlignment = CenterHorizontally + ) { + Box( + modifier = Modifier + .background(color_primary, shape = RoundedCornerShape(4.dp)) + .clickable { + testDrive.next( + mode = IMMEDIATE, animationSpec = spring( + stiffness = Spring.StiffnessLow, + dampingRatio = Spring.DampingRatioLowBouncy, + ) + ) + } + .padding(horizontal = 18.dp, vertical = 9.dp) + ) { + Text("Next") + } + } +} diff --git a/demos/mkdocs/appyx-interactions/interactions/sample2/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/sample2/Sample2Visualisation.kt b/demos/mkdocs/appyx-interactions/interactions/sample2/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/sample2/Sample2Visualisation.kt new file mode 100644 index 000000000..2d7474fb3 --- /dev/null +++ b/demos/mkdocs/appyx-interactions/interactions/sample2/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/sample2/Sample2Visualisation.kt @@ -0,0 +1,154 @@ +package com.bumble.appyx.demos.sample2 + +import androidx.compose.animation.core.SpringSpec +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.unit.Density +import androidx.compose.ui.unit.DpOffset +import androidx.compose.ui.unit.dp +import com.bumble.appyx.components.internal.testdrive.TestDriveModel +import com.bumble.appyx.components.internal.testdrive.TestDriveModel.State.ElementState.A +import com.bumble.appyx.components.internal.testdrive.TestDriveModel.State.ElementState.B +import com.bumble.appyx.components.internal.testdrive.TestDriveModel.State.ElementState.C +import com.bumble.appyx.components.internal.testdrive.TestDriveModel.State.ElementState.D +import com.bumble.appyx.components.internal.testdrive.operation.MoveTo +import com.bumble.appyx.interactions.ui.context.TransitionBounds +import com.bumble.appyx.interactions.ui.context.UiContext +import com.bumble.appyx.interactions.gesture.Drag.Direction8.DOWN +import com.bumble.appyx.interactions.gesture.Drag.Direction8.DOWNLEFT +import com.bumble.appyx.interactions.gesture.Drag.Direction8.DOWNRIGHT +import com.bumble.appyx.interactions.gesture.Drag.Direction8.LEFT +import com.bumble.appyx.interactions.gesture.Drag.Direction8.RIGHT +import com.bumble.appyx.interactions.gesture.Drag.Direction8.UP +import com.bumble.appyx.interactions.gesture.Drag.Direction8.UPLEFT +import com.bumble.appyx.interactions.gesture.Drag.Direction8.UPRIGHT +import com.bumble.appyx.interactions.gesture.Gesture +import com.bumble.appyx.interactions.gesture.GestureFactory +import com.bumble.appyx.interactions.gesture.dragDirection8 +import com.bumble.appyx.interactions.ui.DefaultAnimationSpec +import com.bumble.appyx.interactions.ui.property.impl.BackgroundColor +import com.bumble.appyx.interactions.ui.property.impl.RotationZ +import com.bumble.appyx.interactions.ui.property.impl.position.BiasAlignment.InsideAlignment +import com.bumble.appyx.interactions.ui.property.impl.position.BiasAlignment.InsideAlignment.Companion.BottomEnd +import com.bumble.appyx.interactions.ui.property.impl.position.BiasAlignment.InsideAlignment.Companion.TopEnd +import com.bumble.appyx.interactions.ui.property.impl.position.BiasAlignment.InsideAlignment.Companion.TopStart +import com.bumble.appyx.interactions.ui.property.impl.position.PositionAlignment +import com.bumble.appyx.interactions.ui.property.impl.position.PositionOffset +import com.bumble.appyx.interactions.ui.state.MatchedTargetUiState +import com.bumble.appyx.transitionmodel.BaseVisualisation +import com.bumble.appyx.utils.multiplatform.AppyxLogger + +class Sample2Visualisation( + uiContext: UiContext, + uiAnimationSpec: SpringSpec = DefaultAnimationSpec +) : BaseVisualisation, TargetUiState, MutableUiState>( + uiContext = uiContext, + defaultAnimationSpec = uiAnimationSpec, +) { + override fun TestDriveModel.State.toUiTargets(): + List> = + listOf( + MatchedTargetUiState(element, elementState.toTargetUiState()).also { + AppyxLogger.d("TestDrive", "Matched $elementState -> UiState: ${it.targetUiState}") + } + ) + + companion object { + val bottomOffset = DpOffset(0.dp, (-50).dp) + + fun TestDriveModel.State.ElementState.toTargetUiState(): TargetUiState = + when (this) { + A -> topLeftCorner + B -> topRightCorner + C -> bottomRightCorner + D -> bottomLeftCorner + } + + private val topLeftCorner = TargetUiState( + positionAlignment = PositionAlignment.Target(TopStart), + positionOffset = PositionOffset.Target(DpOffset.Zero), + rotationZ = RotationZ.Target(0f), + backgroundColor = BackgroundColor.Target(color_primary) + ) + + private val topRightCorner = TargetUiState( + positionAlignment = PositionAlignment.Target(TopEnd), + positionOffset = PositionOffset.Target(DpOffset.Zero), + rotationZ = RotationZ.Target(180f), + backgroundColor = BackgroundColor.Target(color_dark) + ) + + private val bottomRightCorner = TargetUiState( + positionAlignment = PositionAlignment.Target(BottomEnd), + positionOffset = PositionOffset.Target(bottomOffset), + rotationZ = RotationZ.Target(270f), + backgroundColor = BackgroundColor.Target(color_secondary) + ) + + private val bottomLeftCorner = TargetUiState( + positionAlignment = PositionAlignment.Target(InsideAlignment.BottomStart), + positionOffset = PositionOffset.Target(bottomOffset), + rotationZ = RotationZ.Target(540f), + backgroundColor = BackgroundColor.Target(color_tertiary) + ) + } + + override fun mutableUiStateFor( + uiContext: UiContext, + targetUiState: TargetUiState + ): MutableUiState = + targetUiState.toMutableUiState(uiContext) + + + class Gestures( + private val transitionBounds: TransitionBounds, + ) : GestureFactory> { + + @Suppress("ComplexMethod") + override fun createGesture( + state: TestDriveModel.State, + delta: Offset, + density: Density + ): Gesture> { + // FIXME 60.dp is the assumed element size, connect it to real value + // TODO automate this whole calculation based on .onPlaced centers of targetUiStates + val maxX = with(density) { + (transitionBounds.widthDp - 60.dp).toPx() + } + val maxY = with(density) { + (transitionBounds.heightDp + bottomOffset.y - 60.dp).toPx() + } + + val direction = dragDirection8(delta) + return when (state.elementState) { + A -> when (direction) { + RIGHT -> Gesture(MoveTo(B), Offset(maxX, 0f)) + DOWNRIGHT -> Gesture(MoveTo(C), Offset(maxX, maxY)) + DOWN -> Gesture(MoveTo(D), Offset(0f, maxY)) + else -> Gesture.Noop() + } + + B -> when (direction) { + DOWN -> Gesture(MoveTo(C), Offset(0f, maxY)) + DOWNLEFT -> Gesture(MoveTo(D), Offset(-maxX, maxY)) + LEFT -> Gesture(MoveTo(A), Offset(-maxX, 0f)) + else -> Gesture.Noop() + } + + C -> when (direction) { + LEFT -> Gesture(MoveTo(D), Offset(-maxX, 0f)) + UPLEFT -> Gesture(MoveTo(A), Offset(-maxX, -maxY)) + UP -> Gesture(MoveTo(B), Offset(0f, -maxY)) + else -> Gesture.Noop() + } + + D -> when (direction) { + UP -> Gesture(MoveTo(A), Offset(0f, -maxY)) + UPRIGHT -> Gesture(MoveTo(B), Offset(maxX, -maxY)) + RIGHT -> Gesture(MoveTo(C), Offset(maxX, 0f)) + else -> Gesture.Noop() + } + } + } + } +} + diff --git a/demos/mkdocs/appyx-interactions/interactions/sample2/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/sample2/TargetUiState.kt b/demos/mkdocs/appyx-interactions/interactions/sample2/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/sample2/TargetUiState.kt new file mode 100644 index 000000000..bd86cc384 --- /dev/null +++ b/demos/mkdocs/appyx-interactions/interactions/sample2/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/sample2/TargetUiState.kt @@ -0,0 +1,18 @@ +package com.bumble.appyx.demos.sample2 + +import com.bumble.appyx.interactions.ui.property.impl.BackgroundColor +import com.bumble.appyx.interactions.ui.property.impl.RotationZ +import com.bumble.appyx.interactions.ui.property.impl.RoundedCorners +import com.bumble.appyx.interactions.ui.property.impl.position.PositionAlignment +import com.bumble.appyx.interactions.ui.property.impl.position.PositionOffset +import com.bumble.appyx.interactions.ui.state.MutableUiStateSpecs + +@Suppress("unused") +@MutableUiStateSpecs +class TargetUiState( + val positionAlignment: PositionAlignment.Target, + val positionOffset: PositionOffset.Target, + val rotationZ: RotationZ.Target, + val roundedCorners: RoundedCorners.Target = RoundedCorners.Target(10), + val backgroundColor: BackgroundColor.Target, +) diff --git a/demos/mkdocs/appyx-interactions/interactions/sample2/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/sample2/main.js.kt b/demos/mkdocs/appyx-interactions/interactions/sample2/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/sample2/main.js.kt new file mode 100644 index 000000000..5f074c3c2 --- /dev/null +++ b/demos/mkdocs/appyx-interactions/interactions/sample2/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/sample2/main.js.kt @@ -0,0 +1,57 @@ +package com.bumble.appyx.demos.sample2 + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.material.Surface +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.ExperimentalComposeUiApi +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.layout.onSizeChanged +import androidx.compose.ui.unit.IntSize +import androidx.compose.ui.unit.dp +import androidx.compose.ui.window.CanvasBasedWindow +import com.bumble.appyx.demos.appyxSample + +val color_bright = Color(0xFFFFFFFF) +val color_dark = Color(0xFF353535) +val color_primary = Color(0xFFFFC629) +val color_secondary = Color(0xFFFE9763) +val color_tertiary = Color(0xFF855353) +val color_neutral1 = Color(0xFFD2D7DF) +val color_neutral2 = Color(0xFF8A897C) +val color_neutral3 = Color(0xFFD9E8ED) +val color_neutral4 = Color(0xFFBEA489) + + +@OptIn(ExperimentalComposeUiApi::class) +fun main() { + appyxSample { + CanvasBasedWindow("Appyx") { + var size by remember { mutableStateOf(IntSize.Zero) } + Surface( + modifier = Modifier + .fillMaxSize() + .onSizeChanged { size = it } + ) { + if (size != IntSize.Zero) { + Sample2( + screenWidthPx = size.width, + screenHeightPx = size.height, + modifier = Modifier + .fillMaxSize() + .background(color_dark) + .padding( + horizontal = 16.dp, + vertical = 16.dp + ) + ) + } + } + } + } +} diff --git a/demos/mkdocs/appyx-interactions/interactions/sample2/web/src/wasmJsMain/resources/index.html b/demos/mkdocs/appyx-interactions/interactions/sample2/web/src/wasmJsMain/resources/index.html new file mode 100644 index 000000000..5ca3e007b --- /dev/null +++ b/demos/mkdocs/appyx-interactions/interactions/sample2/web/src/wasmJsMain/resources/index.html @@ -0,0 +1,15 @@ + + + + + Appyx Interactions + + + + +
+ +
+ + + diff --git a/demos/mkdocs/appyx-interactions/interactions/sample2/web/src/wasmJsMain/resources/styles.css b/demos/mkdocs/appyx-interactions/interactions/sample2/web/src/wasmJsMain/resources/styles.css new file mode 100644 index 000000000..f8b13d234 --- /dev/null +++ b/demos/mkdocs/appyx-interactions/interactions/sample2/web/src/wasmJsMain/resources/styles.css @@ -0,0 +1,19 @@ +#root { + width: 100%; + height: 100vh; +} + +body { + margin: 0; +} + +#root > .compose-web-column > div { + position: relative; +} + +@media (max-width: 511px) { + #ComposeTarget { + scale: 0.5; + transform-origin: 0 0; + } +} diff --git a/demos/mkdocs/appyx-interactions/interactions/sample3/web/build.gradle.kts b/demos/mkdocs/appyx-interactions/interactions/sample3/web/build.gradle.kts index dadf32aa0..d0200f56f 100644 --- a/demos/mkdocs/appyx-interactions/interactions/sample3/web/build.gradle.kts +++ b/demos/mkdocs/appyx-interactions/interactions/sample3/web/build.gradle.kts @@ -1,3 +1,5 @@ +import org.jetbrains.kotlin.gradle.targets.js.dsl.ExperimentalWasmDsl + plugins { id("com.bumble.appyx.multiplatform") id("org.jetbrains.compose") @@ -10,6 +12,12 @@ kotlin { browser() binaries.executable() } + @OptIn(ExperimentalWasmDsl::class) + wasmJs { + moduleName = "appyx-interactions-sample3-web" + browser() + binaries.executable() + } sourceSets { val commonMain by getting { dependencies { @@ -36,4 +44,5 @@ compose.experimental { dependencies { add("kspCommonMainMetadata", project(":ksp:appyx-processor")) add("kspJs", project(":ksp:appyx-processor")) + add("kspWasmJs", project(":ksp:appyx-processor")) } diff --git a/demos/mkdocs/appyx-interactions/interactions/sample3/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/sample3/Sample3.kt b/demos/mkdocs/appyx-interactions/interactions/sample3/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/sample3/Sample3.kt new file mode 100644 index 000000000..c94fa7ad5 --- /dev/null +++ b/demos/mkdocs/appyx-interactions/interactions/sample3/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/sample3/Sample3.kt @@ -0,0 +1,257 @@ +@file:Suppress("MatchingDeclarationName") +package com.bumble.appyx.demos.sample3 + +import androidx.compose.animation.animateColorAsState +import androidx.compose.animation.core.Spring +import androidx.compose.animation.core.spring +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.BoxScope +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.offset +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.State +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.Alignment +import androidx.compose.ui.Alignment.Companion.CenterHorizontally +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.alpha +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.LinearGradientShader +import androidx.compose.ui.graphics.ShaderBrush +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.compose.ui.zIndex +import com.bumble.appyx.components.internal.testdrive.TestDrive +import com.bumble.appyx.components.internal.testdrive.TestDriveModel +import com.bumble.appyx.components.internal.testdrive.TestDriveModel.State.ElementState.A +import com.bumble.appyx.components.internal.testdrive.TestDriveModel.State.ElementState.B +import com.bumble.appyx.components.internal.testdrive.TestDriveModel.State.ElementState.C +import com.bumble.appyx.components.internal.testdrive.TestDriveModel.State.ElementState.D +import com.bumble.appyx.components.internal.testdrive.operation.next +import com.bumble.appyx.demos.sample3.InteractionTarget.Child1 +import com.bumble.appyx.demos.sample3.Sample3Visualisation.Companion.toTargetUiState +import com.bumble.appyx.interactions.composable.AppyxInteractionsContainer +import com.bumble.appyx.interactions.gesture.GestureReferencePoint +import com.bumble.appyx.interactions.model.transition.Keyframes +import com.bumble.appyx.interactions.model.transition.Operation.Mode.IMMEDIATE +import com.bumble.appyx.interactions.model.transition.Operation.Mode.KEYFRAME +import com.bumble.appyx.interactions.model.transition.Update +import com.bumble.appyx.interactions.ui.helper.AppyxComponentSetup + +enum class InteractionTarget { + Child1 +} + +@Composable +fun Sample3( + screenWidthPx: Int, + screenHeightPx: Int, + modifier: Modifier = Modifier, +) { + val coroutineScope = rememberCoroutineScope() + val model = remember { TestDriveModel(Child1, null) } + val testDrive = remember { + TestDrive( + scope = coroutineScope, + model = model, + progressAnimationSpec = spring( + stiffness = Spring.StiffnessVeryLow / 10, + visibilityThreshold = 0.001f + ), + visualisation = { Sample3Visualisation(it) }, + gestureFactory = { Sample3Visualisation.Gestures(it) } + ) + } + + AppyxComponentSetup(testDrive) + + val output = model.output.collectAsState().value + val currentTarget: State?> = + when (output) { + is Keyframes -> output.currentSegmentTargetStateFlow.collectAsState(null) + is Update -> remember(output) { mutableStateOf(output.currentTargetState) } + } + val index = when (output) { + is Keyframes -> output.currentIndex + is Update -> null + } + + Box( + modifier = modifier, + ) { + Background( + screenWidthPx = screenWidthPx, + screenHeightPx = screenHeightPx, + currentTarget = currentTarget.value + ) + Box( + modifier = Modifier.padding(24.dp, 24.dp) + ) { + Target( + boxScope = this, + currentTarget = currentTarget.value, + index = index + ) + ModelUi( + screenWidthPx = screenWidthPx, + screenHeightPx = screenHeightPx, + testDrive = testDrive, + model = model + ) + Controls( + testDrive = testDrive + ) + } + } +} + +@Composable +fun Background( + screenWidthPx: Int, + screenHeightPx: Int, + currentTarget: TestDriveModel.State?, + modifier: Modifier = Modifier.fillMaxSize() +) { + val backgroundColor1 = animateColorAsState( + when (currentTarget?.elementState) { + A -> color_neutral1 + B -> color_neutral2 + C -> color_neutral3 + D -> color_neutral4 + null -> color_bright + }, + animationSpec = spring(stiffness = Spring.StiffnessVeryLow) + ) + + Box( + modifier + .zIndex(0f) + .background( + ShaderBrush( + LinearGradientShader( + from = Offset.Zero, + to = Offset(screenWidthPx.toFloat(), screenHeightPx.toFloat()), + colors = listOf(color_bright, backgroundColor1.value) + ) + ) + ) + ) +} + +@Composable +fun Target( + boxScope: BoxScope, + currentTarget: TestDriveModel.State?, + index: Int?, + modifier: Modifier = Modifier +) { + val targetUiState = currentTarget?.elementState?.toTargetUiState() + targetUiState?.let { + Box( + modifier = with(boxScope) { + modifier + .size(60.dp) + .align(targetUiState.positionAlignment.value) + .offset( + x = targetUiState.positionOffset.value.offset.x, + y = targetUiState.positionOffset.value.offset.y + ) + .alpha(0.35f) + .background( + color = targetUiState.backgroundColor.value, + shape = RoundedCornerShape(targetUiState.roundedCorners.value) + ) + } + ) { + Text( + modifier = Modifier.align(Alignment.Center), + text = index?.toString() ?: "X", + fontSize = 24.sp, + color = Color.White + ) + } + } +} + +@Composable +fun ModelUi( + screenWidthPx: Int, + screenHeightPx: Int, + testDrive: TestDrive, + model: TestDriveModel, + modifier: Modifier = Modifier.fillMaxSize() +) { + AppyxInteractionsContainer( + appyxComponent = testDrive, + screenWidthPx = screenWidthPx, + screenHeightPx = screenHeightPx, + gestureRelativeTo = GestureReferencePoint.Element, + modifier = modifier + ) { + Box( + modifier = Modifier.size(60.dp) + ) { + Text( + modifier = Modifier.align(Alignment.Center), + text = model.output.collectAsState().value.currentTargetState.elementState.name, + fontSize = 24.sp, + color = Color.White + ) + } + } +} + +@Composable +private fun Controls( + testDrive: TestDrive, + modifier: Modifier = Modifier.fillMaxSize() +) { + Column( + modifier = modifier.zIndex(1f), + verticalArrangement = Arrangement.Bottom, + horizontalAlignment = CenterHorizontally + ) { + Row { + Box( + modifier = Modifier + .background(color_primary, shape = RoundedCornerShape(4.dp)) + .clickable { testDrive.next(mode = KEYFRAME) } + .padding(horizontal = 18.dp, vertical = 9.dp) + ) { + Text("Keyframe") + } + Spacer(Modifier.width(16.dp)) + Box( + modifier = Modifier + .background(color_primary, shape = RoundedCornerShape(4.dp)) + .clickable { + testDrive.next( + mode = IMMEDIATE, spring( + stiffness = Spring.StiffnessVeryLow, + dampingRatio = Spring.DampingRatioMediumBouncy + ) + ) + } + .padding(horizontal = 18.dp, vertical = 9.dp) + ) { + Text("Immediate") + } + } + + } +} diff --git a/demos/mkdocs/appyx-interactions/interactions/sample3/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/sample3/Sample3Visualisation.kt b/demos/mkdocs/appyx-interactions/interactions/sample3/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/sample3/Sample3Visualisation.kt new file mode 100644 index 000000000..00dd57a56 --- /dev/null +++ b/demos/mkdocs/appyx-interactions/interactions/sample3/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/sample3/Sample3Visualisation.kt @@ -0,0 +1,148 @@ +package com.bumble.appyx.demos.sample3 + +import androidx.compose.animation.core.SpringSpec +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.unit.Density +import androidx.compose.ui.unit.DpOffset +import androidx.compose.ui.unit.dp +import com.bumble.appyx.components.internal.testdrive.TestDriveModel +import com.bumble.appyx.components.internal.testdrive.TestDriveModel.State.ElementState.A +import com.bumble.appyx.components.internal.testdrive.TestDriveModel.State.ElementState.B +import com.bumble.appyx.components.internal.testdrive.TestDriveModel.State.ElementState.C +import com.bumble.appyx.components.internal.testdrive.TestDriveModel.State.ElementState.D +import com.bumble.appyx.components.internal.testdrive.operation.MoveTo +import com.bumble.appyx.interactions.ui.context.TransitionBounds +import com.bumble.appyx.interactions.ui.context.UiContext +import com.bumble.appyx.interactions.gesture.Drag.Direction8.DOWN +import com.bumble.appyx.interactions.gesture.Drag.Direction8.DOWNLEFT +import com.bumble.appyx.interactions.gesture.Drag.Direction8.DOWNRIGHT +import com.bumble.appyx.interactions.gesture.Drag.Direction8.LEFT +import com.bumble.appyx.interactions.gesture.Drag.Direction8.RIGHT +import com.bumble.appyx.interactions.gesture.Drag.Direction8.UP +import com.bumble.appyx.interactions.gesture.Drag.Direction8.UPLEFT +import com.bumble.appyx.interactions.gesture.Drag.Direction8.UPRIGHT +import com.bumble.appyx.interactions.gesture.Gesture +import com.bumble.appyx.interactions.gesture.GestureFactory +import com.bumble.appyx.interactions.gesture.dragDirection8 +import com.bumble.appyx.interactions.ui.DefaultAnimationSpec +import com.bumble.appyx.interactions.ui.property.impl.BackgroundColor +import com.bumble.appyx.interactions.ui.property.impl.position.BiasAlignment.InsideAlignment.Companion.BottomEnd +import com.bumble.appyx.interactions.ui.property.impl.position.BiasAlignment.InsideAlignment.Companion.BottomStart +import com.bumble.appyx.interactions.ui.property.impl.position.BiasAlignment.InsideAlignment.Companion.TopEnd +import com.bumble.appyx.interactions.ui.property.impl.position.BiasAlignment.InsideAlignment.Companion.TopStart +import com.bumble.appyx.interactions.ui.property.impl.position.PositionAlignment +import com.bumble.appyx.interactions.ui.property.impl.position.PositionOffset +import com.bumble.appyx.interactions.ui.state.MatchedTargetUiState +import com.bumble.appyx.transitionmodel.BaseVisualisation +import com.bumble.appyx.utils.multiplatform.AppyxLogger + +class Sample3Visualisation( + uiContext: UiContext, + uiAnimationSpec: SpringSpec = DefaultAnimationSpec +) : BaseVisualisation, TargetUiState, MutableUiState>( + uiContext = uiContext, + defaultAnimationSpec = uiAnimationSpec, +) { + override fun TestDriveModel.State.toUiTargets(): + List> = + listOf( + MatchedTargetUiState(element, elementState.toTargetUiState()).also { + AppyxLogger.d("TestDrive", "Matched $elementState -> UiState: ${it.targetUiState}") + } + ) + + companion object { + val bottomOffset = DpOffset(0.dp, (-50).dp) + + fun TestDriveModel.State.ElementState.toTargetUiState(): TargetUiState = + when (this) { + A -> topLeftCorner + B -> topRightCorner + C -> bottomRightCorner + D -> bottomLeftCorner + } + + private val topLeftCorner = TargetUiState( + positionAlignment = PositionAlignment.Target(TopStart), + positionOffset = PositionOffset.Target(DpOffset.Zero), + backgroundColor = BackgroundColor.Target(color_primary) + ) + + private val topRightCorner = TargetUiState( + positionAlignment = PositionAlignment.Target(TopEnd), + positionOffset = PositionOffset.Target(DpOffset.Zero), + backgroundColor = BackgroundColor.Target(color_dark) + ) + + private val bottomRightCorner = TargetUiState( + positionAlignment = PositionAlignment.Target(BottomEnd), + positionOffset = PositionOffset.Target(bottomOffset), + backgroundColor = BackgroundColor.Target(color_secondary) + ) + + private val bottomLeftCorner = TargetUiState( + positionAlignment = PositionAlignment.Target(BottomStart), + positionOffset = PositionOffset.Target(bottomOffset), + backgroundColor = BackgroundColor.Target(color_tertiary) + ) + } + + override fun mutableUiStateFor( + uiContext: UiContext, + targetUiState: TargetUiState + ): MutableUiState = + targetUiState.toMutableUiState(uiContext) + + class Gestures( + private val transitionBounds: TransitionBounds, + ) : GestureFactory> { + + @Suppress("ComplexMethod") + override fun createGesture( + state: TestDriveModel.State, + delta: Offset, + density: Density + ): Gesture> { + // FIXME 60.dp is the assumed element size, connect it to real value + // TODO automate this whole calculation based on .onPlaced centers of targetUiStates + val maxX = with(density) { + (transitionBounds.widthDp - 60.dp).toPx() + } + val maxY = with(density) { + (transitionBounds.heightDp + bottomOffset.y - 60.dp).toPx() + } + + val direction = dragDirection8(delta) + return when (state.elementState) { + A -> when (direction) { + RIGHT -> Gesture(MoveTo(B), Offset(maxX, 0f)) + DOWNRIGHT -> Gesture(MoveTo(C), Offset(maxX, maxY)) + DOWN -> Gesture(MoveTo(D), Offset(0f, maxY)) + else -> Gesture.Noop() + } + + B -> when (direction) { + DOWN -> Gesture(MoveTo(C), Offset(0f, maxY)) + DOWNLEFT -> Gesture(MoveTo(D), Offset(-maxX, maxY)) + LEFT -> Gesture(MoveTo(A), Offset(-maxX, 0f)) + else -> Gesture.Noop() + } + + C -> when (direction) { + LEFT -> Gesture(MoveTo(D), Offset(-maxX, 0f)) + UPLEFT -> Gesture(MoveTo(A), Offset(-maxX, -maxY)) + UP -> Gesture(MoveTo(B), Offset(0f, -maxY)) + else -> Gesture.Noop() + } + + D -> when (direction) { + UP -> Gesture(MoveTo(A), Offset(0f, -maxY)) + UPRIGHT -> Gesture(MoveTo(B), Offset(maxX, -maxY)) + RIGHT -> Gesture(MoveTo(C), Offset(maxX, 0f)) + else -> Gesture.Noop() + } + } + } + } +} + diff --git a/demos/mkdocs/appyx-interactions/interactions/sample3/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/sample3/TargetUiState.kt b/demos/mkdocs/appyx-interactions/interactions/sample3/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/sample3/TargetUiState.kt new file mode 100644 index 000000000..9e00c3688 --- /dev/null +++ b/demos/mkdocs/appyx-interactions/interactions/sample3/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/sample3/TargetUiState.kt @@ -0,0 +1,16 @@ +package com.bumble.appyx.demos.sample3 + +import com.bumble.appyx.interactions.ui.property.impl.BackgroundColor +import com.bumble.appyx.interactions.ui.property.impl.RoundedCorners +import com.bumble.appyx.interactions.ui.property.impl.position.PositionAlignment +import com.bumble.appyx.interactions.ui.property.impl.position.PositionOffset +import com.bumble.appyx.interactions.ui.state.MutableUiStateSpecs + +@Suppress("unused") +@MutableUiStateSpecs +class TargetUiState( + val positionAlignment: PositionAlignment.Target, + val positionOffset: PositionOffset.Target, + val roundedCorners: RoundedCorners.Target = RoundedCorners.Target(10), + val backgroundColor: BackgroundColor.Target, +) diff --git a/demos/mkdocs/appyx-interactions/interactions/sample3/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/sample3/main.js.kt b/demos/mkdocs/appyx-interactions/interactions/sample3/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/sample3/main.js.kt new file mode 100644 index 000000000..ae3c04bf4 --- /dev/null +++ b/demos/mkdocs/appyx-interactions/interactions/sample3/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/sample3/main.js.kt @@ -0,0 +1,57 @@ +package com.bumble.appyx.demos.sample3 + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.material.Surface +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.ExperimentalComposeUiApi +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.layout.onSizeChanged +import androidx.compose.ui.unit.IntSize +import androidx.compose.ui.unit.dp +import androidx.compose.ui.window.CanvasBasedWindow +import com.bumble.appyx.demos.appyxSample + +val color_bright = Color(0xFFFFFFFF) +val color_dark = Color(0xFF353535) +val color_primary = Color(0xFFFFC629) +val color_secondary = Color(0xFFFE9763) +val color_tertiary = Color(0xFF855353) +val color_neutral1 = Color(0xFFD2D7DF) +val color_neutral2 = Color(0xFF8A897C) +val color_neutral3 = Color(0xFFD9E8ED) +val color_neutral4 = Color(0xFFBEA489) + + +@OptIn(ExperimentalComposeUiApi::class) +fun main() { + appyxSample { + CanvasBasedWindow("Appyx") { + var size by remember { mutableStateOf(IntSize.Zero) } + Surface( + modifier = Modifier + .fillMaxSize() + .onSizeChanged { size = it } + ) { + if (size != IntSize.Zero) { + Sample3( + screenWidthPx = size.width, + screenHeightPx = size.height, + modifier = Modifier + .fillMaxSize() + .background(color_dark) + .padding( + horizontal = 16.dp, + vertical = 16.dp + ) + ) + } + } + } + } +} diff --git a/demos/mkdocs/appyx-interactions/interactions/sample3/web/src/wasmJsMain/resources/index.html b/demos/mkdocs/appyx-interactions/interactions/sample3/web/src/wasmJsMain/resources/index.html new file mode 100644 index 000000000..5ca3e007b --- /dev/null +++ b/demos/mkdocs/appyx-interactions/interactions/sample3/web/src/wasmJsMain/resources/index.html @@ -0,0 +1,15 @@ + + + + + Appyx Interactions + + + + +
+ +
+ + + diff --git a/demos/mkdocs/appyx-interactions/interactions/sample3/web/src/wasmJsMain/resources/styles.css b/demos/mkdocs/appyx-interactions/interactions/sample3/web/src/wasmJsMain/resources/styles.css new file mode 100644 index 000000000..f8b13d234 --- /dev/null +++ b/demos/mkdocs/appyx-interactions/interactions/sample3/web/src/wasmJsMain/resources/styles.css @@ -0,0 +1,19 @@ +#root { + width: 100%; + height: 100vh; +} + +body { + margin: 0; +} + +#root > .compose-web-column > div { + position: relative; +} + +@media (max-width: 511px) { + #ComposeTarget { + scale: 0.5; + transform-origin: 0 0; + } +} diff --git a/demos/mkdocs/common/build.gradle.kts b/demos/mkdocs/common/build.gradle.kts index 1bb32ce51..61df1d418 100644 --- a/demos/mkdocs/common/build.gradle.kts +++ b/demos/mkdocs/common/build.gradle.kts @@ -1,3 +1,5 @@ +import org.jetbrains.kotlin.gradle.targets.js.dsl.ExperimentalWasmDsl + plugins { id("com.bumble.appyx.multiplatform") id("org.jetbrains.compose") @@ -9,6 +11,12 @@ kotlin { browser() binaries.executable() } + @OptIn(ExperimentalWasmDsl::class) + wasmJs { + moduleName = "demos-mkdocs-common" + browser() + binaries.executable() + } sourceSets { val commonMain by getting { dependencies { diff --git a/demos/mkdocs/common/src/wasmJsMain/kotlin/com/bumble/appyx/demos/AppyxSample.kt b/demos/mkdocs/common/src/wasmJsMain/kotlin/com/bumble/appyx/demos/AppyxSample.kt new file mode 100644 index 000000000..6386e9029 --- /dev/null +++ b/demos/mkdocs/common/src/wasmJsMain/kotlin/com/bumble/appyx/demos/AppyxSample.kt @@ -0,0 +1,65 @@ +package com.bumble.appyx.demos + +import kotlinx.browser.document +import org.w3c.dom.get + +private val LOADER_STYLES = """ + .loader { + width: 48px; + height: 48px; + position: fixed; + left: 50%; + top: 50%; + transform: translate(-50%, -50%); + border: 5px solid rgba(255, 227, 0, 255); + border-bottom-color: transparent; + border-radius: 50%; + animation: rotation 1s linear 1s infinite; + visibility: hidden; + margin: auto; + } + + @keyframes rotation { + 0% { + visibility: visible; + transform: rotate(0deg); + } + 100% { + transform: rotate(360deg); + } + } +""".trimIndent() + +external fun onWasmReady(onReady: () -> Unit) + +fun appyxSample( + block: () -> Unit, +) { + appendLoaderStyles() + appendLoaderElement() + onWasmReady { + block() + removeLoaderElement() + } +} + +private fun appendLoaderStyles() { + val head = document.head ?: document.getElementsByTagName("head")[0] + head?.apply { + val style = document.createElement("style") + head.appendChild(style) + style.appendChild(document.createTextNode(LOADER_STYLES)) + } +} + +private fun appendLoaderElement() { + val composeTarget = document.getElementById("ComposeTarget") + val loader = document.createElement("div") + loader.className = "loader" + composeTarget?.parentNode?.appendChild(loader) +} + +private fun removeLoaderElement() { + val loader = document.getElementsByClassName("loader")[0] + loader?.parentNode?.removeChild(loader) +} \ No newline at end of file diff --git a/demos/sandbox-appyx-navigation/common/build.gradle.kts b/demos/sandbox-appyx-navigation/common/build.gradle.kts index 5762b9f39..208607e11 100644 --- a/demos/sandbox-appyx-navigation/common/build.gradle.kts +++ b/demos/sandbox-appyx-navigation/common/build.gradle.kts @@ -1,3 +1,5 @@ +import org.jetbrains.kotlin.gradle.targets.js.dsl.ExperimentalWasmDsl + plugins { id("com.bumble.appyx.multiplatform") id("org.jetbrains.compose") @@ -24,6 +26,12 @@ kotlin { moduleName = "demo-sandbox-appyx-navigation-common" browser() } + @OptIn(ExperimentalWasmDsl::class) + wasmJs { + // Adding moduleName as a workaround for this issue: https://youtrack.jetbrains.com/issue/KT-51942 + moduleName = "demo-sandbox-appyx-navigation-common" + browser() + } iosX64() iosArm64() iosSimulatorArm64() @@ -93,6 +101,7 @@ dependencies { add("kspAndroid", project(":ksp:appyx-processor")) add("kspDesktop", project(":ksp:appyx-processor")) add("kspJs", project(":ksp:appyx-processor")) + add("kspWasmJs", project(":ksp:appyx-processor")) add("kspIosArm64", project(":ksp:appyx-processor")) add("kspIosX64", project(":ksp:appyx-processor")) add("kspIosSimulatorArm64", project(":ksp:appyx-processor")) diff --git a/demos/sandbox-appyx-navigation/web/build.gradle.kts b/demos/sandbox-appyx-navigation/web/build.gradle.kts index 6d7ea56ed..73aded20d 100644 --- a/demos/sandbox-appyx-navigation/web/build.gradle.kts +++ b/demos/sandbox-appyx-navigation/web/build.gradle.kts @@ -1,3 +1,5 @@ +import org.jetbrains.kotlin.gradle.targets.js.dsl.ExperimentalWasmDsl + plugins { id("com.bumble.appyx.multiplatform") id("org.jetbrains.compose") @@ -9,6 +11,12 @@ kotlin { browser() binaries.executable() } + @OptIn(ExperimentalWasmDsl::class) + wasmJs { + moduleName = "appyx-demos-sandbox-navigation-web" + browser() + binaries.executable() + } sourceSets { val commonMain by getting { dependencies { diff --git a/demos/sandbox-appyx-navigation/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/sandbox/navigation/Main.kt b/demos/sandbox-appyx-navigation/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/sandbox/navigation/Main.kt new file mode 100644 index 000000000..a138c466a --- /dev/null +++ b/demos/sandbox-appyx-navigation/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/sandbox/navigation/Main.kt @@ -0,0 +1,94 @@ +package com.bumble.appyx.demos.sandbox.navigation + +import androidx.compose.foundation.focusable +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.ExperimentalComposeUiApi +import androidx.compose.ui.Modifier +import androidx.compose.ui.focus.FocusRequester +import androidx.compose.ui.focus.focusRequester +import androidx.compose.ui.focus.onFocusChanged +import androidx.compose.ui.input.key.Key +import androidx.compose.ui.input.key.KeyEvent +import androidx.compose.ui.input.key.KeyEventType +import androidx.compose.ui.input.key.key +import androidx.compose.ui.input.key.onKeyEvent +import androidx.compose.ui.input.key.type +import androidx.compose.ui.layout.onSizeChanged +import androidx.compose.ui.unit.dp +import com.bumble.appyx.demos.sandbox.navigation.node.container.MainNavNode +import com.bumble.appyx.demos.sandbox.navigation.ui.AppyxSampleAppTheme +import com.bumble.appyx.navigation.integration.BrowserViewportWindow +import com.bumble.appyx.navigation.integration.ScreenSize +import com.bumble.appyx.navigation.integration.WebNodeHost +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.flow.receiveAsFlow +import kotlinx.coroutines.launch +import org.jetbrains.skiko.wasm.onWasmReady + +fun main() { + val events: Channel = Channel() + onWasmReady { + BrowserViewportWindow("Navigation Demo") { + val requester = remember { FocusRequester() } + var hasFocus by remember { mutableStateOf(false) } + + var screenSize by remember { mutableStateOf(ScreenSize(0.dp, 0.dp)) } + val eventScope = remember { CoroutineScope(SupervisorJob() + Dispatchers.Main) } + + AppyxSampleAppTheme { + Surface( + color = MaterialTheme.colorScheme.background, + modifier = Modifier + .fillMaxSize() + .onSizeChanged { screenSize = ScreenSize(it.width.dp, it.height.dp) } + .onKeyEvent { + onKeyEvent(it, events, eventScope) + } + .focusRequester(requester) + .focusable() + .onFocusChanged { hasFocus = it.hasFocus } + ) { + WebNodeHost( + screenSize = screenSize, + onBackPressedEvents = events.receiveAsFlow(), + ) { nodeContext -> + MainNavNode( + nodeContext = nodeContext, + ) + } + } + + if (!hasFocus) { + LaunchedEffect(Unit) { + requester.requestFocus() + } + } + } + } + } +} + +@OptIn(ExperimentalComposeUiApi::class) +private fun onKeyEvent( + keyEvent: KeyEvent, + events: Channel, + coroutineScope: CoroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.Main), +): Boolean = + when { + keyEvent.type == KeyEventType.KeyUp && keyEvent.key == Key.Backspace -> { + coroutineScope.launch { events.send(Unit) } + true + } + + else -> false + } diff --git a/demos/sandbox-appyx-navigation/web/src/wasmJsMain/resources/index.html b/demos/sandbox-appyx-navigation/web/src/wasmJsMain/resources/index.html new file mode 100644 index 000000000..b09444ce6 --- /dev/null +++ b/demos/sandbox-appyx-navigation/web/src/wasmJsMain/resources/index.html @@ -0,0 +1,15 @@ + + + + + Navigation Demo + + + + +
+ +
+ + + diff --git a/demos/sandbox-appyx-navigation/web/src/wasmJsMain/resources/styles.css b/demos/sandbox-appyx-navigation/web/src/wasmJsMain/resources/styles.css new file mode 100644 index 000000000..8655f2e76 --- /dev/null +++ b/demos/sandbox-appyx-navigation/web/src/wasmJsMain/resources/styles.css @@ -0,0 +1,12 @@ +#root { + width: 100%; + height: 100vh; +} + +body { + margin: 0; +} + +#root > .compose-web-column > div { + position: relative; +} diff --git a/gradle.properties b/gradle.properties index 1c621a29a..1bd124c1e 100644 --- a/gradle.properties +++ b/gradle.properties @@ -12,4 +12,5 @@ org.gradle.caching=true org.gradle.jvmargs=-Xmx4096m -Dfile.encoding=UTF-8 org.gradle.parallel=true org.jetbrains.compose.experimental.jscanvas.enabled=true +org.jetbrains.compose.experimental.wasm.enabled=true org.jetbrains.compose.experimental.uikit.enabled=true diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 8d216f7f4..8b3d047ce 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -21,7 +21,7 @@ kotlin = "1.9.22" ksp = "1.9.22-1.0.17" mvicore = "1.2.6" ribs = "0.39.0" -serialization-json = "1.5.0" +serialization-json = "1.6.2" uuid = "9.0.0" uiautomator = "2.2.0" benchmark-macro-junit4 = "1.2.0" diff --git a/plugins/convention/src/main/kotlin/com/bumble/appyx/multiplatform/MultiplatformConventionPlugin.kt b/plugins/convention/src/main/kotlin/com/bumble/appyx/multiplatform/MultiplatformConventionPlugin.kt index a29a7306f..8240db64b 100644 --- a/plugins/convention/src/main/kotlin/com/bumble/appyx/multiplatform/MultiplatformConventionPlugin.kt +++ b/plugins/convention/src/main/kotlin/com/bumble/appyx/multiplatform/MultiplatformConventionPlugin.kt @@ -12,6 +12,7 @@ import org.gradle.kotlin.dsl.configure import org.gradle.kotlin.dsl.get import org.gradle.kotlin.dsl.getByType import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension +import org.jetbrains.kotlin.gradle.dsl.kotlinExtension import org.jetbrains.kotlin.gradle.targets.js.dsl.KotlinJsTargetDsl class MultiplatformConventionPlugin : Plugin { diff --git a/utils/customisations/build.gradle.kts b/utils/customisations/build.gradle.kts index 6112939c7..6ef9d4805 100644 --- a/utils/customisations/build.gradle.kts +++ b/utils/customisations/build.gradle.kts @@ -1,3 +1,5 @@ +import org.jetbrains.kotlin.gradle.targets.js.dsl.ExperimentalWasmDsl + plugins { id("com.bumble.appyx.multiplatform") id("com.android.library") @@ -22,6 +24,12 @@ kotlin { moduleName = "appyx-utils-customisation" browser() } + @OptIn(ExperimentalWasmDsl::class) + wasmJs { + // Adding moduleName as a workaround for this issue: https://youtrack.jetbrains.com/issue/KT-51942 + moduleName = "appyx-utils-customisation" + browser() + } iosX64() iosArm64() iosSimulatorArm64() diff --git a/utils/customisations/src/wasmJsMain/kotlin/com/bumble/appyx/utils/customisations/NodeCustomisationDirectoryImpl.wasmJs.kt b/utils/customisations/src/wasmJsMain/kotlin/com/bumble/appyx/utils/customisations/NodeCustomisationDirectoryImpl.wasmJs.kt new file mode 100644 index 000000000..7e369f541 --- /dev/null +++ b/utils/customisations/src/wasmJsMain/kotlin/com/bumble/appyx/utils/customisations/NodeCustomisationDirectoryImpl.wasmJs.kt @@ -0,0 +1,33 @@ +package com.bumble.appyx.utils.customisations + +import kotlin.reflect.KClass + +actual open class NodeCustomisationDirectoryImpl actual constructor( + override val parent: NodeCustomisationDirectory? +) : MutableNodeCustomisationDirectory { + + override fun put(key: KClass, valueProvider: () -> T) { + // NO-OP + } + + override fun get(key: KClass): T? = + null + + override fun getRecursively(key: KClass): T? = + null + + override fun putSubDirectory(key: KClass, valueProvider: () -> NodeCustomisationDirectory) { + // NO-OP + } + + override fun getSubDirectory(key: KClass): NodeCustomisationDirectory? = + null + + override fun getSubDirectoryOrSelf(key: KClass): NodeCustomisationDirectory { + return this + } + + operator fun KClass<*>.invoke(block: NodeCustomisationDirectoryImpl.() -> Unit) { + // NO-OP + } +} diff --git a/utils/material3/build.gradle.kts b/utils/material3/build.gradle.kts index 2a7d0914d..7fa5e0b1a 100644 --- a/utils/material3/build.gradle.kts +++ b/utils/material3/build.gradle.kts @@ -1,3 +1,5 @@ +import org.jetbrains.kotlin.gradle.targets.js.dsl.ExperimentalWasmDsl + plugins { id("com.bumble.appyx.multiplatform") id("com.android.library") @@ -23,7 +25,12 @@ kotlin { moduleName = "appyx-utils-material3" browser() } - + @OptIn(ExperimentalWasmDsl::class) + wasmJs { + // Adding moduleName as a workaround for this issue: https://youtrack.jetbrains.com/issue/KT-51942 + moduleName = "appyx-utils-material3" + browser() + } iosX64() iosArm64() iosSimulatorArm64() diff --git a/utils/multiplatform/build.gradle.kts b/utils/multiplatform/build.gradle.kts index eed86cfd3..158f6bc6d 100644 --- a/utils/multiplatform/build.gradle.kts +++ b/utils/multiplatform/build.gradle.kts @@ -1,3 +1,5 @@ +import org.jetbrains.kotlin.gradle.targets.js.dsl.ExperimentalWasmDsl + plugins { id("com.bumble.appyx.multiplatform") kotlin("plugin.serialization") @@ -24,6 +26,12 @@ kotlin { moduleName = "appyx-utils-multiplatform" browser() } + @OptIn(ExperimentalWasmDsl::class) + wasmJs { + // Adding moduleName as a workaround for this issue: https://youtrack.jetbrains.com/issue/KT-51942 + moduleName = "appyx-utils-multiplatform" + browser() + } iosX64() iosArm64() iosSimulatorArm64() diff --git a/utils/multiplatform/src/wasmJsMain/kotlin/com/bumble/appyx/utils/multiplatform/AppyxLogger.wasmJs.kt b/utils/multiplatform/src/wasmJsMain/kotlin/com/bumble/appyx/utils/multiplatform/AppyxLogger.wasmJs.kt new file mode 100644 index 000000000..3fa4a92a8 --- /dev/null +++ b/utils/multiplatform/src/wasmJsMain/kotlin/com/bumble/appyx/utils/multiplatform/AppyxLogger.wasmJs.kt @@ -0,0 +1,50 @@ +package com.bumble.appyx.utils.multiplatform + +import com.bumble.appyx.utils.multiplatform.AppyxLoggingLevel.DEBUG +import com.bumble.appyx.utils.multiplatform.AppyxLoggingLevel.DISABLED +import com.bumble.appyx.utils.multiplatform.AppyxLoggingLevel.ERROR +import com.bumble.appyx.utils.multiplatform.AppyxLoggingLevel.INFO +import com.bumble.appyx.utils.multiplatform.AppyxLoggingLevel.VERBOSE +import com.bumble.appyx.utils.multiplatform.AppyxLoggingLevel.WARN + +external interface Console { + fun log(o: String) +} + +external val console: Console + +actual object AppyxLogger { + + actual var loggingLevel: Int = DISABLED + + actual fun v(tag: String, message: String) { + if (loggingLevel <= VERBOSE) { + console.log("$tag: $message") + } + } + + actual fun d(tag: String, message: String) { + if (loggingLevel <= DEBUG) { + console.log("$tag: $message") + } + } + + + actual fun i(tag: String, message: String) { + if (loggingLevel <= INFO) { + console.log("$tag: $message") + } + } + + actual fun w(tag: String, message: String) { + if (loggingLevel <= WARN) { + console.log("$tag: $message") + } + } + + actual fun e(tag: String, message: String) { + if (loggingLevel <= ERROR) { + console.log("$tag: $message") + } + } +} diff --git a/utils/multiplatform/src/wasmJsMain/kotlin/com/bumble/appyx/utils/multiplatform/BuildFlags.kt b/utils/multiplatform/src/wasmJsMain/kotlin/com/bumble/appyx/utils/multiplatform/BuildFlags.kt new file mode 100644 index 000000000..133ce0315 --- /dev/null +++ b/utils/multiplatform/src/wasmJsMain/kotlin/com/bumble/appyx/utils/multiplatform/BuildFlags.kt @@ -0,0 +1,6 @@ +package com.bumble.appyx.utils.multiplatform + +@Suppress("ForbiddenComment") +actual object BuildFlags { + actual val DEBUG: Boolean = false // TODO: provide value from gradle +} diff --git a/utils/multiplatform/src/wasmJsMain/kotlin/com/bumble/appyx/utils/multiplatform/Parcelable.kt b/utils/multiplatform/src/wasmJsMain/kotlin/com/bumble/appyx/utils/multiplatform/Parcelable.kt new file mode 100644 index 000000000..eef475202 --- /dev/null +++ b/utils/multiplatform/src/wasmJsMain/kotlin/com/bumble/appyx/utils/multiplatform/Parcelable.kt @@ -0,0 +1,8 @@ +package com.bumble.appyx.utils.multiplatform + +actual annotation class Parcelize + +actual interface Parcelable + +@Target(AnnotationTarget.TYPE) +actual annotation class RawValue From 18e57509ddb23fa195043305bebb4d6f02f51353 Mon Sep 17 00:00:00 2001 From: Manel Martos Date: Wed, 3 Apr 2024 21:10:28 +0200 Subject: [PATCH 2/7] Ensure onWasmReady is properly defined for Kotlin/Wasm target --- .../com/bumble/appyx/experimental/puzzle15/web/main.js.kt | 3 ++- .../appyx/navigation/integration/BrowserViewportWindow.kt | 1 - .../appyx/navigation/integration/BrowserViewportWindow.kt | 5 ++--- .../kotlin/com/bumble/appyx/interactions/main.js.kt | 3 ++- .../kotlin/com/bumble/appyx/demos/observemp/main.js.kt | 2 +- .../kotlin/com/bumble/appyx/demos/sandbox/navigation/Main.kt | 5 ++--- .../kotlin/com/bumble/appyx/demos/sandbox/navigation/Main.kt | 3 ++- documentation/navigation/multiplatform.md | 2 +- 8 files changed, 12 insertions(+), 12 deletions(-) diff --git a/appyx-components/experimental/puzzle15/web/src/wasmJsMain/kotlin/com/bumble/appyx/experimental/puzzle15/web/main.js.kt b/appyx-components/experimental/puzzle15/web/src/wasmJsMain/kotlin/com/bumble/appyx/experimental/puzzle15/web/main.js.kt index be5c03358..4adc93d61 100644 --- a/appyx-components/experimental/puzzle15/web/src/wasmJsMain/kotlin/com/bumble/appyx/experimental/puzzle15/web/main.js.kt +++ b/appyx-components/experimental/puzzle15/web/src/wasmJsMain/kotlin/com/bumble/appyx/experimental/puzzle15/web/main.js.kt @@ -18,7 +18,8 @@ import androidx.compose.ui.layout.onSizeChanged import androidx.compose.ui.unit.IntSize import androidx.compose.ui.window.CanvasBasedWindow import com.bumble.appyx.components.experimental.puzzle15.ui.Puzzle15Ui -import org.jetbrains.skiko.wasm.onWasmReady + +external fun onWasmReady(onReady: () -> Unit) @OptIn(ExperimentalComposeUiApi::class) fun main() { diff --git a/appyx-navigation/common/src/jsMain/kotlin/com/bumble/appyx/navigation/integration/BrowserViewportWindow.kt b/appyx-navigation/common/src/jsMain/kotlin/com/bumble/appyx/navigation/integration/BrowserViewportWindow.kt index 90f94b819..ce2954b6e 100644 --- a/appyx-navigation/common/src/jsMain/kotlin/com/bumble/appyx/navigation/integration/BrowserViewportWindow.kt +++ b/appyx-navigation/common/src/jsMain/kotlin/com/bumble/appyx/navigation/integration/BrowserViewportWindow.kt @@ -23,7 +23,6 @@ private const val CANVAS_ELEMENT_ID = "ComposeTarget" // Hardwired into ComposeW /** * A Skiko/Canvas-based top-level window using the browser's entire viewport. Supports resizing. */ -@Composable @Suppress("FunctionNaming") fun BrowserViewportWindow( title: String = "Untitled", diff --git a/appyx-navigation/common/src/wasmJsMain/kotlin/com/bumble/appyx/navigation/integration/BrowserViewportWindow.kt b/appyx-navigation/common/src/wasmJsMain/kotlin/com/bumble/appyx/navigation/integration/BrowserViewportWindow.kt index 90f94b819..786edf653 100644 --- a/appyx-navigation/common/src/wasmJsMain/kotlin/com/bumble/appyx/navigation/integration/BrowserViewportWindow.kt +++ b/appyx-navigation/common/src/wasmJsMain/kotlin/com/bumble/appyx/navigation/integration/BrowserViewportWindow.kt @@ -23,7 +23,6 @@ private const val CANVAS_ELEMENT_ID = "ComposeTarget" // Hardwired into ComposeW /** * A Skiko/Canvas-based top-level window using the browser's entire viewport. Supports resizing. */ -@Composable @Suppress("FunctionNaming") fun BrowserViewportWindow( title: String = "Untitled", @@ -61,7 +60,7 @@ fun BrowserViewportWindow( } ComposeWindow(canvasId = "Appyx", content = content).apply { - window.addEventListener("resize", { + window.addEventListener("resize") { canvas.fillViewportSize() layer.layer.attachTo(canvas) layer.layer.needRedraw() @@ -70,7 +69,7 @@ fun BrowserViewportWindow( (canvas.width / scale * density.density).toInt(), (canvas.height / scale * density.density).toInt() ) - }) + } // WORKAROUND: ComposeWindow does not implement `setTitle(title)` val htmlTitleElement = ( diff --git a/demos/appyx-interactions/web/src/wasmJsMain/kotlin/com/bumble/appyx/interactions/main.js.kt b/demos/appyx-interactions/web/src/wasmJsMain/kotlin/com/bumble/appyx/interactions/main.js.kt index 53b130781..34426f6f8 100644 --- a/demos/appyx-interactions/web/src/wasmJsMain/kotlin/com/bumble/appyx/interactions/main.js.kt +++ b/demos/appyx-interactions/web/src/wasmJsMain/kotlin/com/bumble/appyx/interactions/main.js.kt @@ -25,7 +25,8 @@ import com.bumble.appyx.components.internal.testdrive.ui.md_light_green_500 import com.bumble.appyx.components.internal.testdrive.ui.md_lime_500 import com.bumble.appyx.components.internal.testdrive.ui.md_pink_500 import com.bumble.appyx.components.internal.testdrive.ui.md_teal_500 -import org.jetbrains.skiko.wasm.onWasmReady + +external fun onWasmReady(onReady: () -> Unit) val manatee = Color(0xFF8D99AE) val silver_sand = Color(0xFFBDC6D1) diff --git a/demos/mkdocs/appyx-interactions/interactions/observemp/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/observemp/main.js.kt b/demos/mkdocs/appyx-interactions/interactions/observemp/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/observemp/main.js.kt index 2028b19b8..be0f3f002 100644 --- a/demos/mkdocs/appyx-interactions/interactions/observemp/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/observemp/main.js.kt +++ b/demos/mkdocs/appyx-interactions/interactions/observemp/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/observemp/main.js.kt @@ -15,8 +15,8 @@ import androidx.compose.ui.unit.IntSize import androidx.compose.ui.unit.dp import androidx.compose.ui.window.CanvasBasedWindow import com.bumble.appyx.demos.common.color_dark -import org.jetbrains.skiko.wasm.onWasmReady +external fun onWasmReady(onReady: () -> Unit) @OptIn(ExperimentalComposeUiApi::class) fun main() { diff --git a/demos/sandbox-appyx-navigation/web/src/jsMain/kotlin/com/bumble/appyx/demos/sandbox/navigation/Main.kt b/demos/sandbox-appyx-navigation/web/src/jsMain/kotlin/com/bumble/appyx/demos/sandbox/navigation/Main.kt index b65cea23c..979c5844e 100644 --- a/demos/sandbox-appyx-navigation/web/src/jsMain/kotlin/com/bumble/appyx/demos/sandbox/navigation/Main.kt +++ b/demos/sandbox-appyx-navigation/web/src/jsMain/kotlin/com/bumble/appyx/demos/sandbox/navigation/Main.kt @@ -22,9 +22,9 @@ import androidx.compose.ui.input.key.onKeyEvent import androidx.compose.ui.input.key.type import androidx.compose.ui.layout.onSizeChanged import androidx.compose.ui.unit.dp -import androidx.compose.ui.window.CanvasBasedWindow import com.bumble.appyx.demos.sandbox.navigation.node.container.MainNavNode import com.bumble.appyx.demos.sandbox.navigation.ui.AppyxSampleAppTheme +import com.bumble.appyx.navigation.integration.BrowserViewportWindow import com.bumble.appyx.navigation.integration.ScreenSize import com.bumble.appyx.navigation.integration.WebNodeHost import kotlinx.coroutines.CoroutineScope @@ -35,11 +35,10 @@ import kotlinx.coroutines.flow.receiveAsFlow import kotlinx.coroutines.launch import org.jetbrains.skiko.wasm.onWasmReady -@OptIn(ExperimentalComposeUiApi::class) fun main() { val events: Channel = Channel() onWasmReady { - CanvasBasedWindow("Navigation Demo") { + BrowserViewportWindow("Navigation Demo") { val requester = remember { FocusRequester() } var hasFocus by remember { mutableStateOf(false) } diff --git a/demos/sandbox-appyx-navigation/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/sandbox/navigation/Main.kt b/demos/sandbox-appyx-navigation/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/sandbox/navigation/Main.kt index a138c466a..479f92d98 100644 --- a/demos/sandbox-appyx-navigation/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/sandbox/navigation/Main.kt +++ b/demos/sandbox-appyx-navigation/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/sandbox/navigation/Main.kt @@ -33,7 +33,8 @@ import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.flow.receiveAsFlow import kotlinx.coroutines.launch -import org.jetbrains.skiko.wasm.onWasmReady + +external fun onWasmReady(onReady: () -> Unit) fun main() { val events: Channel = Channel() diff --git a/documentation/navigation/multiplatform.md b/documentation/navigation/multiplatform.md index 2cf2e5d1d..6ca6e830b 100644 --- a/documentation/navigation/multiplatform.md +++ b/documentation/navigation/multiplatform.md @@ -138,7 +138,7 @@ fun main() = application { fun main() { val events: Channel = Channel() onWasmReady { - CanvasBasedWindow("Your app") { + BrowserViewportWindow("Your app") { val requester = remember { FocusRequester() } var hasFocus by remember { mutableStateOf(false) } var screenSize by remember { mutableStateOf(ScreenSize(0.dp, 0.dp)) } From dd1295d73e2cba47e048d6e3f0e21561dcbb0bf4 Mon Sep 17 00:00:00 2001 From: Manel Martos Date: Thu, 4 Apr 2024 09:14:48 +0200 Subject: [PATCH 3/7] Append '-wa' suffix to wasm module names --- appyx-components/experimental/cards/common/build.gradle.kts | 2 +- appyx-components/experimental/promoter/common/build.gradle.kts | 2 +- appyx-components/experimental/puzzle15/common/build.gradle.kts | 2 +- appyx-components/experimental/puzzle15/web/build.gradle.kts | 2 +- appyx-components/internal/test-drive/common/build.gradle.kts | 2 +- appyx-components/standard/backstack/common/build.gradle.kts | 2 +- appyx-components/standard/spotlight/common/build.gradle.kts | 2 +- appyx-interactions/common/build.gradle.kts | 2 +- appyx-navigation/common/build.gradle.kts | 2 +- demos/appyx-interactions/web/build.gradle.kts | 2 +- demos/appyx-navigation/common/build.gradle.kts | 2 +- demos/appyx-navigation/web/build.gradle.kts | 2 +- demos/common/build.gradle.kts | 2 +- demos/image-loader/common/build.gradle.kts | 2 +- .../appyx-components/backstack/fader/web/build.gradle.kts | 2 +- .../appyx-components/backstack/parallax/web/build.gradle.kts | 2 +- .../appyx-components/backstack/slider/web/build.gradle.kts | 2 +- .../appyx-components/backstack/stack3d/web/build.gradle.kts | 2 +- demos/mkdocs/appyx-components/common/build.gradle.kts | 2 +- .../experimental/datingcards/web/build.gradle.kts | 2 +- .../appyx-components/experimental/puzzle15/web/build.gradle.kts | 2 +- .../appyx-components/spotlight/fader/web/build.gradle.kts | 2 +- .../appyx-components/spotlight/slider/web/build.gradle.kts | 2 +- .../spotlight/sliderrotation/web/build.gradle.kts | 2 +- .../appyx-components/spotlight/sliderscale/web/build.gradle.kts | 2 +- .../appyx-components/spotlight/stack3d/web/build.gradle.kts | 2 +- .../gestures/dragprediction/web/build.gradle.kts | 2 +- .../gestures/incompletedrag/web/build.gradle.kts | 2 +- .../interactions/observemp/web/build.gradle.kts | 2 +- .../interactions/sample1/web/build.gradle.kts | 2 +- .../interactions/sample2/web/build.gradle.kts | 2 +- .../interactions/sample3/web/build.gradle.kts | 2 +- demos/mkdocs/common/build.gradle.kts | 2 +- demos/sandbox-appyx-navigation/common/build.gradle.kts | 2 +- demos/sandbox-appyx-navigation/web/build.gradle.kts | 2 +- utils/customisations/build.gradle.kts | 2 +- utils/material3/build.gradle.kts | 2 +- utils/multiplatform/build.gradle.kts | 2 +- 38 files changed, 38 insertions(+), 38 deletions(-) diff --git a/appyx-components/experimental/cards/common/build.gradle.kts b/appyx-components/experimental/cards/common/build.gradle.kts index d5711023d..4b8bd23a7 100644 --- a/appyx-components/experimental/cards/common/build.gradle.kts +++ b/appyx-components/experimental/cards/common/build.gradle.kts @@ -30,7 +30,7 @@ kotlin { @OptIn(ExperimentalWasmDsl::class) wasmJs { // Adding moduleName as a workaround for this issue: https://youtrack.jetbrains.com/issue/KT-51942 - moduleName = "appyx-components-experimental-cards-commons" + moduleName = "appyx-components-experimental-cards-commons-wa" browser() } iosX64() diff --git a/appyx-components/experimental/promoter/common/build.gradle.kts b/appyx-components/experimental/promoter/common/build.gradle.kts index 748523bd9..5ffb02b4b 100644 --- a/appyx-components/experimental/promoter/common/build.gradle.kts +++ b/appyx-components/experimental/promoter/common/build.gradle.kts @@ -30,7 +30,7 @@ kotlin { @OptIn(ExperimentalWasmDsl::class) wasmJs { // Adding moduleName as a workaround for this issue: https://youtrack.jetbrains.com/issue/KT-51942 - moduleName = "appyx-components-experimental-promoter-commons" + moduleName = "appyx-components-experimental-promoter-commons-wa" browser() } iosX64() diff --git a/appyx-components/experimental/puzzle15/common/build.gradle.kts b/appyx-components/experimental/puzzle15/common/build.gradle.kts index 664c2d9ef..587c75601 100644 --- a/appyx-components/experimental/puzzle15/common/build.gradle.kts +++ b/appyx-components/experimental/puzzle15/common/build.gradle.kts @@ -30,7 +30,7 @@ kotlin { @OptIn(ExperimentalWasmDsl::class) wasmJs { // Adding moduleName as a workaround for this issue: https://youtrack.jetbrains.com/issue/KT-51942 - moduleName = "appyx-components-experimental-puzzle15-commons" + moduleName = "appyx-components-experimental-puzzle15-commons-wa" browser() } sourceSets { diff --git a/appyx-components/experimental/puzzle15/web/build.gradle.kts b/appyx-components/experimental/puzzle15/web/build.gradle.kts index 2dda95172..19f8f0a14 100644 --- a/appyx-components/experimental/puzzle15/web/build.gradle.kts +++ b/appyx-components/experimental/puzzle15/web/build.gradle.kts @@ -13,7 +13,7 @@ kotlin { } @OptIn(ExperimentalWasmDsl::class) wasmJs { - moduleName = "appyx-components-experimental-puzzle15-web" + moduleName = "appyx-components-experimental-puzzle15-web-wa" browser() binaries.executable() } diff --git a/appyx-components/internal/test-drive/common/build.gradle.kts b/appyx-components/internal/test-drive/common/build.gradle.kts index 998874b3b..9d2654cc9 100644 --- a/appyx-components/internal/test-drive/common/build.gradle.kts +++ b/appyx-components/internal/test-drive/common/build.gradle.kts @@ -29,7 +29,7 @@ kotlin { @OptIn(ExperimentalWasmDsl::class) wasmJs { // Adding moduleName as a workaround for this issue: https://youtrack.jetbrains.com/issue/KT-51942 - moduleName = "appyx-components-internal-testdrive-common" + moduleName = "appyx-components-internal-testdrive-common-wa" browser() } sourceSets { diff --git a/appyx-components/standard/backstack/common/build.gradle.kts b/appyx-components/standard/backstack/common/build.gradle.kts index 4c04366bd..2ba40769f 100644 --- a/appyx-components/standard/backstack/common/build.gradle.kts +++ b/appyx-components/standard/backstack/common/build.gradle.kts @@ -30,7 +30,7 @@ kotlin { @OptIn(ExperimentalWasmDsl::class) wasmJs { // Adding moduleName as a workaround for this issue: https://youtrack.jetbrains.com/issue/KT-51942 - moduleName = "appyx-components-stable-backstack-commons" + moduleName = "appyx-components-stable-backstack-commons-wa" browser() } diff --git a/appyx-components/standard/spotlight/common/build.gradle.kts b/appyx-components/standard/spotlight/common/build.gradle.kts index b28b77529..656472b8d 100644 --- a/appyx-components/standard/spotlight/common/build.gradle.kts +++ b/appyx-components/standard/spotlight/common/build.gradle.kts @@ -30,7 +30,7 @@ kotlin { @OptIn(ExperimentalWasmDsl::class) wasmJs { // Adding moduleName as a workaround for this issue: https://youtrack.jetbrains.com/issue/KT-51942 - moduleName = "appyx-components-stable-spotlight-commons" + moduleName = "appyx-components-stable-spotlight-commons-wa" browser() } diff --git a/appyx-interactions/common/build.gradle.kts b/appyx-interactions/common/build.gradle.kts index 5d569ba43..20b657ec0 100644 --- a/appyx-interactions/common/build.gradle.kts +++ b/appyx-interactions/common/build.gradle.kts @@ -30,7 +30,7 @@ kotlin { @OptIn(ExperimentalWasmDsl::class) wasmJs { // Adding moduleName as a workaround for this issue: https://youtrack.jetbrains.com/issue/KT-51942 - moduleName = "appyx-interactions-common" + moduleName = "appyx-interactions-common-wa" browser() } diff --git a/appyx-navigation/common/build.gradle.kts b/appyx-navigation/common/build.gradle.kts index 7bdc432fd..134e63a6a 100644 --- a/appyx-navigation/common/build.gradle.kts +++ b/appyx-navigation/common/build.gradle.kts @@ -28,7 +28,7 @@ kotlin { } wasmJs { // Adding moduleName as a workaround for this issue: https://youtrack.jetbrains.com/issue/KT-51942 - moduleName = "appyx-navigation-common" + moduleName = "appyx-navigation-common-wa" browser() } diff --git a/demos/appyx-interactions/web/build.gradle.kts b/demos/appyx-interactions/web/build.gradle.kts index e2dd99fe0..e9eb0d04e 100644 --- a/demos/appyx-interactions/web/build.gradle.kts +++ b/demos/appyx-interactions/web/build.gradle.kts @@ -13,7 +13,7 @@ kotlin { } @OptIn(ExperimentalWasmDsl::class) wasmJs { - moduleName = "appyx-demos-interactions-web" + moduleName = "appyx-demos-interactions-web-wa" browser() binaries.executable() } diff --git a/demos/appyx-navigation/common/build.gradle.kts b/demos/appyx-navigation/common/build.gradle.kts index d02b27822..293f0c9ad 100644 --- a/demos/appyx-navigation/common/build.gradle.kts +++ b/demos/appyx-navigation/common/build.gradle.kts @@ -29,7 +29,7 @@ kotlin { @OptIn(ExperimentalWasmDsl::class) wasmJs { // Adding moduleName as a workaround for this issue: https://youtrack.jetbrains.com/issue/KT-51942 - moduleName = "demo-appyx-navigation-common" + moduleName = "demo-appyx-navigation-common-wa" browser() } iosX64() diff --git a/demos/appyx-navigation/web/build.gradle.kts b/demos/appyx-navigation/web/build.gradle.kts index 9c5166260..4bdfbdcc4 100644 --- a/demos/appyx-navigation/web/build.gradle.kts +++ b/demos/appyx-navigation/web/build.gradle.kts @@ -14,7 +14,7 @@ kotlin { } @OptIn(ExperimentalWasmDsl::class) wasmJs { - moduleName = "appyx-demos-navigation-web" + moduleName = "appyx-demos-navigation-web-wa" browser() binaries.executable() } diff --git a/demos/common/build.gradle.kts b/demos/common/build.gradle.kts index eb6bab564..93d7c4d60 100644 --- a/demos/common/build.gradle.kts +++ b/demos/common/build.gradle.kts @@ -28,7 +28,7 @@ kotlin { @OptIn(ExperimentalWasmDsl::class) wasmJs { // Adding moduleName as a workaround for this issue: https://youtrack.jetbrains.com/issue/KT-51942 - moduleName = "appyx-demos-commons" + moduleName = "appyx-demos-commons-wa" browser() } iosX64() diff --git a/demos/image-loader/common/build.gradle.kts b/demos/image-loader/common/build.gradle.kts index fef202c5c..8b7a3ebe4 100644 --- a/demos/image-loader/common/build.gradle.kts +++ b/demos/image-loader/common/build.gradle.kts @@ -27,7 +27,7 @@ kotlin { @OptIn(ExperimentalWasmDsl::class) wasmJs { // Adding moduleName as a workaround for this issue: https://youtrack.jetbrains.com/issue/KT-51942 - moduleName = "appyx-navigation-imageloader" + moduleName = "appyx-navigation-imageloader-wa" browser() } diff --git a/demos/mkdocs/appyx-components/backstack/fader/web/build.gradle.kts b/demos/mkdocs/appyx-components/backstack/fader/web/build.gradle.kts index 52f832bf1..e399717bf 100644 --- a/demos/mkdocs/appyx-components/backstack/fader/web/build.gradle.kts +++ b/demos/mkdocs/appyx-components/backstack/fader/web/build.gradle.kts @@ -14,7 +14,7 @@ kotlin { } @OptIn(ExperimentalWasmDsl::class) wasmJs { - moduleName = "appyx-demos-backstack-fader-web" + moduleName = "appyx-demos-backstack-fader-web-wa" browser() binaries.executable() } diff --git a/demos/mkdocs/appyx-components/backstack/parallax/web/build.gradle.kts b/demos/mkdocs/appyx-components/backstack/parallax/web/build.gradle.kts index a5e71544e..712941565 100644 --- a/demos/mkdocs/appyx-components/backstack/parallax/web/build.gradle.kts +++ b/demos/mkdocs/appyx-components/backstack/parallax/web/build.gradle.kts @@ -14,7 +14,7 @@ kotlin { } @OptIn(ExperimentalWasmDsl::class) wasmJs { - moduleName = "appyx-demos-backstack-parallax-web" + moduleName = "appyx-demos-backstack-parallax-web-wa" browser() binaries.executable() } diff --git a/demos/mkdocs/appyx-components/backstack/slider/web/build.gradle.kts b/demos/mkdocs/appyx-components/backstack/slider/web/build.gradle.kts index 78b1a43f6..8539ded26 100644 --- a/demos/mkdocs/appyx-components/backstack/slider/web/build.gradle.kts +++ b/demos/mkdocs/appyx-components/backstack/slider/web/build.gradle.kts @@ -14,7 +14,7 @@ kotlin { } @OptIn(ExperimentalWasmDsl::class) wasmJs { - moduleName = "appyx-demos-backstack-slider-web" + moduleName = "appyx-demos-backstack-slider-web-wa" browser() binaries.executable() } diff --git a/demos/mkdocs/appyx-components/backstack/stack3d/web/build.gradle.kts b/demos/mkdocs/appyx-components/backstack/stack3d/web/build.gradle.kts index bfa526112..81ad02377 100644 --- a/demos/mkdocs/appyx-components/backstack/stack3d/web/build.gradle.kts +++ b/demos/mkdocs/appyx-components/backstack/stack3d/web/build.gradle.kts @@ -14,7 +14,7 @@ kotlin { } @OptIn(ExperimentalWasmDsl::class) wasmJs { - moduleName = "appyx-demos-backstack-stack3d-web" + moduleName = "appyx-demos-backstack-stack3d-web-wa" browser() binaries.executable() } diff --git a/demos/mkdocs/appyx-components/common/build.gradle.kts b/demos/mkdocs/appyx-components/common/build.gradle.kts index 1044b6bf2..2c5656e2a 100644 --- a/demos/mkdocs/appyx-components/common/build.gradle.kts +++ b/demos/mkdocs/appyx-components/common/build.gradle.kts @@ -14,7 +14,7 @@ kotlin { } @OptIn(ExperimentalWasmDsl::class) wasmJs { - moduleName = "demos-mkdocs-appyx-components-common" + moduleName = "demos-mkdocs-appyx-components-common-wa" browser() binaries.executable() } diff --git a/demos/mkdocs/appyx-components/experimental/datingcards/web/build.gradle.kts b/demos/mkdocs/appyx-components/experimental/datingcards/web/build.gradle.kts index 1b7b0d658..410f86a4d 100644 --- a/demos/mkdocs/appyx-components/experimental/datingcards/web/build.gradle.kts +++ b/demos/mkdocs/appyx-components/experimental/datingcards/web/build.gradle.kts @@ -14,7 +14,7 @@ kotlin { } @OptIn(ExperimentalWasmDsl::class) wasmJs { - moduleName = "appyx-demos-experimental-datingcards-web" + moduleName = "appyx-demos-experimental-datingcards-web-wa" browser() binaries.executable() } diff --git a/demos/mkdocs/appyx-components/experimental/puzzle15/web/build.gradle.kts b/demos/mkdocs/appyx-components/experimental/puzzle15/web/build.gradle.kts index d1ae761de..d246145d1 100644 --- a/demos/mkdocs/appyx-components/experimental/puzzle15/web/build.gradle.kts +++ b/demos/mkdocs/appyx-components/experimental/puzzle15/web/build.gradle.kts @@ -14,7 +14,7 @@ kotlin { } @OptIn(ExperimentalWasmDsl::class) wasmJs { - moduleName = "appyx-demos-experimental-puzzle15-web" + moduleName = "appyx-demos-experimental-puzzle15-web-wa" browser() binaries.executable() } diff --git a/demos/mkdocs/appyx-components/spotlight/fader/web/build.gradle.kts b/demos/mkdocs/appyx-components/spotlight/fader/web/build.gradle.kts index 591ff0435..f1c72080e 100644 --- a/demos/mkdocs/appyx-components/spotlight/fader/web/build.gradle.kts +++ b/demos/mkdocs/appyx-components/spotlight/fader/web/build.gradle.kts @@ -14,7 +14,7 @@ kotlin { } @OptIn(ExperimentalWasmDsl::class) wasmJs { - moduleName = "appyx-components-spotlight-fader-web" + moduleName = "appyx-components-spotlight-fader-web-wa" browser() binaries.executable() } diff --git a/demos/mkdocs/appyx-components/spotlight/slider/web/build.gradle.kts b/demos/mkdocs/appyx-components/spotlight/slider/web/build.gradle.kts index c24879ef6..b8c2a1102 100644 --- a/demos/mkdocs/appyx-components/spotlight/slider/web/build.gradle.kts +++ b/demos/mkdocs/appyx-components/spotlight/slider/web/build.gradle.kts @@ -14,7 +14,7 @@ kotlin { } @OptIn(ExperimentalWasmDsl::class) wasmJs { - moduleName = "appyx-components-spotlight-slider-web" + moduleName = "appyx-components-spotlight-slider-web-wa" browser() binaries.executable() } diff --git a/demos/mkdocs/appyx-components/spotlight/sliderrotation/web/build.gradle.kts b/demos/mkdocs/appyx-components/spotlight/sliderrotation/web/build.gradle.kts index b2f0e1476..2441aa256 100644 --- a/demos/mkdocs/appyx-components/spotlight/sliderrotation/web/build.gradle.kts +++ b/demos/mkdocs/appyx-components/spotlight/sliderrotation/web/build.gradle.kts @@ -14,7 +14,7 @@ kotlin { } @OptIn(ExperimentalWasmDsl::class) wasmJs { - moduleName = "appyx-components-spotlight-slider-rotation-web" + moduleName = "appyx-components-spotlight-slider-rotation-web-wa" browser() binaries.executable() } diff --git a/demos/mkdocs/appyx-components/spotlight/sliderscale/web/build.gradle.kts b/demos/mkdocs/appyx-components/spotlight/sliderscale/web/build.gradle.kts index 85efadf01..faec8ed02 100644 --- a/demos/mkdocs/appyx-components/spotlight/sliderscale/web/build.gradle.kts +++ b/demos/mkdocs/appyx-components/spotlight/sliderscale/web/build.gradle.kts @@ -14,7 +14,7 @@ kotlin { } @OptIn(ExperimentalWasmDsl::class) wasmJs { - moduleName = "appyx-components-spotlight-slider-scale-web" + moduleName = "appyx-components-spotlight-slider-scale-web-wa" browser() binaries.executable() } diff --git a/demos/mkdocs/appyx-components/spotlight/stack3d/web/build.gradle.kts b/demos/mkdocs/appyx-components/spotlight/stack3d/web/build.gradle.kts index 9073ed4ca..6d2cf60d9 100644 --- a/demos/mkdocs/appyx-components/spotlight/stack3d/web/build.gradle.kts +++ b/demos/mkdocs/appyx-components/spotlight/stack3d/web/build.gradle.kts @@ -14,7 +14,7 @@ kotlin { } @OptIn(ExperimentalWasmDsl::class) wasmJs { - moduleName = "appyx-components-spotlight-stack3d-web" + moduleName = "appyx-components-spotlight-stack3d-web-wa" browser() binaries.executable() } diff --git a/demos/mkdocs/appyx-interactions/gestures/dragprediction/web/build.gradle.kts b/demos/mkdocs/appyx-interactions/gestures/dragprediction/web/build.gradle.kts index 2a746be35..948c40979 100644 --- a/demos/mkdocs/appyx-interactions/gestures/dragprediction/web/build.gradle.kts +++ b/demos/mkdocs/appyx-interactions/gestures/dragprediction/web/build.gradle.kts @@ -14,7 +14,7 @@ kotlin { } @OptIn(ExperimentalWasmDsl::class) wasmJs { - moduleName = "appyx-interactions-gestures-dragpredication-web" + moduleName = "appyx-interactions-gestures-dragpredication-web-wa" browser() binaries.executable() } diff --git a/demos/mkdocs/appyx-interactions/gestures/incompletedrag/web/build.gradle.kts b/demos/mkdocs/appyx-interactions/gestures/incompletedrag/web/build.gradle.kts index 1ecb6dc20..ecbd0dab2 100644 --- a/demos/mkdocs/appyx-interactions/gestures/incompletedrag/web/build.gradle.kts +++ b/demos/mkdocs/appyx-interactions/gestures/incompletedrag/web/build.gradle.kts @@ -14,7 +14,7 @@ kotlin { } @OptIn(ExperimentalWasmDsl::class) wasmJs { - moduleName = "appyx-interactions-gestures-incompletedrag-web" + moduleName = "appyx-interactions-gestures-incompletedrag-web-wa" browser() binaries.executable() } diff --git a/demos/mkdocs/appyx-interactions/interactions/observemp/web/build.gradle.kts b/demos/mkdocs/appyx-interactions/interactions/observemp/web/build.gradle.kts index 365bb4bc0..973a3c9c5 100644 --- a/demos/mkdocs/appyx-interactions/interactions/observemp/web/build.gradle.kts +++ b/demos/mkdocs/appyx-interactions/interactions/observemp/web/build.gradle.kts @@ -14,7 +14,7 @@ kotlin { } @OptIn(ExperimentalWasmDsl::class) wasmJs { - moduleName = "appyx-interactions-observemp-web" + moduleName = "appyx-interactions-observemp-web-wa" browser() binaries.executable() } diff --git a/demos/mkdocs/appyx-interactions/interactions/sample1/web/build.gradle.kts b/demos/mkdocs/appyx-interactions/interactions/sample1/web/build.gradle.kts index 08d56896f..ae944b7d6 100644 --- a/demos/mkdocs/appyx-interactions/interactions/sample1/web/build.gradle.kts +++ b/demos/mkdocs/appyx-interactions/interactions/sample1/web/build.gradle.kts @@ -14,7 +14,7 @@ kotlin { } @OptIn(ExperimentalWasmDsl::class) wasmJs { - moduleName = "appyx-interactions-sample1-web" + moduleName = "appyx-interactions-sample1-web-wa" browser() binaries.executable() } diff --git a/demos/mkdocs/appyx-interactions/interactions/sample2/web/build.gradle.kts b/demos/mkdocs/appyx-interactions/interactions/sample2/web/build.gradle.kts index 114aa1d4c..958f2468d 100644 --- a/demos/mkdocs/appyx-interactions/interactions/sample2/web/build.gradle.kts +++ b/demos/mkdocs/appyx-interactions/interactions/sample2/web/build.gradle.kts @@ -14,7 +14,7 @@ kotlin { } @OptIn(ExperimentalWasmDsl::class) wasmJs { - moduleName = "appyx-interactions-sample2-web" + moduleName = "appyx-interactions-sample2-web-wa" browser() binaries.executable() } diff --git a/demos/mkdocs/appyx-interactions/interactions/sample3/web/build.gradle.kts b/demos/mkdocs/appyx-interactions/interactions/sample3/web/build.gradle.kts index d0200f56f..24ea6496d 100644 --- a/demos/mkdocs/appyx-interactions/interactions/sample3/web/build.gradle.kts +++ b/demos/mkdocs/appyx-interactions/interactions/sample3/web/build.gradle.kts @@ -14,7 +14,7 @@ kotlin { } @OptIn(ExperimentalWasmDsl::class) wasmJs { - moduleName = "appyx-interactions-sample3-web" + moduleName = "appyx-interactions-sample3-web-wa" browser() binaries.executable() } diff --git a/demos/mkdocs/common/build.gradle.kts b/demos/mkdocs/common/build.gradle.kts index 61df1d418..e8dc40a73 100644 --- a/demos/mkdocs/common/build.gradle.kts +++ b/demos/mkdocs/common/build.gradle.kts @@ -13,7 +13,7 @@ kotlin { } @OptIn(ExperimentalWasmDsl::class) wasmJs { - moduleName = "demos-mkdocs-common" + moduleName = "demos-mkdocs-common-wa" browser() binaries.executable() } diff --git a/demos/sandbox-appyx-navigation/common/build.gradle.kts b/demos/sandbox-appyx-navigation/common/build.gradle.kts index 208607e11..a90077d57 100644 --- a/demos/sandbox-appyx-navigation/common/build.gradle.kts +++ b/demos/sandbox-appyx-navigation/common/build.gradle.kts @@ -29,7 +29,7 @@ kotlin { @OptIn(ExperimentalWasmDsl::class) wasmJs { // Adding moduleName as a workaround for this issue: https://youtrack.jetbrains.com/issue/KT-51942 - moduleName = "demo-sandbox-appyx-navigation-common" + moduleName = "demo-sandbox-appyx-navigation-common-wa" browser() } iosX64() diff --git a/demos/sandbox-appyx-navigation/web/build.gradle.kts b/demos/sandbox-appyx-navigation/web/build.gradle.kts index 73aded20d..e594a4a94 100644 --- a/demos/sandbox-appyx-navigation/web/build.gradle.kts +++ b/demos/sandbox-appyx-navigation/web/build.gradle.kts @@ -13,7 +13,7 @@ kotlin { } @OptIn(ExperimentalWasmDsl::class) wasmJs { - moduleName = "appyx-demos-sandbox-navigation-web" + moduleName = "appyx-demos-sandbox-navigation-web-wa" browser() binaries.executable() } diff --git a/utils/customisations/build.gradle.kts b/utils/customisations/build.gradle.kts index 6ef9d4805..5120c0ef5 100644 --- a/utils/customisations/build.gradle.kts +++ b/utils/customisations/build.gradle.kts @@ -27,7 +27,7 @@ kotlin { @OptIn(ExperimentalWasmDsl::class) wasmJs { // Adding moduleName as a workaround for this issue: https://youtrack.jetbrains.com/issue/KT-51942 - moduleName = "appyx-utils-customisation" + moduleName = "appyx-utils-customisation-wa" browser() } iosX64() diff --git a/utils/material3/build.gradle.kts b/utils/material3/build.gradle.kts index 7fa5e0b1a..d68488287 100644 --- a/utils/material3/build.gradle.kts +++ b/utils/material3/build.gradle.kts @@ -28,7 +28,7 @@ kotlin { @OptIn(ExperimentalWasmDsl::class) wasmJs { // Adding moduleName as a workaround for this issue: https://youtrack.jetbrains.com/issue/KT-51942 - moduleName = "appyx-utils-material3" + moduleName = "appyx-utils-material3-wa" browser() } iosX64() diff --git a/utils/multiplatform/build.gradle.kts b/utils/multiplatform/build.gradle.kts index 158f6bc6d..a01d051a7 100644 --- a/utils/multiplatform/build.gradle.kts +++ b/utils/multiplatform/build.gradle.kts @@ -29,7 +29,7 @@ kotlin { @OptIn(ExperimentalWasmDsl::class) wasmJs { // Adding moduleName as a workaround for this issue: https://youtrack.jetbrains.com/issue/KT-51942 - moduleName = "appyx-utils-multiplatform" + moduleName = "appyx-utils-multiplatform-wa" browser() } iosX64() From ac83aa4e81d4210f92f03be05b082d87097dbcf3 Mon Sep 17 00:00:00 2001 From: Manel Martos Date: Thu, 4 Apr 2024 14:55:59 +0200 Subject: [PATCH 4/7] Fix wasm config issues related to tests --- .../cards/common/build.gradle.kts | 16 +++- .../common/karma.config.d/wasm/config.js | 55 +++++++++++++ .../promoter/common/build.gradle.kts | 16 +++- .../common/karma.config.d/wasm/config.js | 55 +++++++++++++ .../puzzle15/common/build.gradle.kts | 16 +++- .../common/karma.config.d/wasm/config.js | 55 +++++++++++++ .../puzzle15/web/build.gradle.kts | 10 ++- .../web/karma.config.d/wasm/config.js | 55 +++++++++++++ .../test-drive/common/build.gradle.kts | 16 +++- .../common/karma.config.d/wasm/config.js | 55 +++++++++++++ .../backstack/common/build.gradle.kts | 17 +++- .../common/karma.config.d/wasm/config.js | 55 +++++++++++++ .../spotlight/common/build.gradle.kts | 16 +++- .../common/karma.config.d/wasm/config.js | 55 +++++++++++++ appyx-interactions/common/build.gradle.kts | 16 +++- .../common/karma.config.d/wasm/config.js | 55 +++++++++++++ appyx-navigation/common/build.gradle.kts | 14 +++- .../integration/BrowserViewportWindow.kt | 81 ------------------- .../integration/BrowserViewportWindow.kt | 81 ------------------- demos/appyx-interactions/web/build.gradle.kts | 10 ++- .../web/karma.config.d/wasm/config.js | 55 +++++++++++++ .../appyx-navigation/common/build.gradle.kts | 16 +++- .../common/karma.config.d/wasm/config.js | 55 +++++++++++++ demos/appyx-navigation/web/build.gradle.kts | 14 +++- .../web/karma.config.d/wasm/config.js | 55 +++++++++++++ demos/common/build.gradle.kts | 16 +++- demos/common/karma.config.d/wasm/config.js | 55 +++++++++++++ demos/image-loader/common/build.gradle.kts | 16 +++- .../common/karma.config.d/wasm/config.js | 55 +++++++++++++ .../backstack/fader/web/build.gradle.kts | 15 +++- .../fader/web/karma.config.d/wasm/config.js | 55 +++++++++++++ .../backstack/parallax/web/build.gradle.kts | 10 ++- .../web/karma.config.d/wasm/config.js | 55 +++++++++++++ .../backstack/slider/web/build.gradle.kts | 10 ++- .../slider/web/karma.config.d/wasm/config.js | 55 +++++++++++++ .../backstack/stack3d/web/build.gradle.kts | 10 ++- .../stack3d/web/karma.config.d/wasm/config.js | 55 +++++++++++++ .../appyx-components/common/build.gradle.kts | 10 ++- .../common/karma.config.d/wasm/config.js | 55 +++++++++++++ .../datingcards/web/build.gradle.kts | 10 ++- .../web/karma.config.d/wasm/config.js | 55 +++++++++++++ .../puzzle15/web/build.gradle.kts | 10 ++- .../web/karma.config.d/wasm/config.js | 55 +++++++++++++ .../spotlight/fader/web/build.gradle.kts | 10 ++- .../fader/web/karma.config.d/wasm/config.js | 55 +++++++++++++ .../spotlight/slider/web/build.gradle.kts | 10 ++- .../slider/web/karma.config.d/wasm/config.js | 55 +++++++++++++ .../sliderrotation/web/build.gradle.kts | 10 ++- .../web/karma.config.d/wasm/config.js | 55 +++++++++++++ .../sliderscale/web/build.gradle.kts | 10 ++- .../web/karma.config.d/wasm/config.js | 55 +++++++++++++ .../spotlight/stack3d/web/build.gradle.kts | 10 ++- .../stack3d/web/karma.config.d/wasm/config.js | 55 +++++++++++++ .../dragprediction/web/build.gradle.kts | 10 ++- .../web/karma.config.d/wasm/config.js | 55 +++++++++++++ .../incompletedrag/web/build.gradle.kts | 10 ++- .../web/karma.config.d/wasm/config.js | 55 +++++++++++++ .../observemp/web/build.gradle.kts | 10 ++- .../web/karma.config.d/wasm/config.js | 55 +++++++++++++ .../interactions/sample1/web/build.gradle.kts | 10 ++- .../sample1/web/karma.config.d/wasm/config.js | 55 +++++++++++++ .../interactions/sample2/web/build.gradle.kts | 10 ++- .../sample2/web/karma.config.d/wasm/config.js | 55 +++++++++++++ .../interactions/sample3/web/build.gradle.kts | 10 ++- .../sample3/web/karma.config.d/wasm/config.js | 55 +++++++++++++ demos/mkdocs/common/build.gradle.kts | 10 ++- .../common/karma.config.d/wasm/config.js | 55 +++++++++++++ .../common/build.gradle.kts | 16 +++- .../common/karma.config.d/wasm/config.js | 55 +++++++++++++ .../web/build.gradle.kts | 10 ++- .../web/karma.config.d/wasm/config.js | 55 +++++++++++++ .../appyx/demos/sandbox/navigation/Main.kt | 5 +- .../appyx/demos/sandbox/navigation/Main.kt | 5 +- documentation/navigation/multiplatform.md | 2 +- utils/customisations/build.gradle.kts | 14 +++- .../karma.config.d/wasm/config.js | 55 +++++++++++++ utils/material3/build.gradle.kts | 16 +++- utils/material3/karma.config.d/wasm/config.js | 55 +++++++++++++ utils/multiplatform/build.gradle.kts | 12 ++- .../karma.config.d/wasm/config.js | 55 +++++++++++++ 80 files changed, 2473 insertions(+), 208 deletions(-) create mode 100644 appyx-components/experimental/cards/common/karma.config.d/wasm/config.js create mode 100644 appyx-components/experimental/promoter/common/karma.config.d/wasm/config.js create mode 100644 appyx-components/experimental/puzzle15/common/karma.config.d/wasm/config.js create mode 100644 appyx-components/experimental/puzzle15/web/karma.config.d/wasm/config.js create mode 100644 appyx-components/internal/test-drive/common/karma.config.d/wasm/config.js create mode 100644 appyx-components/standard/backstack/common/karma.config.d/wasm/config.js create mode 100644 appyx-components/standard/spotlight/common/karma.config.d/wasm/config.js create mode 100644 appyx-interactions/common/karma.config.d/wasm/config.js delete mode 100644 appyx-navigation/common/src/jsMain/kotlin/com/bumble/appyx/navigation/integration/BrowserViewportWindow.kt delete mode 100644 appyx-navigation/common/src/wasmJsMain/kotlin/com/bumble/appyx/navigation/integration/BrowserViewportWindow.kt create mode 100644 demos/appyx-interactions/web/karma.config.d/wasm/config.js create mode 100644 demos/appyx-navigation/common/karma.config.d/wasm/config.js create mode 100644 demos/appyx-navigation/web/karma.config.d/wasm/config.js create mode 100644 demos/common/karma.config.d/wasm/config.js create mode 100644 demos/image-loader/common/karma.config.d/wasm/config.js create mode 100644 demos/mkdocs/appyx-components/backstack/fader/web/karma.config.d/wasm/config.js create mode 100644 demos/mkdocs/appyx-components/backstack/parallax/web/karma.config.d/wasm/config.js create mode 100644 demos/mkdocs/appyx-components/backstack/slider/web/karma.config.d/wasm/config.js create mode 100644 demos/mkdocs/appyx-components/backstack/stack3d/web/karma.config.d/wasm/config.js create mode 100644 demos/mkdocs/appyx-components/common/karma.config.d/wasm/config.js create mode 100644 demos/mkdocs/appyx-components/experimental/datingcards/web/karma.config.d/wasm/config.js create mode 100644 demos/mkdocs/appyx-components/experimental/puzzle15/web/karma.config.d/wasm/config.js create mode 100644 demos/mkdocs/appyx-components/spotlight/fader/web/karma.config.d/wasm/config.js create mode 100644 demos/mkdocs/appyx-components/spotlight/slider/web/karma.config.d/wasm/config.js create mode 100644 demos/mkdocs/appyx-components/spotlight/sliderrotation/web/karma.config.d/wasm/config.js create mode 100644 demos/mkdocs/appyx-components/spotlight/sliderscale/web/karma.config.d/wasm/config.js create mode 100644 demos/mkdocs/appyx-components/spotlight/stack3d/web/karma.config.d/wasm/config.js create mode 100644 demos/mkdocs/appyx-interactions/gestures/dragprediction/web/karma.config.d/wasm/config.js create mode 100644 demos/mkdocs/appyx-interactions/gestures/incompletedrag/web/karma.config.d/wasm/config.js create mode 100644 demos/mkdocs/appyx-interactions/interactions/observemp/web/karma.config.d/wasm/config.js create mode 100644 demos/mkdocs/appyx-interactions/interactions/sample1/web/karma.config.d/wasm/config.js create mode 100644 demos/mkdocs/appyx-interactions/interactions/sample2/web/karma.config.d/wasm/config.js create mode 100644 demos/mkdocs/appyx-interactions/interactions/sample3/web/karma.config.d/wasm/config.js create mode 100644 demos/mkdocs/common/karma.config.d/wasm/config.js create mode 100644 demos/sandbox-appyx-navigation/common/karma.config.d/wasm/config.js create mode 100644 demos/sandbox-appyx-navigation/web/karma.config.d/wasm/config.js create mode 100644 utils/customisations/karma.config.d/wasm/config.js create mode 100644 utils/material3/karma.config.d/wasm/config.js create mode 100644 utils/multiplatform/karma.config.d/wasm/config.js diff --git a/appyx-components/experimental/cards/common/build.gradle.kts b/appyx-components/experimental/cards/common/build.gradle.kts index 4b8bd23a7..dc1a08eb0 100644 --- a/appyx-components/experimental/cards/common/build.gradle.kts +++ b/appyx-components/experimental/cards/common/build.gradle.kts @@ -26,12 +26,22 @@ kotlin { // Adding moduleName as a workaround for this issue: https://youtrack.jetbrains.com/issue/KT-51942 moduleName = "appyx-components-experimental-cards-commons" browser() + binaries.executable() } @OptIn(ExperimentalWasmDsl::class) wasmJs { // Adding moduleName as a workaround for this issue: https://youtrack.jetbrains.com/issue/KT-51942 moduleName = "appyx-components-experimental-cards-commons-wa" - browser() + browser { + // Refer to this Slack thread for more details: https://kotlinlang.slack.com/archives/CDFP59223/p1702977410505449?thread_ts=1702668737.674499&cid=CDFP59223 + testTask { + useKarma { + useChromeHeadless() + useConfigDirectory(project.projectDir.resolve("karma.config.d").resolve("wasm")) + } + } + } + binaries.executable() } iosX64() iosArm64() @@ -64,6 +74,10 @@ kotlin { } } +compose.experimental { + web.application {} +} + dependencies { add("kspCommonMainMetadata", project(":ksp:appyx-processor")) add("kspAndroid", project(":ksp:appyx-processor")) diff --git a/appyx-components/experimental/cards/common/karma.config.d/wasm/config.js b/appyx-components/experimental/cards/common/karma.config.d/wasm/config.js new file mode 100644 index 000000000..22429e585 --- /dev/null +++ b/appyx-components/experimental/cards/common/karma.config.d/wasm/config.js @@ -0,0 +1,55 @@ +// see https://kotlinlang.org/docs/js-project-setup.html#webpack-configuration-file +// This file provides karma.config.d configuration to run tests with k/wasm + +const path = require("path"); + +config.browserConsoleLogOptions.level = "debug"; + +const basePath = config.basePath; +const projectPath = path.resolve(basePath, "..", "..", "..", ".."); +const generatedAssetsPath = path.resolve(projectPath, "build", "karma-webpack-out") + +const debug = message => console.log(`[karma-config] ${message}`); + +debug(`karma basePath: ${basePath}`); +debug(`karma generatedAssetsPath: ${generatedAssetsPath}`); + +config.proxies["/"] = path.resolve(basePath, "kotlin"); + +config.files = [ + {pattern: path.resolve(generatedAssetsPath, "**/*"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.png"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.gif"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.ttf"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.txt"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.json"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.xml"), included: false, served: true, watched: false}, +].concat(config.files); + +function KarmaWebpackOutputFramework(config) { + // This controller is instantiated and set during the preprocessor phase. + const controller = config.__karmaWebpackController; + + // only if webpack has instantiated its controller + if (!controller) { + console.warn( + "Webpack has not instantiated controller yet.\n" + + "Check if you have enabled webpack preprocessor and framework before this framework" + ) + return + } + + config.files.push({ + pattern: `${controller.outputPath}/**/*`, + included: false, + served: true, + watched: false + }) +} + +const KarmaWebpackOutputPlugin = { + 'framework:webpack-output': ['factory', KarmaWebpackOutputFramework], +}; + +config.plugins.push(KarmaWebpackOutputPlugin); +config.frameworks.push("webpack-output"); \ No newline at end of file diff --git a/appyx-components/experimental/promoter/common/build.gradle.kts b/appyx-components/experimental/promoter/common/build.gradle.kts index 5ffb02b4b..54ffbbef3 100644 --- a/appyx-components/experimental/promoter/common/build.gradle.kts +++ b/appyx-components/experimental/promoter/common/build.gradle.kts @@ -26,12 +26,22 @@ kotlin { // Adding moduleName as a workaround for this issue: https://youtrack.jetbrains.com/issue/KT-51942 moduleName = "appyx-components-experimental-promoter-commons" browser() + binaries.executable() } @OptIn(ExperimentalWasmDsl::class) wasmJs { // Adding moduleName as a workaround for this issue: https://youtrack.jetbrains.com/issue/KT-51942 moduleName = "appyx-components-experimental-promoter-commons-wa" - browser() + browser { + // Refer to this Slack thread for more details: https://kotlinlang.slack.com/archives/CDFP59223/p1702977410505449?thread_ts=1702668737.674499&cid=CDFP59223 + testTask { + useKarma { + useChromeHeadless() + useConfigDirectory(project.projectDir.resolve("karma.config.d").resolve("wasm")) + } + } + } + binaries.executable() } iosX64() iosArm64() @@ -65,6 +75,10 @@ kotlin { } } +compose.experimental { + web.application {} +} + dependencies { add("kspCommonMainMetadata", project(":ksp:appyx-processor")) add("kspAndroid", project(":ksp:appyx-processor")) diff --git a/appyx-components/experimental/promoter/common/karma.config.d/wasm/config.js b/appyx-components/experimental/promoter/common/karma.config.d/wasm/config.js new file mode 100644 index 000000000..22429e585 --- /dev/null +++ b/appyx-components/experimental/promoter/common/karma.config.d/wasm/config.js @@ -0,0 +1,55 @@ +// see https://kotlinlang.org/docs/js-project-setup.html#webpack-configuration-file +// This file provides karma.config.d configuration to run tests with k/wasm + +const path = require("path"); + +config.browserConsoleLogOptions.level = "debug"; + +const basePath = config.basePath; +const projectPath = path.resolve(basePath, "..", "..", "..", ".."); +const generatedAssetsPath = path.resolve(projectPath, "build", "karma-webpack-out") + +const debug = message => console.log(`[karma-config] ${message}`); + +debug(`karma basePath: ${basePath}`); +debug(`karma generatedAssetsPath: ${generatedAssetsPath}`); + +config.proxies["/"] = path.resolve(basePath, "kotlin"); + +config.files = [ + {pattern: path.resolve(generatedAssetsPath, "**/*"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.png"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.gif"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.ttf"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.txt"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.json"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.xml"), included: false, served: true, watched: false}, +].concat(config.files); + +function KarmaWebpackOutputFramework(config) { + // This controller is instantiated and set during the preprocessor phase. + const controller = config.__karmaWebpackController; + + // only if webpack has instantiated its controller + if (!controller) { + console.warn( + "Webpack has not instantiated controller yet.\n" + + "Check if you have enabled webpack preprocessor and framework before this framework" + ) + return + } + + config.files.push({ + pattern: `${controller.outputPath}/**/*`, + included: false, + served: true, + watched: false + }) +} + +const KarmaWebpackOutputPlugin = { + 'framework:webpack-output': ['factory', KarmaWebpackOutputFramework], +}; + +config.plugins.push(KarmaWebpackOutputPlugin); +config.frameworks.push("webpack-output"); \ No newline at end of file diff --git a/appyx-components/experimental/puzzle15/common/build.gradle.kts b/appyx-components/experimental/puzzle15/common/build.gradle.kts index 587c75601..6a8a08133 100644 --- a/appyx-components/experimental/puzzle15/common/build.gradle.kts +++ b/appyx-components/experimental/puzzle15/common/build.gradle.kts @@ -26,12 +26,22 @@ kotlin { // Adding moduleName as a workaround for this issue: https://youtrack.jetbrains.com/issue/KT-51942 moduleName = "appyx-components-experimental-puzzle15-commons" browser() + binaries.executable() } @OptIn(ExperimentalWasmDsl::class) wasmJs { // Adding moduleName as a workaround for this issue: https://youtrack.jetbrains.com/issue/KT-51942 moduleName = "appyx-components-experimental-puzzle15-commons-wa" - browser() + browser { + // Refer to this Slack thread for more details: https://kotlinlang.slack.com/archives/CDFP59223/p1702977410505449?thread_ts=1702668737.674499&cid=CDFP59223 + testTask { + useKarma { + useChromeHeadless() + useConfigDirectory(project.projectDir.resolve("karma.config.d").resolve("wasm")) + } + } + } + binaries.executable() } sourceSets { val commonMain by getting { @@ -52,6 +62,10 @@ kotlin { } } +compose.experimental { + web.application {} +} + dependencies { add("kspCommonMainMetadata", project(":ksp:appyx-processor")) add("kspAndroid", project(":ksp:appyx-processor")) diff --git a/appyx-components/experimental/puzzle15/common/karma.config.d/wasm/config.js b/appyx-components/experimental/puzzle15/common/karma.config.d/wasm/config.js new file mode 100644 index 000000000..22429e585 --- /dev/null +++ b/appyx-components/experimental/puzzle15/common/karma.config.d/wasm/config.js @@ -0,0 +1,55 @@ +// see https://kotlinlang.org/docs/js-project-setup.html#webpack-configuration-file +// This file provides karma.config.d configuration to run tests with k/wasm + +const path = require("path"); + +config.browserConsoleLogOptions.level = "debug"; + +const basePath = config.basePath; +const projectPath = path.resolve(basePath, "..", "..", "..", ".."); +const generatedAssetsPath = path.resolve(projectPath, "build", "karma-webpack-out") + +const debug = message => console.log(`[karma-config] ${message}`); + +debug(`karma basePath: ${basePath}`); +debug(`karma generatedAssetsPath: ${generatedAssetsPath}`); + +config.proxies["/"] = path.resolve(basePath, "kotlin"); + +config.files = [ + {pattern: path.resolve(generatedAssetsPath, "**/*"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.png"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.gif"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.ttf"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.txt"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.json"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.xml"), included: false, served: true, watched: false}, +].concat(config.files); + +function KarmaWebpackOutputFramework(config) { + // This controller is instantiated and set during the preprocessor phase. + const controller = config.__karmaWebpackController; + + // only if webpack has instantiated its controller + if (!controller) { + console.warn( + "Webpack has not instantiated controller yet.\n" + + "Check if you have enabled webpack preprocessor and framework before this framework" + ) + return + } + + config.files.push({ + pattern: `${controller.outputPath}/**/*`, + included: false, + served: true, + watched: false + }) +} + +const KarmaWebpackOutputPlugin = { + 'framework:webpack-output': ['factory', KarmaWebpackOutputFramework], +}; + +config.plugins.push(KarmaWebpackOutputPlugin); +config.frameworks.push("webpack-output"); \ No newline at end of file diff --git a/appyx-components/experimental/puzzle15/web/build.gradle.kts b/appyx-components/experimental/puzzle15/web/build.gradle.kts index 19f8f0a14..ef97d2bbe 100644 --- a/appyx-components/experimental/puzzle15/web/build.gradle.kts +++ b/appyx-components/experimental/puzzle15/web/build.gradle.kts @@ -14,7 +14,15 @@ kotlin { @OptIn(ExperimentalWasmDsl::class) wasmJs { moduleName = "appyx-components-experimental-puzzle15-web-wa" - browser() + browser { + // Refer to this Slack thread for more details: https://kotlinlang.slack.com/archives/CDFP59223/p1702977410505449?thread_ts=1702668737.674499&cid=CDFP59223 + testTask { + useKarma { + useChromeHeadless() + useConfigDirectory(project.projectDir.resolve("karma.config.d").resolve("wasm")) + } + } + } binaries.executable() } sourceSets { diff --git a/appyx-components/experimental/puzzle15/web/karma.config.d/wasm/config.js b/appyx-components/experimental/puzzle15/web/karma.config.d/wasm/config.js new file mode 100644 index 000000000..22429e585 --- /dev/null +++ b/appyx-components/experimental/puzzle15/web/karma.config.d/wasm/config.js @@ -0,0 +1,55 @@ +// see https://kotlinlang.org/docs/js-project-setup.html#webpack-configuration-file +// This file provides karma.config.d configuration to run tests with k/wasm + +const path = require("path"); + +config.browserConsoleLogOptions.level = "debug"; + +const basePath = config.basePath; +const projectPath = path.resolve(basePath, "..", "..", "..", ".."); +const generatedAssetsPath = path.resolve(projectPath, "build", "karma-webpack-out") + +const debug = message => console.log(`[karma-config] ${message}`); + +debug(`karma basePath: ${basePath}`); +debug(`karma generatedAssetsPath: ${generatedAssetsPath}`); + +config.proxies["/"] = path.resolve(basePath, "kotlin"); + +config.files = [ + {pattern: path.resolve(generatedAssetsPath, "**/*"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.png"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.gif"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.ttf"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.txt"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.json"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.xml"), included: false, served: true, watched: false}, +].concat(config.files); + +function KarmaWebpackOutputFramework(config) { + // This controller is instantiated and set during the preprocessor phase. + const controller = config.__karmaWebpackController; + + // only if webpack has instantiated its controller + if (!controller) { + console.warn( + "Webpack has not instantiated controller yet.\n" + + "Check if you have enabled webpack preprocessor and framework before this framework" + ) + return + } + + config.files.push({ + pattern: `${controller.outputPath}/**/*`, + included: false, + served: true, + watched: false + }) +} + +const KarmaWebpackOutputPlugin = { + 'framework:webpack-output': ['factory', KarmaWebpackOutputFramework], +}; + +config.plugins.push(KarmaWebpackOutputPlugin); +config.frameworks.push("webpack-output"); \ No newline at end of file diff --git a/appyx-components/internal/test-drive/common/build.gradle.kts b/appyx-components/internal/test-drive/common/build.gradle.kts index 9d2654cc9..6be5ae252 100644 --- a/appyx-components/internal/test-drive/common/build.gradle.kts +++ b/appyx-components/internal/test-drive/common/build.gradle.kts @@ -25,12 +25,22 @@ kotlin { // Adding moduleName as a workaround for this issue: https://youtrack.jetbrains.com/issue/KT-51942 moduleName = "appyx-components-internal-testdrive-common" browser() + binaries.executable() } @OptIn(ExperimentalWasmDsl::class) wasmJs { // Adding moduleName as a workaround for this issue: https://youtrack.jetbrains.com/issue/KT-51942 moduleName = "appyx-components-internal-testdrive-common-wa" - browser() + browser { + // Refer to this Slack thread for more details: https://kotlinlang.slack.com/archives/CDFP59223/p1702977410505449?thread_ts=1702668737.674499&cid=CDFP59223 + testTask { + useKarma { + useChromeHeadless() + useConfigDirectory(project.projectDir.resolve("karma.config.d").resolve("wasm")) + } + } + } + binaries.executable() } sourceSets { val commonMain by getting { @@ -52,6 +62,10 @@ kotlin { } } +compose.experimental { + web.application {} +} + dependencies { add("kspCommonMainMetadata", project(":ksp:appyx-processor")) add("kspAndroid", project(":ksp:appyx-processor")) diff --git a/appyx-components/internal/test-drive/common/karma.config.d/wasm/config.js b/appyx-components/internal/test-drive/common/karma.config.d/wasm/config.js new file mode 100644 index 000000000..22429e585 --- /dev/null +++ b/appyx-components/internal/test-drive/common/karma.config.d/wasm/config.js @@ -0,0 +1,55 @@ +// see https://kotlinlang.org/docs/js-project-setup.html#webpack-configuration-file +// This file provides karma.config.d configuration to run tests with k/wasm + +const path = require("path"); + +config.browserConsoleLogOptions.level = "debug"; + +const basePath = config.basePath; +const projectPath = path.resolve(basePath, "..", "..", "..", ".."); +const generatedAssetsPath = path.resolve(projectPath, "build", "karma-webpack-out") + +const debug = message => console.log(`[karma-config] ${message}`); + +debug(`karma basePath: ${basePath}`); +debug(`karma generatedAssetsPath: ${generatedAssetsPath}`); + +config.proxies["/"] = path.resolve(basePath, "kotlin"); + +config.files = [ + {pattern: path.resolve(generatedAssetsPath, "**/*"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.png"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.gif"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.ttf"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.txt"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.json"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.xml"), included: false, served: true, watched: false}, +].concat(config.files); + +function KarmaWebpackOutputFramework(config) { + // This controller is instantiated and set during the preprocessor phase. + const controller = config.__karmaWebpackController; + + // only if webpack has instantiated its controller + if (!controller) { + console.warn( + "Webpack has not instantiated controller yet.\n" + + "Check if you have enabled webpack preprocessor and framework before this framework" + ) + return + } + + config.files.push({ + pattern: `${controller.outputPath}/**/*`, + included: false, + served: true, + watched: false + }) +} + +const KarmaWebpackOutputPlugin = { + 'framework:webpack-output': ['factory', KarmaWebpackOutputFramework], +}; + +config.plugins.push(KarmaWebpackOutputPlugin); +config.frameworks.push("webpack-output"); \ No newline at end of file diff --git a/appyx-components/standard/backstack/common/build.gradle.kts b/appyx-components/standard/backstack/common/build.gradle.kts index 2ba40769f..195cc2bb2 100644 --- a/appyx-components/standard/backstack/common/build.gradle.kts +++ b/appyx-components/standard/backstack/common/build.gradle.kts @@ -26,12 +26,22 @@ kotlin { // Adding moduleName as a workaround for this issue: https://youtrack.jetbrains.com/issue/KT-51942 moduleName = "appyx-components-stable-backstack-commons" browser() + binaries.executable() } @OptIn(ExperimentalWasmDsl::class) wasmJs { // Adding moduleName as a workaround for this issue: https://youtrack.jetbrains.com/issue/KT-51942 moduleName = "appyx-components-stable-backstack-commons-wa" - browser() + browser { + // Refer to this Slack thread for more details: https://kotlinlang.slack.com/archives/CDFP59223/p1702977410505449?thread_ts=1702668737.674499&cid=CDFP59223 + testTask { + useKarma { + useChromeHeadless() + useConfigDirectory(project.projectDir.resolve("karma.config.d").resolve("wasm")) + } + } + } + binaries.executable() } iosX64() @@ -55,6 +65,7 @@ kotlin { val androidMain by getting val desktopMain by getting val jsMain by getting + val wasmJsMain by getting val iosX64Main by getting val iosArm64Main by getting @@ -68,6 +79,10 @@ kotlin { } } +compose.experimental { + web.application {} +} + dependencies { add("kspCommonMainMetadata", project(":ksp:appyx-processor")) add("kspAndroid", project(":ksp:appyx-processor")) diff --git a/appyx-components/standard/backstack/common/karma.config.d/wasm/config.js b/appyx-components/standard/backstack/common/karma.config.d/wasm/config.js new file mode 100644 index 000000000..22429e585 --- /dev/null +++ b/appyx-components/standard/backstack/common/karma.config.d/wasm/config.js @@ -0,0 +1,55 @@ +// see https://kotlinlang.org/docs/js-project-setup.html#webpack-configuration-file +// This file provides karma.config.d configuration to run tests with k/wasm + +const path = require("path"); + +config.browserConsoleLogOptions.level = "debug"; + +const basePath = config.basePath; +const projectPath = path.resolve(basePath, "..", "..", "..", ".."); +const generatedAssetsPath = path.resolve(projectPath, "build", "karma-webpack-out") + +const debug = message => console.log(`[karma-config] ${message}`); + +debug(`karma basePath: ${basePath}`); +debug(`karma generatedAssetsPath: ${generatedAssetsPath}`); + +config.proxies["/"] = path.resolve(basePath, "kotlin"); + +config.files = [ + {pattern: path.resolve(generatedAssetsPath, "**/*"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.png"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.gif"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.ttf"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.txt"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.json"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.xml"), included: false, served: true, watched: false}, +].concat(config.files); + +function KarmaWebpackOutputFramework(config) { + // This controller is instantiated and set during the preprocessor phase. + const controller = config.__karmaWebpackController; + + // only if webpack has instantiated its controller + if (!controller) { + console.warn( + "Webpack has not instantiated controller yet.\n" + + "Check if you have enabled webpack preprocessor and framework before this framework" + ) + return + } + + config.files.push({ + pattern: `${controller.outputPath}/**/*`, + included: false, + served: true, + watched: false + }) +} + +const KarmaWebpackOutputPlugin = { + 'framework:webpack-output': ['factory', KarmaWebpackOutputFramework], +}; + +config.plugins.push(KarmaWebpackOutputPlugin); +config.frameworks.push("webpack-output"); \ No newline at end of file diff --git a/appyx-components/standard/spotlight/common/build.gradle.kts b/appyx-components/standard/spotlight/common/build.gradle.kts index 656472b8d..5942cad0e 100644 --- a/appyx-components/standard/spotlight/common/build.gradle.kts +++ b/appyx-components/standard/spotlight/common/build.gradle.kts @@ -26,12 +26,22 @@ kotlin { // Adding moduleName as a workaround for this issue: https://youtrack.jetbrains.com/issue/KT-51942 moduleName = "appyx-components-stable-spotlight-commons" browser() + binaries.executable() } @OptIn(ExperimentalWasmDsl::class) wasmJs { // Adding moduleName as a workaround for this issue: https://youtrack.jetbrains.com/issue/KT-51942 moduleName = "appyx-components-stable-spotlight-commons-wa" - browser() + browser { + // Refer to this Slack thread for more details: https://kotlinlang.slack.com/archives/CDFP59223/p1702977410505449?thread_ts=1702668737.674499&cid=CDFP59223 + testTask { + useKarma { + useChromeHeadless() + useConfigDirectory(project.projectDir.resolve("karma.config.d").resolve("wasm")) + } + } + } + binaries.executable() } iosX64() @@ -71,6 +81,10 @@ kotlin { } } +compose.experimental { + web.application {} +} + dependencies { add("kspCommonMainMetadata", project(":ksp:appyx-processor")) add("kspAndroid", project(":ksp:appyx-processor")) diff --git a/appyx-components/standard/spotlight/common/karma.config.d/wasm/config.js b/appyx-components/standard/spotlight/common/karma.config.d/wasm/config.js new file mode 100644 index 000000000..22429e585 --- /dev/null +++ b/appyx-components/standard/spotlight/common/karma.config.d/wasm/config.js @@ -0,0 +1,55 @@ +// see https://kotlinlang.org/docs/js-project-setup.html#webpack-configuration-file +// This file provides karma.config.d configuration to run tests with k/wasm + +const path = require("path"); + +config.browserConsoleLogOptions.level = "debug"; + +const basePath = config.basePath; +const projectPath = path.resolve(basePath, "..", "..", "..", ".."); +const generatedAssetsPath = path.resolve(projectPath, "build", "karma-webpack-out") + +const debug = message => console.log(`[karma-config] ${message}`); + +debug(`karma basePath: ${basePath}`); +debug(`karma generatedAssetsPath: ${generatedAssetsPath}`); + +config.proxies["/"] = path.resolve(basePath, "kotlin"); + +config.files = [ + {pattern: path.resolve(generatedAssetsPath, "**/*"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.png"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.gif"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.ttf"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.txt"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.json"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.xml"), included: false, served: true, watched: false}, +].concat(config.files); + +function KarmaWebpackOutputFramework(config) { + // This controller is instantiated and set during the preprocessor phase. + const controller = config.__karmaWebpackController; + + // only if webpack has instantiated its controller + if (!controller) { + console.warn( + "Webpack has not instantiated controller yet.\n" + + "Check if you have enabled webpack preprocessor and framework before this framework" + ) + return + } + + config.files.push({ + pattern: `${controller.outputPath}/**/*`, + included: false, + served: true, + watched: false + }) +} + +const KarmaWebpackOutputPlugin = { + 'framework:webpack-output': ['factory', KarmaWebpackOutputFramework], +}; + +config.plugins.push(KarmaWebpackOutputPlugin); +config.frameworks.push("webpack-output"); \ No newline at end of file diff --git a/appyx-interactions/common/build.gradle.kts b/appyx-interactions/common/build.gradle.kts index 20b657ec0..effc21525 100644 --- a/appyx-interactions/common/build.gradle.kts +++ b/appyx-interactions/common/build.gradle.kts @@ -26,12 +26,22 @@ kotlin { // Adding moduleName as a workaround for this issue: https://youtrack.jetbrains.com/issue/KT-51942 moduleName = "appyx-interactions-common" browser() + binaries.executable() } @OptIn(ExperimentalWasmDsl::class) wasmJs { // Adding moduleName as a workaround for this issue: https://youtrack.jetbrains.com/issue/KT-51942 moduleName = "appyx-interactions-common-wa" - browser() + browser { + // Refer to this Slack thread for more details: https://kotlinlang.slack.com/archives/CDFP59223/p1702977410505449?thread_ts=1702668737.674499&cid=CDFP59223 + testTask { + useKarma { + useChromeHeadless() + useConfigDirectory(project.projectDir.resolve("karma.config.d").resolve("wasm")) + } + } + } + binaries.executable() } iosX64() @@ -87,3 +97,7 @@ kotlin { } } } + +compose.experimental { + web.application {} +} diff --git a/appyx-interactions/common/karma.config.d/wasm/config.js b/appyx-interactions/common/karma.config.d/wasm/config.js new file mode 100644 index 000000000..22429e585 --- /dev/null +++ b/appyx-interactions/common/karma.config.d/wasm/config.js @@ -0,0 +1,55 @@ +// see https://kotlinlang.org/docs/js-project-setup.html#webpack-configuration-file +// This file provides karma.config.d configuration to run tests with k/wasm + +const path = require("path"); + +config.browserConsoleLogOptions.level = "debug"; + +const basePath = config.basePath; +const projectPath = path.resolve(basePath, "..", "..", "..", ".."); +const generatedAssetsPath = path.resolve(projectPath, "build", "karma-webpack-out") + +const debug = message => console.log(`[karma-config] ${message}`); + +debug(`karma basePath: ${basePath}`); +debug(`karma generatedAssetsPath: ${generatedAssetsPath}`); + +config.proxies["/"] = path.resolve(basePath, "kotlin"); + +config.files = [ + {pattern: path.resolve(generatedAssetsPath, "**/*"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.png"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.gif"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.ttf"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.txt"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.json"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.xml"), included: false, served: true, watched: false}, +].concat(config.files); + +function KarmaWebpackOutputFramework(config) { + // This controller is instantiated and set during the preprocessor phase. + const controller = config.__karmaWebpackController; + + // only if webpack has instantiated its controller + if (!controller) { + console.warn( + "Webpack has not instantiated controller yet.\n" + + "Check if you have enabled webpack preprocessor and framework before this framework" + ) + return + } + + config.files.push({ + pattern: `${controller.outputPath}/**/*`, + included: false, + served: true, + watched: false + }) +} + +const KarmaWebpackOutputPlugin = { + 'framework:webpack-output': ['factory', KarmaWebpackOutputFramework], +}; + +config.plugins.push(KarmaWebpackOutputPlugin); +config.frameworks.push("webpack-output"); \ No newline at end of file diff --git a/appyx-navigation/common/build.gradle.kts b/appyx-navigation/common/build.gradle.kts index 134e63a6a..3e0ebcbbd 100644 --- a/appyx-navigation/common/build.gradle.kts +++ b/appyx-navigation/common/build.gradle.kts @@ -29,7 +29,15 @@ kotlin { wasmJs { // Adding moduleName as a workaround for this issue: https://youtrack.jetbrains.com/issue/KT-51942 moduleName = "appyx-navigation-common-wa" - browser() + browser { + // Refer to this Slack thread for more details: https://kotlinlang.slack.com/archives/CDFP59223/p1702977410505449?thread_ts=1702668737.674499&cid=CDFP59223 + testTask { + useKarma { + useChromeHeadless() + useConfigDirectory(project.projectDir.resolve("karma.config.d").resolve("wasm")) + } + } + } } iosX64() @@ -106,3 +114,7 @@ android { androidTestImplementation(project(":utils:testing-ui")) } } + +compose.experimental { + web.application {} +} diff --git a/appyx-navigation/common/src/jsMain/kotlin/com/bumble/appyx/navigation/integration/BrowserViewportWindow.kt b/appyx-navigation/common/src/jsMain/kotlin/com/bumble/appyx/navigation/integration/BrowserViewportWindow.kt deleted file mode 100644 index ce2954b6e..000000000 --- a/appyx-navigation/common/src/jsMain/kotlin/com/bumble/appyx/navigation/integration/BrowserViewportWindow.kt +++ /dev/null @@ -1,81 +0,0 @@ -// From Slack by OliverO -// See: https://kotlinlang.slack.com/archives/C01F2HV7868/p1660083429206369?thread_ts=1660083398.571449&cid=C01F2HV7868 -// adapted with scaling fix from https://github.com/OliverO2/compose-counting-grid/blob/master/src/frontendJsMain/kotlin/BrowserViewportWindow.kt - -@file:Suppress( - "INVISIBLE_MEMBER", - "INVISIBLE_REFERENCE", - "EXPOSED_PARAMETER_TYPE" -) // WORKAROUND: ComposeWindow and ComposeLayer are internal - -package com.bumble.appyx.navigation.integration - -import androidx.compose.runtime.Composable -import androidx.compose.ui.window.ComposeWindow -import kotlinx.browser.document -import kotlinx.browser.window -import org.w3c.dom.HTMLCanvasElement -import org.w3c.dom.HTMLStyleElement -import org.w3c.dom.HTMLTitleElement - -private const val CANVAS_ELEMENT_ID = "ComposeTarget" // Hardwired into ComposeWindow - -/** - * A Skiko/Canvas-based top-level window using the browser's entire viewport. Supports resizing. - */ -@Suppress("FunctionNaming") -fun BrowserViewportWindow( - title: String = "Untitled", - content: @Composable () -> Unit -) { - val htmlHeadElement = document.head!! - htmlHeadElement.appendChild( - (document.createElement("style") as HTMLStyleElement).apply { - type = "text/css" - appendChild( - document.createTextNode( - """ - html, body { - overflow: hidden; - margin: 0 !important; - padding: 0 !important; - } - - #$CANVAS_ELEMENT_ID { - outline: none; - } - """.trimIndent() - ) - ) - } - ) - - fun HTMLCanvasElement.fillViewportSize() { - setAttribute("width", "${window.innerWidth}") - setAttribute("height", "${window.innerHeight}") - } - - val canvas = (document.getElementById(CANVAS_ELEMENT_ID) as HTMLCanvasElement).apply { - fillViewportSize() - } - - ComposeWindow(canvasId = "Appyx", content = content).apply { - window.addEventListener("resize", { - canvas.fillViewportSize() - layer.layer.attachTo(canvas) - layer.layer.needRedraw() - val scale = layer.layer.contentScale - layer.setSize( - (canvas.width / scale * density.density).toInt(), - (canvas.height / scale * density.density).toInt() - ) - }) - - // WORKAROUND: ComposeWindow does not implement `setTitle(title)` - val htmlTitleElement = ( - htmlHeadElement.getElementsByTagName("title").item(0) - ?: document.createElement("title").also { htmlHeadElement.appendChild(it) } - ) as HTMLTitleElement - htmlTitleElement.textContent = title - } -} diff --git a/appyx-navigation/common/src/wasmJsMain/kotlin/com/bumble/appyx/navigation/integration/BrowserViewportWindow.kt b/appyx-navigation/common/src/wasmJsMain/kotlin/com/bumble/appyx/navigation/integration/BrowserViewportWindow.kt deleted file mode 100644 index 786edf653..000000000 --- a/appyx-navigation/common/src/wasmJsMain/kotlin/com/bumble/appyx/navigation/integration/BrowserViewportWindow.kt +++ /dev/null @@ -1,81 +0,0 @@ -// From Slack by OliverO -// See: https://kotlinlang.slack.com/archives/C01F2HV7868/p1660083429206369?thread_ts=1660083398.571449&cid=C01F2HV7868 -// adapted with scaling fix from https://github.com/OliverO2/compose-counting-grid/blob/master/src/frontendJsMain/kotlin/BrowserViewportWindow.kt - -@file:Suppress( - "INVISIBLE_MEMBER", - "INVISIBLE_REFERENCE", - "EXPOSED_PARAMETER_TYPE" -) // WORKAROUND: ComposeWindow and ComposeLayer are internal - -package com.bumble.appyx.navigation.integration - -import androidx.compose.runtime.Composable -import androidx.compose.ui.window.ComposeWindow -import kotlinx.browser.document -import kotlinx.browser.window -import org.w3c.dom.HTMLCanvasElement -import org.w3c.dom.HTMLStyleElement -import org.w3c.dom.HTMLTitleElement - -private const val CANVAS_ELEMENT_ID = "ComposeTarget" // Hardwired into ComposeWindow - -/** - * A Skiko/Canvas-based top-level window using the browser's entire viewport. Supports resizing. - */ -@Suppress("FunctionNaming") -fun BrowserViewportWindow( - title: String = "Untitled", - content: @Composable () -> Unit -) { - val htmlHeadElement = document.head!! - htmlHeadElement.appendChild( - (document.createElement("style") as HTMLStyleElement).apply { - type = "text/css" - appendChild( - document.createTextNode( - """ - html, body { - overflow: hidden; - margin: 0 !important; - padding: 0 !important; - } - - #$CANVAS_ELEMENT_ID { - outline: none; - } - """.trimIndent() - ) - ) - } - ) - - fun HTMLCanvasElement.fillViewportSize() { - setAttribute("width", "${window.innerWidth}") - setAttribute("height", "${window.innerHeight}") - } - - val canvas = (document.getElementById(CANVAS_ELEMENT_ID) as HTMLCanvasElement).apply { - fillViewportSize() - } - - ComposeWindow(canvasId = "Appyx", content = content).apply { - window.addEventListener("resize") { - canvas.fillViewportSize() - layer.layer.attachTo(canvas) - layer.layer.needRedraw() - val scale = layer.layer.contentScale - layer.setSize( - (canvas.width / scale * density.density).toInt(), - (canvas.height / scale * density.density).toInt() - ) - } - - // WORKAROUND: ComposeWindow does not implement `setTitle(title)` - val htmlTitleElement = ( - htmlHeadElement.getElementsByTagName("title").item(0) - ?: document.createElement("title").also { htmlHeadElement.appendChild(it) } - ) as HTMLTitleElement - htmlTitleElement.textContent = title - } -} diff --git a/demos/appyx-interactions/web/build.gradle.kts b/demos/appyx-interactions/web/build.gradle.kts index e9eb0d04e..b0036472e 100644 --- a/demos/appyx-interactions/web/build.gradle.kts +++ b/demos/appyx-interactions/web/build.gradle.kts @@ -14,7 +14,15 @@ kotlin { @OptIn(ExperimentalWasmDsl::class) wasmJs { moduleName = "appyx-demos-interactions-web-wa" - browser() + browser { + // Refer to this Slack thread for more details: https://kotlinlang.slack.com/archives/CDFP59223/p1702977410505449?thread_ts=1702668737.674499&cid=CDFP59223 + testTask { + useKarma { + useChromeHeadless() + useConfigDirectory(project.projectDir.resolve("karma.config.d").resolve("wasm")) + } + } + } binaries.executable() } sourceSets { diff --git a/demos/appyx-interactions/web/karma.config.d/wasm/config.js b/demos/appyx-interactions/web/karma.config.d/wasm/config.js new file mode 100644 index 000000000..22429e585 --- /dev/null +++ b/demos/appyx-interactions/web/karma.config.d/wasm/config.js @@ -0,0 +1,55 @@ +// see https://kotlinlang.org/docs/js-project-setup.html#webpack-configuration-file +// This file provides karma.config.d configuration to run tests with k/wasm + +const path = require("path"); + +config.browserConsoleLogOptions.level = "debug"; + +const basePath = config.basePath; +const projectPath = path.resolve(basePath, "..", "..", "..", ".."); +const generatedAssetsPath = path.resolve(projectPath, "build", "karma-webpack-out") + +const debug = message => console.log(`[karma-config] ${message}`); + +debug(`karma basePath: ${basePath}`); +debug(`karma generatedAssetsPath: ${generatedAssetsPath}`); + +config.proxies["/"] = path.resolve(basePath, "kotlin"); + +config.files = [ + {pattern: path.resolve(generatedAssetsPath, "**/*"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.png"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.gif"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.ttf"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.txt"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.json"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.xml"), included: false, served: true, watched: false}, +].concat(config.files); + +function KarmaWebpackOutputFramework(config) { + // This controller is instantiated and set during the preprocessor phase. + const controller = config.__karmaWebpackController; + + // only if webpack has instantiated its controller + if (!controller) { + console.warn( + "Webpack has not instantiated controller yet.\n" + + "Check if you have enabled webpack preprocessor and framework before this framework" + ) + return + } + + config.files.push({ + pattern: `${controller.outputPath}/**/*`, + included: false, + served: true, + watched: false + }) +} + +const KarmaWebpackOutputPlugin = { + 'framework:webpack-output': ['factory', KarmaWebpackOutputFramework], +}; + +config.plugins.push(KarmaWebpackOutputPlugin); +config.frameworks.push("webpack-output"); \ No newline at end of file diff --git a/demos/appyx-navigation/common/build.gradle.kts b/demos/appyx-navigation/common/build.gradle.kts index 293f0c9ad..9e77e0a02 100644 --- a/demos/appyx-navigation/common/build.gradle.kts +++ b/demos/appyx-navigation/common/build.gradle.kts @@ -25,12 +25,22 @@ kotlin { // Adding moduleName as a workaround for this issue: https://youtrack.jetbrains.com/issue/KT-51942 moduleName = "demo-appyx-navigation-common" browser() + binaries.executable() } @OptIn(ExperimentalWasmDsl::class) wasmJs { // Adding moduleName as a workaround for this issue: https://youtrack.jetbrains.com/issue/KT-51942 moduleName = "demo-appyx-navigation-common-wa" - browser() + browser { + // Refer to this Slack thread for more details: https://kotlinlang.slack.com/archives/CDFP59223/p1702977410505449?thread_ts=1702668737.674499&cid=CDFP59223 + testTask { + useKarma { + useChromeHeadless() + useConfigDirectory(project.projectDir.resolve("karma.config.d").resolve("wasm")) + } + } + } + binaries.executable() } iosX64() iosArm64() @@ -98,6 +108,10 @@ android { } } +compose.experimental { + web.application {} +} + dependencies { add("kspCommonMainMetadata", project(":ksp:appyx-processor")) add("kspAndroid", project(":ksp:appyx-processor")) diff --git a/demos/appyx-navigation/common/karma.config.d/wasm/config.js b/demos/appyx-navigation/common/karma.config.d/wasm/config.js new file mode 100644 index 000000000..22429e585 --- /dev/null +++ b/demos/appyx-navigation/common/karma.config.d/wasm/config.js @@ -0,0 +1,55 @@ +// see https://kotlinlang.org/docs/js-project-setup.html#webpack-configuration-file +// This file provides karma.config.d configuration to run tests with k/wasm + +const path = require("path"); + +config.browserConsoleLogOptions.level = "debug"; + +const basePath = config.basePath; +const projectPath = path.resolve(basePath, "..", "..", "..", ".."); +const generatedAssetsPath = path.resolve(projectPath, "build", "karma-webpack-out") + +const debug = message => console.log(`[karma-config] ${message}`); + +debug(`karma basePath: ${basePath}`); +debug(`karma generatedAssetsPath: ${generatedAssetsPath}`); + +config.proxies["/"] = path.resolve(basePath, "kotlin"); + +config.files = [ + {pattern: path.resolve(generatedAssetsPath, "**/*"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.png"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.gif"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.ttf"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.txt"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.json"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.xml"), included: false, served: true, watched: false}, +].concat(config.files); + +function KarmaWebpackOutputFramework(config) { + // This controller is instantiated and set during the preprocessor phase. + const controller = config.__karmaWebpackController; + + // only if webpack has instantiated its controller + if (!controller) { + console.warn( + "Webpack has not instantiated controller yet.\n" + + "Check if you have enabled webpack preprocessor and framework before this framework" + ) + return + } + + config.files.push({ + pattern: `${controller.outputPath}/**/*`, + included: false, + served: true, + watched: false + }) +} + +const KarmaWebpackOutputPlugin = { + 'framework:webpack-output': ['factory', KarmaWebpackOutputFramework], +}; + +config.plugins.push(KarmaWebpackOutputPlugin); +config.frameworks.push("webpack-output"); \ No newline at end of file diff --git a/demos/appyx-navigation/web/build.gradle.kts b/demos/appyx-navigation/web/build.gradle.kts index 4bdfbdcc4..668b1c58c 100644 --- a/demos/appyx-navigation/web/build.gradle.kts +++ b/demos/appyx-navigation/web/build.gradle.kts @@ -15,7 +15,15 @@ kotlin { @OptIn(ExperimentalWasmDsl::class) wasmJs { moduleName = "appyx-demos-navigation-web-wa" - browser() + browser { + // Refer to this Slack thread for more details: https://kotlinlang.slack.com/archives/CDFP59223/p1702977410505449?thread_ts=1702668737.674499&cid=CDFP59223 + testTask { + useKarma { + useChromeHeadless() + useConfigDirectory(project.projectDir.resolve("karma.config.d").resolve("wasm")) + } + } + } binaries.executable() } sourceSets { @@ -52,7 +60,7 @@ tasks.register("jsCopyResources") { from("../common/src/commonMain/resources") // Output for web resources - into("$buildDir/processedResources/js/main") + into("${layout.buildDirectory}/processedResources/js/main") include("**/*") } @@ -70,7 +78,7 @@ tasks.register("wasmJsCopyResources") { from("../common/src/commonMain/resources") // Output for web resources - into("$buildDir/processedResources/wasmJs/main") + into("${layout.buildDirectory}/processedResources/wasmJs/main") include("**/*") } diff --git a/demos/appyx-navigation/web/karma.config.d/wasm/config.js b/demos/appyx-navigation/web/karma.config.d/wasm/config.js new file mode 100644 index 000000000..22429e585 --- /dev/null +++ b/demos/appyx-navigation/web/karma.config.d/wasm/config.js @@ -0,0 +1,55 @@ +// see https://kotlinlang.org/docs/js-project-setup.html#webpack-configuration-file +// This file provides karma.config.d configuration to run tests with k/wasm + +const path = require("path"); + +config.browserConsoleLogOptions.level = "debug"; + +const basePath = config.basePath; +const projectPath = path.resolve(basePath, "..", "..", "..", ".."); +const generatedAssetsPath = path.resolve(projectPath, "build", "karma-webpack-out") + +const debug = message => console.log(`[karma-config] ${message}`); + +debug(`karma basePath: ${basePath}`); +debug(`karma generatedAssetsPath: ${generatedAssetsPath}`); + +config.proxies["/"] = path.resolve(basePath, "kotlin"); + +config.files = [ + {pattern: path.resolve(generatedAssetsPath, "**/*"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.png"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.gif"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.ttf"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.txt"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.json"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.xml"), included: false, served: true, watched: false}, +].concat(config.files); + +function KarmaWebpackOutputFramework(config) { + // This controller is instantiated and set during the preprocessor phase. + const controller = config.__karmaWebpackController; + + // only if webpack has instantiated its controller + if (!controller) { + console.warn( + "Webpack has not instantiated controller yet.\n" + + "Check if you have enabled webpack preprocessor and framework before this framework" + ) + return + } + + config.files.push({ + pattern: `${controller.outputPath}/**/*`, + included: false, + served: true, + watched: false + }) +} + +const KarmaWebpackOutputPlugin = { + 'framework:webpack-output': ['factory', KarmaWebpackOutputFramework], +}; + +config.plugins.push(KarmaWebpackOutputPlugin); +config.frameworks.push("webpack-output"); \ No newline at end of file diff --git a/demos/common/build.gradle.kts b/demos/common/build.gradle.kts index 93d7c4d60..2d6862b5f 100644 --- a/demos/common/build.gradle.kts +++ b/demos/common/build.gradle.kts @@ -24,12 +24,22 @@ kotlin { // Adding moduleName as a workaround for this issue: https://youtrack.jetbrains.com/issue/KT-51942 moduleName = "appyx-demos-commons" browser() + binaries.executable() } @OptIn(ExperimentalWasmDsl::class) wasmJs { // Adding moduleName as a workaround for this issue: https://youtrack.jetbrains.com/issue/KT-51942 moduleName = "appyx-demos-commons-wa" - browser() + browser { + // Refer to this Slack thread for more details: https://kotlinlang.slack.com/archives/CDFP59223/p1702977410505449?thread_ts=1702668737.674499&cid=CDFP59223 + testTask { + useKarma { + useChromeHeadless() + useConfigDirectory(project.projectDir.resolve("karma.config.d").resolve("wasm")) + } + } + } + binaries.executable() } iosX64() iosArm64() @@ -69,3 +79,7 @@ kotlin { } } } + +compose.experimental { + web.application {} +} diff --git a/demos/common/karma.config.d/wasm/config.js b/demos/common/karma.config.d/wasm/config.js new file mode 100644 index 000000000..22429e585 --- /dev/null +++ b/demos/common/karma.config.d/wasm/config.js @@ -0,0 +1,55 @@ +// see https://kotlinlang.org/docs/js-project-setup.html#webpack-configuration-file +// This file provides karma.config.d configuration to run tests with k/wasm + +const path = require("path"); + +config.browserConsoleLogOptions.level = "debug"; + +const basePath = config.basePath; +const projectPath = path.resolve(basePath, "..", "..", "..", ".."); +const generatedAssetsPath = path.resolve(projectPath, "build", "karma-webpack-out") + +const debug = message => console.log(`[karma-config] ${message}`); + +debug(`karma basePath: ${basePath}`); +debug(`karma generatedAssetsPath: ${generatedAssetsPath}`); + +config.proxies["/"] = path.resolve(basePath, "kotlin"); + +config.files = [ + {pattern: path.resolve(generatedAssetsPath, "**/*"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.png"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.gif"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.ttf"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.txt"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.json"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.xml"), included: false, served: true, watched: false}, +].concat(config.files); + +function KarmaWebpackOutputFramework(config) { + // This controller is instantiated and set during the preprocessor phase. + const controller = config.__karmaWebpackController; + + // only if webpack has instantiated its controller + if (!controller) { + console.warn( + "Webpack has not instantiated controller yet.\n" + + "Check if you have enabled webpack preprocessor and framework before this framework" + ) + return + } + + config.files.push({ + pattern: `${controller.outputPath}/**/*`, + included: false, + served: true, + watched: false + }) +} + +const KarmaWebpackOutputPlugin = { + 'framework:webpack-output': ['factory', KarmaWebpackOutputFramework], +}; + +config.plugins.push(KarmaWebpackOutputPlugin); +config.frameworks.push("webpack-output"); \ No newline at end of file diff --git a/demos/image-loader/common/build.gradle.kts b/demos/image-loader/common/build.gradle.kts index 8b7a3ebe4..81da378f0 100644 --- a/demos/image-loader/common/build.gradle.kts +++ b/demos/image-loader/common/build.gradle.kts @@ -23,12 +23,22 @@ kotlin { // Adding moduleName as a workaround for this issue: https://youtrack.jetbrains.com/issue/KT-51942 moduleName = "appyx-navigation-imageloader" browser() + binaries.executable() } @OptIn(ExperimentalWasmDsl::class) wasmJs { // Adding moduleName as a workaround for this issue: https://youtrack.jetbrains.com/issue/KT-51942 moduleName = "appyx-navigation-imageloader-wa" - browser() + browser { + // Refer to this Slack thread for more details: https://kotlinlang.slack.com/archives/CDFP59223/p1702977410505449?thread_ts=1702668737.674499&cid=CDFP59223 + testTask { + useKarma { + useChromeHeadless() + useConfigDirectory(project.projectDir.resolve("karma.config.d").resolve("wasm")) + } + } + } + binaries.executable() } iosX64() @@ -95,3 +105,7 @@ android { androidTestImplementation(composeBom) } } + +compose.experimental { + web.application {} +} diff --git a/demos/image-loader/common/karma.config.d/wasm/config.js b/demos/image-loader/common/karma.config.d/wasm/config.js new file mode 100644 index 000000000..22429e585 --- /dev/null +++ b/demos/image-loader/common/karma.config.d/wasm/config.js @@ -0,0 +1,55 @@ +// see https://kotlinlang.org/docs/js-project-setup.html#webpack-configuration-file +// This file provides karma.config.d configuration to run tests with k/wasm + +const path = require("path"); + +config.browserConsoleLogOptions.level = "debug"; + +const basePath = config.basePath; +const projectPath = path.resolve(basePath, "..", "..", "..", ".."); +const generatedAssetsPath = path.resolve(projectPath, "build", "karma-webpack-out") + +const debug = message => console.log(`[karma-config] ${message}`); + +debug(`karma basePath: ${basePath}`); +debug(`karma generatedAssetsPath: ${generatedAssetsPath}`); + +config.proxies["/"] = path.resolve(basePath, "kotlin"); + +config.files = [ + {pattern: path.resolve(generatedAssetsPath, "**/*"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.png"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.gif"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.ttf"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.txt"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.json"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.xml"), included: false, served: true, watched: false}, +].concat(config.files); + +function KarmaWebpackOutputFramework(config) { + // This controller is instantiated and set during the preprocessor phase. + const controller = config.__karmaWebpackController; + + // only if webpack has instantiated its controller + if (!controller) { + console.warn( + "Webpack has not instantiated controller yet.\n" + + "Check if you have enabled webpack preprocessor and framework before this framework" + ) + return + } + + config.files.push({ + pattern: `${controller.outputPath}/**/*`, + included: false, + served: true, + watched: false + }) +} + +const KarmaWebpackOutputPlugin = { + 'framework:webpack-output': ['factory', KarmaWebpackOutputFramework], +}; + +config.plugins.push(KarmaWebpackOutputPlugin); +config.frameworks.push("webpack-output"); \ No newline at end of file diff --git a/demos/mkdocs/appyx-components/backstack/fader/web/build.gradle.kts b/demos/mkdocs/appyx-components/backstack/fader/web/build.gradle.kts index e399717bf..be29c162b 100644 --- a/demos/mkdocs/appyx-components/backstack/fader/web/build.gradle.kts +++ b/demos/mkdocs/appyx-components/backstack/fader/web/build.gradle.kts @@ -15,7 +15,15 @@ kotlin { @OptIn(ExperimentalWasmDsl::class) wasmJs { moduleName = "appyx-demos-backstack-fader-web-wa" - browser() + browser { + // Refer to this Slack thread for more details: https://kotlinlang.slack.com/archives/CDFP59223/p1702977410505449?thread_ts=1702668737.674499&cid=CDFP59223 + testTask { + useKarma { + useChromeHeadless() + useConfigDirectory(project.projectDir.resolve("karma.config.d").resolve("wasm")) + } + } + } binaries.executable() } sourceSets { @@ -34,6 +42,11 @@ kotlin { implementation(project(":demos:mkdocs:common")) } } + val wasmJsMain by getting { + dependencies { + implementation(project(":demos:mkdocs:common")) + } + } } } diff --git a/demos/mkdocs/appyx-components/backstack/fader/web/karma.config.d/wasm/config.js b/demos/mkdocs/appyx-components/backstack/fader/web/karma.config.d/wasm/config.js new file mode 100644 index 000000000..22429e585 --- /dev/null +++ b/demos/mkdocs/appyx-components/backstack/fader/web/karma.config.d/wasm/config.js @@ -0,0 +1,55 @@ +// see https://kotlinlang.org/docs/js-project-setup.html#webpack-configuration-file +// This file provides karma.config.d configuration to run tests with k/wasm + +const path = require("path"); + +config.browserConsoleLogOptions.level = "debug"; + +const basePath = config.basePath; +const projectPath = path.resolve(basePath, "..", "..", "..", ".."); +const generatedAssetsPath = path.resolve(projectPath, "build", "karma-webpack-out") + +const debug = message => console.log(`[karma-config] ${message}`); + +debug(`karma basePath: ${basePath}`); +debug(`karma generatedAssetsPath: ${generatedAssetsPath}`); + +config.proxies["/"] = path.resolve(basePath, "kotlin"); + +config.files = [ + {pattern: path.resolve(generatedAssetsPath, "**/*"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.png"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.gif"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.ttf"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.txt"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.json"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.xml"), included: false, served: true, watched: false}, +].concat(config.files); + +function KarmaWebpackOutputFramework(config) { + // This controller is instantiated and set during the preprocessor phase. + const controller = config.__karmaWebpackController; + + // only if webpack has instantiated its controller + if (!controller) { + console.warn( + "Webpack has not instantiated controller yet.\n" + + "Check if you have enabled webpack preprocessor and framework before this framework" + ) + return + } + + config.files.push({ + pattern: `${controller.outputPath}/**/*`, + included: false, + served: true, + watched: false + }) +} + +const KarmaWebpackOutputPlugin = { + 'framework:webpack-output': ['factory', KarmaWebpackOutputFramework], +}; + +config.plugins.push(KarmaWebpackOutputPlugin); +config.frameworks.push("webpack-output"); \ No newline at end of file diff --git a/demos/mkdocs/appyx-components/backstack/parallax/web/build.gradle.kts b/demos/mkdocs/appyx-components/backstack/parallax/web/build.gradle.kts index 712941565..412125160 100644 --- a/demos/mkdocs/appyx-components/backstack/parallax/web/build.gradle.kts +++ b/demos/mkdocs/appyx-components/backstack/parallax/web/build.gradle.kts @@ -15,7 +15,15 @@ kotlin { @OptIn(ExperimentalWasmDsl::class) wasmJs { moduleName = "appyx-demos-backstack-parallax-web-wa" - browser() + browser { + // Refer to this Slack thread for more details: https://kotlinlang.slack.com/archives/CDFP59223/p1702977410505449?thread_ts=1702668737.674499&cid=CDFP59223 + testTask { + useKarma { + useChromeHeadless() + useConfigDirectory(project.projectDir.resolve("karma.config.d").resolve("wasm")) + } + } + } binaries.executable() } sourceSets { diff --git a/demos/mkdocs/appyx-components/backstack/parallax/web/karma.config.d/wasm/config.js b/demos/mkdocs/appyx-components/backstack/parallax/web/karma.config.d/wasm/config.js new file mode 100644 index 000000000..22429e585 --- /dev/null +++ b/demos/mkdocs/appyx-components/backstack/parallax/web/karma.config.d/wasm/config.js @@ -0,0 +1,55 @@ +// see https://kotlinlang.org/docs/js-project-setup.html#webpack-configuration-file +// This file provides karma.config.d configuration to run tests with k/wasm + +const path = require("path"); + +config.browserConsoleLogOptions.level = "debug"; + +const basePath = config.basePath; +const projectPath = path.resolve(basePath, "..", "..", "..", ".."); +const generatedAssetsPath = path.resolve(projectPath, "build", "karma-webpack-out") + +const debug = message => console.log(`[karma-config] ${message}`); + +debug(`karma basePath: ${basePath}`); +debug(`karma generatedAssetsPath: ${generatedAssetsPath}`); + +config.proxies["/"] = path.resolve(basePath, "kotlin"); + +config.files = [ + {pattern: path.resolve(generatedAssetsPath, "**/*"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.png"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.gif"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.ttf"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.txt"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.json"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.xml"), included: false, served: true, watched: false}, +].concat(config.files); + +function KarmaWebpackOutputFramework(config) { + // This controller is instantiated and set during the preprocessor phase. + const controller = config.__karmaWebpackController; + + // only if webpack has instantiated its controller + if (!controller) { + console.warn( + "Webpack has not instantiated controller yet.\n" + + "Check if you have enabled webpack preprocessor and framework before this framework" + ) + return + } + + config.files.push({ + pattern: `${controller.outputPath}/**/*`, + included: false, + served: true, + watched: false + }) +} + +const KarmaWebpackOutputPlugin = { + 'framework:webpack-output': ['factory', KarmaWebpackOutputFramework], +}; + +config.plugins.push(KarmaWebpackOutputPlugin); +config.frameworks.push("webpack-output"); \ No newline at end of file diff --git a/demos/mkdocs/appyx-components/backstack/slider/web/build.gradle.kts b/demos/mkdocs/appyx-components/backstack/slider/web/build.gradle.kts index 8539ded26..c7f27e013 100644 --- a/demos/mkdocs/appyx-components/backstack/slider/web/build.gradle.kts +++ b/demos/mkdocs/appyx-components/backstack/slider/web/build.gradle.kts @@ -15,7 +15,15 @@ kotlin { @OptIn(ExperimentalWasmDsl::class) wasmJs { moduleName = "appyx-demos-backstack-slider-web-wa" - browser() + browser { + // Refer to this Slack thread for more details: https://kotlinlang.slack.com/archives/CDFP59223/p1702977410505449?thread_ts=1702668737.674499&cid=CDFP59223 + testTask { + useKarma { + useChromeHeadless() + useConfigDirectory(project.projectDir.resolve("karma.config.d").resolve("wasm")) + } + } + } binaries.executable() } sourceSets { diff --git a/demos/mkdocs/appyx-components/backstack/slider/web/karma.config.d/wasm/config.js b/demos/mkdocs/appyx-components/backstack/slider/web/karma.config.d/wasm/config.js new file mode 100644 index 000000000..22429e585 --- /dev/null +++ b/demos/mkdocs/appyx-components/backstack/slider/web/karma.config.d/wasm/config.js @@ -0,0 +1,55 @@ +// see https://kotlinlang.org/docs/js-project-setup.html#webpack-configuration-file +// This file provides karma.config.d configuration to run tests with k/wasm + +const path = require("path"); + +config.browserConsoleLogOptions.level = "debug"; + +const basePath = config.basePath; +const projectPath = path.resolve(basePath, "..", "..", "..", ".."); +const generatedAssetsPath = path.resolve(projectPath, "build", "karma-webpack-out") + +const debug = message => console.log(`[karma-config] ${message}`); + +debug(`karma basePath: ${basePath}`); +debug(`karma generatedAssetsPath: ${generatedAssetsPath}`); + +config.proxies["/"] = path.resolve(basePath, "kotlin"); + +config.files = [ + {pattern: path.resolve(generatedAssetsPath, "**/*"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.png"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.gif"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.ttf"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.txt"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.json"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.xml"), included: false, served: true, watched: false}, +].concat(config.files); + +function KarmaWebpackOutputFramework(config) { + // This controller is instantiated and set during the preprocessor phase. + const controller = config.__karmaWebpackController; + + // only if webpack has instantiated its controller + if (!controller) { + console.warn( + "Webpack has not instantiated controller yet.\n" + + "Check if you have enabled webpack preprocessor and framework before this framework" + ) + return + } + + config.files.push({ + pattern: `${controller.outputPath}/**/*`, + included: false, + served: true, + watched: false + }) +} + +const KarmaWebpackOutputPlugin = { + 'framework:webpack-output': ['factory', KarmaWebpackOutputFramework], +}; + +config.plugins.push(KarmaWebpackOutputPlugin); +config.frameworks.push("webpack-output"); \ No newline at end of file diff --git a/demos/mkdocs/appyx-components/backstack/stack3d/web/build.gradle.kts b/demos/mkdocs/appyx-components/backstack/stack3d/web/build.gradle.kts index 81ad02377..91685b189 100644 --- a/demos/mkdocs/appyx-components/backstack/stack3d/web/build.gradle.kts +++ b/demos/mkdocs/appyx-components/backstack/stack3d/web/build.gradle.kts @@ -15,7 +15,15 @@ kotlin { @OptIn(ExperimentalWasmDsl::class) wasmJs { moduleName = "appyx-demos-backstack-stack3d-web-wa" - browser() + browser { + // Refer to this Slack thread for more details: https://kotlinlang.slack.com/archives/CDFP59223/p1702977410505449?thread_ts=1702668737.674499&cid=CDFP59223 + testTask { + useKarma { + useChromeHeadless() + useConfigDirectory(project.projectDir.resolve("karma.config.d").resolve("wasm")) + } + } + } binaries.executable() } sourceSets { diff --git a/demos/mkdocs/appyx-components/backstack/stack3d/web/karma.config.d/wasm/config.js b/demos/mkdocs/appyx-components/backstack/stack3d/web/karma.config.d/wasm/config.js new file mode 100644 index 000000000..22429e585 --- /dev/null +++ b/demos/mkdocs/appyx-components/backstack/stack3d/web/karma.config.d/wasm/config.js @@ -0,0 +1,55 @@ +// see https://kotlinlang.org/docs/js-project-setup.html#webpack-configuration-file +// This file provides karma.config.d configuration to run tests with k/wasm + +const path = require("path"); + +config.browserConsoleLogOptions.level = "debug"; + +const basePath = config.basePath; +const projectPath = path.resolve(basePath, "..", "..", "..", ".."); +const generatedAssetsPath = path.resolve(projectPath, "build", "karma-webpack-out") + +const debug = message => console.log(`[karma-config] ${message}`); + +debug(`karma basePath: ${basePath}`); +debug(`karma generatedAssetsPath: ${generatedAssetsPath}`); + +config.proxies["/"] = path.resolve(basePath, "kotlin"); + +config.files = [ + {pattern: path.resolve(generatedAssetsPath, "**/*"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.png"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.gif"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.ttf"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.txt"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.json"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.xml"), included: false, served: true, watched: false}, +].concat(config.files); + +function KarmaWebpackOutputFramework(config) { + // This controller is instantiated and set during the preprocessor phase. + const controller = config.__karmaWebpackController; + + // only if webpack has instantiated its controller + if (!controller) { + console.warn( + "Webpack has not instantiated controller yet.\n" + + "Check if you have enabled webpack preprocessor and framework before this framework" + ) + return + } + + config.files.push({ + pattern: `${controller.outputPath}/**/*`, + included: false, + served: true, + watched: false + }) +} + +const KarmaWebpackOutputPlugin = { + 'framework:webpack-output': ['factory', KarmaWebpackOutputFramework], +}; + +config.plugins.push(KarmaWebpackOutputPlugin); +config.frameworks.push("webpack-output"); \ No newline at end of file diff --git a/demos/mkdocs/appyx-components/common/build.gradle.kts b/demos/mkdocs/appyx-components/common/build.gradle.kts index 2c5656e2a..944b9bde7 100644 --- a/demos/mkdocs/appyx-components/common/build.gradle.kts +++ b/demos/mkdocs/appyx-components/common/build.gradle.kts @@ -15,7 +15,15 @@ kotlin { @OptIn(ExperimentalWasmDsl::class) wasmJs { moduleName = "demos-mkdocs-appyx-components-common-wa" - browser() + browser { + // Refer to this Slack thread for more details: https://kotlinlang.slack.com/archives/CDFP59223/p1702977410505449?thread_ts=1702668737.674499&cid=CDFP59223 + testTask { + useKarma { + useChromeHeadless() + useConfigDirectory(project.projectDir.resolve("karma.config.d").resolve("wasm")) + } + } + } binaries.executable() } sourceSets { diff --git a/demos/mkdocs/appyx-components/common/karma.config.d/wasm/config.js b/demos/mkdocs/appyx-components/common/karma.config.d/wasm/config.js new file mode 100644 index 000000000..22429e585 --- /dev/null +++ b/demos/mkdocs/appyx-components/common/karma.config.d/wasm/config.js @@ -0,0 +1,55 @@ +// see https://kotlinlang.org/docs/js-project-setup.html#webpack-configuration-file +// This file provides karma.config.d configuration to run tests with k/wasm + +const path = require("path"); + +config.browserConsoleLogOptions.level = "debug"; + +const basePath = config.basePath; +const projectPath = path.resolve(basePath, "..", "..", "..", ".."); +const generatedAssetsPath = path.resolve(projectPath, "build", "karma-webpack-out") + +const debug = message => console.log(`[karma-config] ${message}`); + +debug(`karma basePath: ${basePath}`); +debug(`karma generatedAssetsPath: ${generatedAssetsPath}`); + +config.proxies["/"] = path.resolve(basePath, "kotlin"); + +config.files = [ + {pattern: path.resolve(generatedAssetsPath, "**/*"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.png"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.gif"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.ttf"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.txt"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.json"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.xml"), included: false, served: true, watched: false}, +].concat(config.files); + +function KarmaWebpackOutputFramework(config) { + // This controller is instantiated and set during the preprocessor phase. + const controller = config.__karmaWebpackController; + + // only if webpack has instantiated its controller + if (!controller) { + console.warn( + "Webpack has not instantiated controller yet.\n" + + "Check if you have enabled webpack preprocessor and framework before this framework" + ) + return + } + + config.files.push({ + pattern: `${controller.outputPath}/**/*`, + included: false, + served: true, + watched: false + }) +} + +const KarmaWebpackOutputPlugin = { + 'framework:webpack-output': ['factory', KarmaWebpackOutputFramework], +}; + +config.plugins.push(KarmaWebpackOutputPlugin); +config.frameworks.push("webpack-output"); \ No newline at end of file diff --git a/demos/mkdocs/appyx-components/experimental/datingcards/web/build.gradle.kts b/demos/mkdocs/appyx-components/experimental/datingcards/web/build.gradle.kts index 410f86a4d..b28e41caf 100644 --- a/demos/mkdocs/appyx-components/experimental/datingcards/web/build.gradle.kts +++ b/demos/mkdocs/appyx-components/experimental/datingcards/web/build.gradle.kts @@ -15,7 +15,15 @@ kotlin { @OptIn(ExperimentalWasmDsl::class) wasmJs { moduleName = "appyx-demos-experimental-datingcards-web-wa" - browser() + browser { + // Refer to this Slack thread for more details: https://kotlinlang.slack.com/archives/CDFP59223/p1702977410505449?thread_ts=1702668737.674499&cid=CDFP59223 + testTask { + useKarma { + useChromeHeadless() + useConfigDirectory(project.projectDir.resolve("karma.config.d").resolve("wasm")) + } + } + } binaries.executable() } sourceSets { diff --git a/demos/mkdocs/appyx-components/experimental/datingcards/web/karma.config.d/wasm/config.js b/demos/mkdocs/appyx-components/experimental/datingcards/web/karma.config.d/wasm/config.js new file mode 100644 index 000000000..22429e585 --- /dev/null +++ b/demos/mkdocs/appyx-components/experimental/datingcards/web/karma.config.d/wasm/config.js @@ -0,0 +1,55 @@ +// see https://kotlinlang.org/docs/js-project-setup.html#webpack-configuration-file +// This file provides karma.config.d configuration to run tests with k/wasm + +const path = require("path"); + +config.browserConsoleLogOptions.level = "debug"; + +const basePath = config.basePath; +const projectPath = path.resolve(basePath, "..", "..", "..", ".."); +const generatedAssetsPath = path.resolve(projectPath, "build", "karma-webpack-out") + +const debug = message => console.log(`[karma-config] ${message}`); + +debug(`karma basePath: ${basePath}`); +debug(`karma generatedAssetsPath: ${generatedAssetsPath}`); + +config.proxies["/"] = path.resolve(basePath, "kotlin"); + +config.files = [ + {pattern: path.resolve(generatedAssetsPath, "**/*"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.png"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.gif"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.ttf"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.txt"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.json"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.xml"), included: false, served: true, watched: false}, +].concat(config.files); + +function KarmaWebpackOutputFramework(config) { + // This controller is instantiated and set during the preprocessor phase. + const controller = config.__karmaWebpackController; + + // only if webpack has instantiated its controller + if (!controller) { + console.warn( + "Webpack has not instantiated controller yet.\n" + + "Check if you have enabled webpack preprocessor and framework before this framework" + ) + return + } + + config.files.push({ + pattern: `${controller.outputPath}/**/*`, + included: false, + served: true, + watched: false + }) +} + +const KarmaWebpackOutputPlugin = { + 'framework:webpack-output': ['factory', KarmaWebpackOutputFramework], +}; + +config.plugins.push(KarmaWebpackOutputPlugin); +config.frameworks.push("webpack-output"); \ No newline at end of file diff --git a/demos/mkdocs/appyx-components/experimental/puzzle15/web/build.gradle.kts b/demos/mkdocs/appyx-components/experimental/puzzle15/web/build.gradle.kts index d246145d1..8f6739d1f 100644 --- a/demos/mkdocs/appyx-components/experimental/puzzle15/web/build.gradle.kts +++ b/demos/mkdocs/appyx-components/experimental/puzzle15/web/build.gradle.kts @@ -15,7 +15,15 @@ kotlin { @OptIn(ExperimentalWasmDsl::class) wasmJs { moduleName = "appyx-demos-experimental-puzzle15-web-wa" - browser() + browser { + // Refer to this Slack thread for more details: https://kotlinlang.slack.com/archives/CDFP59223/p1702977410505449?thread_ts=1702668737.674499&cid=CDFP59223 + testTask { + useKarma { + useChromeHeadless() + useConfigDirectory(project.projectDir.resolve("karma.config.d").resolve("wasm")) + } + } + } binaries.executable() } sourceSets { diff --git a/demos/mkdocs/appyx-components/experimental/puzzle15/web/karma.config.d/wasm/config.js b/demos/mkdocs/appyx-components/experimental/puzzle15/web/karma.config.d/wasm/config.js new file mode 100644 index 000000000..22429e585 --- /dev/null +++ b/demos/mkdocs/appyx-components/experimental/puzzle15/web/karma.config.d/wasm/config.js @@ -0,0 +1,55 @@ +// see https://kotlinlang.org/docs/js-project-setup.html#webpack-configuration-file +// This file provides karma.config.d configuration to run tests with k/wasm + +const path = require("path"); + +config.browserConsoleLogOptions.level = "debug"; + +const basePath = config.basePath; +const projectPath = path.resolve(basePath, "..", "..", "..", ".."); +const generatedAssetsPath = path.resolve(projectPath, "build", "karma-webpack-out") + +const debug = message => console.log(`[karma-config] ${message}`); + +debug(`karma basePath: ${basePath}`); +debug(`karma generatedAssetsPath: ${generatedAssetsPath}`); + +config.proxies["/"] = path.resolve(basePath, "kotlin"); + +config.files = [ + {pattern: path.resolve(generatedAssetsPath, "**/*"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.png"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.gif"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.ttf"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.txt"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.json"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.xml"), included: false, served: true, watched: false}, +].concat(config.files); + +function KarmaWebpackOutputFramework(config) { + // This controller is instantiated and set during the preprocessor phase. + const controller = config.__karmaWebpackController; + + // only if webpack has instantiated its controller + if (!controller) { + console.warn( + "Webpack has not instantiated controller yet.\n" + + "Check if you have enabled webpack preprocessor and framework before this framework" + ) + return + } + + config.files.push({ + pattern: `${controller.outputPath}/**/*`, + included: false, + served: true, + watched: false + }) +} + +const KarmaWebpackOutputPlugin = { + 'framework:webpack-output': ['factory', KarmaWebpackOutputFramework], +}; + +config.plugins.push(KarmaWebpackOutputPlugin); +config.frameworks.push("webpack-output"); \ No newline at end of file diff --git a/demos/mkdocs/appyx-components/spotlight/fader/web/build.gradle.kts b/demos/mkdocs/appyx-components/spotlight/fader/web/build.gradle.kts index f1c72080e..5b40e1c9a 100644 --- a/demos/mkdocs/appyx-components/spotlight/fader/web/build.gradle.kts +++ b/demos/mkdocs/appyx-components/spotlight/fader/web/build.gradle.kts @@ -15,7 +15,15 @@ kotlin { @OptIn(ExperimentalWasmDsl::class) wasmJs { moduleName = "appyx-components-spotlight-fader-web-wa" - browser() + browser { + // Refer to this Slack thread for more details: https://kotlinlang.slack.com/archives/CDFP59223/p1702977410505449?thread_ts=1702668737.674499&cid=CDFP59223 + testTask { + useKarma { + useChromeHeadless() + useConfigDirectory(project.projectDir.resolve("karma.config.d").resolve("wasm")) + } + } + } binaries.executable() } sourceSets { diff --git a/demos/mkdocs/appyx-components/spotlight/fader/web/karma.config.d/wasm/config.js b/demos/mkdocs/appyx-components/spotlight/fader/web/karma.config.d/wasm/config.js new file mode 100644 index 000000000..22429e585 --- /dev/null +++ b/demos/mkdocs/appyx-components/spotlight/fader/web/karma.config.d/wasm/config.js @@ -0,0 +1,55 @@ +// see https://kotlinlang.org/docs/js-project-setup.html#webpack-configuration-file +// This file provides karma.config.d configuration to run tests with k/wasm + +const path = require("path"); + +config.browserConsoleLogOptions.level = "debug"; + +const basePath = config.basePath; +const projectPath = path.resolve(basePath, "..", "..", "..", ".."); +const generatedAssetsPath = path.resolve(projectPath, "build", "karma-webpack-out") + +const debug = message => console.log(`[karma-config] ${message}`); + +debug(`karma basePath: ${basePath}`); +debug(`karma generatedAssetsPath: ${generatedAssetsPath}`); + +config.proxies["/"] = path.resolve(basePath, "kotlin"); + +config.files = [ + {pattern: path.resolve(generatedAssetsPath, "**/*"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.png"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.gif"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.ttf"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.txt"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.json"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.xml"), included: false, served: true, watched: false}, +].concat(config.files); + +function KarmaWebpackOutputFramework(config) { + // This controller is instantiated and set during the preprocessor phase. + const controller = config.__karmaWebpackController; + + // only if webpack has instantiated its controller + if (!controller) { + console.warn( + "Webpack has not instantiated controller yet.\n" + + "Check if you have enabled webpack preprocessor and framework before this framework" + ) + return + } + + config.files.push({ + pattern: `${controller.outputPath}/**/*`, + included: false, + served: true, + watched: false + }) +} + +const KarmaWebpackOutputPlugin = { + 'framework:webpack-output': ['factory', KarmaWebpackOutputFramework], +}; + +config.plugins.push(KarmaWebpackOutputPlugin); +config.frameworks.push("webpack-output"); \ No newline at end of file diff --git a/demos/mkdocs/appyx-components/spotlight/slider/web/build.gradle.kts b/demos/mkdocs/appyx-components/spotlight/slider/web/build.gradle.kts index b8c2a1102..48fd78ee9 100644 --- a/demos/mkdocs/appyx-components/spotlight/slider/web/build.gradle.kts +++ b/demos/mkdocs/appyx-components/spotlight/slider/web/build.gradle.kts @@ -15,7 +15,15 @@ kotlin { @OptIn(ExperimentalWasmDsl::class) wasmJs { moduleName = "appyx-components-spotlight-slider-web-wa" - browser() + browser { + // Refer to this Slack thread for more details: https://kotlinlang.slack.com/archives/CDFP59223/p1702977410505449?thread_ts=1702668737.674499&cid=CDFP59223 + testTask { + useKarma { + useChromeHeadless() + useConfigDirectory(project.projectDir.resolve("karma.config.d").resolve("wasm")) + } + } + } binaries.executable() } sourceSets { diff --git a/demos/mkdocs/appyx-components/spotlight/slider/web/karma.config.d/wasm/config.js b/demos/mkdocs/appyx-components/spotlight/slider/web/karma.config.d/wasm/config.js new file mode 100644 index 000000000..22429e585 --- /dev/null +++ b/demos/mkdocs/appyx-components/spotlight/slider/web/karma.config.d/wasm/config.js @@ -0,0 +1,55 @@ +// see https://kotlinlang.org/docs/js-project-setup.html#webpack-configuration-file +// This file provides karma.config.d configuration to run tests with k/wasm + +const path = require("path"); + +config.browserConsoleLogOptions.level = "debug"; + +const basePath = config.basePath; +const projectPath = path.resolve(basePath, "..", "..", "..", ".."); +const generatedAssetsPath = path.resolve(projectPath, "build", "karma-webpack-out") + +const debug = message => console.log(`[karma-config] ${message}`); + +debug(`karma basePath: ${basePath}`); +debug(`karma generatedAssetsPath: ${generatedAssetsPath}`); + +config.proxies["/"] = path.resolve(basePath, "kotlin"); + +config.files = [ + {pattern: path.resolve(generatedAssetsPath, "**/*"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.png"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.gif"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.ttf"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.txt"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.json"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.xml"), included: false, served: true, watched: false}, +].concat(config.files); + +function KarmaWebpackOutputFramework(config) { + // This controller is instantiated and set during the preprocessor phase. + const controller = config.__karmaWebpackController; + + // only if webpack has instantiated its controller + if (!controller) { + console.warn( + "Webpack has not instantiated controller yet.\n" + + "Check if you have enabled webpack preprocessor and framework before this framework" + ) + return + } + + config.files.push({ + pattern: `${controller.outputPath}/**/*`, + included: false, + served: true, + watched: false + }) +} + +const KarmaWebpackOutputPlugin = { + 'framework:webpack-output': ['factory', KarmaWebpackOutputFramework], +}; + +config.plugins.push(KarmaWebpackOutputPlugin); +config.frameworks.push("webpack-output"); \ No newline at end of file diff --git a/demos/mkdocs/appyx-components/spotlight/sliderrotation/web/build.gradle.kts b/demos/mkdocs/appyx-components/spotlight/sliderrotation/web/build.gradle.kts index 2441aa256..975eab2dd 100644 --- a/demos/mkdocs/appyx-components/spotlight/sliderrotation/web/build.gradle.kts +++ b/demos/mkdocs/appyx-components/spotlight/sliderrotation/web/build.gradle.kts @@ -15,7 +15,15 @@ kotlin { @OptIn(ExperimentalWasmDsl::class) wasmJs { moduleName = "appyx-components-spotlight-slider-rotation-web-wa" - browser() + browser { + // Refer to this Slack thread for more details: https://kotlinlang.slack.com/archives/CDFP59223/p1702977410505449?thread_ts=1702668737.674499&cid=CDFP59223 + testTask { + useKarma { + useChromeHeadless() + useConfigDirectory(project.projectDir.resolve("karma.config.d").resolve("wasm")) + } + } + } binaries.executable() } sourceSets { diff --git a/demos/mkdocs/appyx-components/spotlight/sliderrotation/web/karma.config.d/wasm/config.js b/demos/mkdocs/appyx-components/spotlight/sliderrotation/web/karma.config.d/wasm/config.js new file mode 100644 index 000000000..22429e585 --- /dev/null +++ b/demos/mkdocs/appyx-components/spotlight/sliderrotation/web/karma.config.d/wasm/config.js @@ -0,0 +1,55 @@ +// see https://kotlinlang.org/docs/js-project-setup.html#webpack-configuration-file +// This file provides karma.config.d configuration to run tests with k/wasm + +const path = require("path"); + +config.browserConsoleLogOptions.level = "debug"; + +const basePath = config.basePath; +const projectPath = path.resolve(basePath, "..", "..", "..", ".."); +const generatedAssetsPath = path.resolve(projectPath, "build", "karma-webpack-out") + +const debug = message => console.log(`[karma-config] ${message}`); + +debug(`karma basePath: ${basePath}`); +debug(`karma generatedAssetsPath: ${generatedAssetsPath}`); + +config.proxies["/"] = path.resolve(basePath, "kotlin"); + +config.files = [ + {pattern: path.resolve(generatedAssetsPath, "**/*"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.png"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.gif"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.ttf"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.txt"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.json"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.xml"), included: false, served: true, watched: false}, +].concat(config.files); + +function KarmaWebpackOutputFramework(config) { + // This controller is instantiated and set during the preprocessor phase. + const controller = config.__karmaWebpackController; + + // only if webpack has instantiated its controller + if (!controller) { + console.warn( + "Webpack has not instantiated controller yet.\n" + + "Check if you have enabled webpack preprocessor and framework before this framework" + ) + return + } + + config.files.push({ + pattern: `${controller.outputPath}/**/*`, + included: false, + served: true, + watched: false + }) +} + +const KarmaWebpackOutputPlugin = { + 'framework:webpack-output': ['factory', KarmaWebpackOutputFramework], +}; + +config.plugins.push(KarmaWebpackOutputPlugin); +config.frameworks.push("webpack-output"); \ No newline at end of file diff --git a/demos/mkdocs/appyx-components/spotlight/sliderscale/web/build.gradle.kts b/demos/mkdocs/appyx-components/spotlight/sliderscale/web/build.gradle.kts index faec8ed02..5269df2ab 100644 --- a/demos/mkdocs/appyx-components/spotlight/sliderscale/web/build.gradle.kts +++ b/demos/mkdocs/appyx-components/spotlight/sliderscale/web/build.gradle.kts @@ -15,7 +15,15 @@ kotlin { @OptIn(ExperimentalWasmDsl::class) wasmJs { moduleName = "appyx-components-spotlight-slider-scale-web-wa" - browser() + browser { + // Refer to this Slack thread for more details: https://kotlinlang.slack.com/archives/CDFP59223/p1702977410505449?thread_ts=1702668737.674499&cid=CDFP59223 + testTask { + useKarma { + useChromeHeadless() + useConfigDirectory(project.projectDir.resolve("karma.config.d").resolve("wasm")) + } + } + } binaries.executable() } sourceSets { diff --git a/demos/mkdocs/appyx-components/spotlight/sliderscale/web/karma.config.d/wasm/config.js b/demos/mkdocs/appyx-components/spotlight/sliderscale/web/karma.config.d/wasm/config.js new file mode 100644 index 000000000..22429e585 --- /dev/null +++ b/demos/mkdocs/appyx-components/spotlight/sliderscale/web/karma.config.d/wasm/config.js @@ -0,0 +1,55 @@ +// see https://kotlinlang.org/docs/js-project-setup.html#webpack-configuration-file +// This file provides karma.config.d configuration to run tests with k/wasm + +const path = require("path"); + +config.browserConsoleLogOptions.level = "debug"; + +const basePath = config.basePath; +const projectPath = path.resolve(basePath, "..", "..", "..", ".."); +const generatedAssetsPath = path.resolve(projectPath, "build", "karma-webpack-out") + +const debug = message => console.log(`[karma-config] ${message}`); + +debug(`karma basePath: ${basePath}`); +debug(`karma generatedAssetsPath: ${generatedAssetsPath}`); + +config.proxies["/"] = path.resolve(basePath, "kotlin"); + +config.files = [ + {pattern: path.resolve(generatedAssetsPath, "**/*"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.png"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.gif"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.ttf"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.txt"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.json"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.xml"), included: false, served: true, watched: false}, +].concat(config.files); + +function KarmaWebpackOutputFramework(config) { + // This controller is instantiated and set during the preprocessor phase. + const controller = config.__karmaWebpackController; + + // only if webpack has instantiated its controller + if (!controller) { + console.warn( + "Webpack has not instantiated controller yet.\n" + + "Check if you have enabled webpack preprocessor and framework before this framework" + ) + return + } + + config.files.push({ + pattern: `${controller.outputPath}/**/*`, + included: false, + served: true, + watched: false + }) +} + +const KarmaWebpackOutputPlugin = { + 'framework:webpack-output': ['factory', KarmaWebpackOutputFramework], +}; + +config.plugins.push(KarmaWebpackOutputPlugin); +config.frameworks.push("webpack-output"); \ No newline at end of file diff --git a/demos/mkdocs/appyx-components/spotlight/stack3d/web/build.gradle.kts b/demos/mkdocs/appyx-components/spotlight/stack3d/web/build.gradle.kts index 6d2cf60d9..7e19b4aae 100644 --- a/demos/mkdocs/appyx-components/spotlight/stack3d/web/build.gradle.kts +++ b/demos/mkdocs/appyx-components/spotlight/stack3d/web/build.gradle.kts @@ -15,7 +15,15 @@ kotlin { @OptIn(ExperimentalWasmDsl::class) wasmJs { moduleName = "appyx-components-spotlight-stack3d-web-wa" - browser() + browser { + // Refer to this Slack thread for more details: https://kotlinlang.slack.com/archives/CDFP59223/p1702977410505449?thread_ts=1702668737.674499&cid=CDFP59223 + testTask { + useKarma { + useChromeHeadless() + useConfigDirectory(project.projectDir.resolve("karma.config.d").resolve("wasm")) + } + } + } binaries.executable() } sourceSets { diff --git a/demos/mkdocs/appyx-components/spotlight/stack3d/web/karma.config.d/wasm/config.js b/demos/mkdocs/appyx-components/spotlight/stack3d/web/karma.config.d/wasm/config.js new file mode 100644 index 000000000..22429e585 --- /dev/null +++ b/demos/mkdocs/appyx-components/spotlight/stack3d/web/karma.config.d/wasm/config.js @@ -0,0 +1,55 @@ +// see https://kotlinlang.org/docs/js-project-setup.html#webpack-configuration-file +// This file provides karma.config.d configuration to run tests with k/wasm + +const path = require("path"); + +config.browserConsoleLogOptions.level = "debug"; + +const basePath = config.basePath; +const projectPath = path.resolve(basePath, "..", "..", "..", ".."); +const generatedAssetsPath = path.resolve(projectPath, "build", "karma-webpack-out") + +const debug = message => console.log(`[karma-config] ${message}`); + +debug(`karma basePath: ${basePath}`); +debug(`karma generatedAssetsPath: ${generatedAssetsPath}`); + +config.proxies["/"] = path.resolve(basePath, "kotlin"); + +config.files = [ + {pattern: path.resolve(generatedAssetsPath, "**/*"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.png"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.gif"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.ttf"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.txt"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.json"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.xml"), included: false, served: true, watched: false}, +].concat(config.files); + +function KarmaWebpackOutputFramework(config) { + // This controller is instantiated and set during the preprocessor phase. + const controller = config.__karmaWebpackController; + + // only if webpack has instantiated its controller + if (!controller) { + console.warn( + "Webpack has not instantiated controller yet.\n" + + "Check if you have enabled webpack preprocessor and framework before this framework" + ) + return + } + + config.files.push({ + pattern: `${controller.outputPath}/**/*`, + included: false, + served: true, + watched: false + }) +} + +const KarmaWebpackOutputPlugin = { + 'framework:webpack-output': ['factory', KarmaWebpackOutputFramework], +}; + +config.plugins.push(KarmaWebpackOutputPlugin); +config.frameworks.push("webpack-output"); \ No newline at end of file diff --git a/demos/mkdocs/appyx-interactions/gestures/dragprediction/web/build.gradle.kts b/demos/mkdocs/appyx-interactions/gestures/dragprediction/web/build.gradle.kts index 948c40979..cefebc6a7 100644 --- a/demos/mkdocs/appyx-interactions/gestures/dragprediction/web/build.gradle.kts +++ b/demos/mkdocs/appyx-interactions/gestures/dragprediction/web/build.gradle.kts @@ -15,7 +15,15 @@ kotlin { @OptIn(ExperimentalWasmDsl::class) wasmJs { moduleName = "appyx-interactions-gestures-dragpredication-web-wa" - browser() + browser { + // Refer to this Slack thread for more details: https://kotlinlang.slack.com/archives/CDFP59223/p1702977410505449?thread_ts=1702668737.674499&cid=CDFP59223 + testTask { + useKarma { + useChromeHeadless() + useConfigDirectory(project.projectDir.resolve("karma.config.d").resolve("wasm")) + } + } + } binaries.executable() } sourceSets { diff --git a/demos/mkdocs/appyx-interactions/gestures/dragprediction/web/karma.config.d/wasm/config.js b/demos/mkdocs/appyx-interactions/gestures/dragprediction/web/karma.config.d/wasm/config.js new file mode 100644 index 000000000..22429e585 --- /dev/null +++ b/demos/mkdocs/appyx-interactions/gestures/dragprediction/web/karma.config.d/wasm/config.js @@ -0,0 +1,55 @@ +// see https://kotlinlang.org/docs/js-project-setup.html#webpack-configuration-file +// This file provides karma.config.d configuration to run tests with k/wasm + +const path = require("path"); + +config.browserConsoleLogOptions.level = "debug"; + +const basePath = config.basePath; +const projectPath = path.resolve(basePath, "..", "..", "..", ".."); +const generatedAssetsPath = path.resolve(projectPath, "build", "karma-webpack-out") + +const debug = message => console.log(`[karma-config] ${message}`); + +debug(`karma basePath: ${basePath}`); +debug(`karma generatedAssetsPath: ${generatedAssetsPath}`); + +config.proxies["/"] = path.resolve(basePath, "kotlin"); + +config.files = [ + {pattern: path.resolve(generatedAssetsPath, "**/*"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.png"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.gif"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.ttf"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.txt"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.json"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.xml"), included: false, served: true, watched: false}, +].concat(config.files); + +function KarmaWebpackOutputFramework(config) { + // This controller is instantiated and set during the preprocessor phase. + const controller = config.__karmaWebpackController; + + // only if webpack has instantiated its controller + if (!controller) { + console.warn( + "Webpack has not instantiated controller yet.\n" + + "Check if you have enabled webpack preprocessor and framework before this framework" + ) + return + } + + config.files.push({ + pattern: `${controller.outputPath}/**/*`, + included: false, + served: true, + watched: false + }) +} + +const KarmaWebpackOutputPlugin = { + 'framework:webpack-output': ['factory', KarmaWebpackOutputFramework], +}; + +config.plugins.push(KarmaWebpackOutputPlugin); +config.frameworks.push("webpack-output"); \ No newline at end of file diff --git a/demos/mkdocs/appyx-interactions/gestures/incompletedrag/web/build.gradle.kts b/demos/mkdocs/appyx-interactions/gestures/incompletedrag/web/build.gradle.kts index ecbd0dab2..d6c84aeda 100644 --- a/demos/mkdocs/appyx-interactions/gestures/incompletedrag/web/build.gradle.kts +++ b/demos/mkdocs/appyx-interactions/gestures/incompletedrag/web/build.gradle.kts @@ -15,7 +15,15 @@ kotlin { @OptIn(ExperimentalWasmDsl::class) wasmJs { moduleName = "appyx-interactions-gestures-incompletedrag-web-wa" - browser() + browser { + // Refer to this Slack thread for more details: https://kotlinlang.slack.com/archives/CDFP59223/p1702977410505449?thread_ts=1702668737.674499&cid=CDFP59223 + testTask { + useKarma { + useChromeHeadless() + useConfigDirectory(project.projectDir.resolve("karma.config.d").resolve("wasm")) + } + } + } binaries.executable() } sourceSets { diff --git a/demos/mkdocs/appyx-interactions/gestures/incompletedrag/web/karma.config.d/wasm/config.js b/demos/mkdocs/appyx-interactions/gestures/incompletedrag/web/karma.config.d/wasm/config.js new file mode 100644 index 000000000..22429e585 --- /dev/null +++ b/demos/mkdocs/appyx-interactions/gestures/incompletedrag/web/karma.config.d/wasm/config.js @@ -0,0 +1,55 @@ +// see https://kotlinlang.org/docs/js-project-setup.html#webpack-configuration-file +// This file provides karma.config.d configuration to run tests with k/wasm + +const path = require("path"); + +config.browserConsoleLogOptions.level = "debug"; + +const basePath = config.basePath; +const projectPath = path.resolve(basePath, "..", "..", "..", ".."); +const generatedAssetsPath = path.resolve(projectPath, "build", "karma-webpack-out") + +const debug = message => console.log(`[karma-config] ${message}`); + +debug(`karma basePath: ${basePath}`); +debug(`karma generatedAssetsPath: ${generatedAssetsPath}`); + +config.proxies["/"] = path.resolve(basePath, "kotlin"); + +config.files = [ + {pattern: path.resolve(generatedAssetsPath, "**/*"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.png"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.gif"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.ttf"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.txt"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.json"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.xml"), included: false, served: true, watched: false}, +].concat(config.files); + +function KarmaWebpackOutputFramework(config) { + // This controller is instantiated and set during the preprocessor phase. + const controller = config.__karmaWebpackController; + + // only if webpack has instantiated its controller + if (!controller) { + console.warn( + "Webpack has not instantiated controller yet.\n" + + "Check if you have enabled webpack preprocessor and framework before this framework" + ) + return + } + + config.files.push({ + pattern: `${controller.outputPath}/**/*`, + included: false, + served: true, + watched: false + }) +} + +const KarmaWebpackOutputPlugin = { + 'framework:webpack-output': ['factory', KarmaWebpackOutputFramework], +}; + +config.plugins.push(KarmaWebpackOutputPlugin); +config.frameworks.push("webpack-output"); \ No newline at end of file diff --git a/demos/mkdocs/appyx-interactions/interactions/observemp/web/build.gradle.kts b/demos/mkdocs/appyx-interactions/interactions/observemp/web/build.gradle.kts index 973a3c9c5..1f3d46046 100644 --- a/demos/mkdocs/appyx-interactions/interactions/observemp/web/build.gradle.kts +++ b/demos/mkdocs/appyx-interactions/interactions/observemp/web/build.gradle.kts @@ -15,7 +15,15 @@ kotlin { @OptIn(ExperimentalWasmDsl::class) wasmJs { moduleName = "appyx-interactions-observemp-web-wa" - browser() + browser { + // Refer to this Slack thread for more details: https://kotlinlang.slack.com/archives/CDFP59223/p1702977410505449?thread_ts=1702668737.674499&cid=CDFP59223 + testTask { + useKarma { + useChromeHeadless() + useConfigDirectory(project.projectDir.resolve("karma.config.d").resolve("wasm")) + } + } + } binaries.executable() } sourceSets { diff --git a/demos/mkdocs/appyx-interactions/interactions/observemp/web/karma.config.d/wasm/config.js b/demos/mkdocs/appyx-interactions/interactions/observemp/web/karma.config.d/wasm/config.js new file mode 100644 index 000000000..22429e585 --- /dev/null +++ b/demos/mkdocs/appyx-interactions/interactions/observemp/web/karma.config.d/wasm/config.js @@ -0,0 +1,55 @@ +// see https://kotlinlang.org/docs/js-project-setup.html#webpack-configuration-file +// This file provides karma.config.d configuration to run tests with k/wasm + +const path = require("path"); + +config.browserConsoleLogOptions.level = "debug"; + +const basePath = config.basePath; +const projectPath = path.resolve(basePath, "..", "..", "..", ".."); +const generatedAssetsPath = path.resolve(projectPath, "build", "karma-webpack-out") + +const debug = message => console.log(`[karma-config] ${message}`); + +debug(`karma basePath: ${basePath}`); +debug(`karma generatedAssetsPath: ${generatedAssetsPath}`); + +config.proxies["/"] = path.resolve(basePath, "kotlin"); + +config.files = [ + {pattern: path.resolve(generatedAssetsPath, "**/*"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.png"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.gif"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.ttf"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.txt"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.json"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.xml"), included: false, served: true, watched: false}, +].concat(config.files); + +function KarmaWebpackOutputFramework(config) { + // This controller is instantiated and set during the preprocessor phase. + const controller = config.__karmaWebpackController; + + // only if webpack has instantiated its controller + if (!controller) { + console.warn( + "Webpack has not instantiated controller yet.\n" + + "Check if you have enabled webpack preprocessor and framework before this framework" + ) + return + } + + config.files.push({ + pattern: `${controller.outputPath}/**/*`, + included: false, + served: true, + watched: false + }) +} + +const KarmaWebpackOutputPlugin = { + 'framework:webpack-output': ['factory', KarmaWebpackOutputFramework], +}; + +config.plugins.push(KarmaWebpackOutputPlugin); +config.frameworks.push("webpack-output"); \ No newline at end of file diff --git a/demos/mkdocs/appyx-interactions/interactions/sample1/web/build.gradle.kts b/demos/mkdocs/appyx-interactions/interactions/sample1/web/build.gradle.kts index ae944b7d6..94023af25 100644 --- a/demos/mkdocs/appyx-interactions/interactions/sample1/web/build.gradle.kts +++ b/demos/mkdocs/appyx-interactions/interactions/sample1/web/build.gradle.kts @@ -15,7 +15,15 @@ kotlin { @OptIn(ExperimentalWasmDsl::class) wasmJs { moduleName = "appyx-interactions-sample1-web-wa" - browser() + browser { + // Refer to this Slack thread for more details: https://kotlinlang.slack.com/archives/CDFP59223/p1702977410505449?thread_ts=1702668737.674499&cid=CDFP59223 + testTask { + useKarma { + useChromeHeadless() + useConfigDirectory(project.projectDir.resolve("karma.config.d").resolve("wasm")) + } + } + } binaries.executable() } sourceSets { diff --git a/demos/mkdocs/appyx-interactions/interactions/sample1/web/karma.config.d/wasm/config.js b/demos/mkdocs/appyx-interactions/interactions/sample1/web/karma.config.d/wasm/config.js new file mode 100644 index 000000000..22429e585 --- /dev/null +++ b/demos/mkdocs/appyx-interactions/interactions/sample1/web/karma.config.d/wasm/config.js @@ -0,0 +1,55 @@ +// see https://kotlinlang.org/docs/js-project-setup.html#webpack-configuration-file +// This file provides karma.config.d configuration to run tests with k/wasm + +const path = require("path"); + +config.browserConsoleLogOptions.level = "debug"; + +const basePath = config.basePath; +const projectPath = path.resolve(basePath, "..", "..", "..", ".."); +const generatedAssetsPath = path.resolve(projectPath, "build", "karma-webpack-out") + +const debug = message => console.log(`[karma-config] ${message}`); + +debug(`karma basePath: ${basePath}`); +debug(`karma generatedAssetsPath: ${generatedAssetsPath}`); + +config.proxies["/"] = path.resolve(basePath, "kotlin"); + +config.files = [ + {pattern: path.resolve(generatedAssetsPath, "**/*"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.png"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.gif"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.ttf"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.txt"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.json"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.xml"), included: false, served: true, watched: false}, +].concat(config.files); + +function KarmaWebpackOutputFramework(config) { + // This controller is instantiated and set during the preprocessor phase. + const controller = config.__karmaWebpackController; + + // only if webpack has instantiated its controller + if (!controller) { + console.warn( + "Webpack has not instantiated controller yet.\n" + + "Check if you have enabled webpack preprocessor and framework before this framework" + ) + return + } + + config.files.push({ + pattern: `${controller.outputPath}/**/*`, + included: false, + served: true, + watched: false + }) +} + +const KarmaWebpackOutputPlugin = { + 'framework:webpack-output': ['factory', KarmaWebpackOutputFramework], +}; + +config.plugins.push(KarmaWebpackOutputPlugin); +config.frameworks.push("webpack-output"); \ No newline at end of file diff --git a/demos/mkdocs/appyx-interactions/interactions/sample2/web/build.gradle.kts b/demos/mkdocs/appyx-interactions/interactions/sample2/web/build.gradle.kts index 958f2468d..d3ae7a978 100644 --- a/demos/mkdocs/appyx-interactions/interactions/sample2/web/build.gradle.kts +++ b/demos/mkdocs/appyx-interactions/interactions/sample2/web/build.gradle.kts @@ -15,7 +15,15 @@ kotlin { @OptIn(ExperimentalWasmDsl::class) wasmJs { moduleName = "appyx-interactions-sample2-web-wa" - browser() + browser { + // Refer to this Slack thread for more details: https://kotlinlang.slack.com/archives/CDFP59223/p1702977410505449?thread_ts=1702668737.674499&cid=CDFP59223 + testTask { + useKarma { + useChromeHeadless() + useConfigDirectory(project.projectDir.resolve("karma.config.d").resolve("wasm")) + } + } + } binaries.executable() } sourceSets { diff --git a/demos/mkdocs/appyx-interactions/interactions/sample2/web/karma.config.d/wasm/config.js b/demos/mkdocs/appyx-interactions/interactions/sample2/web/karma.config.d/wasm/config.js new file mode 100644 index 000000000..22429e585 --- /dev/null +++ b/demos/mkdocs/appyx-interactions/interactions/sample2/web/karma.config.d/wasm/config.js @@ -0,0 +1,55 @@ +// see https://kotlinlang.org/docs/js-project-setup.html#webpack-configuration-file +// This file provides karma.config.d configuration to run tests with k/wasm + +const path = require("path"); + +config.browserConsoleLogOptions.level = "debug"; + +const basePath = config.basePath; +const projectPath = path.resolve(basePath, "..", "..", "..", ".."); +const generatedAssetsPath = path.resolve(projectPath, "build", "karma-webpack-out") + +const debug = message => console.log(`[karma-config] ${message}`); + +debug(`karma basePath: ${basePath}`); +debug(`karma generatedAssetsPath: ${generatedAssetsPath}`); + +config.proxies["/"] = path.resolve(basePath, "kotlin"); + +config.files = [ + {pattern: path.resolve(generatedAssetsPath, "**/*"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.png"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.gif"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.ttf"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.txt"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.json"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.xml"), included: false, served: true, watched: false}, +].concat(config.files); + +function KarmaWebpackOutputFramework(config) { + // This controller is instantiated and set during the preprocessor phase. + const controller = config.__karmaWebpackController; + + // only if webpack has instantiated its controller + if (!controller) { + console.warn( + "Webpack has not instantiated controller yet.\n" + + "Check if you have enabled webpack preprocessor and framework before this framework" + ) + return + } + + config.files.push({ + pattern: `${controller.outputPath}/**/*`, + included: false, + served: true, + watched: false + }) +} + +const KarmaWebpackOutputPlugin = { + 'framework:webpack-output': ['factory', KarmaWebpackOutputFramework], +}; + +config.plugins.push(KarmaWebpackOutputPlugin); +config.frameworks.push("webpack-output"); \ No newline at end of file diff --git a/demos/mkdocs/appyx-interactions/interactions/sample3/web/build.gradle.kts b/demos/mkdocs/appyx-interactions/interactions/sample3/web/build.gradle.kts index 24ea6496d..192ffe498 100644 --- a/demos/mkdocs/appyx-interactions/interactions/sample3/web/build.gradle.kts +++ b/demos/mkdocs/appyx-interactions/interactions/sample3/web/build.gradle.kts @@ -15,7 +15,15 @@ kotlin { @OptIn(ExperimentalWasmDsl::class) wasmJs { moduleName = "appyx-interactions-sample3-web-wa" - browser() + browser { + // Refer to this Slack thread for more details: https://kotlinlang.slack.com/archives/CDFP59223/p1702977410505449?thread_ts=1702668737.674499&cid=CDFP59223 + testTask { + useKarma { + useChromeHeadless() + useConfigDirectory(project.projectDir.resolve("karma.config.d").resolve("wasm")) + } + } + } binaries.executable() } sourceSets { diff --git a/demos/mkdocs/appyx-interactions/interactions/sample3/web/karma.config.d/wasm/config.js b/demos/mkdocs/appyx-interactions/interactions/sample3/web/karma.config.d/wasm/config.js new file mode 100644 index 000000000..22429e585 --- /dev/null +++ b/demos/mkdocs/appyx-interactions/interactions/sample3/web/karma.config.d/wasm/config.js @@ -0,0 +1,55 @@ +// see https://kotlinlang.org/docs/js-project-setup.html#webpack-configuration-file +// This file provides karma.config.d configuration to run tests with k/wasm + +const path = require("path"); + +config.browserConsoleLogOptions.level = "debug"; + +const basePath = config.basePath; +const projectPath = path.resolve(basePath, "..", "..", "..", ".."); +const generatedAssetsPath = path.resolve(projectPath, "build", "karma-webpack-out") + +const debug = message => console.log(`[karma-config] ${message}`); + +debug(`karma basePath: ${basePath}`); +debug(`karma generatedAssetsPath: ${generatedAssetsPath}`); + +config.proxies["/"] = path.resolve(basePath, "kotlin"); + +config.files = [ + {pattern: path.resolve(generatedAssetsPath, "**/*"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.png"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.gif"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.ttf"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.txt"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.json"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.xml"), included: false, served: true, watched: false}, +].concat(config.files); + +function KarmaWebpackOutputFramework(config) { + // This controller is instantiated and set during the preprocessor phase. + const controller = config.__karmaWebpackController; + + // only if webpack has instantiated its controller + if (!controller) { + console.warn( + "Webpack has not instantiated controller yet.\n" + + "Check if you have enabled webpack preprocessor and framework before this framework" + ) + return + } + + config.files.push({ + pattern: `${controller.outputPath}/**/*`, + included: false, + served: true, + watched: false + }) +} + +const KarmaWebpackOutputPlugin = { + 'framework:webpack-output': ['factory', KarmaWebpackOutputFramework], +}; + +config.plugins.push(KarmaWebpackOutputPlugin); +config.frameworks.push("webpack-output"); \ No newline at end of file diff --git a/demos/mkdocs/common/build.gradle.kts b/demos/mkdocs/common/build.gradle.kts index e8dc40a73..e92a3c463 100644 --- a/demos/mkdocs/common/build.gradle.kts +++ b/demos/mkdocs/common/build.gradle.kts @@ -14,7 +14,15 @@ kotlin { @OptIn(ExperimentalWasmDsl::class) wasmJs { moduleName = "demos-mkdocs-common-wa" - browser() + browser { + // Refer to this Slack thread for more details: https://kotlinlang.slack.com/archives/CDFP59223/p1702977410505449?thread_ts=1702668737.674499&cid=CDFP59223 + testTask { + useKarma { + useChromeHeadless() + useConfigDirectory(project.projectDir.resolve("karma.config.d").resolve("wasm")) + } + } + } binaries.executable() } sourceSets { diff --git a/demos/mkdocs/common/karma.config.d/wasm/config.js b/demos/mkdocs/common/karma.config.d/wasm/config.js new file mode 100644 index 000000000..22429e585 --- /dev/null +++ b/demos/mkdocs/common/karma.config.d/wasm/config.js @@ -0,0 +1,55 @@ +// see https://kotlinlang.org/docs/js-project-setup.html#webpack-configuration-file +// This file provides karma.config.d configuration to run tests with k/wasm + +const path = require("path"); + +config.browserConsoleLogOptions.level = "debug"; + +const basePath = config.basePath; +const projectPath = path.resolve(basePath, "..", "..", "..", ".."); +const generatedAssetsPath = path.resolve(projectPath, "build", "karma-webpack-out") + +const debug = message => console.log(`[karma-config] ${message}`); + +debug(`karma basePath: ${basePath}`); +debug(`karma generatedAssetsPath: ${generatedAssetsPath}`); + +config.proxies["/"] = path.resolve(basePath, "kotlin"); + +config.files = [ + {pattern: path.resolve(generatedAssetsPath, "**/*"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.png"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.gif"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.ttf"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.txt"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.json"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.xml"), included: false, served: true, watched: false}, +].concat(config.files); + +function KarmaWebpackOutputFramework(config) { + // This controller is instantiated and set during the preprocessor phase. + const controller = config.__karmaWebpackController; + + // only if webpack has instantiated its controller + if (!controller) { + console.warn( + "Webpack has not instantiated controller yet.\n" + + "Check if you have enabled webpack preprocessor and framework before this framework" + ) + return + } + + config.files.push({ + pattern: `${controller.outputPath}/**/*`, + included: false, + served: true, + watched: false + }) +} + +const KarmaWebpackOutputPlugin = { + 'framework:webpack-output': ['factory', KarmaWebpackOutputFramework], +}; + +config.plugins.push(KarmaWebpackOutputPlugin); +config.frameworks.push("webpack-output"); \ No newline at end of file diff --git a/demos/sandbox-appyx-navigation/common/build.gradle.kts b/demos/sandbox-appyx-navigation/common/build.gradle.kts index a90077d57..3ed22313b 100644 --- a/demos/sandbox-appyx-navigation/common/build.gradle.kts +++ b/demos/sandbox-appyx-navigation/common/build.gradle.kts @@ -25,12 +25,22 @@ kotlin { // Adding moduleName as a workaround for this issue: https://youtrack.jetbrains.com/issue/KT-51942 moduleName = "demo-sandbox-appyx-navigation-common" browser() + binaries.executable() } @OptIn(ExperimentalWasmDsl::class) wasmJs { // Adding moduleName as a workaround for this issue: https://youtrack.jetbrains.com/issue/KT-51942 moduleName = "demo-sandbox-appyx-navigation-common-wa" - browser() + browser { + // Refer to this Slack thread for more details: https://kotlinlang.slack.com/archives/CDFP59223/p1702977410505449?thread_ts=1702668737.674499&cid=CDFP59223 + testTask { + useKarma { + useChromeHeadless() + useConfigDirectory(project.projectDir.resolve("karma.config.d").resolve("wasm")) + } + } + } + binaries.executable() } iosX64() iosArm64() @@ -96,6 +106,10 @@ android { } } +compose.experimental { + web.application {} +} + dependencies { add("kspCommonMainMetadata", project(":ksp:appyx-processor")) add("kspAndroid", project(":ksp:appyx-processor")) diff --git a/demos/sandbox-appyx-navigation/common/karma.config.d/wasm/config.js b/demos/sandbox-appyx-navigation/common/karma.config.d/wasm/config.js new file mode 100644 index 000000000..22429e585 --- /dev/null +++ b/demos/sandbox-appyx-navigation/common/karma.config.d/wasm/config.js @@ -0,0 +1,55 @@ +// see https://kotlinlang.org/docs/js-project-setup.html#webpack-configuration-file +// This file provides karma.config.d configuration to run tests with k/wasm + +const path = require("path"); + +config.browserConsoleLogOptions.level = "debug"; + +const basePath = config.basePath; +const projectPath = path.resolve(basePath, "..", "..", "..", ".."); +const generatedAssetsPath = path.resolve(projectPath, "build", "karma-webpack-out") + +const debug = message => console.log(`[karma-config] ${message}`); + +debug(`karma basePath: ${basePath}`); +debug(`karma generatedAssetsPath: ${generatedAssetsPath}`); + +config.proxies["/"] = path.resolve(basePath, "kotlin"); + +config.files = [ + {pattern: path.resolve(generatedAssetsPath, "**/*"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.png"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.gif"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.ttf"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.txt"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.json"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.xml"), included: false, served: true, watched: false}, +].concat(config.files); + +function KarmaWebpackOutputFramework(config) { + // This controller is instantiated and set during the preprocessor phase. + const controller = config.__karmaWebpackController; + + // only if webpack has instantiated its controller + if (!controller) { + console.warn( + "Webpack has not instantiated controller yet.\n" + + "Check if you have enabled webpack preprocessor and framework before this framework" + ) + return + } + + config.files.push({ + pattern: `${controller.outputPath}/**/*`, + included: false, + served: true, + watched: false + }) +} + +const KarmaWebpackOutputPlugin = { + 'framework:webpack-output': ['factory', KarmaWebpackOutputFramework], +}; + +config.plugins.push(KarmaWebpackOutputPlugin); +config.frameworks.push("webpack-output"); \ No newline at end of file diff --git a/demos/sandbox-appyx-navigation/web/build.gradle.kts b/demos/sandbox-appyx-navigation/web/build.gradle.kts index e594a4a94..f946ce471 100644 --- a/demos/sandbox-appyx-navigation/web/build.gradle.kts +++ b/demos/sandbox-appyx-navigation/web/build.gradle.kts @@ -14,7 +14,15 @@ kotlin { @OptIn(ExperimentalWasmDsl::class) wasmJs { moduleName = "appyx-demos-sandbox-navigation-web-wa" - browser() + browser { + // Refer to this Slack thread for more details: https://kotlinlang.slack.com/archives/CDFP59223/p1702977410505449?thread_ts=1702668737.674499&cid=CDFP59223 + testTask { + useKarma { + useChromeHeadless() + useConfigDirectory(project.projectDir.resolve("karma.config.d").resolve("wasm")) + } + } + } binaries.executable() } sourceSets { diff --git a/demos/sandbox-appyx-navigation/web/karma.config.d/wasm/config.js b/demos/sandbox-appyx-navigation/web/karma.config.d/wasm/config.js new file mode 100644 index 000000000..22429e585 --- /dev/null +++ b/demos/sandbox-appyx-navigation/web/karma.config.d/wasm/config.js @@ -0,0 +1,55 @@ +// see https://kotlinlang.org/docs/js-project-setup.html#webpack-configuration-file +// This file provides karma.config.d configuration to run tests with k/wasm + +const path = require("path"); + +config.browserConsoleLogOptions.level = "debug"; + +const basePath = config.basePath; +const projectPath = path.resolve(basePath, "..", "..", "..", ".."); +const generatedAssetsPath = path.resolve(projectPath, "build", "karma-webpack-out") + +const debug = message => console.log(`[karma-config] ${message}`); + +debug(`karma basePath: ${basePath}`); +debug(`karma generatedAssetsPath: ${generatedAssetsPath}`); + +config.proxies["/"] = path.resolve(basePath, "kotlin"); + +config.files = [ + {pattern: path.resolve(generatedAssetsPath, "**/*"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.png"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.gif"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.ttf"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.txt"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.json"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.xml"), included: false, served: true, watched: false}, +].concat(config.files); + +function KarmaWebpackOutputFramework(config) { + // This controller is instantiated and set during the preprocessor phase. + const controller = config.__karmaWebpackController; + + // only if webpack has instantiated its controller + if (!controller) { + console.warn( + "Webpack has not instantiated controller yet.\n" + + "Check if you have enabled webpack preprocessor and framework before this framework" + ) + return + } + + config.files.push({ + pattern: `${controller.outputPath}/**/*`, + included: false, + served: true, + watched: false + }) +} + +const KarmaWebpackOutputPlugin = { + 'framework:webpack-output': ['factory', KarmaWebpackOutputFramework], +}; + +config.plugins.push(KarmaWebpackOutputPlugin); +config.frameworks.push("webpack-output"); \ No newline at end of file diff --git a/demos/sandbox-appyx-navigation/web/src/jsMain/kotlin/com/bumble/appyx/demos/sandbox/navigation/Main.kt b/demos/sandbox-appyx-navigation/web/src/jsMain/kotlin/com/bumble/appyx/demos/sandbox/navigation/Main.kt index 979c5844e..b65cea23c 100644 --- a/demos/sandbox-appyx-navigation/web/src/jsMain/kotlin/com/bumble/appyx/demos/sandbox/navigation/Main.kt +++ b/demos/sandbox-appyx-navigation/web/src/jsMain/kotlin/com/bumble/appyx/demos/sandbox/navigation/Main.kt @@ -22,9 +22,9 @@ import androidx.compose.ui.input.key.onKeyEvent import androidx.compose.ui.input.key.type import androidx.compose.ui.layout.onSizeChanged import androidx.compose.ui.unit.dp +import androidx.compose.ui.window.CanvasBasedWindow import com.bumble.appyx.demos.sandbox.navigation.node.container.MainNavNode import com.bumble.appyx.demos.sandbox.navigation.ui.AppyxSampleAppTheme -import com.bumble.appyx.navigation.integration.BrowserViewportWindow import com.bumble.appyx.navigation.integration.ScreenSize import com.bumble.appyx.navigation.integration.WebNodeHost import kotlinx.coroutines.CoroutineScope @@ -35,10 +35,11 @@ import kotlinx.coroutines.flow.receiveAsFlow import kotlinx.coroutines.launch import org.jetbrains.skiko.wasm.onWasmReady +@OptIn(ExperimentalComposeUiApi::class) fun main() { val events: Channel = Channel() onWasmReady { - BrowserViewportWindow("Navigation Demo") { + CanvasBasedWindow("Navigation Demo") { val requester = remember { FocusRequester() } var hasFocus by remember { mutableStateOf(false) } diff --git a/demos/sandbox-appyx-navigation/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/sandbox/navigation/Main.kt b/demos/sandbox-appyx-navigation/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/sandbox/navigation/Main.kt index 479f92d98..19753aae1 100644 --- a/demos/sandbox-appyx-navigation/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/sandbox/navigation/Main.kt +++ b/demos/sandbox-appyx-navigation/web/src/wasmJsMain/kotlin/com/bumble/appyx/demos/sandbox/navigation/Main.kt @@ -22,9 +22,9 @@ import androidx.compose.ui.input.key.onKeyEvent import androidx.compose.ui.input.key.type import androidx.compose.ui.layout.onSizeChanged import androidx.compose.ui.unit.dp +import androidx.compose.ui.window.CanvasBasedWindow import com.bumble.appyx.demos.sandbox.navigation.node.container.MainNavNode import com.bumble.appyx.demos.sandbox.navigation.ui.AppyxSampleAppTheme -import com.bumble.appyx.navigation.integration.BrowserViewportWindow import com.bumble.appyx.navigation.integration.ScreenSize import com.bumble.appyx.navigation.integration.WebNodeHost import kotlinx.coroutines.CoroutineScope @@ -36,10 +36,11 @@ import kotlinx.coroutines.launch external fun onWasmReady(onReady: () -> Unit) +@OptIn(ExperimentalComposeUiApi::class) fun main() { val events: Channel = Channel() onWasmReady { - BrowserViewportWindow("Navigation Demo") { + CanvasBasedWindow("Navigation Demo") { val requester = remember { FocusRequester() } var hasFocus by remember { mutableStateOf(false) } diff --git a/documentation/navigation/multiplatform.md b/documentation/navigation/multiplatform.md index 6ca6e830b..e23501fe1 100644 --- a/documentation/navigation/multiplatform.md +++ b/documentation/navigation/multiplatform.md @@ -138,7 +138,7 @@ fun main() = application { fun main() { val events: Channel = Channel() onWasmReady { - BrowserViewportWindow("Your app") { + CanvasBasedWindow("Your app") { val requester = remember { FocusRequester() } var hasFocus by remember { mutableStateOf(false) } var screenSize by remember { mutableStateOf(ScreenSize(0.dp, 0.dp)) } diff --git a/utils/customisations/build.gradle.kts b/utils/customisations/build.gradle.kts index 5120c0ef5..6270c521d 100644 --- a/utils/customisations/build.gradle.kts +++ b/utils/customisations/build.gradle.kts @@ -23,12 +23,22 @@ kotlin { // Adding moduleName as a workaround for this issue: https://youtrack.jetbrains.com/issue/KT-51942 moduleName = "appyx-utils-customisation" browser() + binaries.executable() } @OptIn(ExperimentalWasmDsl::class) wasmJs { // Adding moduleName as a workaround for this issue: https://youtrack.jetbrains.com/issue/KT-51942 moduleName = "appyx-utils-customisation-wa" - browser() + browser { + // Refer to this Slack thread for more details: https://kotlinlang.slack.com/archives/CDFP59223/p1702977410505449?thread_ts=1702668737.674499&cid=CDFP59223 + testTask { + useKarma { + useChromeHeadless() + useConfigDirectory(project.projectDir.resolve("karma.config.d").resolve("wasm")) + } + } + } + binaries.executable() } iosX64() iosArm64() @@ -48,4 +58,4 @@ kotlin { iosSimulatorArm64Main.dependsOn(this) } } -} +} \ No newline at end of file diff --git a/utils/customisations/karma.config.d/wasm/config.js b/utils/customisations/karma.config.d/wasm/config.js new file mode 100644 index 000000000..22429e585 --- /dev/null +++ b/utils/customisations/karma.config.d/wasm/config.js @@ -0,0 +1,55 @@ +// see https://kotlinlang.org/docs/js-project-setup.html#webpack-configuration-file +// This file provides karma.config.d configuration to run tests with k/wasm + +const path = require("path"); + +config.browserConsoleLogOptions.level = "debug"; + +const basePath = config.basePath; +const projectPath = path.resolve(basePath, "..", "..", "..", ".."); +const generatedAssetsPath = path.resolve(projectPath, "build", "karma-webpack-out") + +const debug = message => console.log(`[karma-config] ${message}`); + +debug(`karma basePath: ${basePath}`); +debug(`karma generatedAssetsPath: ${generatedAssetsPath}`); + +config.proxies["/"] = path.resolve(basePath, "kotlin"); + +config.files = [ + {pattern: path.resolve(generatedAssetsPath, "**/*"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.png"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.gif"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.ttf"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.txt"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.json"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.xml"), included: false, served: true, watched: false}, +].concat(config.files); + +function KarmaWebpackOutputFramework(config) { + // This controller is instantiated and set during the preprocessor phase. + const controller = config.__karmaWebpackController; + + // only if webpack has instantiated its controller + if (!controller) { + console.warn( + "Webpack has not instantiated controller yet.\n" + + "Check if you have enabled webpack preprocessor and framework before this framework" + ) + return + } + + config.files.push({ + pattern: `${controller.outputPath}/**/*`, + included: false, + served: true, + watched: false + }) +} + +const KarmaWebpackOutputPlugin = { + 'framework:webpack-output': ['factory', KarmaWebpackOutputFramework], +}; + +config.plugins.push(KarmaWebpackOutputPlugin); +config.frameworks.push("webpack-output"); \ No newline at end of file diff --git a/utils/material3/build.gradle.kts b/utils/material3/build.gradle.kts index d68488287..0b77ea233 100644 --- a/utils/material3/build.gradle.kts +++ b/utils/material3/build.gradle.kts @@ -24,12 +24,22 @@ kotlin { // Adding moduleName as a workaround for this issue: https://youtrack.jetbrains.com/issue/KT-51942 moduleName = "appyx-utils-material3" browser() + binaries.executable() } @OptIn(ExperimentalWasmDsl::class) wasmJs { // Adding moduleName as a workaround for this issue: https://youtrack.jetbrains.com/issue/KT-51942 moduleName = "appyx-utils-material3-wa" - browser() + browser { + // Refer to this Slack thread for more details: https://kotlinlang.slack.com/archives/CDFP59223/p1702977410505449?thread_ts=1702668737.674499&cid=CDFP59223 + testTask { + useKarma { + useChromeHeadless() + useConfigDirectory(project.projectDir.resolve("karma.config.d").resolve("wasm")) + } + } + } + binaries.executable() } iosX64() iosArm64() @@ -59,3 +69,7 @@ kotlin { } } } + +compose.experimental { + web.application {} +} diff --git a/utils/material3/karma.config.d/wasm/config.js b/utils/material3/karma.config.d/wasm/config.js new file mode 100644 index 000000000..22429e585 --- /dev/null +++ b/utils/material3/karma.config.d/wasm/config.js @@ -0,0 +1,55 @@ +// see https://kotlinlang.org/docs/js-project-setup.html#webpack-configuration-file +// This file provides karma.config.d configuration to run tests with k/wasm + +const path = require("path"); + +config.browserConsoleLogOptions.level = "debug"; + +const basePath = config.basePath; +const projectPath = path.resolve(basePath, "..", "..", "..", ".."); +const generatedAssetsPath = path.resolve(projectPath, "build", "karma-webpack-out") + +const debug = message => console.log(`[karma-config] ${message}`); + +debug(`karma basePath: ${basePath}`); +debug(`karma generatedAssetsPath: ${generatedAssetsPath}`); + +config.proxies["/"] = path.resolve(basePath, "kotlin"); + +config.files = [ + {pattern: path.resolve(generatedAssetsPath, "**/*"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.png"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.gif"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.ttf"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.txt"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.json"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.xml"), included: false, served: true, watched: false}, +].concat(config.files); + +function KarmaWebpackOutputFramework(config) { + // This controller is instantiated and set during the preprocessor phase. + const controller = config.__karmaWebpackController; + + // only if webpack has instantiated its controller + if (!controller) { + console.warn( + "Webpack has not instantiated controller yet.\n" + + "Check if you have enabled webpack preprocessor and framework before this framework" + ) + return + } + + config.files.push({ + pattern: `${controller.outputPath}/**/*`, + included: false, + served: true, + watched: false + }) +} + +const KarmaWebpackOutputPlugin = { + 'framework:webpack-output': ['factory', KarmaWebpackOutputFramework], +}; + +config.plugins.push(KarmaWebpackOutputPlugin); +config.frameworks.push("webpack-output"); \ No newline at end of file diff --git a/utils/multiplatform/build.gradle.kts b/utils/multiplatform/build.gradle.kts index a01d051a7..22938fbd7 100644 --- a/utils/multiplatform/build.gradle.kts +++ b/utils/multiplatform/build.gradle.kts @@ -25,12 +25,22 @@ kotlin { // Adding moduleName as a workaround for this issue: https://youtrack.jetbrains.com/issue/KT-51942 moduleName = "appyx-utils-multiplatform" browser() + binaries.executable() } @OptIn(ExperimentalWasmDsl::class) wasmJs { // Adding moduleName as a workaround for this issue: https://youtrack.jetbrains.com/issue/KT-51942 moduleName = "appyx-utils-multiplatform-wa" - browser() + browser { + // Refer to this Slack thread for more details: https://kotlinlang.slack.com/archives/CDFP59223/p1702977410505449?thread_ts=1702668737.674499&cid=CDFP59223 + testTask { + useKarma { + useChromeHeadless() + useConfigDirectory(project.projectDir.resolve("karma.config.d").resolve("wasm")) + } + } + } + binaries.executable() } iosX64() iosArm64() diff --git a/utils/multiplatform/karma.config.d/wasm/config.js b/utils/multiplatform/karma.config.d/wasm/config.js new file mode 100644 index 000000000..22429e585 --- /dev/null +++ b/utils/multiplatform/karma.config.d/wasm/config.js @@ -0,0 +1,55 @@ +// see https://kotlinlang.org/docs/js-project-setup.html#webpack-configuration-file +// This file provides karma.config.d configuration to run tests with k/wasm + +const path = require("path"); + +config.browserConsoleLogOptions.level = "debug"; + +const basePath = config.basePath; +const projectPath = path.resolve(basePath, "..", "..", "..", ".."); +const generatedAssetsPath = path.resolve(projectPath, "build", "karma-webpack-out") + +const debug = message => console.log(`[karma-config] ${message}`); + +debug(`karma basePath: ${basePath}`); +debug(`karma generatedAssetsPath: ${generatedAssetsPath}`); + +config.proxies["/"] = path.resolve(basePath, "kotlin"); + +config.files = [ + {pattern: path.resolve(generatedAssetsPath, "**/*"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.png"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.gif"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.ttf"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.txt"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.json"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.xml"), included: false, served: true, watched: false}, +].concat(config.files); + +function KarmaWebpackOutputFramework(config) { + // This controller is instantiated and set during the preprocessor phase. + const controller = config.__karmaWebpackController; + + // only if webpack has instantiated its controller + if (!controller) { + console.warn( + "Webpack has not instantiated controller yet.\n" + + "Check if you have enabled webpack preprocessor and framework before this framework" + ) + return + } + + config.files.push({ + pattern: `${controller.outputPath}/**/*`, + included: false, + served: true, + watched: false + }) +} + +const KarmaWebpackOutputPlugin = { + 'framework:webpack-output': ['factory', KarmaWebpackOutputFramework], +}; + +config.plugins.push(KarmaWebpackOutputPlugin); +config.frameworks.push("webpack-output"); \ No newline at end of file From 2d49f7f3c1ac39e98413d668762775224c8c10dc Mon Sep 17 00:00:00 2001 From: Manel Martos Date: Thu, 4 Apr 2024 15:30:13 +0200 Subject: [PATCH 5/7] Fix wasm config issues related to tests --- appyx-navigation/common/build.gradle.kts | 2 + .../common/karma.config.d/wasm/config.js | 55 +++++++++++++++++++ 2 files changed, 57 insertions(+) create mode 100644 appyx-navigation/common/karma.config.d/wasm/config.js diff --git a/appyx-navigation/common/build.gradle.kts b/appyx-navigation/common/build.gradle.kts index 3e0ebcbbd..4b5df9db5 100644 --- a/appyx-navigation/common/build.gradle.kts +++ b/appyx-navigation/common/build.gradle.kts @@ -25,6 +25,7 @@ kotlin { // Adding moduleName as a workaround for this issue: https://youtrack.jetbrains.com/issue/KT-51942 moduleName = "appyx-navigation-common" browser() + binaries.executable() } wasmJs { // Adding moduleName as a workaround for this issue: https://youtrack.jetbrains.com/issue/KT-51942 @@ -38,6 +39,7 @@ kotlin { } } } + binaries.executable() } iosX64() diff --git a/appyx-navigation/common/karma.config.d/wasm/config.js b/appyx-navigation/common/karma.config.d/wasm/config.js new file mode 100644 index 000000000..22429e585 --- /dev/null +++ b/appyx-navigation/common/karma.config.d/wasm/config.js @@ -0,0 +1,55 @@ +// see https://kotlinlang.org/docs/js-project-setup.html#webpack-configuration-file +// This file provides karma.config.d configuration to run tests with k/wasm + +const path = require("path"); + +config.browserConsoleLogOptions.level = "debug"; + +const basePath = config.basePath; +const projectPath = path.resolve(basePath, "..", "..", "..", ".."); +const generatedAssetsPath = path.resolve(projectPath, "build", "karma-webpack-out") + +const debug = message => console.log(`[karma-config] ${message}`); + +debug(`karma basePath: ${basePath}`); +debug(`karma generatedAssetsPath: ${generatedAssetsPath}`); + +config.proxies["/"] = path.resolve(basePath, "kotlin"); + +config.files = [ + {pattern: path.resolve(generatedAssetsPath, "**/*"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.png"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.gif"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.ttf"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.txt"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.json"), included: false, served: true, watched: false}, + {pattern: path.resolve(basePath, "kotlin", "**/*.xml"), included: false, served: true, watched: false}, +].concat(config.files); + +function KarmaWebpackOutputFramework(config) { + // This controller is instantiated and set during the preprocessor phase. + const controller = config.__karmaWebpackController; + + // only if webpack has instantiated its controller + if (!controller) { + console.warn( + "Webpack has not instantiated controller yet.\n" + + "Check if you have enabled webpack preprocessor and framework before this framework" + ) + return + } + + config.files.push({ + pattern: `${controller.outputPath}/**/*`, + included: false, + served: true, + watched: false + }) +} + +const KarmaWebpackOutputPlugin = { + 'framework:webpack-output': ['factory', KarmaWebpackOutputFramework], +}; + +config.plugins.push(KarmaWebpackOutputPlugin); +config.frameworks.push("webpack-output"); \ No newline at end of file From 5ef7f38787ef0f00fb0e2c508797a2c4cabfb532 Mon Sep 17 00:00:00 2001 From: Manel Martos Date: Fri, 5 Apr 2024 08:51:25 +0200 Subject: [PATCH 6/7] Update convention plugin --- .../bumble/appyx/multiplatform/MultiplatformConventionPlugin.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plugins/convention/src/main/kotlin/com/bumble/appyx/multiplatform/MultiplatformConventionPlugin.kt b/plugins/convention/src/main/kotlin/com/bumble/appyx/multiplatform/MultiplatformConventionPlugin.kt index 8240db64b..04f47b8aa 100644 --- a/plugins/convention/src/main/kotlin/com/bumble/appyx/multiplatform/MultiplatformConventionPlugin.kt +++ b/plugins/convention/src/main/kotlin/com/bumble/appyx/multiplatform/MultiplatformConventionPlugin.kt @@ -35,6 +35,8 @@ class MultiplatformConventionPlugin : Plugin { "src/desktopTest/kotlin", "src/jsMain/kotlin", "src/jsTest/kotlin", + "src/wasmJsMain/kotlin", + "src/wasmJsTest/kotlin", "src/iosMain/kotlin", ) } From 8ff134addcec402b14ceb635ae8099832a346348 Mon Sep 17 00:00:00 2001 From: Manel Martos Date: Mon, 8 Apr 2024 09:57:19 +0200 Subject: [PATCH 7/7] Fix detekt issues --- .../interactions/{SystemClock.wasmJs.kt => SystemClock.kt} | 2 +- ...irectoryImpl.wasmJs.kt => NodeCustomisationDirectoryImpl.kt} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename appyx-interactions/common/src/wasmJsMain/kotlin/com/bumble/appyx/interactions/{SystemClock.wasmJs.kt => SystemClock.kt} (99%) rename utils/customisations/src/wasmJsMain/kotlin/com/bumble/appyx/utils/customisations/{NodeCustomisationDirectoryImpl.wasmJs.kt => NodeCustomisationDirectoryImpl.kt} (100%) diff --git a/appyx-interactions/common/src/wasmJsMain/kotlin/com/bumble/appyx/interactions/SystemClock.wasmJs.kt b/appyx-interactions/common/src/wasmJsMain/kotlin/com/bumble/appyx/interactions/SystemClock.kt similarity index 99% rename from appyx-interactions/common/src/wasmJsMain/kotlin/com/bumble/appyx/interactions/SystemClock.wasmJs.kt rename to appyx-interactions/common/src/wasmJsMain/kotlin/com/bumble/appyx/interactions/SystemClock.kt index ba43a8c19..90b265262 100644 --- a/appyx-interactions/common/src/wasmJsMain/kotlin/com/bumble/appyx/interactions/SystemClock.wasmJs.kt +++ b/appyx-interactions/common/src/wasmJsMain/kotlin/com/bumble/appyx/interactions/SystemClock.kt @@ -10,4 +10,4 @@ actual object SystemClock { private const val MillisToNanos = 1_000_000L -} \ No newline at end of file +} diff --git a/utils/customisations/src/wasmJsMain/kotlin/com/bumble/appyx/utils/customisations/NodeCustomisationDirectoryImpl.wasmJs.kt b/utils/customisations/src/wasmJsMain/kotlin/com/bumble/appyx/utils/customisations/NodeCustomisationDirectoryImpl.kt similarity index 100% rename from utils/customisations/src/wasmJsMain/kotlin/com/bumble/appyx/utils/customisations/NodeCustomisationDirectoryImpl.wasmJs.kt rename to utils/customisations/src/wasmJsMain/kotlin/com/bumble/appyx/utils/customisations/NodeCustomisationDirectoryImpl.kt