Add initial layouts panel and add explicit panels for theme and navigation tabs
This commit is contained in:
parent
876cb4784d
commit
88018aff4e
|
@ -1,7 +1,7 @@
|
||||||
import { getFrontendStore } from "./store/frontend"
|
import { getFrontendStore } from "./store/frontend"
|
||||||
import { getAutomationStore } from "./store/automation"
|
import { getAutomationStore } from "./store/automation"
|
||||||
import { getThemeStore } from "./store/theme"
|
import { getThemeStore } from "./store/theme"
|
||||||
import { derived, writable } from "svelte/store"
|
import { derived } from "svelte/store"
|
||||||
import { LAYOUT_NAMES } from "../constants"
|
import { LAYOUT_NAMES } from "../constants"
|
||||||
import { findComponent, findComponentPath } from "./componentUtils"
|
import { findComponent, findComponentPath } from "./componentUtils"
|
||||||
import { RoleUtils } from "@budibase/frontend-core"
|
import { RoleUtils } from "@budibase/frontend-core"
|
||||||
|
@ -14,6 +14,20 @@ 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 selectedLayout = derived(store, $store => {
|
||||||
|
return $store.layouts?.find(layout => layout._id === $store.selectedLayoutId)
|
||||||
|
})
|
||||||
|
|
||||||
|
export const selectedComponent = derived(
|
||||||
|
[store, selectedScreen],
|
||||||
|
([$store, $selectedScreen]) => {
|
||||||
|
if (!$selectedScreen || !$store.selectedComponentId) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
return findComponent($selectedScreen?.props, $store.selectedComponentId)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
export const sortedScreens = derived(store, $store => {
|
export const sortedScreens = derived(store, $store => {
|
||||||
return $store.screens.slice().sort((a, b) => {
|
return $store.screens.slice().sort((a, b) => {
|
||||||
// Sort by role first
|
// Sort by role first
|
||||||
|
@ -43,16 +57,6 @@ export const sortedScreens = derived(store, $store => {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
export const selectedComponent = derived(
|
|
||||||
[store, selectedScreen],
|
|
||||||
([$store, $selectedScreen]) => {
|
|
||||||
if (!$selectedScreen || !$store.selectedComponentId) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
return findComponent($selectedScreen?.props, $store.selectedComponentId)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
export const selectedComponentPath = derived(
|
export const selectedComponentPath = derived(
|
||||||
[store, selectedScreen],
|
[store, selectedScreen],
|
||||||
([$store, $selectedScreen]) => {
|
([$store, $selectedScreen]) => {
|
||||||
|
|
|
@ -43,7 +43,6 @@ const INITIAL_FRONTEND_STATE = {
|
||||||
continueIfAction: false,
|
continueIfAction: false,
|
||||||
},
|
},
|
||||||
currentFrontEndType: "none",
|
currentFrontEndType: "none",
|
||||||
selectedLayoutId: "",
|
|
||||||
errors: [],
|
errors: [],
|
||||||
hasAppPackage: false,
|
hasAppPackage: false,
|
||||||
libraries: null,
|
libraries: null,
|
||||||
|
@ -57,6 +56,7 @@ const INITIAL_FRONTEND_STATE = {
|
||||||
// URL params
|
// URL params
|
||||||
selectedScreenId: null,
|
selectedScreenId: null,
|
||||||
selectedComponentId: null,
|
selectedComponentId: null,
|
||||||
|
selectedLayoutId: null,
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getFrontendStore = () => {
|
export const getFrontendStore = () => {
|
||||||
|
|
|
@ -1,7 +1,12 @@
|
||||||
<script>
|
<script>
|
||||||
import { get } from "svelte/store"
|
import { get } from "svelte/store"
|
||||||
import { onMount, onDestroy } from "svelte"
|
import { onMount, onDestroy } from "svelte"
|
||||||
import { store, selectedScreen, currentAsset } from "builderStore"
|
import {
|
||||||
|
store,
|
||||||
|
selectedScreen,
|
||||||
|
selectedLayout,
|
||||||
|
currentAsset,
|
||||||
|
} from "builderStore"
|
||||||
import iframeTemplate from "./iframeTemplate"
|
import iframeTemplate from "./iframeTemplate"
|
||||||
import ConfirmDialog from "components/common/ConfirmDialog.svelte"
|
import ConfirmDialog from "components/common/ConfirmDialog.svelte"
|
||||||
import {
|
import {
|
||||||
|
@ -40,8 +45,15 @@
|
||||||
// Extract data to pass to the iframe
|
// Extract data to pass to the iframe
|
||||||
$: {
|
$: {
|
||||||
screen = $selectedScreen
|
screen = $selectedScreen
|
||||||
layout = $store.layouts.find(layout => layout._id === screen?.layoutId)
|
|
||||||
|
// If viewing legacy layouts, always show the custom layout
|
||||||
|
if ($isActive("./layouts")) {
|
||||||
|
layout = $selectedLayout
|
||||||
|
} else {
|
||||||
|
layout = $store.layouts.find(layout => layout._id === screen?.layoutId)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Don't show selected components unless on the components tab
|
// Don't show selected components unless on the components tab
|
||||||
$: selectedComponentId = $isActive("./components")
|
$: selectedComponentId = $isActive("./components")
|
||||||
? $store.selectedComponentId
|
? $store.selectedComponentId
|
||||||
|
@ -59,7 +71,10 @@
|
||||||
navigation: $store.navigation,
|
navigation: $store.navigation,
|
||||||
isBudibaseEvent: true,
|
isBudibaseEvent: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Refresh the preview when required
|
||||||
$: json = JSON.stringify(previewData)
|
$: json = JSON.stringify(previewData)
|
||||||
|
$: refreshContent(json)
|
||||||
|
|
||||||
// Update the iframe with the builder info to render the correct preview
|
// Update the iframe with the builder info to render the correct preview
|
||||||
const refreshContent = message => {
|
const refreshContent = message => {
|
||||||
|
@ -68,10 +83,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Refresh the preview when required
|
const receiveMessage = message => {
|
||||||
$: refreshContent(json)
|
|
||||||
|
|
||||||
function receiveMessage(message) {
|
|
||||||
const handlers = {
|
const handlers = {
|
||||||
[MessageTypes.READY]: () => {
|
[MessageTypes.READY]: () => {
|
||||||
// Initialise the app when mounted
|
// Initialise the app when mounted
|
||||||
|
@ -97,46 +109,6 @@
|
||||||
messageHandler(message)
|
messageHandler(message)
|
||||||
}
|
}
|
||||||
|
|
||||||
onMount(() => {
|
|
||||||
window.addEventListener("message", receiveMessage)
|
|
||||||
if (!$store.clientFeatures.messagePassing) {
|
|
||||||
// Legacy - remove in later versions of BB
|
|
||||||
iframe.contentWindow.addEventListener(
|
|
||||||
"ready",
|
|
||||||
() => {
|
|
||||||
receiveMessage({ data: { type: MessageTypes.READY } })
|
|
||||||
},
|
|
||||||
{ once: true }
|
|
||||||
)
|
|
||||||
iframe.contentWindow.addEventListener(
|
|
||||||
"error",
|
|
||||||
event => {
|
|
||||||
receiveMessage({
|
|
||||||
data: { type: MessageTypes.ERROR, error: event.detail },
|
|
||||||
})
|
|
||||||
},
|
|
||||||
{ once: true }
|
|
||||||
)
|
|
||||||
// Add listener for events sent by client library in preview
|
|
||||||
iframe.contentWindow.addEventListener("bb-event", handleBudibaseEvent)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// Remove all iframe event listeners on component destroy
|
|
||||||
onDestroy(() => {
|
|
||||||
window.removeEventListener("message", receiveMessage)
|
|
||||||
|
|
||||||
if (iframe.contentWindow) {
|
|
||||||
if (!$store.clientFeatures.messagePassing) {
|
|
||||||
// Legacy - remove in later versions of BB
|
|
||||||
iframe.contentWindow.removeEventListener(
|
|
||||||
"bb-event",
|
|
||||||
handleBudibaseEvent
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const handleBudibaseEvent = async event => {
|
const handleBudibaseEvent = async event => {
|
||||||
const { type, data } = event.data || event.detail
|
const { type, data } = event.data || event.detail
|
||||||
if (!type) {
|
if (!type) {
|
||||||
|
@ -212,6 +184,46 @@
|
||||||
const cancelDeleteComponent = () => {
|
const cancelDeleteComponent = () => {
|
||||||
idToDelete = null
|
idToDelete = null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
window.addEventListener("message", receiveMessage)
|
||||||
|
if (!$store.clientFeatures.messagePassing) {
|
||||||
|
// Legacy - remove in later versions of BB
|
||||||
|
iframe.contentWindow.addEventListener(
|
||||||
|
"ready",
|
||||||
|
() => {
|
||||||
|
receiveMessage({ data: { type: MessageTypes.READY } })
|
||||||
|
},
|
||||||
|
{ once: true }
|
||||||
|
)
|
||||||
|
iframe.contentWindow.addEventListener(
|
||||||
|
"error",
|
||||||
|
event => {
|
||||||
|
receiveMessage({
|
||||||
|
data: { type: MessageTypes.ERROR, error: event.detail },
|
||||||
|
})
|
||||||
|
},
|
||||||
|
{ once: true }
|
||||||
|
)
|
||||||
|
// Add listener for events sent by client library in preview
|
||||||
|
iframe.contentWindow.addEventListener("bb-event", handleBudibaseEvent)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Remove all iframe event listeners on component destroy
|
||||||
|
onDestroy(() => {
|
||||||
|
window.removeEventListener("message", receiveMessage)
|
||||||
|
|
||||||
|
if (iframe.contentWindow) {
|
||||||
|
if (!$store.clientFeatures.messagePassing) {
|
||||||
|
// Legacy - remove in later versions of BB
|
||||||
|
iframe.contentWindow.removeEventListener(
|
||||||
|
"bb-event",
|
||||||
|
handleBudibaseEvent
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="component-container">
|
<div class="component-container">
|
||||||
|
|
|
@ -2,21 +2,11 @@
|
||||||
import { store } from "builderStore"
|
import { store } from "builderStore"
|
||||||
import { notifications } from "@budibase/bbui"
|
import { notifications } from "@budibase/bbui"
|
||||||
import ConfirmDialog from "components/common/ConfirmDialog.svelte"
|
import ConfirmDialog from "components/common/ConfirmDialog.svelte"
|
||||||
import {
|
import { ActionMenu, MenuItem, Icon } from "@budibase/bbui"
|
||||||
ActionMenu,
|
|
||||||
MenuItem,
|
|
||||||
Icon,
|
|
||||||
Modal,
|
|
||||||
ModalContent,
|
|
||||||
Input,
|
|
||||||
} from "@budibase/bbui"
|
|
||||||
import { cloneDeep } from "lodash/fp"
|
|
||||||
|
|
||||||
export let layout
|
export let layout
|
||||||
|
|
||||||
let confirmDeleteDialog
|
let confirmDeleteDialog
|
||||||
let editLayoutNameModal
|
|
||||||
let name = layout.name
|
|
||||||
|
|
||||||
const deleteLayout = async () => {
|
const deleteLayout = async () => {
|
||||||
try {
|
try {
|
||||||
|
@ -26,24 +16,12 @@
|
||||||
notifications.error("Error deleting layout")
|
notifications.error("Error deleting layout")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const saveLayout = async () => {
|
|
||||||
try {
|
|
||||||
const layoutToSave = cloneDeep(layout)
|
|
||||||
layoutToSave.name = name
|
|
||||||
await store.actions.layouts.save(layoutToSave)
|
|
||||||
notifications.success("Layout saved successfully")
|
|
||||||
} catch (err) {
|
|
||||||
notifications.error("Error saving layout")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<ActionMenu>
|
<ActionMenu>
|
||||||
<div slot="control" class="icon">
|
<div slot="control" class="icon">
|
||||||
<Icon size="S" hoverable name="MoreSmallList" />
|
<Icon size="S" hoverable name="MoreSmallList" />
|
||||||
</div>
|
</div>
|
||||||
<MenuItem icon="Edit" on:click={editLayoutNameModal.show}>Edit</MenuItem>
|
|
||||||
<MenuItem icon="Delete" on:click={confirmDeleteDialog.show}>Delete</MenuItem>
|
<MenuItem icon="Delete" on:click={confirmDeleteDialog.show}>Delete</MenuItem>
|
||||||
</ActionMenu>
|
</ActionMenu>
|
||||||
|
|
||||||
|
@ -55,17 +33,6 @@
|
||||||
onOk={deleteLayout}
|
onOk={deleteLayout}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Modal bind:this={editLayoutNameModal}>
|
|
||||||
<ModalContent
|
|
||||||
title="Edit Layout Name"
|
|
||||||
confirmText="Save"
|
|
||||||
onConfirm={saveLayout}
|
|
||||||
disabled={!name}
|
|
||||||
>
|
|
||||||
<Input thin type="text" label="Name" bind:value={name} />
|
|
||||||
</ModalContent>
|
|
||||||
</Modal>
|
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.icon {
|
.icon {
|
||||||
display: grid;
|
display: grid;
|
|
@ -0,0 +1,34 @@
|
||||||
|
<script>
|
||||||
|
import NavigationPanel from "components/design/navigation/NavigationPanel.svelte"
|
||||||
|
import { Banner, Layout } from "@budibase/bbui"
|
||||||
|
import NavItem from "components/common/NavItem.svelte"
|
||||||
|
import { store } from "builderStore"
|
||||||
|
import LayoutDropdownMenu from "./LayoutDropdownMenu.svelte"
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<NavigationPanel title="Layouts">
|
||||||
|
<Layout paddingX="L" paddingY="XL" gap="S">
|
||||||
|
<Banner
|
||||||
|
type="warning"
|
||||||
|
showCloseButton={false}
|
||||||
|
extraButtonText="View details"
|
||||||
|
extraButtonAction={() => {}}
|
||||||
|
>
|
||||||
|
Custom layouts are being deprecated. They will be removed from June 1st.
|
||||||
|
</Banner>
|
||||||
|
</Layout>
|
||||||
|
{#each $store.layouts as layout (layout._id)}
|
||||||
|
<NavItem
|
||||||
|
icon="Experience"
|
||||||
|
indentLevel={0}
|
||||||
|
selected={$store.selectedLayoutId === layout._id}
|
||||||
|
text={layout.name}
|
||||||
|
on:click={() => ($store.selectedLayoutId = layout._id)}
|
||||||
|
>
|
||||||
|
<LayoutDropdownMenu {layout} />
|
||||||
|
</NavItem>
|
||||||
|
{/each}
|
||||||
|
</NavigationPanel>
|
||||||
|
|
||||||
|
<!-- -->
|
||||||
|
<!-- color={RoleUtils.getRoleColour(screen.routing.roleId)}-->
|
|
@ -0,0 +1,5 @@
|
||||||
|
<script>
|
||||||
|
import SettingsPanel from "components/design/settings/SettingsPanel.svelte"
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<
|
|
@ -0,0 +1,20 @@
|
||||||
|
<script>
|
||||||
|
import { syncURLToState } from "helpers/urlStateSync"
|
||||||
|
import { store } from "builderStore"
|
||||||
|
import * as routify from "@roxi/routify"
|
||||||
|
import { onDestroy } from "svelte"
|
||||||
|
|
||||||
|
// Keep URL and state in sync for selected component ID
|
||||||
|
const stopSyncing = syncURLToState({
|
||||||
|
urlParam: "layoutId",
|
||||||
|
stateKey: "selectedLayoutId",
|
||||||
|
validate: id => $store.layouts?.some(layout => layout._id === id),
|
||||||
|
fallbackUrl: "../",
|
||||||
|
store,
|
||||||
|
routify,
|
||||||
|
})
|
||||||
|
|
||||||
|
onDestroy(stopSyncing)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<slot />
|
|
@ -0,0 +1,5 @@
|
||||||
|
<script>
|
||||||
|
import LayoutNavigationPanel from "./_components/LayoutNavigationPanel.svelte"
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<LayoutNavigationPanel />
|
|
@ -1,5 +1,13 @@
|
||||||
<script>
|
<script>
|
||||||
import NavigationPanel from "components/design/navigation/NavigationPanel.svelte"
|
import { store } from "builderStore"
|
||||||
|
import { onMount } from "svelte"
|
||||||
|
import { redirect } from "@roxi/routify"
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
if ($store.layouts?.length) {
|
||||||
|
$redirect(`./${$store.layouts[0]._id}`)
|
||||||
|
}
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<NavigationPanel title="Layouts" />
|
You don't have any layouts
|
||||||
|
|
|
@ -0,0 +1,118 @@
|
||||||
|
<script>
|
||||||
|
import NavigationPanel from "components/design/navigation/NavigationPanel.svelte"
|
||||||
|
import {
|
||||||
|
Body,
|
||||||
|
Layout,
|
||||||
|
Label,
|
||||||
|
ActionGroup,
|
||||||
|
ActionButton,
|
||||||
|
Checkbox,
|
||||||
|
Select,
|
||||||
|
ColorPicker,
|
||||||
|
Input,
|
||||||
|
notifications,
|
||||||
|
} from "@budibase/bbui"
|
||||||
|
import NavigationLinksEditor from "./NavigationLinksEditor.svelte"
|
||||||
|
import { store } from "builderStore"
|
||||||
|
import { DefaultAppTheme } from "constants"
|
||||||
|
|
||||||
|
const update = async (key, value) => {
|
||||||
|
try {
|
||||||
|
let navigation = $store.navigation
|
||||||
|
navigation[key] = value
|
||||||
|
await store.actions.navigation.save(navigation)
|
||||||
|
} catch (error) {
|
||||||
|
notifications.error("Error updating navigation settings")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<NavigationPanel title="Navigation">
|
||||||
|
<Layout paddingX="L" paddingY="XL" gap="S">
|
||||||
|
<Body size="S">
|
||||||
|
Your navigation is configured for all the screens within your app
|
||||||
|
</Body>
|
||||||
|
<Body size="S">
|
||||||
|
You can hide and show your navigation for each screen in the screen
|
||||||
|
settings
|
||||||
|
</Body>
|
||||||
|
<NavigationLinksEditor />
|
||||||
|
<Layout noPadding gap="XS">
|
||||||
|
<Label>Position</Label>
|
||||||
|
<ActionGroup quiet>
|
||||||
|
<ActionButton
|
||||||
|
selected={$store.navigation.navigation === "Top"}
|
||||||
|
quiet={$store.navigation.navigation !== "Top"}
|
||||||
|
icon="PaddingTop"
|
||||||
|
on:click={() => update("navigation", "Top")}
|
||||||
|
/>
|
||||||
|
<ActionButton
|
||||||
|
selected={$store.navigation.navigation === "Left"}
|
||||||
|
quiet={$store.navigation.navigation !== "Left"}
|
||||||
|
icon="PaddingLeft"
|
||||||
|
on:click={() => update("navigation", "Left")}
|
||||||
|
/>
|
||||||
|
</ActionGroup>
|
||||||
|
</Layout>
|
||||||
|
{#if $store.navigation.navigation === "Top"}
|
||||||
|
<Checkbox
|
||||||
|
text="Sticky header"
|
||||||
|
value={$store.navigation.sticky}
|
||||||
|
on:change={e => update("sticky", e.detail)}
|
||||||
|
/>
|
||||||
|
<Select
|
||||||
|
label="Width"
|
||||||
|
options={["Max", "Large", "Medium", "Small"]}
|
||||||
|
plaveholder={null}
|
||||||
|
value={$store.navigation.navWidth}
|
||||||
|
on:change={e => update("navWidth", e.detail)}
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
|
<Layout noPadding gap="XS">
|
||||||
|
<Checkbox
|
||||||
|
text="Logo"
|
||||||
|
value={!$store.navigation.hideLogo}
|
||||||
|
on:change={e => update("hideLogo", !e.detail)}
|
||||||
|
/>
|
||||||
|
{#if !$store.navigation.hideLogo}
|
||||||
|
<Input
|
||||||
|
value={$store.navigation.logoUrl}
|
||||||
|
on:change={e => update("logoUrl", e.detail)}
|
||||||
|
placeholder="Add logo URL"
|
||||||
|
updateOnChange={false}
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
|
</Layout>
|
||||||
|
<Layout noPadding gap="XS">
|
||||||
|
<Checkbox
|
||||||
|
text="Title"
|
||||||
|
value={!$store.navigation.hideTitle}
|
||||||
|
on:change={e => update("hideTitle", !e.detail)}
|
||||||
|
/>
|
||||||
|
{#if !$store.navigation.hideTitle}
|
||||||
|
<Input
|
||||||
|
value={$store.navigation.title}
|
||||||
|
on:change={e => update("title", e.detail)}
|
||||||
|
placeholder="Add title"
|
||||||
|
updateOnChange={false}
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
|
</Layout>
|
||||||
|
<Layout noPadding gap="XS">
|
||||||
|
<Label>Background color</Label>
|
||||||
|
<ColorPicker
|
||||||
|
spectrumTheme={$store.theme}
|
||||||
|
value={$store.navigation.navBackground || DefaultAppTheme.navBackground}
|
||||||
|
on:change={e => update("navBackground", e.detail)}
|
||||||
|
/>
|
||||||
|
</Layout>
|
||||||
|
<Layout noPadding gap="XS">
|
||||||
|
<Label>Text color</Label>
|
||||||
|
<ColorPicker
|
||||||
|
spectrumTheme={$store.theme}
|
||||||
|
value={$store.navigation.navTextColor || DefaultAppTheme.navTextColor}
|
||||||
|
on:change={e => update("navTextColor", e.detail)}
|
||||||
|
/>
|
||||||
|
</Layout>
|
||||||
|
</Layout>
|
||||||
|
</NavigationPanel>
|
|
@ -1,118 +1,5 @@
|
||||||
<script>
|
<script>
|
||||||
import NavigationPanel from "components/design/navigation/NavigationPanel.svelte"
|
import NavigationSettingsPanel from "./_components/NavigationSettingsPanel.svelte"
|
||||||
import {
|
|
||||||
Body,
|
|
||||||
Layout,
|
|
||||||
Label,
|
|
||||||
ActionGroup,
|
|
||||||
ActionButton,
|
|
||||||
Checkbox,
|
|
||||||
Select,
|
|
||||||
ColorPicker,
|
|
||||||
Input,
|
|
||||||
notifications,
|
|
||||||
} from "@budibase/bbui"
|
|
||||||
import NavigationLinksEditor from "./_components/NavigationLinksEditor.svelte"
|
|
||||||
import { store } from "builderStore"
|
|
||||||
import { DefaultAppTheme } from "constants"
|
|
||||||
|
|
||||||
const update = async (key, value) => {
|
|
||||||
try {
|
|
||||||
let navigation = $store.navigation
|
|
||||||
navigation[key] = value
|
|
||||||
await store.actions.navigation.save(navigation)
|
|
||||||
} catch (error) {
|
|
||||||
notifications.error("Error updating navigation settings")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<NavigationPanel title="Navigation">
|
<NavigationSettingsPanel />
|
||||||
<Layout paddingX="L" paddingY="XL" gap="S">
|
|
||||||
<Body size="S">
|
|
||||||
Your navigation is configured for all the screens within your app
|
|
||||||
</Body>
|
|
||||||
<Body size="S">
|
|
||||||
You can hide and show your navigation for each screen in the screen
|
|
||||||
settings
|
|
||||||
</Body>
|
|
||||||
<NavigationLinksEditor />
|
|
||||||
<Layout noPadding gap="XS">
|
|
||||||
<Label>Position</Label>
|
|
||||||
<ActionGroup quiet>
|
|
||||||
<ActionButton
|
|
||||||
selected={$store.navigation.navigation === "Top"}
|
|
||||||
quiet={$store.navigation.navigation !== "Top"}
|
|
||||||
icon="PaddingTop"
|
|
||||||
on:click={() => update("navigation", "Top")}
|
|
||||||
/>
|
|
||||||
<ActionButton
|
|
||||||
selected={$store.navigation.navigation === "Left"}
|
|
||||||
quiet={$store.navigation.navigation !== "Left"}
|
|
||||||
icon="PaddingLeft"
|
|
||||||
on:click={() => update("navigation", "Left")}
|
|
||||||
/>
|
|
||||||
</ActionGroup>
|
|
||||||
</Layout>
|
|
||||||
{#if $store.navigation.navigation === "Top"}
|
|
||||||
<Checkbox
|
|
||||||
text="Sticky header"
|
|
||||||
value={$store.navigation.sticky}
|
|
||||||
on:change={e => update("sticky", e.detail)}
|
|
||||||
/>
|
|
||||||
<Select
|
|
||||||
label="Width"
|
|
||||||
options={["Max", "Large", "Medium", "Small"]}
|
|
||||||
plaveholder={null}
|
|
||||||
value={$store.navigation.navWidth}
|
|
||||||
on:change={e => update("navWidth", e.detail)}
|
|
||||||
/>
|
|
||||||
{/if}
|
|
||||||
<Layout noPadding gap="XS">
|
|
||||||
<Checkbox
|
|
||||||
text="Logo"
|
|
||||||
value={!$store.navigation.hideLogo}
|
|
||||||
on:change={e => update("hideLogo", !e.detail)}
|
|
||||||
/>
|
|
||||||
{#if !$store.navigation.hideLogo}
|
|
||||||
<Input
|
|
||||||
value={$store.navigation.logoUrl}
|
|
||||||
on:change={e => update("logoUrl", e.detail)}
|
|
||||||
placeholder="Add logo URL"
|
|
||||||
updateOnChange={false}
|
|
||||||
/>
|
|
||||||
{/if}
|
|
||||||
</Layout>
|
|
||||||
<Layout noPadding gap="XS">
|
|
||||||
<Checkbox
|
|
||||||
text="Title"
|
|
||||||
value={!$store.navigation.hideTitle}
|
|
||||||
on:change={e => update("hideTitle", !e.detail)}
|
|
||||||
/>
|
|
||||||
{#if !$store.navigation.hideTitle}
|
|
||||||
<Input
|
|
||||||
value={$store.navigation.title}
|
|
||||||
on:change={e => update("title", e.detail)}
|
|
||||||
placeholder="Add title"
|
|
||||||
updateOnChange={false}
|
|
||||||
/>
|
|
||||||
{/if}
|
|
||||||
</Layout>
|
|
||||||
<Layout noPadding gap="XS">
|
|
||||||
<Label>Background color</Label>
|
|
||||||
<ColorPicker
|
|
||||||
spectrumTheme={$store.theme}
|
|
||||||
value={$store.navigation.navBackground || DefaultAppTheme.navBackground}
|
|
||||||
on:change={e => update("navBackground", e.detail)}
|
|
||||||
/>
|
|
||||||
</Layout>
|
|
||||||
<Layout noPadding gap="XS">
|
|
||||||
<Label>Text color</Label>
|
|
||||||
<ColorPicker
|
|
||||||
spectrumTheme={$store.theme}
|
|
||||||
value={$store.navigation.navTextColor || DefaultAppTheme.navTextColor}
|
|
||||||
on:change={e => update("navTextColor", e.detail)}
|
|
||||||
/>
|
|
||||||
</Layout>
|
|
||||||
</Layout>
|
|
||||||
</NavigationPanel>
|
|
||||||
|
|
|
@ -0,0 +1,120 @@
|
||||||
|
<script>
|
||||||
|
import NavigationPanel from "components/design/navigation/NavigationPanel.svelte"
|
||||||
|
import {
|
||||||
|
Body,
|
||||||
|
Layout,
|
||||||
|
Label,
|
||||||
|
ColorPicker,
|
||||||
|
Button,
|
||||||
|
notifications,
|
||||||
|
} from "@budibase/bbui"
|
||||||
|
import { store } from "builderStore"
|
||||||
|
import { get } from "svelte/store"
|
||||||
|
import { DefaultAppTheme } from "constants"
|
||||||
|
import AppThemeSelect from "./AppThemeSelect.svelte"
|
||||||
|
|
||||||
|
const ButtonBorderRadiusOptions = [
|
||||||
|
{
|
||||||
|
label: "Square",
|
||||||
|
value: "0",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Soft edge",
|
||||||
|
value: "4px",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Curved",
|
||||||
|
value: "8px",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Round",
|
||||||
|
value: "16px",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
$: customTheme = $store.customTheme || {}
|
||||||
|
|
||||||
|
const update = async (property, value) => {
|
||||||
|
try {
|
||||||
|
store.actions.customTheme.save({
|
||||||
|
...get(store).customTheme,
|
||||||
|
[property]: value,
|
||||||
|
})
|
||||||
|
} catch (error) {
|
||||||
|
notifications.error("Error updating custom theme")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<NavigationPanel title="Theme">
|
||||||
|
<Layout paddingX="L" paddingY="XL" gap="S">
|
||||||
|
<Body size="S">
|
||||||
|
Your theme is set across all the screens within your app
|
||||||
|
</Body>
|
||||||
|
<Layout noPadding gap="XS">
|
||||||
|
<Label>Theme</Label>
|
||||||
|
<AppThemeSelect />
|
||||||
|
</Layout>
|
||||||
|
<Layout noPadding gap="XS">
|
||||||
|
<Label>Buttons</Label>
|
||||||
|
<div class="buttons">
|
||||||
|
{#each ButtonBorderRadiusOptions as option}
|
||||||
|
<div
|
||||||
|
class:active={customTheme.buttonBorderRadius === option.value}
|
||||||
|
style={`--radius: ${option.value}`}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
secondary
|
||||||
|
on:click={() => update("buttonBorderRadius", option.value)}
|
||||||
|
>
|
||||||
|
{option.label}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
</Layout>
|
||||||
|
<Layout noPadding gap="XS">
|
||||||
|
<Label>Accent color</Label>
|
||||||
|
<ColorPicker
|
||||||
|
spectrumTheme={$store.theme}
|
||||||
|
value={customTheme.primaryColor || DefaultAppTheme.primaryColor}
|
||||||
|
on:change={e => update("primaryColor", e.detail)}
|
||||||
|
/>
|
||||||
|
</Layout>
|
||||||
|
<Layout noPadding gap="XS">
|
||||||
|
<Label>Accent color (hover)</Label>
|
||||||
|
<ColorPicker
|
||||||
|
spectrumTheme={$store.theme}
|
||||||
|
value={customTheme.primaryColorHover ||
|
||||||
|
DefaultAppTheme.primaryColorHover}
|
||||||
|
on:change={e => update("primaryColorHover", e.detail)}
|
||||||
|
/>
|
||||||
|
</Layout>
|
||||||
|
</Layout>
|
||||||
|
</NavigationPanel>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.buttons {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 100px 100px;
|
||||||
|
gap: var(--spacing-m);
|
||||||
|
}
|
||||||
|
.buttons > div {
|
||||||
|
display: contents;
|
||||||
|
}
|
||||||
|
.buttons > div :global(.spectrum-Button) {
|
||||||
|
border-radius: var(--radius) !important;
|
||||||
|
border-width: 1px;
|
||||||
|
border-color: var(--spectrum-global-color-gray-400);
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
.buttons > div:hover :global(.spectrum-Button) {
|
||||||
|
background: var(--spectrum-global-color-gray-700);
|
||||||
|
border-color: var(--spectrum-global-color-gray-700);
|
||||||
|
}
|
||||||
|
.buttons > div.active :global(.spectrum-Button) {
|
||||||
|
background: var(--spectrum-global-color-gray-200);
|
||||||
|
color: var(--spectrum-global-color-gray-800);
|
||||||
|
border-color: var(--spectrum-global-color-gray-600);
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -1,120 +1,5 @@
|
||||||
<script>
|
<script>
|
||||||
import NavigationPanel from "components/design/navigation/NavigationPanel.svelte"
|
import ThemeSettingsPanel from "./_components/ThemeSettingsPanel.svelte"
|
||||||
import {
|
|
||||||
Body,
|
|
||||||
Layout,
|
|
||||||
Label,
|
|
||||||
ColorPicker,
|
|
||||||
Button,
|
|
||||||
notifications,
|
|
||||||
} from "@budibase/bbui"
|
|
||||||
import { store } from "builderStore"
|
|
||||||
import { get } from "svelte/store"
|
|
||||||
import { DefaultAppTheme } from "constants"
|
|
||||||
import AppThemeSelect from "./_components/AppThemeSelect.svelte"
|
|
||||||
|
|
||||||
const ButtonBorderRadiusOptions = [
|
|
||||||
{
|
|
||||||
label: "Square",
|
|
||||||
value: "0",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Soft edge",
|
|
||||||
value: "4px",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Curved",
|
|
||||||
value: "8px",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Round",
|
|
||||||
value: "16px",
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
$: customTheme = $store.customTheme || {}
|
|
||||||
|
|
||||||
const update = async (property, value) => {
|
|
||||||
try {
|
|
||||||
store.actions.customTheme.save({
|
|
||||||
...get(store).customTheme,
|
|
||||||
[property]: value,
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
notifications.error("Error updating custom theme")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<NavigationPanel title="Theme">
|
<ThemeSettingsPanel />
|
||||||
<Layout paddingX="L" paddingY="XL" gap="S">
|
|
||||||
<Body size="S">
|
|
||||||
Your theme is set across all the screens within your app
|
|
||||||
</Body>
|
|
||||||
<Layout noPadding gap="XS">
|
|
||||||
<Label>Theme</Label>
|
|
||||||
<AppThemeSelect />
|
|
||||||
</Layout>
|
|
||||||
<Layout noPadding gap="XS">
|
|
||||||
<Label>Buttons</Label>
|
|
||||||
<div class="buttons">
|
|
||||||
{#each ButtonBorderRadiusOptions as option}
|
|
||||||
<div
|
|
||||||
class:active={customTheme.buttonBorderRadius === option.value}
|
|
||||||
style={`--radius: ${option.value}`}
|
|
||||||
>
|
|
||||||
<Button
|
|
||||||
secondary
|
|
||||||
on:click={() => update("buttonBorderRadius", option.value)}
|
|
||||||
>
|
|
||||||
{option.label}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
{/each}
|
|
||||||
</div>
|
|
||||||
</Layout>
|
|
||||||
<Layout noPadding gap="XS">
|
|
||||||
<Label>Accent color</Label>
|
|
||||||
<ColorPicker
|
|
||||||
spectrumTheme={$store.theme}
|
|
||||||
value={customTheme.primaryColor || DefaultAppTheme.primaryColor}
|
|
||||||
on:change={e => update("primaryColor", e.detail)}
|
|
||||||
/>
|
|
||||||
</Layout>
|
|
||||||
<Layout noPadding gap="XS">
|
|
||||||
<Label>Accent color (hover)</Label>
|
|
||||||
<ColorPicker
|
|
||||||
spectrumTheme={$store.theme}
|
|
||||||
value={customTheme.primaryColorHover ||
|
|
||||||
DefaultAppTheme.primaryColorHover}
|
|
||||||
on:change={e => update("primaryColorHover", e.detail)}
|
|
||||||
/>
|
|
||||||
</Layout>
|
|
||||||
</Layout>
|
|
||||||
</NavigationPanel>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.buttons {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 100px 100px;
|
|
||||||
gap: var(--spacing-m);
|
|
||||||
}
|
|
||||||
.buttons > div {
|
|
||||||
display: contents;
|
|
||||||
}
|
|
||||||
.buttons > div :global(.spectrum-Button) {
|
|
||||||
border-radius: var(--radius) !important;
|
|
||||||
border-width: 1px;
|
|
||||||
border-color: var(--spectrum-global-color-gray-400);
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
.buttons > div:hover :global(.spectrum-Button) {
|
|
||||||
background: var(--spectrum-global-color-gray-700);
|
|
||||||
border-color: var(--spectrum-global-color-gray-700);
|
|
||||||
}
|
|
||||||
.buttons > div.active :global(.spectrum-Button) {
|
|
||||||
background: var(--spectrum-global-color-gray-200);
|
|
||||||
color: var(--spectrum-global-color-gray-800);
|
|
||||||
border-color: var(--spectrum-global-color-gray-600);
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
Loading…
Reference in New Issue