All Downloads are FREE. Search and download functionalities are using the official Maven repository.

korolev.internal.ApplicationInstance.scala Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2017-2018 Aleksey Fomkin
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package korolev.internal

import java.util.concurrent.atomic.AtomicInteger

import korolev.Context._
import korolev.Async.AsyncOps
import korolev.execution.Scheduler
import korolev._
import korolev.state.{StateDeserializer, StateManager, StateSerializer}
import levsha.events.calculateEventPropagation
import levsha.impl.DiffRenderContext
import levsha.{Document, Id, XmlNs}

final class ApplicationInstance
  [
    F[_]: Async: Scheduler,
    S: StateSerializer: StateDeserializer,
    M
  ](
    sessionId: QualifiedSessionId,
    connection: Connection[F],
    stateManager: StateManager[F, S],
    initialState: S,
    render: PartialFunction[S, Document.Node[Effect[F, S, M]]],
    router: Router[F, S],
    fromScratch: Boolean,
    reporter: Reporter
  ) {

  import reporter.Implicit

  private val devMode = new DevMode.ForRenderContext(sessionId.toString, fromScratch)
  private val currentRenderNum = new AtomicInteger(0)
  private val renderContext = DiffRenderContext[Effect[F, S, M]](savedBuffer = devMode.loadRenderContext())
  private val frontend = new ClientSideApi[F](connection, reporter)

  val topLevelComponentInstance: ComponentInstance[F, S, M, S, Any, M] = {
    val renderer = render.lift
    val eventRegistry = new EventRegistry[F](frontend)
    val component = new Component[F, S, Any, M](initialState, Component.TopLevelComponentId) {
      def render(parameters: Any, state: S): Document.Node[Effect[F, S, M]] = {
        renderer(state).getOrElse {
          reporter.error(s"Render is not defined for $state")
          Document.Node[Effect[F, S, M]] { rc =>
            rc.openNode(XmlNs.html, "body")
            rc.addTextNode("Render is not defined for the state")
            rc.closeNode("body")
          }
        }
      }
    }
    new ComponentInstance[F, S, M, S, Any, M](
      Id.TopLevel, sessionId, frontend, eventRegistry,
      stateManager, () => currentRenderNum.get(), component, reporter
    )
  }

  private def onState(): F[Unit] = stateManager.snapshot.map { snapshot =>
    renderContext.synchronized {
      // Set page url if router exists
      router.fromState
        .lift(snapshot(Id.TopLevel).getOrElse(initialState))
        .foreach(path => frontend.changePageUrl(path))

      // Prepare render context
      renderContext.swap()

      // Perform rendering
      topLevelComponentInstance.applyRenderContext(
        parameters = (), // Boxed unit as parameter. Top level component doesn't need parameters
        snapshot = snapshot,
        rc = renderContext
      )

      // Infer changes
      frontend.startDomChanges()
      renderContext.diff(frontend)
      frontend.flushDomChanges()

      if (devMode.isActive)
        devMode.saveRenderContext(renderContext)

      // Make korolev ready to next render
      topLevelComponentInstance.dropObsoleteMisc()
      frontend.setRenderNum(currentRenderNum.incrementAndGet())
    }
  }

  frontend.setHandlers(
    onHistory = { path =>
      stateManager
        .read[S](Id.TopLevel)
        .flatMap { maybeTopLevelState =>
          router
            .toState
            .lift(path)
            .fold(Async[F].delay(Option.empty[S]))(_(maybeTopLevelState.getOrElse(initialState)).map(Some(_)))
        }
        .flatMap {
          case Some(newState) =>
            stateManager.write(Id.TopLevel, newState)
              .flatMap(_ => onState())
          case None =>
            Async[F].unit
        }
        .runIgnoreResult
    },
    onEvent = { (renderNum, target, tpe) =>
      if (currentRenderNum.get == renderNum) {
        calculateEventPropagation(target, tpe) forall { eventId =>
          topLevelComponentInstance.applyEvent(eventId)
        }
        ()
      }
    },
    onFormDataProgress = topLevelComponentInstance.handleFormDataProgress
  )

  frontend.setRenderNum(0)

  stateManager.snapshot runOrReport { snapshot =>
    // Content should be created from scratch
    // Remove all element from document.body
    if (fromScratch)
      frontend.cleanRoot()

    // If dev mode is enabled and active
    // CSS should be reloaded
    if (devMode.isActive)
      frontend.reloadCss()

    // Perform initial rendering
    if (fromScratch) {
      renderContext.openNode(levsha.XmlNs.html, "body")
      renderContext.closeNode("body")
      renderContext.diff(DiffRenderContext.DummyChangesPerformer)
    } else {

      if (devMode.hasSavedRenderContext) {
        renderContext.swap()
        topLevelComponentInstance.applyRenderContext((), renderContext, snapshot)
        frontend.startDomChanges()
        renderContext.diff(frontend)
        frontend.flushDomChanges()
        devMode.saveRenderContext(renderContext)
      } else {

        topLevelComponentInstance.applyRenderContext((), renderContext, snapshot)
        renderContext.diff(DiffRenderContext.DummyChangesPerformer)

        if (devMode.isActive)
          devMode.saveRenderContext(renderContext)
      }
    }

    topLevelComponentInstance.subscribeStateChange { (_, _) =>
      onState().runIgnoreResult
    }

    if (fromScratch)
      onState().runIgnoreResult
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy