budibase/packages/builder/src/components/design/NavigationPanel/FrontendNavigatePane.svelte

215 lines
5.3 KiB
Svelte

<script>
import { onMount, setContext } from "svelte"
import { goto, params } from "@roxi/routify"
import {
store,
allScreens,
selectedAccessRole,
screenSearchString,
} from "builderStore"
import { roles } from "stores/backend"
import ComponentNavigationTree from "components/design/NavigationPanel/ComponentNavigationTree/index.svelte"
import Layout from "components/design/NavigationPanel/Layout.svelte"
import NewLayoutModal from "components/design/NavigationPanel/NewLayoutModal.svelte"
import {
Icon,
Modal,
Select,
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",
key: "screen",
},
{
title: "Layouts",
key: "layout",
},
]
let newLayoutModal
$: selected = tabs.find(t => t.key === $params.assetType)?.title || "Screens"
const navigate = ({ detail }) => {
const { key } = tabs.find(t => t.title === detail)
$goto(`../${key}`)
}
const updateAccessRole = event => {
const role = event.detail
// Select a valid screen with this new role - otherwise we'll not be
// able to change role at all because ComponentNavigationTree will kick us
// back the current role again because the same screen ID is still selected
const firstValidScreenId = $allScreens.find(
screen => screen.routing.roleId === role
)?._id
if (firstValidScreenId) {
store.actions.screens.select(firstValidScreenId)
}
// Otherwise clear the selected screen ID so that the first new valid screen
// can be selected by ComponentNavigationTree
else {
store.update(state => {
state.selectedScreenId = null
return state
})
}
selectedAccessRole.set(role)
}
onMount(async () => {
try {
await store.actions.routing.fetch()
} catch (error) {
notifications.error("Error fetching routes")
}
})
</script>
<div class="title">
<Tabs {selected} on:select={navigate}>
<Tab title="Screens">
<div class="tab-content-padding">
<BBUILayout noPadding gap="XS">
<Select
on:change={updateAccessRole}
value={$selectedAccessRole}
label="Filter by Access"
getOptionLabel={role => role.name}
getOptionValue={role => role._id}
options={$roles}
/>
<Search
placeholder="Enter a route to search"
label="Search Screens"
bind:value={$screenSearchString}
/>
</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}
<Modal bind:this={newLayoutModal}>
<NewLayoutModal />
</Modal>
</div>
</Tab>
</Tabs>
<div class="add-button">
<Icon
hoverable
name="AddCircle"
on:click={selected === "Layouts" ? newLayoutModal.show() : showModal()}
/>
</div>
</div>
<style>
.title {
display: flex;
flex-direction: column;
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);
}
.tab-content-padding {
padding: 0 var(--spacing-xl);
height: 100%;
display: flex;
flex-direction: column;
justify-content: flex-start;
align-items: stretch;
gap: 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;
}
</style>