eygraber / immich-kmp

MIT License
2 stars 0 forks source link

JS: Need to remove custom domain prefix if part of the path is included in it #10

Closed github-actions[bot] closed 7 months ago

github-actions[bot] commented 8 months ago

https://github.com/eygraber/immich-kmp/blob/ddfa6bf5797871eb1d2beff0efca85381dd6d47b/virtue/history/src/jsMain/kotlin/com/eygraber/virtue/history/JsHistory.kt#L59


package com.eygraber.virtue.history

import com.eygraber.virtue.back.press.dispatch.OnBackPressedDispatcher
import com.eygraber.virtue.di.scopes.SessionSingleton
import kotlinx.coroutines.flow.Flow
import me.tatarka.inject.annotations.Inject
import org.w3c.dom.PopStateEvent
import org.w3c.dom.events.Event

public external interface HistoryState {
  public val index: Int
  public val state: String
}

@Suppress("UNUSED_PARAMETER")
public fun historyState(index: Int, stateKey: String): dynamic = js("({ index: index, stateKey: stateKey })")

@SessionSingleton
@Inject
public class JsHistory(
  private val browserHistory: org.w3c.dom.History,
  private val browserLocation: org.w3c.dom.Location,
  private val browserWindow: org.w3c.dom.Window,
  private val history: TimelineHistory,
  private val backPressDispatcher: OnBackPressedDispatcher,
) : History {
  override val currentItem: History.Item get() = history.currentItem
  override val canGoBack: Boolean get() = history.canGoBack
  override val canGoForward: Boolean get() = history.canGoForward

  override val updates: Flow<History.Item> = history.updates

  private val popStateListener: (Event) -> Unit = { event ->
    val state = (event as PopStateEvent).state?.unsafeCast<HistoryState>()
    if(state != null) {
      if(isBackPress(state)) {
        // if someone is going to intercept the back press (ignoring the session handler)
        // then we move the browser back to where it was before the back press (the url might flash)
        // it will cause another popstate to fire but this should be a no-op
        // since at that point the browser history and timeline history should be the same
        if(backPressDispatcher.hasEnabledCallbacks()) {
          browserHistory.go(1)
        }

        backPressDispatcher.onBackPressed()
      }
      else {
        handlePopStateEvent(state)
      }
    }
  }

  override fun initialize() {
    browserWindow.addEventListener("popstate", popStateListener)

    history.initialize()

    if(!history.isRestored) {
      // TODO: need to remove custom domain prefix if part of the path is included in it
      update(history.currentItem.payload.copy(urlPath = browserLocation.pathname))
    }
  }

  override fun destroy() {
    browserWindow.removeEventListener("popstate", popStateListener)
    history.destroy()
  }

  override fun push(payload: History.Item.Payload): History.Item =
    history.push(payload).apply {
      browserHistory.pushState(historyState(index, payload.stateKey), "", payload.urlPath)
    }

  override fun update(payload: History.Item.Payload): History.Item =
    history.update(payload).apply {
      browserHistory.replaceState(historyState(index, payload.stateKey), "", payload.urlPath)
    }

  override fun move(delta: Int) {
    history.move(delta)
    browserHistory.go(delta)
  }

  override fun moveBack() {
    history.moveBack()
    browserHistory.back()
  }

  override fun moveForward() {
    history.moveForward()
    browserHistory.forward()
  }

  override fun onBackPressed() {
    // don't use browserHistory because this event came from the browser
    history.moveBack()
  }

  private fun handlePopStateEvent(eventState: HistoryState) {
    val current = currentItem.index

    val delta = maxOf(current, eventState.index) - minOf(current, eventState.index)

    // don't use browserHistory, because this event came from the browser (would cause an infinite loop)
    history.move(delta)
  }

  private fun isBackPress(eventState: HistoryState): Boolean =
    eventState.index == history.currentItem.index - 1
}