Add vertical scrolling to selected component and support scrolling to selected screen
This commit is contained in:
parent
733fe1fe3f
commit
7c39430d1c
|
@ -1,6 +1,6 @@
|
||||||
<script>
|
<script>
|
||||||
import { Icon } from "@budibase/bbui"
|
import { Icon } from "@budibase/bbui"
|
||||||
import { createEventDispatcher } from "svelte"
|
import { createEventDispatcher, getContext } from "svelte"
|
||||||
|
|
||||||
export let icon
|
export let icon
|
||||||
export let withArrow = false
|
export let withArrow = false
|
||||||
|
@ -14,13 +14,25 @@
|
||||||
export let iconText
|
export let iconText
|
||||||
export let iconColor
|
export let iconColor
|
||||||
|
|
||||||
|
const scrollApi = getContext("scroll")
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
let textRef
|
|
||||||
|
let contentRef
|
||||||
|
$: selected && contentRef && scrollToView()
|
||||||
|
|
||||||
function onIconClick(event) {
|
function onIconClick(event) {
|
||||||
event.stopPropagation()
|
event.stopPropagation()
|
||||||
dispatch("iconClick")
|
dispatch("iconClick")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const scrollToView = () => {
|
||||||
|
if (!scrollApi || !contentRef) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
console.log("selected", text)
|
||||||
|
const bounds = contentRef.getBoundingClientRect()
|
||||||
|
scrollApi.scrollTo(bounds)
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
|
@ -34,13 +46,10 @@
|
||||||
on:dragover
|
on:dragover
|
||||||
on:drop
|
on:drop
|
||||||
on:click
|
on:click
|
||||||
on:mouseover={() => {
|
|
||||||
const size = textRef.getBoundingClientRect()
|
|
||||||
dispatch("mouseover", size.width)
|
|
||||||
}}
|
|
||||||
ondragover="return false"
|
ondragover="return false"
|
||||||
ondragenter="return false"
|
ondragenter="return false"
|
||||||
>
|
>
|
||||||
|
<div class="nav-item-content" bind:this={contentRef}>
|
||||||
{#if withArrow}
|
{#if withArrow}
|
||||||
<div class:opened class="icon arrow" on:click={onIconClick}>
|
<div class:opened class="icon arrow" on:click={onIconClick}>
|
||||||
<Icon size="S" name="ChevronRight" />
|
<Icon size="S" name="ChevronRight" />
|
||||||
|
@ -57,13 +66,14 @@
|
||||||
<Icon color={iconColor} size="S" name={icon} />
|
<Icon color={iconColor} size="S" name={icon} />
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
<div class="text" bind:this={textRef}>{text}</div>
|
<div class="text">{text}</div>
|
||||||
{#if withActions}
|
{#if withActions}
|
||||||
<div class="actions">
|
<div class="actions">
|
||||||
<slot />
|
<slot />
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.nav-item {
|
.nav-item {
|
||||||
|
@ -74,11 +84,9 @@
|
||||||
padding: 0 var(--spacing-m) 0 var(--spacing-xl);
|
padding: 0 var(--spacing-m) 0 var(--spacing-xl);
|
||||||
height: 32px;
|
height: 32px;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: column;
|
||||||
justify-content: flex-start;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: flex-start;
|
||||||
gap: var(--spacing-xs);
|
|
||||||
width: calc(max-content - 64px);
|
|
||||||
}
|
}
|
||||||
.nav-item.selected {
|
.nav-item.selected {
|
||||||
background-color: var(--grey-2);
|
background-color: var(--grey-2);
|
||||||
|
@ -91,6 +99,16 @@
|
||||||
visibility: visible;
|
visibility: visible;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.nav-item-content {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: flex-start;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--spacing-xs);
|
||||||
|
width: max-content;
|
||||||
|
}
|
||||||
|
|
||||||
.icon {
|
.icon {
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
flex: 0 0 20px;
|
flex: 0 0 20px;
|
||||||
|
@ -117,6 +135,7 @@
|
||||||
max-width: 180px;
|
max-width: 180px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
|
flex: 0 0 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.actions {
|
.actions {
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
<script>
|
<script>
|
||||||
import { getContext } from "svelte"
|
|
||||||
import { store } from "builderStore"
|
import { store } from "builderStore"
|
||||||
import { DropEffect, DropPosition } from "./dragDropStore"
|
import { DropEffect, DropPosition } from "./dragDropStore"
|
||||||
import ComponentDropdownMenu from "./ComponentDropdownMenu.svelte"
|
import ComponentDropdownMenu from "./ComponentDropdownMenu.svelte"
|
||||||
|
@ -13,8 +12,6 @@
|
||||||
export let level = 0
|
export let level = 0
|
||||||
export let dragDropStore
|
export let dragDropStore
|
||||||
|
|
||||||
const scrollApi = getContext("scroll")
|
|
||||||
|
|
||||||
let closedNodes = {}
|
let closedNodes = {}
|
||||||
|
|
||||||
const selectComponent = component => {
|
const selectComponent = component => {
|
||||||
|
@ -96,9 +93,6 @@
|
||||||
on:dragover={dragover(component, index)}
|
on:dragover={dragover(component, index)}
|
||||||
on:iconClick={() => toggleNodeOpen(component._id)}
|
on:iconClick={() => toggleNodeOpen(component._id)}
|
||||||
on:drop={onDrop}
|
on:drop={onDrop}
|
||||||
on:mouseover={e => {
|
|
||||||
scrollApi.scrollTo(level + 1, e.detail)
|
|
||||||
}}
|
|
||||||
text={getComponentText(component)}
|
text={getComponentText(component)}
|
||||||
withArrow
|
withArrow
|
||||||
indentLevel={level + 1}
|
indentLevel={level + 1}
|
||||||
|
|
|
@ -66,6 +66,9 @@
|
||||||
}
|
}
|
||||||
routeManuallyOpened = !routeManuallyOpened
|
routeManuallyOpened = !routeManuallyOpened
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$: console.log($store.selectedComponentId)
|
||||||
|
$: console.log()
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if !noSearchMatch}
|
{#if !noSearchMatch}
|
||||||
|
@ -76,9 +79,6 @@
|
||||||
opened={routeOpened}
|
opened={routeOpened}
|
||||||
{border}
|
{border}
|
||||||
withArrow={route.subpaths}
|
withArrow={route.subpaths}
|
||||||
on:mouseover={e => {
|
|
||||||
scrollApi.scrollTo(0, e.detail)
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<PathDropdownMenu screens={allScreens} {path} />
|
<PathDropdownMenu screens={allScreens} {path} />
|
||||||
</NavItem>
|
</NavItem>
|
||||||
|
@ -88,14 +88,12 @@
|
||||||
<NavItem
|
<NavItem
|
||||||
icon="WebPage"
|
icon="WebPage"
|
||||||
indentLevel={indent || 1}
|
indentLevel={indent || 1}
|
||||||
selected={$store.selectedScreenId === screen.id}
|
selected={$store.selectedScreenId === screen.id &&
|
||||||
|
$store.currentView === "detail"}
|
||||||
opened={$store.selectedScreenId === screen.id}
|
opened={$store.selectedScreenId === screen.id}
|
||||||
text={ROUTE_NAME_MAP[screen.route]?.[screen.role] || screen.route}
|
text={ROUTE_NAME_MAP[screen.route]?.[screen.role] || screen.route}
|
||||||
withArrow={route.subpaths}
|
withArrow={route.subpaths}
|
||||||
on:click={() => changeScreen(screen.id)}
|
on:click={() => changeScreen(screen.id)}
|
||||||
on:mouseover={e => {
|
|
||||||
scrollApi.scrollTo(1, e.detail)
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<ScreenDropdownMenu screenId={screen.id} />
|
<ScreenDropdownMenu screenId={screen.id} />
|
||||||
</NavItem>
|
</NavItem>
|
||||||
|
|
|
@ -26,32 +26,50 @@
|
||||||
|
|
||||||
let scrollRef
|
let scrollRef
|
||||||
|
|
||||||
const scrollTo = (indent, width) => {
|
const scrollTo = bounds => {
|
||||||
if (!indent) {
|
if (!bounds) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Padding left + icon
|
const sidebarWidth = 259
|
||||||
const end = indent * 14 + 20 + 16 + 4 + width + 40
|
const navItemHeight = 32
|
||||||
let scrollLeft = 0
|
|
||||||
|
|
||||||
if (end > 259) {
|
let scrollBounds = scrollRef.getBoundingClientRect()
|
||||||
scrollLeft = end - 259
|
let newScrollOffsets = {}
|
||||||
|
|
||||||
|
// Calculate left offset
|
||||||
|
const offsetX = bounds.left + bounds.width + scrollRef.scrollLeft + 20
|
||||||
|
if (offsetX > sidebarWidth) {
|
||||||
|
newScrollOffsets.left = offsetX - sidebarWidth
|
||||||
|
} else {
|
||||||
|
newScrollOffsets.left = 0
|
||||||
|
}
|
||||||
|
if (newScrollOffsets.left === scrollRef.scrollLeft) {
|
||||||
|
delete newScrollOffsets.left
|
||||||
}
|
}
|
||||||
|
|
||||||
if (scrollLeft === scrollRef.scrollLeft) {
|
// Calculate top offset
|
||||||
|
const offsetY = bounds.top - scrollBounds?.top + scrollRef.scrollTop
|
||||||
|
const upperOffset = 2 * navItemHeight - 8
|
||||||
|
const lowerOffset = navItemHeight + 8
|
||||||
|
if (offsetY > scrollRef.scrollTop + scrollRef.offsetHeight - upperOffset) {
|
||||||
|
newScrollOffsets.top = offsetY - scrollRef.offsetHeight + upperOffset
|
||||||
|
} else if (offsetY < scrollRef.scrollTop + lowerOffset) {
|
||||||
|
newScrollOffsets.top = offsetY - lowerOffset
|
||||||
|
} else {
|
||||||
|
delete newScrollOffsets.top
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip if offset is unchanged
|
||||||
|
if (newScrollOffsets.left == null && newScrollOffsets.top == null) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Smoothly scroll to the offset
|
||||||
scrollRef.scroll({
|
scrollRef.scroll({
|
||||||
left: scrollLeft,
|
...newScrollOffsets,
|
||||||
behavior: "smooth",
|
behavior: "smooth",
|
||||||
})
|
})
|
||||||
|
|
||||||
// if (indentLevel === 0) {
|
|
||||||
// } else {
|
|
||||||
// scrollRef.scrollLeft = 4 + indentLevel * 14
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setContext("scroll", {
|
setContext("scroll", {
|
||||||
|
@ -192,5 +210,6 @@
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
height: 0;
|
height: 0;
|
||||||
|
position: relative;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
Loading…
Reference in New Issue