Delete old design UI code
This commit is contained in:
parent
155a60160d
commit
4d7666fea2
|
@ -1,74 +0,0 @@
|
||||||
<script>
|
|
||||||
import { store } from "builderStore"
|
|
||||||
import PathTree from "./PathTree.svelte"
|
|
||||||
|
|
||||||
let routes = {}
|
|
||||||
let paths = []
|
|
||||||
|
|
||||||
$: allRoutes = $store.routes
|
|
||||||
$: selectedScreenId = $store.selectedScreenId
|
|
||||||
$: updatePaths(allRoutes, $selectedAccessRole, selectedScreenId)
|
|
||||||
|
|
||||||
const updatePaths = (allRoutes, selectedRoleId, selectedScreenId) => {
|
|
||||||
const sortedPaths = Object.keys(allRoutes || {}).sort()
|
|
||||||
|
|
||||||
let found = false
|
|
||||||
let firstValidScreenId
|
|
||||||
let filteredRoutes = {}
|
|
||||||
let screenRoleId
|
|
||||||
|
|
||||||
// Filter all routes down to only those which match the current role
|
|
||||||
sortedPaths.forEach(path => {
|
|
||||||
const config = allRoutes[path]
|
|
||||||
Object.entries(config.subpaths).forEach(([subpath, pathConfig]) => {
|
|
||||||
Object.entries(pathConfig.screens).forEach(([roleId, screenId]) => {
|
|
||||||
if (screenId === selectedScreenId) {
|
|
||||||
screenRoleId = roleId
|
|
||||||
found = roleId === selectedRoleId
|
|
||||||
}
|
|
||||||
if (roleId === selectedRoleId) {
|
|
||||||
if (!firstValidScreenId) {
|
|
||||||
firstValidScreenId = screenId
|
|
||||||
}
|
|
||||||
if (!filteredRoutes[path]) {
|
|
||||||
filteredRoutes[path] = { subpaths: {} }
|
|
||||||
}
|
|
||||||
filteredRoutes[path].subpaths[subpath] = {
|
|
||||||
screens: {
|
|
||||||
[selectedRoleId]: screenId,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
routes = { ...filteredRoutes }
|
|
||||||
paths = Object.keys(routes || {}).sort()
|
|
||||||
|
|
||||||
// Select the correct role for the current screen ID
|
|
||||||
if (!found && screenRoleId) {
|
|
||||||
selectedAccessRole.set(screenRoleId)
|
|
||||||
if (screenRoleId !== selectedRoleId) {
|
|
||||||
updatePaths(allRoutes, screenRoleId, selectedScreenId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the selected screen isn't in this filtered list, select the first one
|
|
||||||
else if (!found && firstValidScreenId) {
|
|
||||||
store.actions.screens.select(firstValidScreenId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div class="root" class:has-screens={!!paths?.length}>
|
|
||||||
{#each paths as path, idx (path)}
|
|
||||||
<PathTree border={idx > 0} {path} route={routes[path]} />
|
|
||||||
{/each}
|
|
||||||
{#if !paths.length}{/if}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.root.has-screens {
|
|
||||||
min-width: max-content;
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,198 +0,0 @@
|
||||||
<script>
|
|
||||||
import { onMount, setContext } from "svelte"
|
|
||||||
import { goto, params } from "@roxi/routify"
|
|
||||||
import { store, selectedAccessRole, screenSearchString } from "builderStore"
|
|
||||||
import { roles } from "stores/backend"
|
|
||||||
import ComponentNavigationTree from "components/design/navigation/ComponentNavigationTree/index.svelte"
|
|
||||||
import Layout from "components/design/navigation/Layout.svelte"
|
|
||||||
import NewLayoutModal from "components/design/navigation/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 = $store.screens.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" />
|
|
||||||
</div>
|
|
||||||
</Tab>
|
|
||||||
<Tab title="Layouts">
|
|
||||||
<div class="tab-content-padding">
|
|
||||||
<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>
|
|
||||||
</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--layouts {
|
|
||||||
border-top: none;
|
|
||||||
margin-top: calc(-1 * var(--spectrum-global-dimension-static-size-150));
|
|
||||||
}
|
|
||||||
|
|
||||||
.layouts-container {
|
|
||||||
min-width: max-content;
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,36 +0,0 @@
|
||||||
<script>
|
|
||||||
import ComponentTree from "./ComponentNavigationTree/ComponentTree.svelte"
|
|
||||||
import LayoutDropdownMenu from "./ComponentNavigationTree/LayoutDropdownMenu.svelte"
|
|
||||||
import initDragDropStore from "./ComponentNavigationTree/dragDropStore"
|
|
||||||
import NavItem from "components/common/NavItem.svelte"
|
|
||||||
import { store, selectedComponent } from "builderStore"
|
|
||||||
|
|
||||||
export let layout
|
|
||||||
export let border
|
|
||||||
|
|
||||||
const dragDropStore = initDragDropStore()
|
|
||||||
|
|
||||||
const selectLayout = () => {
|
|
||||||
store.actions.layouts.select(layout._id)
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<NavItem
|
|
||||||
{border}
|
|
||||||
icon="ClassicGridView"
|
|
||||||
text={layout.name}
|
|
||||||
withArrow
|
|
||||||
selected={$store.selectedLayoutId === layout._id}
|
|
||||||
opened={$store.selectedLayoutId === layout._id}
|
|
||||||
on:click={selectLayout}
|
|
||||||
>
|
|
||||||
<LayoutDropdownMenu {layout} />
|
|
||||||
</NavItem>
|
|
||||||
|
|
||||||
{#if $store.selectedLayoutId === layout._id && layout.props?._children}
|
|
||||||
<ComponentTree
|
|
||||||
components={layout.props._children}
|
|
||||||
currentComponent={$selectedComponent}
|
|
||||||
{dragDropStore}
|
|
||||||
/>
|
|
||||||
{/if}
|
|
|
@ -1,93 +0,0 @@
|
||||||
<script>
|
|
||||||
import { ModalContent, Body, Detail } from "@budibase/bbui"
|
|
||||||
|
|
||||||
export let selectedScreens
|
|
||||||
export let chooseModal
|
|
||||||
export let save
|
|
||||||
let selectedNav
|
|
||||||
let createdScreens = []
|
|
||||||
$: blankSelected = selectedScreens.length === 1
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<ModalContent
|
|
||||||
title="Select navigation"
|
|
||||||
cancelText="Back"
|
|
||||||
onCancel={() => (blankSelected ? chooseModal(1) : chooseModal(0))}
|
|
||||||
size="M"
|
|
||||||
onConfirm={() => {
|
|
||||||
save(createdScreens)
|
|
||||||
}}
|
|
||||||
disabled={!selectedNav}
|
|
||||||
>
|
|
||||||
<Body size="S"
|
|
||||||
>Please select your preferred layout for the new application:</Body
|
|
||||||
>
|
|
||||||
|
|
||||||
<div class="wrapper">
|
|
||||||
<div
|
|
||||||
data-cy="left-nav"
|
|
||||||
on:click={() => (selectedNav = "Left")}
|
|
||||||
class:unselected={selectedNav && selectedNav !== "Left"}
|
|
||||||
>
|
|
||||||
<div class="box">
|
|
||||||
<div class="side-nav" />
|
|
||||||
</div>
|
|
||||||
<div><Detail>Side Nav</Detail></div>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
on:click={() => (selectedNav = "Top")}
|
|
||||||
class:unselected={selectedNav && selectedNav !== "Top"}
|
|
||||||
>
|
|
||||||
<div class="box">
|
|
||||||
<div class="top-nav" />
|
|
||||||
</div>
|
|
||||||
<div><Detail>Top Nav</Detail></div>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
on:click={() => (selectedNav = "None")}
|
|
||||||
class:unselected={selectedNav && selectedNav !== "None"}
|
|
||||||
>
|
|
||||||
<div class="box" />
|
|
||||||
<div><Detail>No Nav</Detail></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</ModalContent>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.side-nav {
|
|
||||||
float: left;
|
|
||||||
background: #d3d3d3 0% 0% no-repeat padding-box;
|
|
||||||
border-radius: 2px 0px 0px 2px;
|
|
||||||
height: 100%;
|
|
||||||
width: 10%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.top-nav {
|
|
||||||
background: #d3d3d3 0% 0% no-repeat padding-box;
|
|
||||||
vertical-align: top;
|
|
||||||
width: 100%;
|
|
||||||
height: 15%;
|
|
||||||
}
|
|
||||||
.box {
|
|
||||||
display: inline-block;
|
|
||||||
background: #eaeaea 0% 0% no-repeat padding-box;
|
|
||||||
border: 1px solid #d3d3d3;
|
|
||||||
border-radius: 2px;
|
|
||||||
opacity: 1;
|
|
||||||
width: 120px;
|
|
||||||
height: 70px;
|
|
||||||
margin-right: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.wrapper {
|
|
||||||
display: flex;
|
|
||||||
padding-top: 4%;
|
|
||||||
list-style-type: none;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
margin-right: 5%;
|
|
||||||
}
|
|
||||||
.unselected {
|
|
||||||
opacity: 0.3;
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,20 +0,0 @@
|
||||||
<script>
|
|
||||||
import { notifications } from "@budibase/bbui"
|
|
||||||
import { store } from "builderStore"
|
|
||||||
import { Input, ModalContent } from "@budibase/bbui"
|
|
||||||
|
|
||||||
let name = ""
|
|
||||||
|
|
||||||
async function save() {
|
|
||||||
try {
|
|
||||||
await store.actions.layouts.save({ name })
|
|
||||||
notifications.success(`Layout ${name} created successfully`)
|
|
||||||
} catch (error) {
|
|
||||||
notifications.error("Error creating layout")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<ModalContent title="Create Layout" confirmText="Create" onConfirm={save}>
|
|
||||||
<Input thin label="Name" bind:value={name} />
|
|
||||||
</ModalContent>
|
|
|
@ -1,82 +0,0 @@
|
||||||
<script>
|
|
||||||
import { goto } from "@roxi/routify"
|
|
||||||
import { store } from "builderStore"
|
|
||||||
import ConfirmDialog from "components/common/ConfirmDialog.svelte"
|
|
||||||
import {
|
|
||||||
ActionMenu,
|
|
||||||
MenuItem,
|
|
||||||
Icon,
|
|
||||||
Layout,
|
|
||||||
notifications,
|
|
||||||
} from "@budibase/bbui"
|
|
||||||
import { get } from "svelte/store"
|
|
||||||
|
|
||||||
export let path
|
|
||||||
export let screens
|
|
||||||
|
|
||||||
let confirmDeleteDialog
|
|
||||||
|
|
||||||
const deleteScreens = async () => {
|
|
||||||
if (!screens?.length) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
for (let { id } of screens) {
|
|
||||||
// We have to fetch the screen to be deleted immediately before deleting
|
|
||||||
// as otherwise we're very likely to 409
|
|
||||||
const screen = get(store).screens.find(screen => screen._id === id)
|
|
||||||
if (!screen) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
await store.actions.screens.delete(screen)
|
|
||||||
}
|
|
||||||
notifications.success("Screens deleted successfully")
|
|
||||||
$goto("../")
|
|
||||||
} catch (error) {
|
|
||||||
notifications.error("Error deleting screens")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<ActionMenu>
|
|
||||||
<div slot="control" class="icon">
|
|
||||||
<Icon size="S" hoverable name="MoreSmallList" />
|
|
||||||
</div>
|
|
||||||
<MenuItem icon="Delete" on:click={confirmDeleteDialog.show}>
|
|
||||||
Delete all screens
|
|
||||||
</MenuItem>
|
|
||||||
</ActionMenu>
|
|
||||||
|
|
||||||
<ConfirmDialog
|
|
||||||
bind:this={confirmDeleteDialog}
|
|
||||||
title="Confirm Deletion"
|
|
||||||
okText="Delete screens"
|
|
||||||
onOk={deleteScreens}
|
|
||||||
>
|
|
||||||
<Layout noPadding gap="S">
|
|
||||||
<div>
|
|
||||||
Are you sure you want to delete all screens under the <b>{path}</b> route?
|
|
||||||
</div>
|
|
||||||
<div>The following screens will be deleted:</div>
|
|
||||||
<div class="to-delete">
|
|
||||||
{#each screens as screen}
|
|
||||||
<div>{screen.route}</div>
|
|
||||||
{/each}
|
|
||||||
</div>
|
|
||||||
</Layout>
|
|
||||||
</ConfirmDialog>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.to-delete {
|
|
||||||
font-weight: bold;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: flex-start;
|
|
||||||
align-items: flex-start;
|
|
||||||
padding-left: var(--spacing-xl);
|
|
||||||
}
|
|
||||||
.icon {
|
|
||||||
display: grid;
|
|
||||||
place-items: center;
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,59 +0,0 @@
|
||||||
<script>
|
|
||||||
import { store, selectedComponent, currentAsset } from "builderStore"
|
|
||||||
import instantiateStore from "./dragDropStore"
|
|
||||||
import ComponentTree from "./ComponentTree.svelte"
|
|
||||||
import PathDropdownMenu from "./PathDropdownMenu.svelte"
|
|
||||||
import { get } from "svelte/store"
|
|
||||||
|
|
||||||
const ROUTE_NAME_MAP = {
|
|
||||||
"/": {
|
|
||||||
BASIC: "Home",
|
|
||||||
PUBLIC: "Home",
|
|
||||||
ADMIN: "Home",
|
|
||||||
POWER: "Home",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
const dragDropStore = instantiateStore()
|
|
||||||
|
|
||||||
export let route
|
|
||||||
export let path
|
|
||||||
export let indent
|
|
||||||
export let border
|
|
||||||
|
|
||||||
let routeManuallyOpened = false
|
|
||||||
|
|
||||||
$: selectedScreen = $currentAsset
|
|
||||||
$: allScreens = getAllScreens(route)
|
|
||||||
$: hasSearchMatch = $screenSearchString && filteredScreens.length > 0
|
|
||||||
$: noSearchMatch = $screenSearchString && !filteredScreens.length
|
|
||||||
$: routeSelected =
|
|
||||||
route.subpaths[selectedScreen?.routing?.route] !== undefined
|
|
||||||
$: routeOpened = routeManuallyOpened || routeSelected || hasSearchMatch
|
|
||||||
|
|
||||||
const changeScreen = screenId => {
|
|
||||||
store.actions.screens.select(screenId)
|
|
||||||
}
|
|
||||||
|
|
||||||
const toggleManuallyOpened = () => {
|
|
||||||
if (get(screenSearchString)) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
routeManuallyOpened = !routeManuallyOpened
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
{#if !noSearchMatch}
|
|
||||||
<NavItem
|
|
||||||
icon="FolderOutline"
|
|
||||||
text={path}
|
|
||||||
on:click={toggleManuallyOpened}
|
|
||||||
opened={routeOpened}
|
|
||||||
{border}
|
|
||||||
withArrow={route.subpaths}
|
|
||||||
>
|
|
||||||
<PathDropdownMenu screens={allScreens} {path} />
|
|
||||||
</NavItem>
|
|
||||||
|
|
||||||
{#if routeOpened}{/if}
|
|
||||||
{/if}
|
|
|
@ -1,63 +0,0 @@
|
||||||
<script>
|
|
||||||
import { store, selectedComponent, currentAsset } from "builderStore"
|
|
||||||
import { Tabs, Tab } from "@budibase/bbui"
|
|
||||||
import ScreenSettingsSection from "./ScreenSettingsSection.svelte"
|
|
||||||
import ComponentSettingsSection from "./ComponentSettingsSection.svelte"
|
|
||||||
import DesignSection from "./DesignSection.svelte"
|
|
||||||
import CustomStylesSection from "./CustomStylesSection.svelte"
|
|
||||||
import ConditionalUISection from "./ConditionalUISection.svelte"
|
|
||||||
import {
|
|
||||||
getBindableProperties,
|
|
||||||
getComponentBindableProperties,
|
|
||||||
} from "builderStore/dataBinding"
|
|
||||||
|
|
||||||
$: componentInstance = $selectedComponent
|
|
||||||
$: componentDefinition = store.actions.components.getDefinition(
|
|
||||||
$selectedComponent?._component
|
|
||||||
)
|
|
||||||
$: bindings = getBindableProperties($currentAsset, $store.selectedComponentId)
|
|
||||||
$: componentBindings = getComponentBindableProperties(
|
|
||||||
$currentAsset,
|
|
||||||
$store.selectedComponentId
|
|
||||||
)
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<Tabs selected="Settings" noPadding>
|
|
||||||
<Tab title="Settings">
|
|
||||||
<div class="container">
|
|
||||||
{#key componentInstance?._id}
|
|
||||||
<ScreenSettingsSection
|
|
||||||
{componentInstance}
|
|
||||||
{componentDefinition}
|
|
||||||
{bindings}
|
|
||||||
/>
|
|
||||||
<ComponentSettingsSection
|
|
||||||
{componentInstance}
|
|
||||||
{componentDefinition}
|
|
||||||
{bindings}
|
|
||||||
{componentBindings}
|
|
||||||
/>
|
|
||||||
<DesignSection {componentInstance} {componentDefinition} {bindings} />
|
|
||||||
<CustomStylesSection
|
|
||||||
{componentInstance}
|
|
||||||
{componentDefinition}
|
|
||||||
{bindings}
|
|
||||||
/>
|
|
||||||
<ConditionalUISection
|
|
||||||
{componentInstance}
|
|
||||||
{componentDefinition}
|
|
||||||
{bindings}
|
|
||||||
/>
|
|
||||||
{/key}
|
|
||||||
</div>
|
|
||||||
</Tab>
|
|
||||||
</Tabs>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.container {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: flex-start;
|
|
||||||
align-items: stretch;
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,111 +0,0 @@
|
||||||
<script>
|
|
||||||
import { get } from "svelte/store"
|
|
||||||
import { get as deepGet, setWith } from "lodash"
|
|
||||||
import { Input, DetailSummary, notifications } from "@budibase/bbui"
|
|
||||||
import PropertyControl from "./PropertyControls/PropertyControl.svelte"
|
|
||||||
import LayoutSelect from "./PropertyControls/LayoutSelect.svelte"
|
|
||||||
import RoleSelect from "./PropertyControls/RoleSelect.svelte"
|
|
||||||
import { currentAsset, store } from "builderStore"
|
|
||||||
import { FrontendTypes } from "constants"
|
|
||||||
import sanitizeUrl from "builderStore/store/screenTemplates/utils/sanitizeUrl"
|
|
||||||
import { store, selectedAccessRole } from "builderStore"
|
|
||||||
|
|
||||||
export let componentInstance
|
|
||||||
export let bindings
|
|
||||||
|
|
||||||
let errors = {}
|
|
||||||
|
|
||||||
const routeTaken = url => {
|
|
||||||
const roleId = get(selectedAccessRole) || "BASIC"
|
|
||||||
return get(store).screens.some(
|
|
||||||
screen =>
|
|
||||||
screen.routing.route.toLowerCase() === url.toLowerCase() &&
|
|
||||||
screen.routing.roleId === roleId
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const roleTaken = roleId => {
|
|
||||||
const url = get(currentAsset)?.routing.route
|
|
||||||
return get(store).screens.some(
|
|
||||||
screen =>
|
|
||||||
screen.routing.route.toLowerCase() === url.toLowerCase() &&
|
|
||||||
screen.routing.roleId === roleId
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const setAssetProps = (name, value, parser, validate) => {
|
|
||||||
if (parser) {
|
|
||||||
value = parser(value)
|
|
||||||
}
|
|
||||||
if (validate) {
|
|
||||||
const error = validate(value)
|
|
||||||
errors = {
|
|
||||||
...errors,
|
|
||||||
[name]: error,
|
|
||||||
}
|
|
||||||
if (error) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
errors = {
|
|
||||||
...errors,
|
|
||||||
[name]: null,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const selectedAsset = get(currentAsset)
|
|
||||||
store.update(state => {
|
|
||||||
if (
|
|
||||||
name === "_instanceName" &&
|
|
||||||
state.currentFrontEndType === FrontendTypes.SCREEN
|
|
||||||
) {
|
|
||||||
selectedAsset.props._instanceName = value
|
|
||||||
} else {
|
|
||||||
setWith(selectedAsset, name.split("."), value, Object)
|
|
||||||
}
|
|
||||||
return state
|
|
||||||
})
|
|
||||||
|
|
||||||
try {
|
|
||||||
store.actions.preview.saveSelected()
|
|
||||||
} catch (error) {
|
|
||||||
notifications.error("Error saving settings")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const screenSettings = [
|
|
||||||
{
|
|
||||||
key: "routing.route",
|
|
||||||
label: "Route",
|
|
||||||
control: Input,
|
|
||||||
parser: val => {
|
|
||||||
if (!val.startsWith("/")) {
|
|
||||||
val = "/" + val
|
|
||||||
}
|
|
||||||
return sanitizeUrl(val)
|
|
||||||
},
|
|
||||||
validate: val => {
|
|
||||||
const exisingValue = get(currentAsset)?.routing.route
|
|
||||||
if (val !== exisingValue && routeTaken(val)) {
|
|
||||||
return "That URL is already in use for this role"
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "routing.roleId",
|
|
||||||
label: "Access",
|
|
||||||
control: RoleSelect,
|
|
||||||
validate: val => {
|
|
||||||
const exisingValue = get(currentAsset)?.routing.roleId
|
|
||||||
if (val !== exisingValue && roleTaken(val)) {
|
|
||||||
return "That role is already in use for this URL"
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{ key: "layoutId", label: "Layout", control: LayoutSelect },
|
|
||||||
]
|
|
||||||
</script>
|
|
||||||
|
|
||||||
{#if $store.currentView !== "component" && $currentAsset && $store.currentFrontEndType === FrontendTypes.SCREEN}{/if}
|
|
|
@ -1,194 +0,0 @@
|
||||||
<script>
|
|
||||||
import { store, currentAsset, selectedComponent } from "builderStore"
|
|
||||||
import { Detail, Layout, Button, Icon } from "@budibase/bbui"
|
|
||||||
|
|
||||||
import CurrentItemPreview from "components/design/AppPreview"
|
|
||||||
import PropertiesPanel from "components/design/PropertiesPanel/PropertiesPanel.svelte"
|
|
||||||
import ComponentSelectionList from "components/design/AppPreview/ComponentSelectionList.svelte"
|
|
||||||
import FrontendNavigatePane from "components/design/navigation/FrontendNavigatePane.svelte"
|
|
||||||
import { goto, leftover, params } from "@roxi/routify"
|
|
||||||
import { FrontendTypes } from "constants"
|
|
||||||
import { findComponent, findComponentPath } from "builderStore/componentUtils"
|
|
||||||
import { get } from "svelte/store"
|
|
||||||
import AppThemeSelect from "components/design/AppPreview/AppThemeSelect.svelte"
|
|
||||||
import ThemeEditor from "components/design/AppPreview/ThemeEditor.svelte"
|
|
||||||
import DevicePreviewSelect from "components/design/AppPreview/DevicePreviewSelect.svelte"
|
|
||||||
import ScreenWizard from "components/design/navigation/ScreenWizard.svelte"
|
|
||||||
|
|
||||||
// Cache previous values so we don't update the URL more than necessary
|
|
||||||
let previousType
|
|
||||||
let previousAsset
|
|
||||||
let previousComponentId
|
|
||||||
let hydrationComplete = false
|
|
||||||
|
|
||||||
// Manage the layout modal flow from here
|
|
||||||
let showModal
|
|
||||||
|
|
||||||
// Hydrate state from URL params
|
|
||||||
$: hydrateStateFromURL($params, $leftover)
|
|
||||||
|
|
||||||
// Keep URL in sync with state
|
|
||||||
$: updateURLFromState(
|
|
||||||
$store.currentFrontEndType,
|
|
||||||
$currentAsset,
|
|
||||||
$store.selectedComponentId
|
|
||||||
)
|
|
||||||
|
|
||||||
const hydrateStateFromURL = (params, leftover) => {
|
|
||||||
if (hydrationComplete) {
|
|
||||||
return
|
|
||||||
} else {
|
|
||||||
hydrationComplete = true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Do nothing if no asset type, as that means we've left the page
|
|
||||||
if (!params.assetType) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const state = get(store)
|
|
||||||
const selectedAsset = get(currentAsset)
|
|
||||||
|
|
||||||
// Hydrate asset type
|
|
||||||
let assetType = params.assetType
|
|
||||||
if (![FrontendTypes.LAYOUT, FrontendTypes.SCREEN].includes(assetType)) {
|
|
||||||
assetType = FrontendTypes.SCREEN
|
|
||||||
}
|
|
||||||
if (assetType !== state.currentFrontEndType) {
|
|
||||||
store.update(state => {
|
|
||||||
state.currentFrontEndType = assetType
|
|
||||||
return state
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Hydrate asset
|
|
||||||
const assetId = decodeURI(params.asset)
|
|
||||||
let asset
|
|
||||||
if (assetId) {
|
|
||||||
let assetList
|
|
||||||
let actions
|
|
||||||
|
|
||||||
// Determine screens or layouts based on the URL
|
|
||||||
if (assetType === FrontendTypes.SCREEN) {
|
|
||||||
assetList = get(allScreens)
|
|
||||||
actions = store.actions.screens
|
|
||||||
} else {
|
|
||||||
assetList = state.layouts
|
|
||||||
actions = store.actions.layouts
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find and select the current asset
|
|
||||||
asset = assetList.find(asset => asset._id === assetId)
|
|
||||||
if (asset && asset._id !== selectedAsset?._id) {
|
|
||||||
actions.select(assetId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Hydrate component ID if one is present in the URL
|
|
||||||
const selectedComponentId = leftover.split("/").pop()
|
|
||||||
if (asset && selectedComponentId) {
|
|
||||||
const component = findComponent(asset.props, selectedComponentId)
|
|
||||||
if (component && component._id !== state.selectedComponentId) {
|
|
||||||
store.actions.components.select(component)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Updates the route params in the URL to the specified values
|
|
||||||
const updateURLFromState = (assetType, asset, componentId) => {
|
|
||||||
// Check we have different params than last invocation
|
|
||||||
if (
|
|
||||||
assetType === previousType &&
|
|
||||||
asset === previousAsset &&
|
|
||||||
componentId === previousComponentId
|
|
||||||
) {
|
|
||||||
return
|
|
||||||
} else {
|
|
||||||
previousType = assetType
|
|
||||||
previousAsset = asset
|
|
||||||
previousComponentId = componentId
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract current URL params
|
|
||||||
const currentParams = get(params)
|
|
||||||
const currentLeftover = get(leftover)
|
|
||||||
const paramAssetType = currentParams.assetType
|
|
||||||
const paramAssetId = currentParams.asset
|
|
||||||
const paramComponentId = currentLeftover.split("/").pop()
|
|
||||||
|
|
||||||
// Only update params if the params actually changed
|
|
||||||
if (
|
|
||||||
assetType !== paramAssetType ||
|
|
||||||
asset?._id !== paramAssetId ||
|
|
||||||
componentId !== paramComponentId
|
|
||||||
) {
|
|
||||||
// Build and navigate to a valid URL
|
|
||||||
let url = "../"
|
|
||||||
if ([FrontendTypes.SCREEN, FrontendTypes.LAYOUT].includes(assetType)) {
|
|
||||||
url += `${assetType}`
|
|
||||||
if (asset?._id) {
|
|
||||||
url += `/${asset._id}`
|
|
||||||
if (componentId) {
|
|
||||||
const componentPath = findComponentPath(asset?.props, componentId)
|
|
||||||
const componentURL = componentPath
|
|
||||||
.slice(1)
|
|
||||||
.map(comp => comp._id)
|
|
||||||
.join("/")
|
|
||||||
url += `/${componentURL}`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$goto(url)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<!-- routify:options index=1 -->
|
|
||||||
<div class="root">
|
|
||||||
<div class="ui-nav">
|
|
||||||
<FrontendNavigatePane {showModal} />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="preview-pane" />
|
|
||||||
|
|
||||||
{#if $selectedComponent != null}
|
|
||||||
<div class="components-pane">
|
|
||||||
<PropertiesPanel />
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<slot />
|
|
||||||
|
|
||||||
<ScreenWizard bind:showModal />
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.root {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 260px 1fr 260px;
|
|
||||||
align-items: stretch;
|
|
||||||
flex: 1 1 auto;
|
|
||||||
height: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ui-nav {
|
|
||||||
grid-column: 1;
|
|
||||||
background-color: var(--background);
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: var(--spacing-l);
|
|
||||||
border-right: var(--border-light);
|
|
||||||
}
|
|
||||||
|
|
||||||
.components-pane {
|
|
||||||
grid-column: 3;
|
|
||||||
background-color: var(--background);
|
|
||||||
overflow-y: auto;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: flex-start;
|
|
||||||
align-items: stretch;
|
|
||||||
border-left: var(--border-light);
|
|
||||||
overflow-x: hidden;
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,59 +0,0 @@
|
||||||
<script>
|
|
||||||
import { get } from "svelte/store"
|
|
||||||
import { store, allScreens, selectedAccessRole } from "builderStore"
|
|
||||||
import { FrontendTypes } from "constants"
|
|
||||||
import { params } from "@roxi/routify"
|
|
||||||
|
|
||||||
$: selectValidAsset($params.assetType)
|
|
||||||
|
|
||||||
// If we ever land on this index page we want to correctly update state
|
|
||||||
// to select a valid asset. The layout page will in turn update the URL
|
|
||||||
// to reflect state.
|
|
||||||
const selectValidAsset = assetType => {
|
|
||||||
let id
|
|
||||||
const state = get(store)
|
|
||||||
const screens = get(allScreens)
|
|
||||||
const role = get(selectedAccessRole)
|
|
||||||
|
|
||||||
// Get ID or first correct asset type and select it
|
|
||||||
if (assetType === FrontendTypes.LAYOUT) {
|
|
||||||
if (
|
|
||||||
state.selectedLayoutId &&
|
|
||||||
state.layouts.find(layout => layout._id === state.selectedLayoutId)
|
|
||||||
) {
|
|
||||||
id = state.selectedLayoutId
|
|
||||||
} else {
|
|
||||||
id = state.layouts[0]?._id
|
|
||||||
}
|
|
||||||
if (id) {
|
|
||||||
store.actions.layouts.select(id)
|
|
||||||
}
|
|
||||||
} else if (assetType === FrontendTypes.SCREEN) {
|
|
||||||
if (
|
|
||||||
state.selectedScreenId &&
|
|
||||||
screens.find(screen => screen._id === state.selectedScreenId)
|
|
||||||
) {
|
|
||||||
id = state.selectedScreenId
|
|
||||||
} else {
|
|
||||||
// Select the first screen matching the selected role ID
|
|
||||||
const filteredScreens = screens.filter(screen => {
|
|
||||||
return screen.routing?.roleId === role
|
|
||||||
})
|
|
||||||
id = filteredScreens[0]?._id
|
|
||||||
}
|
|
||||||
if (id) {
|
|
||||||
store.actions.screens.select(id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we didn't find a valid asset, just update the preview type
|
|
||||||
if (!id) {
|
|
||||||
store.update(state => {
|
|
||||||
state.currentFrontEndType = assetType
|
|
||||||
return state
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<!-- routify:options index=false -->
|
|
Loading…
Reference in New Issue