mirror of https://github.com/Budibase/budibase.git
12 changed files with 0 additions and 989 deletions
@ -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