Add initial work on horizontally scrollable component tree with automatic scrolling
This commit is contained in:
parent
d96347ee74
commit
b0761ed36c
|
@ -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 {
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue