|
|
|
@ -1,5 +1,5 @@ |
|
|
|
<script> |
|
|
|
import { onMount } from "svelte" |
|
|
|
import { onMount, setContext } from "svelte" |
|
|
|
import { goto, params } from "@roxi/routify" |
|
|
|
import { |
|
|
|
store, |
|
|
|
@ -18,11 +18,63 @@ |
|
|
|
Search, |
|
|
|
Tabs, |
|
|
|
Tab, |
|
|
|
Layout as BBUILayout, |
|
|
|
notifications, |
|
|
|
} from "@budibase/bbui" |
|
|
|
|
|
|
|
export let showModal |
|
|
|
|
|
|
|
let scrollRef |
|
|
|
|
|
|
|
const scrollTo = bounds => { |
|
|
|
if (!bounds) { |
|
|
|
return |
|
|
|
} |
|
|
|
|
|
|
|
const sidebarWidth = 259 |
|
|
|
const navItemHeight = 32 |
|
|
|
const { scrollLeft, scrollTop, offsetHeight } = scrollRef |
|
|
|
|
|
|
|
let scrollBounds = scrollRef.getBoundingClientRect() |
|
|
|
let newOffsets = {} |
|
|
|
|
|
|
|
// Calculate left offset |
|
|
|
const offsetX = bounds.left + bounds.width + scrollLeft + 20 |
|
|
|
if (offsetX > sidebarWidth) { |
|
|
|
newOffsets.left = offsetX - sidebarWidth |
|
|
|
} else { |
|
|
|
newOffsets.left = 0 |
|
|
|
} |
|
|
|
if (newOffsets.left === scrollLeft) { |
|
|
|
delete newOffsets.left |
|
|
|
} |
|
|
|
|
|
|
|
// Calculate top offset |
|
|
|
const offsetY = bounds.top - scrollBounds?.top + scrollTop |
|
|
|
if (offsetY > scrollTop + offsetHeight - 2 * navItemHeight) { |
|
|
|
newOffsets.top = offsetY - offsetHeight + 2 * navItemHeight |
|
|
|
} else if (offsetY < scrollTop + navItemHeight) { |
|
|
|
newOffsets.top = offsetY - navItemHeight |
|
|
|
} else { |
|
|
|
delete newOffsets.top |
|
|
|
} |
|
|
|
|
|
|
|
// Skip if offset is unchanged |
|
|
|
if (newOffsets.left == null && newOffsets.top == null) { |
|
|
|
return |
|
|
|
} |
|
|
|
|
|
|
|
// Smoothly scroll to the offset |
|
|
|
scrollRef.scroll({ |
|
|
|
...newOffsets, |
|
|
|
behavior: "smooth", |
|
|
|
}) |
|
|
|
} |
|
|
|
|
|
|
|
setContext("scroll", { |
|
|
|
scrollTo, |
|
|
|
}) |
|
|
|
|
|
|
|
const tabs = [ |
|
|
|
{ |
|
|
|
title: "Screens", |
|
|
|
@ -79,7 +131,7 @@ |
|
|
|
<Tabs {selected} on:select={navigate}> |
|
|
|
<Tab title="Screens"> |
|
|
|
<div class="tab-content-padding"> |
|
|
|
<div class="role-select"> |
|
|
|
<BBUILayout noPadding gap="XS"> |
|
|
|
<Select |
|
|
|
on:change={updateAccessRole} |
|
|
|
value={$selectedAccessRole} |
|
|
|
@ -93,17 +145,24 @@ |
|
|
|
label="Search Screens" |
|
|
|
bind:value={$screenSearchString} |
|
|
|
/> |
|
|
|
</div> |
|
|
|
<div class="nav-items-container"> |
|
|
|
</BBUILayout> |
|
|
|
<div class="nav-items-container" bind:this={scrollRef}> |
|
|
|
<ComponentNavigationTree /> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</Tab> |
|
|
|
<Tab title="Layouts"> |
|
|
|
<div class="tab-content-padding"> |
|
|
|
{#each $store.layouts as layout, idx (layout._id)} |
|
|
|
<Layout {layout} border={idx > 0} /> |
|
|
|
{/each} |
|
|
|
<div |
|
|
|
class="nav-items-container nav-items-container--layouts" |
|
|
|
bind:this={scrollRef} |
|
|
|
> |
|
|
|
<div class="layouts-container"> |
|
|
|
{#each $store.layouts as layout, idx (layout._id)} |
|
|
|
<Layout {layout} border={idx > 0} /> |
|
|
|
{/each} |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
<Modal bind:this={newLayoutModal}> |
|
|
|
<NewLayoutModal /> |
|
|
|
</Modal> |
|
|
|
@ -126,23 +185,45 @@ |
|
|
|
justify-content: flex-start; |
|
|
|
align-items: stretch; |
|
|
|
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 { |
|
|
|
position: absolute; |
|
|
|
top: var(--spacing-l); |
|
|
|
right: var(--spacing-xl); |
|
|
|
} |
|
|
|
|
|
|
|
.role-select { |
|
|
|
.tab-content-padding { |
|
|
|
padding: 0 var(--spacing-xl); |
|
|
|
height: 100%; |
|
|
|
display: flex; |
|
|
|
flex-direction: column; |
|
|
|
justify-content: flex-start; |
|
|
|
align-items: stretch; |
|
|
|
margin-bottom: var(--spacing-m); |
|
|
|
gap: var(--spacing-m); |
|
|
|
gap: var(--spacing-xl); |
|
|
|
} |
|
|
|
|
|
|
|
.tab-content-padding { |
|
|
|
padding: 0 var(--spacing-xl); |
|
|
|
.nav-items-container { |
|
|
|
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; |
|
|
|
position: relative; |
|
|
|
} |
|
|
|
.nav-items-container--layouts { |
|
|
|
border-top: none; |
|
|
|
margin-top: calc(-1 * var(--spectrum-global-dimension-static-size-150)); |
|
|
|
} |
|
|
|
|
|
|
|
.layouts-container { |
|
|
|
min-width: max-content; |
|
|
|
} |
|
|
|
</style> |
|
|
|
|