Fix more navigation bugs

This commit is contained in:
Jon Staab 2024-03-21 16:47:42 -07:00
parent 780f0b3407
commit 5187f3d0e9
8 changed files with 76 additions and 69 deletions

View File

@ -6,6 +6,7 @@
- [x] Skip notifying admin when the person joining/leaving groups is the admin
- [x] Remove group share modal, skip straight to create invite link
- [x] Rank groups by WoT
- [x] Fix subtle navigation bugs
# 0.4.4

View File

@ -345,7 +345,7 @@
let scrollY
const unsubHistory = router.history.subscribe($history => {
if ($history[0].config.modal) {
if ($history[0].modal) {
// This is not idempotent, so don't duplicate it
if (document.body.style.position !== "fixed") {
scrollY = window.scrollY

View File

@ -73,7 +73,7 @@
<i class="fa fa-cog" /> Settings
</MenuMobileItem>
{#if !$env.FORCE_GROUP && $env.PLATFORM_RELAYS.length === 0}
<MenuMobileItem href="/settings/relays">
<MenuMobileItem href="/settings/relays" on:click={closeMenu}>
<i class="fa fa-server" />
<div class="relative inline-block">
Relays
@ -84,7 +84,7 @@
</div>
</MenuMobileItem>
{/if}
<MenuMobileItem disabled={!$pubkey} href="/notifications">
<MenuMobileItem disabled={!$pubkey} href="/notifications" on:click={closeMenu}>
<i class="fa fa-bell" />
<div class="relative inline-block">
Notifications
@ -95,11 +95,11 @@
</div>
</MenuMobileItem>
{#if $env.FORCE_GROUP}
<MenuMobileItem href="/events">
<MenuMobileItem href="/events" on:click={closeMenu}>
<i class="fa fa-calendar-days" /> Calendar
</MenuMobileItem>
{#if $env.ENABLE_MARKET}
<MenuMobileItem href="/listings">
<MenuMobileItem href="/listings" on:click={closeMenu}>
<i class="fa fa-store" /> Market
</MenuMobileItem>
{/if}
@ -108,7 +108,7 @@
<i class="fa fa-people-pulling" /> Community
</MenuMobileItem>
{/if}
<MenuMobileItem disabled={!$canSign} href="/channels">
<MenuMobileItem disabled={!$canSign} href="/channels" on:click={closeMenu}>
<i class="fa fa-message" />
<div class="relative inline-block">
Messages
@ -118,7 +118,7 @@
{/if}
</div>
</MenuMobileItem>
<MenuMobileItem href="/notes">
<MenuMobileItem href="/notes" on:click={closeMenu}>
<i class="fa fa-rss" /> Feed
</MenuMobileItem>
</div>
@ -134,16 +134,16 @@
<SliderMenu onEscape={closeSubMenu}>
<p class="staatliches mb-8 text-center text-3xl">Community</p>
<div class="staatliches m-auto grid grid-cols-2 gap-3">
<MenuMobileItem href="/events">
<MenuMobileItem href="/events" on:click={closeMenu}>
<i class="fa fa-calendar-days" /> Calendar
</MenuMobileItem>
{#if $env.ENABLE_MARKET}
<MenuMobileItem href="/listings">
<MenuMobileItem href="/listings" on:click={closeMenu}>
<i class="fa fa-store" /> Market
</MenuMobileItem>
{/if}
{#if !$env.FORCE_GROUP}
<MenuMobileItem href="/groups">
<MenuMobileItem href="/groups" on:click={closeMenu}>
<i class="fa fa-circle-nodes" /> Groups
</MenuMobileItem>
{/if}
@ -156,20 +156,20 @@
<p class="staatliches mb-8 text-center text-3xl">Settings</p>
<div class="staatliches m-auto grid grid-cols-2 gap-3">
{#if $installPrompt}
<MenuMobileItem on:click={installAsPWA}>
<MenuMobileItem on:click={installAsPWA} on:click={closeMenu}>
<i class="fa fa-rocket" /> Install
</MenuMobileItem>
{/if}
<MenuMobileItem on:click={toggleTheme}>
<MenuMobileItem on:click={toggleTheme} on:click={closeMenu}>
<i class="fa fa-palette" /> Theme
</MenuMobileItem>
<MenuMobileItem disabled={!$pubkey} href="/settings/data">
<MenuMobileItem disabled={!$pubkey} href="/settings/data" on:click={closeMenu}>
<i class="fa fa-database" /> Database
</MenuMobileItem>
<MenuMobileItem disabled={!$canSign} href="/settings/content">
<MenuMobileItem disabled={!$canSign} href="/settings/content" on:click={closeMenu}>
<i class="fa fa-volume-xmark" /> Content
</MenuMobileItem>
<MenuMobileItem disabled={!$canSign} href="/settings">
<MenuMobileItem disabled={!$canSign} href="/settings" on:click={closeMenu}>
<i class="fa fa-sliders" /> App Settings
</MenuMobileItem>
</div>
@ -180,13 +180,13 @@
<SliderMenu onEscape={closeSubMenu}>
<p class="staatliches mb-8 text-center text-3xl">Account</p>
<div class="staatliches m-auto mb-8 grid grid-cols-2 gap-3">
<MenuMobileItem href="/settings/keys">
<MenuMobileItem href="/settings/keys" on:click={closeMenu}>
<i class="fa fa-key" /> Keys
</MenuMobileItem>
<MenuMobileItem href={router.at("people").of($pubkey).toString()}>
<MenuMobileItem href={router.at("people").of($pubkey).toString()} on:click={closeMenu}>
<i class="fa fa-user-circle" /> Profile
</MenuMobileItem>
<MenuMobileItem href={router.at("invite/create").qp({initialPubkey: $pubkey}).toString()}>
<MenuMobileItem href={router.at("invite/create").qp({initialPubkey: $pubkey}).toString()} on:click={closeMenu}>
<i class="fa fa-paper-plane" /> Create Invite
</MenuMobileItem>
</div>

View File

@ -30,12 +30,12 @@
$: {
// Redirect if we have no user
if (!$session && $page && router.getMatch($page.path).route.requireUser) {
router.go("/", {replace: true})
router.go({path: "/", replace: true})
}
// Redirect if we need a signer
if (!$signer.isEnabled() && $page && router.getMatch($page.path).route.requireSigner) {
router.go("/", {replace: true})
router.go({path: "/", replace: true})
}
const props = router.getProps($current)
@ -44,7 +44,7 @@
// This is usually due to a malformed url.
for (const k of router.getMatch($current.path).route.required || []) {
if (!props[k]) {
router.go("/", {replace: true})
router.go({path: "/", replace: true})
break
}
}
@ -71,9 +71,9 @@
</div>
{/key}
{#each reverse($modals).filter(m => !m.config.virtual) as m, i (router.getKey(m) + i)}
{#each reverse($modals).filter(m => !m.virtual) as m, i (router.getKey(m) + i)}
{@const promise = router.getMatch(m.path).route.component}
<Modal virtual={false} canClose={!m.config.noEscape}>
<Modal virtual={false} canClose={!m.noEscape}>
{#key $stateKey}
{#await promise}
<!-- pass -->

View File

@ -12,15 +12,17 @@
const dispatch = createEventDispatcher()
const getClick = e => {
if (!e.touches) {
return null
const t = Date.now()
if (e.touches?.[0]) {
return {x: e.touches[0].clientX, y: e.touches[0].clientY, t}
}
return {
x: e.x || e.touches[0].clientX,
y: e.y || e.touches[0].clientY,
t: Date.now(),
if (e.x || e.y) {
return {x: e.x, y: e.y, t}
}
return null
}
const startClick = e => {

View File

@ -53,7 +53,7 @@
}
historyItem = $history[0]
isNested = Boolean($history[1]?.config.modal)
isNested = Boolean($history[1]?.modal)
// If history changes and removes this modal, notify the caller if virtual
const unsub = history.subscribe($history => {

View File

@ -38,7 +38,6 @@ const createHistory = source => {
to,
{state = {}, replace = false, preserveScroll = false, blurActiveElement = true} = {},
) {
state = {...state, key: Date.now() + ""}
// try...catch iOS Safari limits to 100 pushState calls
try {
if (replace) source.history.replaceState(state, "", to)

View File

@ -1,5 +1,5 @@
import {takeWhile, find, filter, identity, mergeLeft, reject, path as getPath} from "ramda"
import {first, filterVals} from "hurdak"
import {takeWhile, pick, find, filter, identity, mergeLeft, reject, prop} from "ramda"
import {first, randomId, filterVals} from "hurdak"
import logger from "src/util/logger"
import {buildQueryString, parseQueryString, updateIn} from "src/util/misc"
import {globalHistory} from "src/util/history"
@ -135,7 +135,8 @@ export type Route = RegisterOpts & {
component: any
}
export type RouteConfig = {
export type HistoryItem = {
path: string
key?: string
mini?: boolean
modal?: boolean
@ -145,11 +146,6 @@ export type RouteConfig = {
context?: Record<string, any>
}
export type HistoryItem = {
path: string
config: RouteConfig
}
export const asPath = (...parts: string[]) => {
let path = parts.filter(identity).join("/")
@ -230,15 +226,15 @@ class RouterExtension {
return path
}
go = (newConfig = {}) => {
const config = filterVals(identity, {
go = (newConfig = {}) =>
this.router.go(
filterVals(identity, {
...this.config,
...newConfig,
context: this.context,
path: this.toString(),
})
this.router.go(this.toString(), config)
}
)
push = (config = {}) => this.go(config)
@ -253,11 +249,11 @@ export class Router {
routes: Route[] = []
extensions: Record<string, RouterExtension> = {}
history = writable<HistoryItem[]>([])
nonVirtual = this.history.derived(reject(getPath(["config", "virtual"])))
pages = this.nonVirtual.derived(filter((h: HistoryItem) => !h.config.modal))
page = this.nonVirtual.derived(find((h: HistoryItem) => !h.config.modal))
modals = this.nonVirtual.derived(takeWhile((h: HistoryItem) => h.config.modal))
modal = this.nonVirtual.derived(find((h: HistoryItem) => h.config.modal))
nonVirtual = this.history.derived(reject(prop('virtual')))
pages = this.nonVirtual.derived(filter((h: HistoryItem) => !h.modal))
page = this.nonVirtual.derived(find((h: HistoryItem) => !h.modal))
modals = this.nonVirtual.derived(takeWhile((h: HistoryItem) => h.modal))
modal = this.nonVirtual.derived(find((h: HistoryItem) => h.modal))
current = this.nonVirtual.derived(history => history[0])
init() {
@ -271,14 +267,17 @@ export class Router {
listen() {
return globalHistory.listen(({location, action}) => {
const {state, pathname, search} = location
const path = pathname + search
const [cur, prev] = this.history.get()
const key = this.getKey(location.state)
const key = this.getKey({path: pathname, ...state})
// If we're going back, splice instead of push
if (action === "POP" && prev && key === this.getKey(prev)) {
if (action === "POP") {
if (prev && path === prev.path && key !== this.getKey(cur)) {
this.history.update($history => $history.slice(1))
} else if (key !== this.getKey(cur)) {
this.go(location.pathname + location.search)
} else if (path !== cur.path) {
this.go({path})
}
}
})
}
@ -301,20 +300,26 @@ export class Router {
return match
}
go(path, {replace, ...config}: RouteConfig = {}) {
const state = {path, config}
go({replace, ...state}: HistoryItem = {}) {
if (!state.path) {
throw new Error("router.go called without a path")
}
if (!state.key) {
state.key = randomId()
}
this.history.update($history => {
// Drop one if we're replacing
if (replace) {
$history.splice(0, 1)
$history = $history.slice(1)
}
// Keep our history at 100 entries
return [state, ...$history.slice(0, 100)]
})
globalHistory.navigate(path, {replace, state})
globalHistory.navigate(state.path, {replace, state: {key: state.key}})
}
pop() {
@ -328,18 +333,18 @@ export class Router {
}
remove(key) {
this.history.update(reject(($item: HistoryItem) => $item.config.key === key))
this.history.update(reject(($item: HistoryItem) => $item.key === key))
}
clearModals() {
this.history.update($history => {
while ($history[0]?.config?.modal) {
while ($history[0]?.modal) {
$history.splice(0, 1)
}
// Make sure we don't completely clear history out
if ($history.length === 0) {
$history.push({path: "/", config: {}, ...this.getMatch("/")})
$history.push({path: "/"})
}
globalHistory.navigate($history[0].path || "/")
@ -362,7 +367,7 @@ export class Router {
const path = first(historyItem.path.split("?"))
const params = this.decodeQueryString(historyItem.path)
return this.at(path).qp(params).cg(historyItem.config)
return this.at(path).qp(params).cg(historyItem)
}
fromCurrent() {
@ -415,11 +420,11 @@ export class Router {
return data
}
getKey = (item: HistoryItem) => item.config.key || item.path
getKey = (item: HistoryItem) => item.key || item.path
getProps = (item: HistoryItem) => ({
...this.decodeQueryString(item.path),
...this.decodeRouteParams(item.path),
...item.config.context,
...item.context,
})
}