Add vertical scrolling to selected component and support scrolling to selected screen

This commit is contained in:
Andrew Kingston 2022-03-21 13:38:53 +00:00
parent 61e077bf0a
commit 697c43187a
4 changed files with 89 additions and 59 deletions

View File

@ -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,35 +46,33 @@
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"
> >
{#if withArrow} <div class="nav-item-content" bind:this={contentRef}>
<div class:opened class="icon arrow" on:click={onIconClick}> {#if withArrow}
<Icon size="S" name="ChevronRight" /> <div class:opened class="icon arrow" on:click={onIconClick}>
</div> <Icon size="S" name="ChevronRight" />
{/if} </div>
{/if}
<slot name="icon" /> <slot name="icon" />
{#if iconText} {#if iconText}
<div class="iconText" style={iconColor ? `color: ${iconColor};` : ""}> <div class="iconText" style={iconColor ? `color: ${iconColor};` : ""}>
{iconText} {iconText}
</div> </div>
{:else if icon} {:else if icon}
<div class="icon"> <div class="icon">
<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>
@ -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 {

View File

@ -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}

View File

@ -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>

View File

@ -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>