Add drag and drop for space icons
This commit is contained in:
@@ -6,10 +6,73 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
const {urls}: Props = $props()
|
const {urls}: Props = $props()
|
||||||
|
|
||||||
|
const reorderUrls = (sourceUrl: string, targetUrl: string, currentUrls: string[]) => {
|
||||||
|
if (sourceUrl === targetUrl) {
|
||||||
|
return currentUrls
|
||||||
|
}
|
||||||
|
|
||||||
|
const sourceIndex = currentUrls.indexOf(sourceUrl)
|
||||||
|
const targetIndex = currentUrls.indexOf(targetUrl)
|
||||||
|
|
||||||
|
if (sourceIndex === -1 || targetIndex === -1) {
|
||||||
|
return currentUrls
|
||||||
|
}
|
||||||
|
|
||||||
|
const nextUrls = currentUrls.filter(url => url !== sourceUrl)
|
||||||
|
|
||||||
|
nextUrls.splice(targetIndex, 0, sourceUrl)
|
||||||
|
|
||||||
|
return nextUrls
|
||||||
|
}
|
||||||
|
|
||||||
|
const moveDraggedUrl = (targetUrl: string) => {
|
||||||
|
if (!draggedUrl) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
orderedUrls = reorderUrls(draggedUrl, targetUrl, orderedUrls)
|
||||||
|
}
|
||||||
|
|
||||||
|
const onDragStart = (e: DragEvent, url: string) => {
|
||||||
|
draggedUrl = url
|
||||||
|
|
||||||
|
if (e.dataTransfer) {
|
||||||
|
e.dataTransfer.effectAllowed = "move"
|
||||||
|
e.dataTransfer.setData("text/plain", url)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const onDragOver = (e: DragEvent, targetUrl: string) => {
|
||||||
|
e.preventDefault()
|
||||||
|
moveDraggedUrl(targetUrl)
|
||||||
|
}
|
||||||
|
|
||||||
|
const onDrop = (e: DragEvent, targetUrl: string) => {
|
||||||
|
e.preventDefault()
|
||||||
|
moveDraggedUrl(targetUrl)
|
||||||
|
draggedUrl = undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
const onDragEnd = () => {
|
||||||
|
draggedUrl = undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
let orderedUrls = $state([...urls])
|
||||||
|
let draggedUrl = $state<string | undefined>()
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="column menu gap-2">
|
<div class="column menu gap-2 p-3" role="list">
|
||||||
{#each urls as url (url)}
|
{#each orderedUrls as url (url)}
|
||||||
<MenuSpacesItem {url} />
|
<div
|
||||||
|
class:opacity-60={draggedUrl === url}
|
||||||
|
draggable="true"
|
||||||
|
role="listitem"
|
||||||
|
ondragstart={e => onDragStart(e, url)}
|
||||||
|
ondragover={e => onDragOver(e, url)}
|
||||||
|
ondrop={e => onDrop(e, url)}
|
||||||
|
ondragend={onDragEnd}>
|
||||||
|
<MenuSpacesItem {url} />
|
||||||
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type {Snippet} from "svelte"
|
import type {Snippet} from "svelte"
|
||||||
import {goto} from "$app/navigation"
|
import {goto} from "$app/navigation"
|
||||||
import {splitAt} from "@welshman/lib"
|
|
||||||
import {userProfile, shouldUnwrap} from "@welshman/app"
|
import {userProfile, shouldUnwrap} from "@welshman/app"
|
||||||
import Widget from "@assets/icons/widget.svg?dataurl"
|
import Widget from "@assets/icons/widget.svg?dataurl"
|
||||||
import Compass from "@assets/icons/compass.svg?dataurl"
|
import Compass from "@assets/icons/compass.svg?dataurl"
|
||||||
@@ -28,7 +27,7 @@
|
|||||||
|
|
||||||
const {children}: Props = $props()
|
const {children}: Props = $props()
|
||||||
|
|
||||||
const showOtherSpacesMenu = () => pushModal(MenuOtherSpaces, {urls: secondarySpaceUrls})
|
const showOtherSpacesMenu = () => pushModal(MenuOtherSpaces, {urls: $userSpaceUrls})
|
||||||
|
|
||||||
const showSettingsMenu = () => pushModal(MenuSettings)
|
const showSettingsMenu = () => pushModal(MenuSettings)
|
||||||
|
|
||||||
@@ -39,9 +38,9 @@
|
|||||||
const itemHeight = 56
|
const itemHeight = 56
|
||||||
const navPadding = 8 * itemHeight
|
const navPadding = 8 * itemHeight
|
||||||
const itemLimit = $derived((windowHeight - navPadding) / itemHeight)
|
const itemLimit = $derived((windowHeight - navPadding) / itemHeight)
|
||||||
const [primarySpaceUrls, secondarySpaceUrls] = $derived(splitAt(itemLimit, $userSpaceUrls))
|
const primarySpaceUrls = $derived($userSpaceUrls.slice(0, itemLimit))
|
||||||
const anySpaceNotifications = $derived($userSpaceUrls.some(p => $notifications.has(p)))
|
const anySpaceNotifications = $derived($userSpaceUrls.some(p => $notifications.has(p)))
|
||||||
const otherSpaceNotifications = $derived(secondarySpaceUrls.some(p => $notifications.has(p)))
|
const otherSpaceNotifications = $derived($userSpaceUrls.some(p => $notifications.has(p)))
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:window bind:innerHeight={windowHeight} />
|
<svelte:window bind:innerHeight={windowHeight} />
|
||||||
@@ -60,15 +59,13 @@
|
|||||||
{#each primarySpaceUrls as url (url)}
|
{#each primarySpaceUrls as url (url)}
|
||||||
<PrimaryNavItemSpace {url} />
|
<PrimaryNavItemSpace {url} />
|
||||||
{/each}
|
{/each}
|
||||||
{#if secondarySpaceUrls.length > 0}
|
<PrimaryNavItem
|
||||||
<PrimaryNavItem
|
title="All Spaces"
|
||||||
title="Other Spaces"
|
class="tooltip-right"
|
||||||
class="tooltip-right"
|
onclick={showOtherSpacesMenu}
|
||||||
onclick={showOtherSpacesMenu}
|
notification={otherSpaceNotifications}>
|
||||||
notification={otherSpaceNotifications}>
|
<ImageIcon alt="All Spaces" src={Widget} size={8} />
|
||||||
<ImageIcon alt="Other Spaces" src={Widget} size={8} />
|
</PrimaryNavItem>
|
||||||
</PrimaryNavItem>
|
|
||||||
{/if}
|
|
||||||
<PrimaryNavItem title="Add a Space" href="/discover" class="tooltip-right">
|
<PrimaryNavItem title="Add a Space" href="/discover" class="tooltip-right">
|
||||||
<ImageIcon alt="Add a Space" src={Compass} size={8} />
|
<ImageIcon alt="Add a Space" src={Compass} size={8} />
|
||||||
</PrimaryNavItem>
|
</PrimaryNavItem>
|
||||||
|
|||||||
@@ -12,6 +12,83 @@
|
|||||||
import {pushModal} from "@app/util/modal"
|
import {pushModal} from "@app/util/modal"
|
||||||
|
|
||||||
const addSpace = () => pushModal(SpaceAdd)
|
const addSpace = () => pushModal(SpaceAdd)
|
||||||
|
|
||||||
|
const reconcileUrls = (currentUrls: string[], nextUrls: string[]) => {
|
||||||
|
const mergedUrls = currentUrls.filter(url => nextUrls.includes(url))
|
||||||
|
|
||||||
|
for (const url of nextUrls) {
|
||||||
|
if (!mergedUrls.includes(url)) {
|
||||||
|
mergedUrls.push(url)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return mergedUrls
|
||||||
|
}
|
||||||
|
|
||||||
|
const isSameOrder = (a: string[], b: string[]) =>
|
||||||
|
a.length === b.length && a.every((url, index) => url === b[index])
|
||||||
|
|
||||||
|
const reorderUrls = (sourceUrl: string, targetUrl: string, currentUrls: string[]) => {
|
||||||
|
if (sourceUrl === targetUrl) {
|
||||||
|
return currentUrls
|
||||||
|
}
|
||||||
|
|
||||||
|
const sourceIndex = currentUrls.indexOf(sourceUrl)
|
||||||
|
const targetIndex = currentUrls.indexOf(targetUrl)
|
||||||
|
|
||||||
|
if (sourceIndex === -1 || targetIndex === -1) {
|
||||||
|
return currentUrls
|
||||||
|
}
|
||||||
|
|
||||||
|
const nextUrls = currentUrls.filter(url => url !== sourceUrl)
|
||||||
|
|
||||||
|
nextUrls.splice(targetIndex, 0, sourceUrl)
|
||||||
|
|
||||||
|
return nextUrls
|
||||||
|
}
|
||||||
|
|
||||||
|
const moveDraggedUrl = (targetUrl: string) => {
|
||||||
|
if (!draggedUrl) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
orderedSpaceUrls = reorderUrls(draggedUrl, targetUrl, orderedSpaceUrls)
|
||||||
|
}
|
||||||
|
|
||||||
|
const onDragStart = (e: DragEvent, url: string) => {
|
||||||
|
draggedUrl = url
|
||||||
|
|
||||||
|
if (e.dataTransfer) {
|
||||||
|
e.dataTransfer.effectAllowed = "move"
|
||||||
|
e.dataTransfer.setData("text/plain", url)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const onDragOver = (e: DragEvent, targetUrl: string) => {
|
||||||
|
e.preventDefault()
|
||||||
|
moveDraggedUrl(targetUrl)
|
||||||
|
}
|
||||||
|
|
||||||
|
const onDrop = (e: DragEvent, targetUrl: string) => {
|
||||||
|
e.preventDefault()
|
||||||
|
moveDraggedUrl(targetUrl)
|
||||||
|
draggedUrl = undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
const onDragEnd = () => {
|
||||||
|
draggedUrl = undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
$effect(() => {
|
||||||
|
const nextUrls = reconcileUrls(orderedSpaceUrls, $userSpaceUrls)
|
||||||
|
|
||||||
|
if (!isSameOrder(nextUrls, orderedSpaceUrls)) {
|
||||||
|
orderedSpaceUrls = nextUrls
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
let orderedSpaceUrls = $state<string[]>([])
|
||||||
|
let draggedUrl = $state<string | undefined>()
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Page class="cw-full">
|
<Page class="cw-full">
|
||||||
@@ -33,7 +110,7 @@
|
|||||||
{/if}
|
{/if}
|
||||||
{/snippet}
|
{/snippet}
|
||||||
</PageBar>
|
</PageBar>
|
||||||
<PageContent class="cw-full flex flex-col gap-2 p-2 pt-4">
|
<PageContent class="cw-full flex flex-col gap-2 p-4 pt-6">
|
||||||
{#each PLATFORM_RELAYS as url (url)}
|
{#each PLATFORM_RELAYS as url (url)}
|
||||||
<MenuSpacesItem {url} />
|
<MenuSpacesItem {url} />
|
||||||
{:else}
|
{:else}
|
||||||
@@ -43,8 +120,17 @@
|
|||||||
Loading your spaces...
|
Loading your spaces...
|
||||||
</div>
|
</div>
|
||||||
{:then}
|
{:then}
|
||||||
{#each $userSpaceUrls as url (url)}
|
{#each orderedSpaceUrls as url (url)}
|
||||||
<MenuSpacesItem {url} />
|
<div
|
||||||
|
class:opacity-60={draggedUrl === url}
|
||||||
|
draggable="true"
|
||||||
|
role="listitem"
|
||||||
|
ondragstart={e => onDragStart(e, url)}
|
||||||
|
ondragover={e => onDragOver(e, url)}
|
||||||
|
ondrop={e => onDrop(e, url)}
|
||||||
|
ondragend={onDragEnd}>
|
||||||
|
<MenuSpacesItem {url} />
|
||||||
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
<div class="flex flex-col gap-8 items-center py-20">
|
<div class="flex flex-col gap-8 items-center py-20">
|
||||||
<p>You haven't added any spaces yet!</p>
|
<p>You haven't added any spaces yet!</p>
|
||||||
|
|||||||
Reference in New Issue
Block a user