Add initial work on horizontally scrollable component tree with automatic scrolling

This commit is contained in:
Andrew Kingston 2022-03-18 19:52:11 +00:00
parent d96347ee74
commit b0761ed36c
6 changed files with 118 additions and 50 deletions

View File

@ -15,6 +15,7 @@
export let iconColor export let iconColor
const dispatch = createEventDispatcher() const dispatch = createEventDispatcher()
let textRef
function onIconClick(event) { function onIconClick(event) {
event.stopPropagation() event.stopPropagation()
@ -26,49 +27,58 @@
class="nav-item" class="nav-item"
class:border class:border
class:selected class:selected
style={`padding-left: ${indentLevel * 14}px`} style={`padding-left: ${20 + indentLevel * 14}px`}
{draggable} {draggable}
on:dragend on:dragend
on:dragstart on:dragstart
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="content"> {#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" /> </div>
</div> {/if}
{/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">{text}</div> <div class="text" bind:this={textRef}>{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 {
border-radius: var(--border-radius-s);
cursor: pointer; cursor: pointer;
color: var(--grey-7); color: var(--grey-7);
transition: background-color transition: background-color
var(--spectrum-global-animation-duration-100, 130ms) ease-in-out; var(--spectrum-global-animation-duration-100, 130ms) ease-in-out;
padding: 0 var(--spacing-m) 0 var(--spacing-xl);
height: 32px;
display: flex;
flex-direction: row;
justify-content: flex-start;
align-items: center;
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);
@ -81,16 +91,6 @@
visibility: visible; visibility: visible;
} }
.content {
padding: 0 var(--spacing-s);
height: 32px;
display: flex;
flex-direction: row;
justify-content: flex-start;
align-items: center;
gap: var(--spacing-xs);
}
.icon { .icon {
font-size: 16px; font-size: 16px;
flex: 0 0 20px; flex: 0 0 20px;
@ -111,12 +111,12 @@
} }
.text { .text {
flex: 1 1 auto;
font-weight: 600; font-weight: 600;
font-size: var(--spectrum-global-dimension-font-size-75); font-size: var(--spectrum-global-dimension-font-size-75);
white-space: nowrap;
max-width: 180px;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap;
} }
.actions { .actions {
@ -128,6 +128,7 @@
flex-direction: row; flex-direction: row;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
margin-left: var(--spacing-s);
} }
.iconText { .iconText {

View File

@ -1,4 +1,5 @@
<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"
@ -12,6 +13,8 @@
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 => {
@ -93,6 +96,9 @@
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}
@ -133,6 +139,10 @@
padding-left: 0; padding-left: 0;
margin: 0; margin: 0;
} }
ul,
li {
min-width: max-content;
}
.drop-item { .drop-item {
border-radius: var(--border-radius-m); border-radius: var(--border-radius-m);

View File

@ -1,4 +1,5 @@
<script> <script>
import { getContext } from "svelte"
import { import {
store, store,
selectedComponent, selectedComponent,
@ -21,6 +22,7 @@
} }
const dragDropStore = instantiateStore() const dragDropStore = instantiateStore()
const scrollApi = getContext("scroll")
export let route export let route
export let path export let path
@ -73,6 +75,9 @@
opened={routeOpened} opened={routeOpened}
{border} {border}
withArrow={route.subpaths} withArrow={route.subpaths}
on:mouseover={e => {
scrollApi.scrollTo(0, e.detail)
}}
/> />
{#if routeOpened} {#if routeOpened}
@ -85,6 +90,9 @@
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

@ -68,9 +68,12 @@
</div> </div>
<style> <style>
.root {
min-width: max-content;
}
div.empty { div.empty {
font-size: var(--font-size-xs); font-size: var(--font-size-s);
color: var(--grey-5); color: var(--grey-5);
padding-top: var(--spacing-xs); padding: var(--spacing-xs) var(--spacing-xl);
} }
</style> </style>

View File

@ -1,5 +1,5 @@
<script> <script>
import { onMount } from "svelte" import { onMount, setContext } from "svelte"
import { goto, params } from "@roxi/routify" import { goto, params } from "@roxi/routify"
import { import {
store, store,
@ -18,11 +18,46 @@
Search, Search,
Tabs, Tabs,
Tab, Tab,
Layout as BBUILayout,
notifications, notifications,
} from "@budibase/bbui" } from "@budibase/bbui"
export let showModal export let showModal
let scrollRef
const scrollTo = (indent, width) => {
if (!indent) {
return
}
// Padding left + icon
const end = indent * 14 + 20 + 16 + 4 + width + 40
let scrollLeft = 0
if (end > 259) {
scrollLeft = end - 259
}
if (scrollLeft === scrollRef.scrollLeft) {
return
}
scrollRef.scroll({
left: scrollLeft,
behavior: "smooth",
})
// if (indentLevel === 0) {
// } else {
// scrollRef.scrollLeft = 4 + indentLevel * 14
// }
}
setContext("scroll", {
scrollTo,
})
const tabs = [ const tabs = [
{ {
title: "Screens", title: "Screens",
@ -79,7 +114,7 @@
<Tabs {selected} on:select={navigate}> <Tabs {selected} on:select={navigate}>
<Tab title="Screens"> <Tab title="Screens">
<div class="tab-content-padding"> <div class="tab-content-padding">
<div class="role-select"> <BBUILayout noPadding gap="XS">
<Select <Select
on:change={updateAccessRole} on:change={updateAccessRole}
value={$selectedAccessRole} value={$selectedAccessRole}
@ -93,8 +128,8 @@
label="Search Screens" label="Search Screens"
bind:value={$screenSearchString} bind:value={$screenSearchString}
/> />
</div> </BBUILayout>
<div class="nav-items-container"> <div class="nav-items-container" bind:this={scrollRef}>
<ComponentNavigationTree /> <ComponentNavigationTree />
</div> </div>
</div> </div>
@ -126,23 +161,36 @@
justify-content: flex-start; justify-content: flex-start;
align-items: stretch; align-items: stretch;
position: relative; position: relative;
flex: 1 1 auto;
} }
.title :global(.spectrum-Tabs-content),
.title :global(.spectrum-Tabs-content > div),
.title :global(.spectrum-Tabs-content > div > div) {
height: 100%;
}
.add-button { .add-button {
position: absolute; position: absolute;
top: var(--spacing-l); top: var(--spacing-l);
right: var(--spacing-xl); right: var(--spacing-xl);
} }
.role-select { .tab-content-padding {
padding: 0 var(--spacing-xl);
height: 100%;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
justify-content: flex-start; justify-content: flex-start;
align-items: stretch; align-items: stretch;
margin-bottom: var(--spacing-m); gap: var(--spacing-xl);
gap: var(--spacing-m);
} }
.tab-content-padding { .nav-items-container {
padding: 0 var(--spacing-xl); border-top: var(--border-light);
margin: 0 calc(-1 * var(--spacing-xl));
padding: var(--spacing-m) 0;
flex: 1 1 auto;
overflow: auto;
height: 0;
} }
</style> </style>

View File

@ -244,8 +244,6 @@
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: var(--spacing-l); gap: var(--spacing-l);
padding: 0 0 60px 0;
overflow-y: auto;
border-right: var(--border-light); border-right: var(--border-light);
} }