Add screen settings panel
This commit is contained in:
parent
1e59576a30
commit
877791970a
|
@ -13,14 +13,7 @@ export const selectedScreen = derived(store, $store => {
|
||||||
return $store.screens.find(screen => screen._id === $store.selectedScreenId)
|
return $store.screens.find(screen => screen._id === $store.selectedScreenId)
|
||||||
})
|
})
|
||||||
|
|
||||||
export const currentAsset = derived(store, $store => {
|
export const currentAsset = selectedScreen
|
||||||
const type = $store.currentFrontEndType
|
|
||||||
if (type === FrontendTypes.SCREEN) {
|
|
||||||
} else if (type === FrontendTypes.LAYOUT) {
|
|
||||||
return $store.layouts.find(layout => layout._id === $store.selectedLayoutId)
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
})
|
|
||||||
|
|
||||||
export const selectedComponent = derived(
|
export const selectedComponent = derived(
|
||||||
[store, currentAsset],
|
[store, currentAsset],
|
||||||
|
|
|
@ -46,7 +46,7 @@
|
||||||
class:border
|
class:border
|
||||||
class:selected
|
class:selected
|
||||||
class:withActions
|
class:withActions
|
||||||
style={`padding-left: ${14 + indentLevel * 14}px`}
|
style={`padding-left: calc(var(--spacing-l) + ${indentLevel * 14}px)`}
|
||||||
{draggable}
|
{draggable}
|
||||||
on:dragend
|
on:dragend
|
||||||
on:dragstart
|
on:dragstart
|
||||||
|
|
|
@ -31,7 +31,7 @@
|
||||||
align-items: stretch;
|
align-items: stretch;
|
||||||
}
|
}
|
||||||
.header {
|
.header {
|
||||||
height: 55px;
|
height: 48px;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
@ -51,8 +51,8 @@
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
.add-button {
|
.add-button {
|
||||||
flex: 0 0 32px;
|
flex: 0 0 30px;
|
||||||
height: 32px;
|
height: 30px;
|
||||||
display: grid;
|
display: grid;
|
||||||
place-items: center;
|
place-items: center;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
|
|
|
@ -108,19 +108,4 @@
|
||||||
]
|
]
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if $store.currentView !== "component" && $currentAsset && $store.currentFrontEndType === FrontendTypes.SCREEN}
|
{#if $store.currentView !== "component" && $currentAsset && $store.currentFrontEndType === FrontendTypes.SCREEN}{/if}
|
||||||
<DetailSummary name="Screen" collapsible={false}>
|
|
||||||
{#each screenSettings as def (`${componentInstance._id}-${def.key}`)}
|
|
||||||
<PropertyControl
|
|
||||||
control={def.control}
|
|
||||||
label={def.label}
|
|
||||||
key={def.key}
|
|
||||||
error="asdasds"
|
|
||||||
value={deepGet($currentAsset, def.key)}
|
|
||||||
onChange={val => setAssetProps(def.key, val, def.parser, def.validate)}
|
|
||||||
{bindings}
|
|
||||||
props={{ error: errors[def.key] }}
|
|
||||||
/>
|
|
||||||
{/each}
|
|
||||||
</DetailSummary>
|
|
||||||
{/if}
|
|
||||||
|
|
|
@ -30,7 +30,7 @@
|
||||||
align-items: stretch;
|
align-items: stretch;
|
||||||
}
|
}
|
||||||
.header {
|
.header {
|
||||||
height: 55px;
|
height: 50px;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
|
|
|
@ -1,45 +1,11 @@
|
||||||
<script>
|
<script>
|
||||||
import { Search, Layout, Select } from "@budibase/bbui"
|
import { store } from "builderStore"
|
||||||
import NavigationPanel from "components/design/NavigationPanel/NavigationPanel.svelte"
|
|
||||||
import { roles } from "stores/backend"
|
|
||||||
import { store, selectedScreen } from "builderStore"
|
|
||||||
import NavItem from "components/common/NavItem.svelte"
|
|
||||||
import ScreenDropdownMenu from "./_components/ScreenDropdownMenu.svelte"
|
|
||||||
import { RoleColours } from "constants"
|
|
||||||
import ScreenWizard from "./_components/ScreenWizard.svelte"
|
|
||||||
import { onDestroy } from "svelte"
|
import { onDestroy } from "svelte"
|
||||||
import { syncURLToState } from "helpers/urlStateSync"
|
import { syncURLToState } from "helpers/urlStateSync"
|
||||||
import { goto, params, redirect } from "@roxi/routify"
|
import { goto, params, redirect } from "@roxi/routify"
|
||||||
import AppPanel from "components/design/AppPanel/AppPanel.svelte"
|
import AppPanel from "components/design/AppPanel/AppPanel.svelte"
|
||||||
import SettingsPanel from "components/design/SettingsPanel/SettingsPanel.svelte"
|
import ScreenNavigationPanel from "./_components/ScreenNavigationPanel.svelte"
|
||||||
|
import ScreenSettingsPanel from "./_components/ScreenSettingsPanel.svelte"
|
||||||
let searchString
|
|
||||||
let accessRole = "all"
|
|
||||||
let showNewScreenModal
|
|
||||||
|
|
||||||
$: filteredScreens = getFilteredScreens(
|
|
||||||
$store.screens,
|
|
||||||
searchString,
|
|
||||||
accessRole
|
|
||||||
)
|
|
||||||
|
|
||||||
const getFilteredScreens = (screens, search, role) => {
|
|
||||||
return screens
|
|
||||||
.filter(screen => {
|
|
||||||
const searchMatch = !search || screen.routing.route.includes(search)
|
|
||||||
const roleMatch =
|
|
||||||
!role || role === "all" || screen.routing.roleId === role
|
|
||||||
return searchMatch && roleMatch
|
|
||||||
})
|
|
||||||
.slice()
|
|
||||||
.sort((a, b) => {
|
|
||||||
return a.routing.route < b.routing.route ? -1 : 1
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const getRoleColor = roleId => {
|
|
||||||
return RoleColours[roleId] || "pink"
|
|
||||||
}
|
|
||||||
|
|
||||||
// Keep URL and state in sync for selected screen ID
|
// Keep URL and state in sync for selected screen ID
|
||||||
const stopSyncing = syncURLToState({
|
const stopSyncing = syncURLToState({
|
||||||
|
@ -60,44 +26,6 @@
|
||||||
onDestroy(stopSyncing)
|
onDestroy(stopSyncing)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<NavigationPanel
|
<ScreenNavigationPanel />
|
||||||
title="Screens"
|
|
||||||
showAddButton
|
|
||||||
onClickAddButton={showNewScreenModal}
|
|
||||||
>
|
|
||||||
<Layout paddingX="L" paddingY="XL" gap="S">
|
|
||||||
<Search
|
|
||||||
placeholder="Enter a route to search"
|
|
||||||
value={searchString}
|
|
||||||
on:change={e => (searchString = e.detail)}
|
|
||||||
/>
|
|
||||||
<Select
|
|
||||||
bind:value={accessRole}
|
|
||||||
placeholder={null}
|
|
||||||
getOptionLabel={role => role.name}
|
|
||||||
getOptionValue={role => role._id}
|
|
||||||
options={[{ name: "All screens", _id: "all" }, ...$roles]}
|
|
||||||
/>
|
|
||||||
</Layout>
|
|
||||||
{#each filteredScreens as screen (screen._id)}
|
|
||||||
<NavItem
|
|
||||||
icon={screen.routing.route === "/" ? "Home" : "WebPage"}
|
|
||||||
indentLevel={0}
|
|
||||||
selected={$store.selectedScreenId === screen._id}
|
|
||||||
text={screen.routing.route}
|
|
||||||
on:click={() => ($store.selectedScreenId = screen._id)}
|
|
||||||
color={getRoleColor(screen.routing.roleId)}
|
|
||||||
>
|
|
||||||
<ScreenDropdownMenu screenId={screen._id} />
|
|
||||||
</NavItem>
|
|
||||||
{/each}
|
|
||||||
</NavigationPanel>
|
|
||||||
|
|
||||||
<AppPanel />
|
<AppPanel />
|
||||||
|
<ScreenSettingsPanel />
|
||||||
<SettingsPanel
|
|
||||||
title={$selectedScreen?.routing.route}
|
|
||||||
icon={$selectedScreen.routing.route === "/" ? "Home" : "WebPage"}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<ScreenWizard bind:showModal={showNewScreenModal} />
|
|
||||||
|
|
|
@ -0,0 +1,72 @@
|
||||||
|
<script>
|
||||||
|
import { Search, Layout, Select } from "@budibase/bbui"
|
||||||
|
import NavigationPanel from "components/design/NavigationPanel/NavigationPanel.svelte"
|
||||||
|
import { roles } from "stores/backend"
|
||||||
|
import { store } from "builderStore"
|
||||||
|
import NavItem from "components/common/NavItem.svelte"
|
||||||
|
import ScreenDropdownMenu from "./ScreenDropdownMenu.svelte"
|
||||||
|
import { RoleColours } from "constants"
|
||||||
|
import ScreenWizard from "./ScreenWizard.svelte"
|
||||||
|
let searchString
|
||||||
|
let accessRole = "all"
|
||||||
|
let showNewScreenModal
|
||||||
|
|
||||||
|
$: filteredScreens = getFilteredScreens(
|
||||||
|
$store.screens,
|
||||||
|
searchString,
|
||||||
|
accessRole
|
||||||
|
)
|
||||||
|
|
||||||
|
const getFilteredScreens = (screens, search, role) => {
|
||||||
|
return screens
|
||||||
|
.filter(screen => {
|
||||||
|
const searchMatch = !search || screen.routing.route.includes(search)
|
||||||
|
const roleMatch =
|
||||||
|
!role || role === "all" || screen.routing.roleId === role
|
||||||
|
return searchMatch && roleMatch
|
||||||
|
})
|
||||||
|
.slice()
|
||||||
|
.sort((a, b) => {
|
||||||
|
return a.routing.route < b.routing.route ? -1 : 1
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const getRoleColor = roleId => {
|
||||||
|
return RoleColours[roleId] || "#ffa500"
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<NavigationPanel
|
||||||
|
title="Screens"
|
||||||
|
showAddButton
|
||||||
|
onClickAddButton={showNewScreenModal}
|
||||||
|
>
|
||||||
|
<Layout paddingX="L" paddingY="XL" gap="S">
|
||||||
|
<Search
|
||||||
|
placeholder="Enter a route to search"
|
||||||
|
value={searchString}
|
||||||
|
on:change={e => (searchString = e.detail)}
|
||||||
|
/>
|
||||||
|
<Select
|
||||||
|
bind:value={accessRole}
|
||||||
|
placeholder={null}
|
||||||
|
getOptionLabel={role => role.name}
|
||||||
|
getOptionValue={role => role._id}
|
||||||
|
options={[{ name: "All screens", _id: "all" }, ...$roles]}
|
||||||
|
/>
|
||||||
|
</Layout>
|
||||||
|
{#each filteredScreens as screen (screen._id)}
|
||||||
|
<NavItem
|
||||||
|
icon={screen.routing.homeScreen ? "Home" : "WebPage"}
|
||||||
|
indentLevel={0}
|
||||||
|
selected={$store.selectedScreenId === screen._id}
|
||||||
|
text={screen.routing.route}
|
||||||
|
on:click={() => ($store.selectedScreenId = screen._id)}
|
||||||
|
color={getRoleColor(screen.routing.roleId)}
|
||||||
|
>
|
||||||
|
<ScreenDropdownMenu screenId={screen._id} />
|
||||||
|
</NavItem>
|
||||||
|
{/each}
|
||||||
|
</NavigationPanel>
|
||||||
|
|
||||||
|
<ScreenWizard bind:showModal={showNewScreenModal} />
|
|
@ -0,0 +1,146 @@
|
||||||
|
<script>
|
||||||
|
import { selectedScreen } from "builderStore"
|
||||||
|
import SettingsPanel from "components/design/SettingsPanel/SettingsPanel.svelte"
|
||||||
|
import { get } from "svelte/store"
|
||||||
|
import { get as deepGet, setWith } from "lodash"
|
||||||
|
import {
|
||||||
|
Input,
|
||||||
|
Layout,
|
||||||
|
Button,
|
||||||
|
Toggle,
|
||||||
|
Checkbox,
|
||||||
|
notifications,
|
||||||
|
} from "@budibase/bbui"
|
||||||
|
import PropertyControl from "components/design/PropertiesPanel/PropertyControls/PropertyControl.svelte"
|
||||||
|
import RoleSelect from "components/design/PropertiesPanel/PropertyControls/RoleSelect.svelte"
|
||||||
|
import { currentAsset, store, selectedAccessRole } from "builderStore"
|
||||||
|
import { FrontendTypes } from "constants"
|
||||||
|
import sanitizeUrl from "builderStore/store/screenTemplates/utils/sanitizeUrl"
|
||||||
|
|
||||||
|
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.homeScreen",
|
||||||
|
control: Checkbox,
|
||||||
|
props: {
|
||||||
|
text: "Set as home screen",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
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: "showNavigation",
|
||||||
|
label: "Navigation",
|
||||||
|
control: Toggle,
|
||||||
|
props: {
|
||||||
|
text: "Show navigation",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<SettingsPanel
|
||||||
|
title={$selectedScreen?.routing.route}
|
||||||
|
icon={$selectedScreen.routing.route === "/" ? "Home" : "WebPage"}
|
||||||
|
>
|
||||||
|
<Layout gap="S" paddingX="L" paddingY="XL">
|
||||||
|
{#each screenSettings as def (def.key)}
|
||||||
|
<PropertyControl
|
||||||
|
control={def.control}
|
||||||
|
label={def.label}
|
||||||
|
key={def.key}
|
||||||
|
value={deepGet($currentAsset, def.key)}
|
||||||
|
onChange={val => setAssetProps(def.key, val, def.parser, def.validate)}
|
||||||
|
props={{ ...def.props, error: errors[def.key] }}
|
||||||
|
/>
|
||||||
|
{/each}
|
||||||
|
<Button cta>View components</Button>
|
||||||
|
</Layout>
|
||||||
|
</SettingsPanel>
|
Loading…
Reference in New Issue