Merge pull request #1735 from Budibase/feature/layout-poc

Responsive layouts, responsive autoscreens, sections and navigation customisation
This commit is contained in:
Andrew Kingston 2021-06-21 07:51:51 +01:00 committed by GitHub
commit 17d17bf45b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
52 changed files with 968 additions and 2563 deletions

View File

@ -63,9 +63,9 @@
on:focus={() => (focus = true)}
on:blur={() => (focus = false)}
on:change={onChange}
{value}
value={value || ""}
placeholder={placeholder || ""}
{disabled}
{placeholder}
class="spectrum-Textfield-input spectrum-InputGroup-input"
/>
</div>

View File

@ -359,7 +359,7 @@
background-color: var(--spectrum-alias-background-color-secondary);
overflow: hidden;
position: relative;
z-index: 1;
z-index: 0;
}
.container {

View File

@ -78,7 +78,7 @@
"posthog-js": "1.4.5",
"remixicon": "2.5.0",
"shortid": "2.2.15",
"svelte-dnd-action": "^0.8.9",
"svelte-dnd-action": "^0.9.8",
"svelte-loading-spinners": "^0.1.1",
"svelte-portal": "0.1.0",
"uuid": "8.3.1",

View File

@ -333,7 +333,7 @@ const buildFormSchema = component => {
*/
export function removeBindings(obj) {
for (let [key, value] of Object.entries(obj)) {
if (typeof value === "object") {
if (value && typeof value === "object") {
obj[key] = removeBindings(value)
} else if (typeof value === "string") {
obj[key] = value.replace(CAPTURE_HBS_TEMPLATE, "Invalid binding")

View File

@ -305,7 +305,6 @@ export const getFrontendStore = () => {
_id: uuid(),
_component: definition.component,
_styles: { normal: {}, hover: {}, active: {} },
_transition: "",
_instanceName: `New ${definition.name}`,
...cloneDeep(props),
...extras,
@ -508,15 +507,6 @@ export const getFrontendStore = () => {
selected._styles = { normal: {}, hover: {}, active: {} }
await store.actions.preview.saveSelected()
},
updateTransition: async transition => {
const selected = get(selectedComponent)
if (transition == null || transition === "") {
selected._transition = ""
} else {
selected._transition = transition
}
await store.actions.preview.saveSelected()
},
updateProp: async (name, value) => {
let component = get(selectedComponent)
if (!name || !component) {
@ -536,37 +526,50 @@ export const getFrontendStore = () => {
return
}
// Find a nav bar in the main layout
const nav = findComponentType(
layout.props,
"@budibase/standard-components/navigation"
)
if (!nav) {
return
}
let newLink
if (nav._children && nav._children.length) {
// Clone an existing link if one exists
newLink = cloneDeep(nav._children[0])
// Set our new props
newLink._id = uuid()
newLink._instanceName = `${title} Link`
newLink.url = url
newLink.text = title
} else {
// Otherwise create vanilla new link
newLink = {
...store.actions.components.createInstance("link"),
url,
// Add link setting to main layout
if (layout.props._component.endsWith("layout")) {
// If using a new SDK, add to the layout component settings
if (!layout.props.links) {
layout.props.links = []
}
layout.props.links.push({
text: title,
_instanceName: `${title} Link`,
url,
})
} else {
// If using an old SDK, add to the navigation component
// TODO: remove this when we can assume everyone has updated
const nav = findComponentType(
layout.props,
"@budibase/standard-components/navigation"
)
if (!nav) {
return
}
let newLink
if (nav._children && nav._children.length) {
// Clone an existing link if one exists
newLink = cloneDeep(nav._children[0])
// Set our new props
newLink._id = uuid()
newLink._instanceName = `${title} Link`
newLink.url = url
newLink.text = title
} else {
// Otherwise create vanilla new link
newLink = {
...store.actions.components.createInstance("link"),
url,
text: title,
_instanceName: `${title} Link`,
}
nav._children = [...nav._children, newLink]
}
}
// Save layout
nav._children = [...nav._children, newLink]
await store.actions.layouts.save(layout)
},
},

View File

@ -128,7 +128,6 @@ const createScreen = table => {
background: "white",
"border-radius": "0.5rem",
"box-shadow": "0 1px 2px 0 rgba(0, 0, 0, 0.05)",
"margin-top": "20px",
"border-width": "2px",
"border-color": "rgba(0, 0, 0, 0.1)",
"border-style": "None",
@ -136,7 +135,6 @@ const createScreen = table => {
"padding-bottom": "48px",
"padding-right": "48px",
"padding-left": "48px",
"margin-bottom": "20px",
})
.customProps({
direction: "column",

View File

@ -14,7 +14,6 @@ export class Component extends BaseStructure {
active: {},
selected: {},
},
_transition: "",
_instanceName: "",
_children: [],
}
@ -40,11 +39,6 @@ export class Component extends BaseStructure {
return this
}
transition(transition) {
this._json._transition = transition
return this
}
// Shorthand for custom props "type"
type(type) {
this._json.type = type

View File

@ -40,12 +40,10 @@ export function makeMainForm() {
padding: "0px",
"border-radius": "0.5rem",
"box-shadow": "0 1px 2px 0 rgba(0, 0, 0, 0.05)",
"margin-top": "20px",
"padding-top": "48px",
"padding-bottom": "48px",
"padding-right": "48px",
"padding-left": "48px",
"margin-bottom": "20px",
})
.instanceName("Form")
}
@ -58,6 +56,8 @@ export function makeBreadcrumbContainer(tableName, text, capitalise = false) {
.normalStyle({
"margin-right": "4px",
"margin-left": "4px",
"margin-top": "0px",
"margin-bottom": "0px",
})
.customStyle(spectrumColor(700))
.text(">")
@ -65,6 +65,8 @@ export function makeBreadcrumbContainer(tableName, text, capitalise = false) {
const textStyling = {
color: "#000000",
"margin-top": "0px",
"margin-bottom": "0px",
}
if (capitalise) {
textStyling["text-transform"] = "capitalize"
@ -140,10 +142,6 @@ export function makeTitleContainer(title) {
const heading = new Component("@budibase/standard-components/heading")
.normalStyle({
margin: "0px",
"margin-bottom": "0px",
"margin-right": "0px",
"margin-top": "0px",
"margin-left": "0px",
flex: "1 1 auto",
})
.customStyle(spectrumColor(900))

View File

@ -1,10 +1,17 @@
<script>
import { ActionMenu, ActionButton, MenuItem, Icon } from "@budibase/bbui"
import { store, currentAssetName } from "builderStore"
import { store, currentAssetName, selectedComponent } from "builderStore"
import structure from "./componentStructure.json"
$: enrichedStructure = enrichStructure(structure, $store.components)
const isChildAllowed = ({ name }, selectedComponent) => {
const currentComponent = store.actions.components.getDefinition(
selectedComponent?._component
)
return currentComponent?.illegalChildren?.includes(name.toLowerCase())
}
const enrichStructure = (structure, definitions) => {
let enrichedStructure = []
structure.forEach(item => {
@ -39,6 +46,7 @@
<ActionMenu disabled={!item.isCategory}>
<ActionButton
icon={item.icon}
disabled={isChildAllowed(item, $selectedComponent)}
quiet
size="S"
slot="control"

View File

@ -1,4 +1,5 @@
[
"section",
"container",
"dataprovider",
"table",
@ -61,8 +62,7 @@
"name": "Other",
"icon": "More",
"children": [
"screenslot",
"navigation"
"screenslot"
]
}
]

View File

@ -12,16 +12,24 @@ export default `
rel="stylesheet"
/>
<style>
html,
body {
margin: 0;
html, body {
padding: 0;
height: 100%;
width: 100%;
margin: 0;
}
html {
height: calc(100% - 16px);
width: calc(100% - 16px);
overflow: hidden;
margin: 8px;
display: flex;
flex-direction: column;
justify-content: flex-start;
align-items: stretch;
box-shadow: 0 0 8px 0 rgba(0, 0, 0, 0.1);
}
body {
padding: 2px;
flex: 1 1 auto;
overflow: hidden;
}
*,

View File

@ -10,11 +10,10 @@
$: screen = $allScreens.find(screen => screen._id === screenId)
const deleteScreen = () => {
const deleteScreen = async () => {
try {
store.actions.screens.delete(screen)
store.actions.routing.fetch()
confirmDeleteDialog.hide()
await store.actions.screens.delete(screen)
await store.actions.routing.fetch()
$goto("../")
notifications.success("Deleted screen successfully.")
} catch (err) {

View File

@ -55,7 +55,6 @@
if (routeError) return false
draftScreen.props._instanceName = name
draftScreen.props._transition = "fade"
draftScreen.props._component = baseComponent
draftScreen.routing = { route, roleId }

View File

@ -1,5 +1,5 @@
<script>
import { TextArea, DetailSummary, Button, Select } from "@budibase/bbui"
import { TextArea, DetailSummary, Button } from "@budibase/bbui"
import PropertyGroup from "./PropertyControls/PropertyGroup.svelte"
import FlatButtonGroup from "./PropertyControls/FlatButtonGroup"
import { allStyles } from "./componentStyles"
@ -8,7 +8,6 @@
export let componentInstance = {}
export let onStyleChanged = () => {}
export let onCustomStyleChanged = () => {}
export let onUpdateTransition = () => {}
export let onResetStyles = () => {}
let selectedCategory = "normal"
@ -24,16 +23,6 @@
{ value: "active", text: "Active" },
]
const transitions = [
"none",
"fade",
"blur",
"fly",
"scale", // slide is hidden because it does not seem to result in any effect
]
const capitalize = ([first, ...rest]) => first.toUpperCase() + rest.join("")
$: groups = componentDefinition?.styleable ? Object.keys(allStyles) : []
</script>
@ -78,18 +67,6 @@
{/if}
</div>
</div>
{#if componentDefinition?.transitionable}
<div class="transitions">
<Select
value={componentInstance._transition}
on:change={event => onUpdateTransition(event.detail)}
name="transition"
label="Transition"
options={transitions}
getOptionLabel={capitalize}
/>
</div>
{/if}
</div>
<style>

View File

@ -8,7 +8,7 @@
import { setWith } from "lodash"
$: definition = store.actions.components.getDefinition(
$selectedComponent._component
$selectedComponent?._component
)
$: isComponentOrScreen =
$store.currentView === "component" ||
@ -18,7 +18,6 @@
const onStyleChanged = store.actions.components.updateStyle
const onCustomStyleChanged = store.actions.components.updateCustomStyle
const onUpdateTransition = store.actions.components.updateTransition
const onResetStyles = store.actions.components.resetStyles
function setAssetProps(name, value) {
@ -64,7 +63,6 @@
componentDefinition={definition}
{onStyleChanged}
{onCustomStyleChanged}
{onUpdateTransition}
{onResetStyles}
/>
</div>

View File

@ -1,5 +1,5 @@
<script>
import { Button, Drawer } from "@budibase/bbui"
import { ActionButton, Button, Drawer } from "@budibase/bbui"
import { createEventDispatcher } from "svelte"
import { notifications } from "@budibase/bbui"
import EventEditor from "./EventEditor.svelte"
@ -51,7 +51,7 @@
}
</script>
<Button secondary on:click={drawer.show}>Define Actions</Button>
<ActionButton on:click={drawer.show}>Define Actions</ActionButton>
<Drawer bind:this={drawer} title={"Actions"}>
<svelte:fragment slot="description">
Define what actions to run.

View File

@ -1,6 +1,7 @@
<script>
import {
notifications,
ActionButton,
Button,
Drawer,
Body,
@ -46,7 +47,7 @@
}
</script>
<Button secondary on:click={drawer.show}>Define Filters</Button>
<ActionButton on:click={drawer.show}>Define Filters</ActionButton>
<Drawer bind:this={drawer} title="Filtering">
<Button cta slot="buttons" on:click={saveFilter}>Save</Button>
<DrawerContent slot="body">

View File

@ -8,7 +8,7 @@
</script>
<script>
import { Popover, Button, Input } from "@budibase/bbui"
import { Popover, ActionButton, Button, Input } from "@budibase/bbui"
import { createEventDispatcher, tick } from "svelte"
const dispatch = createEventDispatcher()
@ -117,7 +117,7 @@
</script>
<div bind:this={buttonAnchor}>
<Button secondary small on:click={dropdown.show}>{displayValue}</Button>
<ActionButton on:click={dropdown.show}>{displayValue}</ActionButton>
</div>
<Popover bind:this={dropdown} on:open={setSelectedUI} anchor={buttonAnchor}>
<div class="container">

View File

@ -0,0 +1,114 @@
<script>
import {
Button,
Icon,
DrawerContent,
Layout,
Input,
Combobox,
} from "@budibase/bbui"
import { flip } from "svelte/animate"
import { dndzone } from "svelte-dnd-action"
import { generate } from "shortid"
import { store } from "builderStore"
export let links = []
const flipDurationMs = 150
$: links.forEach(link => {
if (!link.id) {
link.id = generate()
}
})
$: urlOptions = $store.screens
.map(screen => screen.routing?.route)
.filter(x => x != null)
const addLink = () => {
links = [...links, {}]
}
const removeLink = id => {
links = links.filter(link => link.id !== id)
}
const updateLinks = e => {
links = e.detail.items
}
</script>
<DrawerContent>
<div class="container">
<Layout>
{#if links?.length}
<div
class="links"
use:dndzone={{
items: links,
flipDurationMs,
dropTargetStyle: { outline: "none" },
}}
on:finalize={updateLinks}
on:consider={updateLinks}
>
{#each links as link (link.id)}
<div class="link" animate:flip={{ duration: flipDurationMs }}>
<Icon name="DragHandle" size="XL" />
<Input bind:value={link.text} placeholder="Text" />
<Combobox
bind:value={link.url}
placeholder="URL"
options={urlOptions}
/>
<Icon
name="Close"
hoverable
size="S"
on:click={() => removeLink(link.id)}
/>
</div>
{/each}
</div>
{/if}
<div class="button-container">
<Button secondary icon="Add" on:click={addLink}>Add Link</Button>
</div>
</Layout>
</div>
</DrawerContent>
<style>
.container {
width: 100%;
max-width: 600px;
margin: var(--spacing-m) auto;
}
.links {
display: flex;
flex-direction: column;
justify-content: flex-start;
align-items: stretch;
}
.link {
padding: 4px 8px;
gap: var(--spacing-l);
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
border-radius: var(--border-radius-s);
transition: background-color ease-in-out 130ms;
}
.link:hover {
background-color: var(--spectrum-global-color-gray-100);
}
.link > :global(.spectrum-Form-item) {
flex: 1 1 auto;
width: 0;
}
.button-container {
margin-left: var(--spacing-l);
}
</style>

View File

@ -0,0 +1,23 @@
<script>
import { Button, ActionButton, Drawer } from "@budibase/bbui"
import { createEventDispatcher } from "svelte"
import NavigationDrawer from "./NavigationDrawer.svelte"
export let value = []
let drawer
const dispatch = createEventDispatcher()
const save = () => {
dispatch("change", value)
drawer.hide()
}
</script>
<ActionButton on:click={drawer.show}>Configure Links</ActionButton>
<Drawer bind:this={drawer} title={"Navigation Links"}>
<svelte:fragment slot="description">
Configure the links in your navigation bar.
</svelte:fragment>
<Button cta slot="buttons" on:click={save}>Save</Button>
<NavigationDrawer slot="body" bind:links={value} />
</Drawer>

View File

@ -0,0 +1,75 @@
<script>
import { createEventDispatcher } from "svelte"
import { ActionButton, Body, Icon, Modal, ModalContent } from "@budibase/bbui"
const dispatch = createEventDispatcher()
export let value = ""
let selected = value
let modal
let layoutMap = {
mainSidebar: {
name: "Main with Sidebar",
icon: "ColumnTwoB",
},
sidebarMain: {
name: "Sidebar with Main",
icon: "ColumnTwoC",
},
twoColumns: {
name: "Two columns",
icon: "ColumnTwoA",
},
threeColumns: {
name: "Three columns",
icon: "ViewColumn",
},
}
</script>
<ActionButton on:click={modal.show}>{layoutMap[value].name}</ActionButton>
<Modal bind:this={modal}>
<ModalContent
onConfirm={() => dispatch("change", selected)}
size="L"
title="Select layout"
>
<div class="container">
{#each Object.entries(layoutMap) as [key, value]}
<button
class:selected={selected === key}
on:click={() => (selected = key)}
class="layout"
>
<Icon color="white" size="L" name={value.icon} />
<Body size="XS">{value.name}</Body>
</button>
{/each}
</div>
</ModalContent>
</Modal>
<style>
.container {
display: grid;
grid-gap: 20px;
grid-template-columns: 1fr 1fr 1fr;
}
.layout {
color: var(--spectrum-body-m-text-color, var(--spectrum-alias-text-color));
border: none;
box-sizing: border-box;
display: grid;
place-items: center;
background: var(--background-alt);
grid-gap: var(--spectrum-alias-grid-margin-xsmall);
padding: var(--spectrum-alias-item-padding-s);
transition: 0.3s all;
border-radius: var(--spacing-s);
}
.selected {
background: var(--spectrum-alias-background-color-tertiary);
}
</style>

View File

@ -15,6 +15,8 @@
import FieldSelect from "./PropertyControls/FieldSelect.svelte"
import MultiFieldSelect from "./PropertyControls/MultiFieldSelect.svelte"
import SchemaSelect from "./PropertyControls/SchemaSelect.svelte"
import SectionSelect from "./PropertyControls/SectionSelect.svelte"
import NavigationEditor from "./PropertyControls/NavigationEditor/NavigationEditor.svelte"
import EventsEditor from "./PropertyControls/EventsEditor"
import FilterEditor from "./PropertyControls/FilterEditor/FilterEditor.svelte"
import { IconSelect } from "./PropertyControls/IconSelect"
@ -62,6 +64,8 @@
field: FieldSelect,
multifield: MultiFieldSelect,
schema: SchemaSelect,
section: SectionSelect,
navigation: NavigationEditor,
filter: FilterEditor,
"field/string": StringFieldSelect,
"field/number": NumberFieldSelect,

View File

@ -1,54 +0,0 @@
<script>
import { Button } from "@budibase/bbui"
export let remove = false
</script>
<div class="container">
<div class="content">
<div class="img"><img src="https://picsum.photos/60/60" alt="zoom" /></div>
<div class="body">
<div class="title">Zoom</div>
<div class="description">
Lorem, ipsum dolor sit amet consectetur adipisicing elit
</div>
</div>
</div>
<div class="footer">
<Button wide error={remove} secondary={!remove} on:click>
<span>{remove ? "Remove" : "Add"}</span>
</Button>
</div>
</div>
<style>
.container {
display: grid;
padding: 12px;
background: var(--light-grey);
grid-gap: 20px;
}
span {
font-size: 12px;
font-weight: bold;
}
.content {
display: grid;
grid-gap: 12px;
grid-template-columns: 60px auto;
}
.title {
font-size: 14px;
font-weight: bold;
}
.description {
font-size: 12px;
}
.img {
border-radius: 3px;
overflow: hidden;
}
img {
height: 60px;
width: 60px;
}
</style>

View File

@ -1,33 +0,0 @@
<script>
import SettingsModal from "./SettingsModal.svelte"
import { Modal, Icon } from "@budibase/bbui"
let modal
</script>
<div
class="topnavitemright settings"
data-cy="settings-icon"
on:click={modal.show}
>
<Icon hoverable name="Settings" />
</div>
<Modal bind:this={modal} width="600px">
<SettingsModal />
</Modal>
<style>
.topnavitemright {
cursor: pointer;
color: var(--grey-7);
margin: 0 12px 0 0;
font-weight: 600;
font-size: 1rem;
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
height: 24px;
width: 24px;
}
</style>

View File

@ -1,34 +0,0 @@
<script>
import { General, DangerZone } from "./tabs"
import { ModalContent, Tab, Tabs } from "@budibase/bbui"
</script>
<ModalContent
title="Settings"
showConfirmButton={false}
showCancelButton={false}
>
<div class="container">
<Tabs selected="General">
<Tab title="General">
<General />
</Tab>
<!-- <Tab title="API Keys">
<APIKeys />
</Tab> -->
<Tab title="Danger Zone">
<DangerZone />
</Tab>
</Tabs>
</div>
</ModalContent>
<style>
.container :global(section > header) {
/* Fix margin defined in BBUI as L rather than XL */
margin-bottom: var(--spacing-xl);
}
.container :global(textarea) {
min-height: 60px;
}
</style>

View File

@ -1,62 +0,0 @@
<script>
import { Input, Label, Link } from "@budibase/bbui"
import api from "builderStore/api"
import { notifications } from "@budibase/bbui"
import { database } from "stores/backend"
import analytics from "analytics"
let keys = { budibase: "" }
async function updateKey([key, value]) {
if (key === "budibase") {
const isValid = await analytics.identifyByApiKey(value)
if (!isValid) {
notifications.error("Your API Key is invalid.")
keys = { ...keys }
return
}
}
const response = await api.put(`/api/keys/${key}`, { value })
const res = await response.json()
keys = { ...keys, ...res }
notifications.success("API Key saved.")
}
// Get Keys
async function fetchKeys() {
const response = await api.get(`/api/keys/`)
const res = await response.json()
// dont want this to ever be editable, as its fetched based on Api Key
if (res.userId) delete res.userId
keys = res
}
fetchKeys()
</script>
<div class="container">
<Input
on:change={e => updateKey(["budibase", e.detail])}
value={keys.budibase}
label="Budibase Cloud API Key"
/>
<Link primary href="https://portal.budi.live">
Log in to the Budibase Hosting Portal to get your API Key. →
</Link>
<div>
<Label extraSmall grey>Instance ID (Webhooks)</Label>
<span>{$database._id}</span>
</div>
</div>
<style>
.container {
display: grid;
grid-gap: var(--spacing-xl);
}
span {
font-size: var(--font-size-xs);
font-weight: 600;
}
</style>

View File

@ -1,56 +0,0 @@
<script>
import { params, goto } from "@roxi/routify"
import { Input, Button, Body } from "@budibase/bbui"
import { del } from "builderStore/api"
let value = ""
let loading = false
async function deleteApp() {
loading = true
const id = $params.application
await del(`/api/applications/${id}`)
loading = false
$goto("/builder")
}
</script>
<div class="background">
<Body>
Type
<b>DELETE</b>
into the textbox, then click the following button to delete your entire web app.
</Body>
<Input
on:change={e => (value = e.detail)}
disabled={loading}
placeholder=""
/>
<div class="buttons">
<Button
warning
disabled={value !== "DELETE" || loading}
on:click={deleteApp}
>
Delete Entire App
</Button>
</div>
</div>
<style>
.background {
display: grid;
grid-gap: var(--spacing-xl);
}
.background :global(p) {
line-height: 1.2;
margin: 0;
}
.buttons {
display: flex;
flex-direction: row;
justify-content: flex-end;
align-items: center;
}
</style>

View File

@ -1,97 +0,0 @@
<script>
import { Input, TextArea } from "@budibase/bbui"
import { store, hostingStore } from "builderStore"
import api from "builderStore/api"
import { object, string } from "yup"
import { onMount } from "svelte"
import { get } from "svelte/store"
let nameValidation, nameError
let urlValidation, urlError
$: checkName($store.name)
$: checkUrl($store.url)
async function updateApplication(data) {
const response = await api.put(`/api/applications/${$store.appId}`, data)
await response.json()
store.update(state => {
state = {
...state,
...data,
}
return state
})
}
async function checkValidation(input, validation) {
if (!input || !validation) {
return
}
try {
await object(validation).validate(input, { abortEarly: false })
} catch (error) {
if (!error || !error.inner) return ""
return error.inner.reduce((acc, err) => {
return acc + err.message
}, "")
}
}
async function checkName(name) {
nameError = await checkValidation({ name }, nameValidation)
}
async function checkUrl(url) {
urlError = await checkValidation({ url: url.toLowerCase() }, urlValidation)
}
onMount(async () => {
const nameError = "Your application must have a name.",
urlError = "Your application must have a URL."
await hostingStore.actions.fetchDeployedApps()
const existingAppNames = get(hostingStore).deployedAppNames
const existingAppUrls = get(hostingStore).deployedAppUrls
const nameIdx = existingAppNames.indexOf(get(store).name)
const urlIdx = existingAppUrls.indexOf(get(store).url)
if (nameIdx !== -1) {
existingAppNames.splice(nameIdx, 1)
}
if (urlIdx !== -1) {
existingAppUrls.splice(urlIdx, 1)
}
nameValidation = {
name: string().required(nameError).notOneOf(existingAppNames),
}
urlValidation = {
url: string().required(urlError).notOneOf(existingAppUrls),
}
})
</script>
<div class="container">
<Input
on:change={e => updateApplication({ name: e.detail })}
value={$store.name}
error={nameError}
label="App Name"
/>
<Input
on:change={e => updateApplication({ url: e.detail })}
value={$store.url}
error={urlError}
label="App URL"
/>
<TextArea
on:change={e => updateApplication({ description: e.detail })}
value={$store.description}
label="App Description"
/>
</div>
<style>
.container {
display: grid;
grid-gap: var(--spacing-xl);
}
</style>

View File

@ -1,40 +0,0 @@
<script>
import Integration from "../Integration.svelte"
</script>
<div class="container">
<div class="title">Your Integrations</div>
<div class="integrations">
<Integration remove />
<Integration remove />
<Integration remove />
<Integration remove />
</div>
<div class="apps">Recommended apps</div>
<div class="integrations">
<Integration />
<Integration />
<Integration />
<Integration />
</div>
</div>
<style>
.container {
display: grid;
grid-gap: 16px;
}
.integrations {
display: grid;
grid-template-columns: 1fr 1fr;
grid-gap: 20px;
}
.title {
font-size: 18px;
font-weight: bold;
}
.apps {
font-size: 14px;
font-weight: bold;
}
</style>

View File

@ -1,5 +0,0 @@
export { default as General } from "./General.svelte"
export { default as Integrations } from "./Integrations.svelte"
export { default as Permissions } from "./Permissions.svelte"
export { default as APIKeys } from "./APIKeys.svelte"
export { default as DangerZone } from "./DangerZone.svelte"

View File

@ -190,11 +190,10 @@
flex-direction: column;
justify-content: flex-start;
align-items: stretch;
gap: var(--spacing-l);
padding: var(--spacing-l) 40px var(--spacing-xl) 40px;
gap: var(--spacing-m);
padding: var(--spacing-xl) 40px;
}
.preview-content {
box-shadow: 0 0 12px rgba(0, 0, 0, 0.05);
flex: 1 1 auto;
}

File diff suppressed because it is too large Load Diff

View File

@ -65,7 +65,9 @@
>
<Provider key="user" data={$authStore} {actions}>
<div id="app-root">
<Component instance={$screenStore.activeLayout.props} />
{#key $screenStore.activeLayout._id}
<Component instance={$screenStore.activeLayout.props} />
{/key}
</div>
<NotificationDisplay />
<!-- Key block needs to be outside the if statement or it breaks -->
@ -95,8 +97,6 @@
#app-root {
height: 100%;
width: 100%;
overflow-y: auto;
overflow-x: hidden;
position: relative;
}
</style>

View File

@ -54,7 +54,6 @@
children: children.length,
styles: { ...instance._styles, id, empty, interactive },
empty,
transition: instance._transition,
selected,
props: componentProps,
name,

View File

@ -41,6 +41,5 @@
<style>
div {
position: relative;
overflow-x: auto;
}
</style>

View File

@ -34,7 +34,7 @@
// Vertically, always render above unless no room, then render inside
let newTop = elBounds.top + scrollY - verticalOffset - height
if (newTop < 0) {
newTop = elBounds.top + scrollY + verticalOffset
newTop = 0
}
// Horizontally, try to center first.

View File

@ -7,7 +7,6 @@ import {
builderStore,
} from "./store"
import { styleable } from "./utils/styleable"
import transition from "./utils/transition"
import { linkable } from "./utils/linkable"
import { getAction } from "./utils/getAction"
import Provider from "./components/Provider.svelte"
@ -21,7 +20,6 @@ export default {
screenStore,
builderStore,
styleable,
transition,
linkable,
getAction,
Provider,

View File

@ -1,16 +0,0 @@
import { fade, blur, scale, fly } from "svelte/transition"
// Default options
const transitions = new Map([
["fade", { tn: fade, opt: {} }],
["blur", { tn: blur, opt: {} }],
// This one seems to not result in any effect
// ["slide", { tn: slide, opt: {} }],
["scale", { tn: scale, opt: {} }],
["fly", { tn: fly, opt: { y: 80 } }],
])
export default function transition(node, { type, options = {} }) {
const { tn, opt } = transitions.get(type) || { tn: () => {}, opt: {} }
return tn(node, { ...opt, ...options })
}

View File

@ -28,10 +28,10 @@
chalk "^2.0.0"
js-tokens "^4.0.0"
"@budibase/bbui@^0.9.46":
version "0.9.46"
resolved "https://registry.yarnpkg.com/@budibase/bbui/-/bbui-0.9.46.tgz#3109666b618daa65b29d1c7c45549420c62e6489"
integrity sha512-PRW8kR9+QrMiom6hVzisMYd268dj03ojC0ruzEkDhKMONg2I021ST62hzKXdb7zh5LgoYXtapmM9qsKwoHfkPg==
"@budibase/bbui@^0.9.47":
version "0.9.47"
resolved "https://registry.yarnpkg.com/@budibase/bbui/-/bbui-0.9.47.tgz#d8664a05203432d522cd91a0bad1cdd8518baf93"
integrity sha512-LXvJCgUSoc4EJKafBaKfUzU4GUOQGmts/8F4V6LTFtTyMZavgq2/KFAgPbR3QeYvidLsshtwop/pQfoszXTQnQ==
dependencies:
"@adobe/spectrum-css-workflow-icons" "^1.2.1"
"@spectrum-css/actionbutton" "^1.0.1"
@ -108,12 +108,12 @@
to-gfm-code-block "^0.1.1"
year "^0.2.1"
"@budibase/standard-components@^0.9.46":
version "0.9.46"
resolved "https://registry.yarnpkg.com/@budibase/standard-components/-/standard-components-0.9.46.tgz#a31a253ca51a2029c3aaf5d8aca5c953358e1d67"
integrity sha512-QjW4tukMw4Xa477wGTle2UPz85ygodQ3KG+WEdPAWKq7j0IDv0Fad0oDmWtzLvGxxB+AiRbEnM6T1QV6X1ItCA==
"@budibase/standard-components@^0.9.47":
version "0.9.47"
resolved "https://registry.yarnpkg.com/@budibase/standard-components/-/standard-components-0.9.47.tgz#8e4f27c43b5a6f65d3d296c61f842195e297f061"
integrity sha512-0+Ndg67Jgk7cqOYluGKpixNFvEqvy2oguKLEr1l83Sf0oWTQ3RCmUGs2mU66ljwnE+o4/JN/EdkA2uSqKInQtg==
dependencies:
"@budibase/bbui" "^0.9.46"
"@budibase/bbui" "^0.9.47"
"@spectrum-css/page" "^3.0.1"
"@spectrum-css/vars" "^3.0.1"
apexcharts "^3.22.1"
@ -121,10 +121,10 @@
svelte-apexcharts "^1.0.2"
svelte-flatpickr "^3.1.0"
"@budibase/string-templates@^0.9.46":
version "0.9.46"
resolved "https://registry.yarnpkg.com/@budibase/string-templates/-/string-templates-0.9.46.tgz#e43f87513977879a892ae52f3941d3320cb9ff88"
integrity sha512-yOVS7Y/QLATj31QuBu8KP78Oyzhs60V09JEQKa7n4vRP8TBemcev/LFShln8Iiv70YZSpdJviguQuJ6Ow0aUNA==
"@budibase/string-templates@^0.9.47":
version "0.9.47"
resolved "https://registry.yarnpkg.com/@budibase/string-templates/-/string-templates-0.9.47.tgz#484ce5ce29a6ddaef3480368b1a24ce8c3852324"
integrity sha512-I16Ps4AW7VW8MrSdsoZdwLutiX7GhRkiH6m1AdFcmzh2mZI6YyFM000PuKGEt+sREXK2NI6cBzmi9ZpKIAPJJw==
dependencies:
"@budibase/handlebars-helpers" "^0.11.4"
dayjs "^1.10.4"

View File

@ -1,4 +1,7 @@
const { EMPTY_LAYOUT } = require("../../constants/layouts")
const {
EMPTY_LAYOUT,
BASE_LAYOUT_PROP_IDS,
} = require("../../constants/layouts")
const CouchDB = require("../../db")
const { generateLayoutID, getScreenParams } = require("../../db/utils")
@ -26,15 +29,19 @@ exports.destroy = async function (ctx) {
const layoutId = ctx.params.layoutId,
layoutRev = ctx.params.layoutRev
const layoutsUsedByScreens = (
await db.allDocs(
getScreenParams(null, {
include_docs: true,
})
)
).rows.map(element => element.doc.layoutId)
if (layoutsUsedByScreens.includes(layoutId)) {
ctx.throw(400, "Cannot delete a layout that's being used by a screen")
if (Object.values(BASE_LAYOUT_PROP_IDS).includes(layoutId)) {
ctx.throw(400, "Cannot delete a built-in layout")
} else {
const layoutsUsedByScreens = (
await db.allDocs(
getScreenParams(null, {
include_docs: true,
})
)
).rows.map(element => element.doc.layoutId)
if (layoutsUsedByScreens.includes(layoutId)) {
ctx.throw(400, "Cannot delete a layout that's being used by a screen")
}
}
await db.remove(layoutId, layoutRev)

View File

@ -10,7 +10,7 @@ const EMPTY_LAYOUT = {
stylesheets: [],
props: {
_id: "30b8822a-d07b-49f4-9531-551e37c6899b",
_component: "@budibase/standard-components/container",
_component: "@budibase/standard-components/layout",
_children: [
{
_id: "7fcf11e4-6f5b-4085-8e0d-9f3d44c98967",
@ -22,9 +22,6 @@ const EMPTY_LAYOUT = {
"flex-direction": "column",
"justify-content": "flex-start",
"align-items": "stretch",
"max-width": "100%",
width: "1400px",
padding: "20px",
},
hover: {},
active: {},
@ -36,16 +33,17 @@ const EMPTY_LAYOUT = {
_styles: {
active: {},
hover: {},
normal: {
"min-height": "100%",
"background-image": "#f5f5f5",
},
normal: {},
selected: {},
},
direction: "column",
hAlign: "center",
vAlign: "top",
size: "grow",
navigation: "Top",
width: "Large",
links: [
{
text: "Home",
url: "/",
},
],
},
}
@ -56,83 +54,11 @@ const BASE_LAYOUTS = [
title: "{{ name }}",
favicon: "./_shared/favicon.png",
stylesheets: [],
name: "Top Navigation Layout",
name: "Navigation Layout",
props: {
_id: "4f569166-a4f3-47ea-a09e-6d218c75586f",
_component: "@budibase/standard-components/container",
_component: "@budibase/standard-components/layout",
_children: [
{
_id: "c74f07266980c4b6eafc33e2a6caa783d",
_component: "@budibase/standard-components/container",
_styles: {
normal: {
background: "#fff",
width: "100%",
"box-shadow": "0 1px 2px 0 rgba(0, 0, 0, 0.05)",
},
hover: {},
active: {},
selected: {},
},
_instanceName: "Header",
_children: [
{
_id: "49e0e519-9e5e-4127-885a-ee6a0a49e2c1",
_component: "@budibase/standard-components/navigation",
_styles: {
normal: {
"max-width": "1400px",
padding: "20px",
"font-weight": "400",
"font-size": "16px",
flex: "1 1 auto",
},
hover: {},
active: {},
selected: {},
},
title: "",
backgroundColor: "",
color: "",
borderWidth: "",
borderColor: "",
borderStyle: "",
_instanceName: "Navigation",
_children: [
{
_id: "48b35328-4c91-4343-a6a3-1a1fd77b3386",
_component: "@budibase/standard-components/link",
_styles: {
normal: {
"font-weight": "600",
"text-decoration-line": "none",
"font-size": "16px",
},
hover: {
color: "#4285f4",
},
active: {},
selected: {},
},
url: "/",
openInNewTab: false,
text: "Home",
color: "",
hoverColor: "",
underline: false,
fontSize: "",
fontFamily: "initial",
_instanceName: "Home Link",
_children: [],
},
],
},
],
direction: "row",
hAlign: "center",
vAlign: "middle",
size: "shrink",
},
{
_id: "7fcf11e4-6f5b-4085-8e0d-9f3d44c98967",
_component: "@budibase/standard-components/screenslot",
@ -143,9 +69,6 @@ const BASE_LAYOUTS = [
"flex-direction": "column",
"justify-content": "flex-start",
"align-items": "stretch",
"max-width": "100%",
width: "1400px",
padding: "20px",
},
hover: {},
active: {},
@ -157,16 +80,17 @@ const BASE_LAYOUTS = [
_styles: {
active: {},
hover: {},
normal: {
"min-height": "100%",
background: "#f5f5f5",
},
normal: {},
selected: {},
},
direction: "column",
hAlign: "center",
vAlign: "top",
size: "grow",
navigation: "Top",
width: "Large",
links: [
{
text: "Home",
url: "/",
},
],
},
},
{
@ -178,7 +102,7 @@ const BASE_LAYOUTS = [
name: "Empty Layout",
props: {
_id: "3723ffa1-f9e0-4c05-8013-98195c788ed6",
_component: "@budibase/standard-components/container",
_component: "@budibase/standard-components/layout",
_children: [
{
_id: "7fcf11e4-6f5b-4085-8e0d-9f3d44c98967",
@ -190,9 +114,6 @@ const BASE_LAYOUTS = [
"flex-direction": "column",
"justify-content": "flex-start",
"align-items": "stretch",
"max-width": "100%",
width: "1400px",
padding: "20px",
},
hover: {},
active: {},
@ -204,16 +125,17 @@ const BASE_LAYOUTS = [
_styles: {
active: {},
hover: {},
normal: {
"min-height": "100%",
background: "#f5f5f5",
},
normal: {},
selected: {},
},
direction: "column",
hAlign: "center",
vAlign: "top",
size: "grow",
navigation: "Top",
width: "Large",
links: [
{
text: "Home",
url: "/",
},
],
},
},
]

View File

@ -930,10 +930,10 @@
"@babel/helper-validator-identifier" "^7.14.0"
to-fast-properties "^2.0.0"
"@budibase/auth@^0.9.39":
version "0.9.39"
resolved "https://registry.yarnpkg.com/@budibase/auth/-/auth-0.9.39.tgz#8a890b4713aea23a96076578aead702453fadd76"
integrity sha512-+3zPSD4DF6E5ZNjpod5JMehcvjREepgISgw603kVQOksuX/cJItj0+EOHbK/P++4DYFr1dVgc0K3aylbCpxfjA==
"@budibase/auth@^0.9.47":
version "0.9.47"
resolved "https://registry.yarnpkg.com/@budibase/auth/-/auth-0.9.47.tgz#d0518f6bb7b29780799eee4574a0354ce9c07065"
integrity sha512-H2fbhVIZ4zwvN4XD3JcqhhwmxuAuL23Cssld7S8qNXYHNj84NdjPYVuGHQPzZudT9htiOKtOgpLkRzSWXUyxjw==
dependencies:
aws-sdk "^2.901.0"
bcryptjs "^2.4.3"
@ -951,10 +951,10 @@
uuid "^8.3.2"
zlib "^1.0.5"
"@budibase/bbui@^0.9.39":
version "0.9.39"
resolved "https://registry.yarnpkg.com/@budibase/bbui/-/bbui-0.9.39.tgz#3bccaa86ed7ae02f67661dadc57becd3db5ca9b6"
integrity sha512-5cPMFOGBOpvu3fMWLGBvgVpw+O8gvUh2g6xgWTZi2bCAgtXej+ryL0s7Xs4/mmoS2Y4z5ggc+wE4mOtCM9eFUQ==
"@budibase/bbui@^0.9.47":
version "0.9.47"
resolved "https://registry.yarnpkg.com/@budibase/bbui/-/bbui-0.9.47.tgz#d8664a05203432d522cd91a0bad1cdd8518baf93"
integrity sha512-LXvJCgUSoc4EJKafBaKfUzU4GUOQGmts/8F4V6LTFtTyMZavgq2/KFAgPbR3QeYvidLsshtwop/pQfoszXTQnQ==
dependencies:
"@adobe/spectrum-css-workflow-icons" "^1.2.1"
"@spectrum-css/actionbutton" "^1.0.1"
@ -999,20 +999,22 @@
svelte-flatpickr "^3.1.0"
svelte-portal "^1.0.0"
"@budibase/client@^0.9.39":
version "0.9.39"
resolved "https://registry.yarnpkg.com/@budibase/client/-/client-0.9.39.tgz#5916ab01f11a9f1835ccac57fffdaa63265704e4"
integrity sha512-1+teUP/OFZZT1j3wPu7+oXO6Htuhc938aOyI8Le13hkS9r4RuZBmnzI6QeZ4oVOWgwhxn9pczxcbeW4U3Upa0w==
"@budibase/client@^0.9.47":
version "0.9.47"
resolved "https://registry.yarnpkg.com/@budibase/client/-/client-0.9.47.tgz#d0871246f69ebe2d9b8a3ab7663d1e687a81a323"
integrity sha512-RhN8lDavcPnSdlHpFIfjKeuiWi7Y+0YhIQMcXav5p7SgOAsJde+Bq1ubZLgKCEl7OMTMrdHO/b7mHj9naXuyTA==
dependencies:
"@budibase/string-templates" "^0.9.39"
"@budibase/bbui" "^0.9.47"
"@budibase/standard-components" "^0.9.47"
"@budibase/string-templates" "^0.9.47"
regexparam "^1.3.0"
shortid "^2.2.15"
svelte-spa-router "^3.0.5"
"@budibase/handlebars-helpers@^0.11.3":
version "0.11.3"
resolved "https://registry.yarnpkg.com/@budibase/handlebars-helpers/-/handlebars-helpers-0.11.3.tgz#b6e5c91b83e8906e7d7ff10ddde277a3d561016e"
integrity sha512-MS1ptZEYq8o9J3tNLM7cZ2RGSSJIer4GiMIUHtbBI3sC9UKqZebao1JYNfmZKpNjntuqhZKgjqc5GfnVIEjsYQ==
"@budibase/handlebars-helpers@^0.11.4":
version "0.11.4"
resolved "https://registry.yarnpkg.com/@budibase/handlebars-helpers/-/handlebars-helpers-0.11.4.tgz#8acfa2ee84134f7be4b2906e710fce6d25c5d000"
integrity sha512-AJNJYlJnxZmn9QJ8tBl8nrm4YxbwHP4AR0pbiVGK+EoOylkNBlUnZ/QDL1VyqM5fTkAE/Z2IZVLKrrG3kxuWLA==
dependencies:
arr-flatten "^1.1.0"
array-sort "^0.1.4"
@ -1041,12 +1043,12 @@
to-gfm-code-block "^0.1.1"
year "^0.2.1"
"@budibase/standard-components@^0.9.39":
version "0.9.39"
resolved "https://registry.yarnpkg.com/@budibase/standard-components/-/standard-components-0.9.39.tgz#69807bb5f91c4ef367878ee042a625a6a578ca8e"
integrity sha512-6iuPMsYQSgVOuDq20oIeBkTHlWMB9CPNAQiteZSrAZfLxcSpHx/6ArTA0unxE6n2OwxBwCfxIU+nsWTmQmlFtA==
"@budibase/standard-components@^0.9.47":
version "0.9.47"
resolved "https://registry.yarnpkg.com/@budibase/standard-components/-/standard-components-0.9.47.tgz#8e4f27c43b5a6f65d3d296c61f842195e297f061"
integrity sha512-0+Ndg67Jgk7cqOYluGKpixNFvEqvy2oguKLEr1l83Sf0oWTQ3RCmUGs2mU66ljwnE+o4/JN/EdkA2uSqKInQtg==
dependencies:
"@budibase/bbui" "^0.9.39"
"@budibase/bbui" "^0.9.47"
"@spectrum-css/page" "^3.0.1"
"@spectrum-css/vars" "^3.0.1"
apexcharts "^3.22.1"
@ -1054,12 +1056,12 @@
svelte-apexcharts "^1.0.2"
svelte-flatpickr "^3.1.0"
"@budibase/string-templates@^0.9.39":
version "0.9.39"
resolved "https://registry.yarnpkg.com/@budibase/string-templates/-/string-templates-0.9.39.tgz#25c7a0fd2ad72b109116a3de76563860ed9f0fd6"
integrity sha512-DS7V01uerBoEAmgoQq6lOwJD1w3i5UQ30wtCPoZBj0Bz8NsKbhWEMyjTU2PrtOelBMjksAMdwyBHsxzJAuSn+Q==
"@budibase/string-templates@^0.9.47":
version "0.9.47"
resolved "https://registry.yarnpkg.com/@budibase/string-templates/-/string-templates-0.9.47.tgz#484ce5ce29a6ddaef3480368b1a24ce8c3852324"
integrity sha512-I16Ps4AW7VW8MrSdsoZdwLutiX7GhRkiH6m1AdFcmzh2mZI6YyFM000PuKGEt+sREXK2NI6cBzmi9ZpKIAPJJw==
dependencies:
"@budibase/handlebars-helpers" "^0.11.3"
"@budibase/handlebars-helpers" "^0.11.4"
dayjs "^1.10.4"
handlebars "^4.7.6"
handlebars-utils "^1.0.6"

View File

@ -1,11 +1,67 @@
{
"layout": {
"name": "Layout",
"description": "This component is specific only to layouts",
"icon": "Sandbox",
"hasChildren": true,
"styleable": true,
"illegalChildren": [],
"settings": [
{
"type": "text",
"label": "Logo URL",
"key": "logoUrl"
},
{
"type": "text",
"label": "Title",
"key": "title"
},
{
"type": "select",
"label": "Navigation",
"key": "navigation",
"options": ["Top", "Left", "None"],
"defaultValue": "Top"
},
{
"type": "select",
"label": "Width",
"key": "width",
"options": ["Small", "Medium", "Large"],
"defaultValue": "Large"
},
{
"type": "navigation",
"label": "Links",
"key": "links"
},
{
"type": "boolean",
"label": "Hide title",
"key": "hideTitle",
"defaultValue": false
},
{
"type": "boolean",
"label": "Hide logo",
"key": "hideLogo",
"defaultValue": false
},
{
"type": "boolean",
"label": "Sticky header",
"key": "sticky",
"defaultValue": false
}
]
},
"container": {
"name": "Container",
"description": "This component contains things within itself",
"icon": "Sandbox",
"hasChildren": true,
"styleable": true,
"transitionable": true,
"showSettingsBar": true,
"settings": [
{
@ -118,6 +174,23 @@
}
]
},
"section": {
"name": "Section",
"description": "Add a section to your application",
"icon": "ColumnTwoB",
"hasChildren": true,
"styleable": true,
"illegalChildren": ["section"],
"showEmptyState": false,
"settings": [
{
"type": "section",
"label": "Type",
"key": "type",
"defaultValue": "mainSidebar"
}
]
},
"screenslot": {
"name": "Screenslot",
"icon": "WebPage",
@ -128,6 +201,7 @@
"name": "Button",
"description": "A basic html button that is ready for styling",
"icon": "Button",
"illegalChildren": ["section"],
"styleable": true,
"settings": [
{
@ -135,15 +209,15 @@
"label": "Text",
"key": "text"
},
{
"type": "boolean",
"label": "Disabled",
"key": "disabled"
},
{
"type": "event",
"label": "On Click",
"key": "onClick"
},
{
"type": "boolean",
"label": "Disabled",
"key": "disabled"
}
]
},
@ -152,6 +226,7 @@
"description": "A configurable data list that attaches to your backend tables.",
"icon": "ViewList",
"styleable": true,
"illegalChildren": ["section"],
"hasChildren": true,
"settings": [
{
@ -180,6 +255,7 @@
"icon": "TaskList",
"description": "A basic card component that can contain content and actions.",
"styleable": true,
"illegalChildren": ["section"],
"settings": [
{
"type": "text",
@ -209,6 +285,7 @@
"description": "A basic card component that can contain content and actions.",
"icon": "ViewColumn",
"styleable": true,
"illegalChildren": ["section"],
"settings": [
{
"type": "text",
@ -269,6 +346,7 @@
"description": "A component for displaying paragraph text.",
"icon": "TextParagraph",
"styleable": true,
"illegalChildren": ["section"],
"settings": [
{
"type": "text",
@ -282,6 +360,7 @@
"icon": "TextBold",
"description": "A component for displaying heading text",
"styleable": true,
"illegalChildren": ["section"],
"settings": [
{
"type": "text",
@ -302,6 +381,7 @@
"description": "A basic component for displaying images",
"icon": "Image",
"styleable": true,
"illegalChildren": ["section"],
"settings": [
{
"type": "text",
@ -315,6 +395,7 @@
"description": "A background image",
"icon": "Images",
"styleable": true,
"illegalChildren": ["section"],
"settings": [
{
"type": "text",
@ -372,6 +453,7 @@
"description": "A basic component for displaying icons",
"icon": "Bell",
"styleable": true,
"illegalChildren": ["section"],
"settings": [
{
"type": "icon",
@ -419,6 +501,7 @@
"description": "A component for handling the navigation within your app.",
"icon": "BreadcrumbNavigation",
"styleable": true,
"illegalChildren": ["section"],
"hasChildren": true,
"settings": [
{
@ -439,6 +522,7 @@
"description": "A basic link component for internal and external links",
"icon": "Link",
"styleable": true,
"illegalChildren": ["section"],
"settings": [
{
"type": "text",
@ -468,6 +552,7 @@
"description": "A basic card component that can contain content and actions.",
"icon": "ViewRow",
"styleable": true,
"illegalChildren": ["section"],
"settings": [
{
"type": "text",
@ -540,6 +625,7 @@
"description": "A card component for displaying numbers.",
"icon": "Card",
"styleable": true,
"illegalChildren": ["section"],
"settings": [
{
"type": "text",
@ -566,6 +652,7 @@
"icon": "Code",
"description": "Embed content from 3rd party sources",
"styleable": true,
"illegalChildren": ["section"],
"settings": [
{
"type": "text",
@ -579,6 +666,7 @@
"description": "Bar chart",
"icon": "GraphBarVertical",
"styleable": true,
"illegalChildren": ["section"],
"settings": [
{
"type": "text",
@ -679,6 +767,7 @@
"description": "Line chart",
"icon": "GraphTrend",
"styleable": true,
"illegalChildren": ["section"],
"settings": [
{
"type": "text",
@ -780,6 +869,7 @@
"description": "Line chart",
"icon": "GraphAreaStacked",
"styleable": true,
"illegalChildren": ["section"],
"settings": [
{
"type": "text",
@ -893,6 +983,7 @@
"description": "Pie chart",
"icon": "GraphPie",
"styleable": true,
"illegalChildren": ["section"],
"settings": [
{
"type": "text",
@ -970,6 +1061,7 @@
"description": "Donut chart",
"icon": "GraphDonut",
"styleable": true,
"illegalChildren": ["section"],
"settings": [
{
"type": "text",
@ -1047,6 +1139,7 @@
"description": "Candlestick chart",
"icon": "GraphBarVerticalStacked",
"styleable": true,
"illegalChildren": ["section"],
"settings": [
{
"type": "text",
@ -1129,6 +1222,7 @@
"icon": "Form",
"styleable": true,
"hasChildren": true,
"illegalChildren": ["section"],
"actions": [
"ValidateForm"
],
@ -1200,6 +1294,7 @@
"name": "Field Group",
"icon": "Group",
"styleable": true,
"illegalChildren": ["section"],
"hasChildren": true,
"settings": [
{
@ -1228,6 +1323,7 @@
"name": "Text Field",
"icon": "Text",
"styleable": true,
"illegalChildren": ["section"],
"settings": [
{
"type": "field/string",
@ -1256,6 +1352,7 @@
"name": "Number Field",
"icon": "123",
"styleable": true,
"illegalChildren": ["section"],
"settings": [
{
"type": "field/number",
@ -1284,6 +1381,7 @@
"name": "Password Field",
"icon": "LockClosed",
"styleable": true,
"illegalChildren": ["section"],
"settings": [
{
"type": "field/string",
@ -1312,6 +1410,7 @@
"name": "Options Picker",
"icon": "ViewList",
"styleable": true,
"illegalChildren": ["section"],
"settings": [
{
"type": "field/options",
@ -1341,6 +1440,7 @@
"name": "Checkbox",
"icon": "Checkmark",
"styleable": true,
"illegalChildren": ["section"],
"settings": [
{
"type": "field/boolean",
@ -1369,6 +1469,7 @@
"name": "Rich Text",
"icon": "TextParagraph",
"styleable": true,
"illegalChildren": ["section"],
"settings": [
{
"type": "field/longform",
@ -1398,6 +1499,7 @@
"name": "Date Picker",
"icon": "DateInput",
"styleable": true,
"illegalChildren": ["section"],
"settings": [
{
"type": "field/datetime",
@ -1432,6 +1534,7 @@
"name": "Attachment",
"icon": "Attach",
"styleable": true,
"illegalChildren": ["section"],
"settings": [
{
"type": "field/attachment",
@ -1455,6 +1558,7 @@
"name": "Relationship Picker",
"icon": "TaskList",
"styleable": true,
"illegalChildren": ["section"],
"settings": [
{
"type": "field/link",
@ -1484,6 +1588,7 @@
"info": "Pagination is only available for data stored in internal tables.",
"icon": "Data",
"styleable": false,
"illegalChildren": ["section"],
"hasChildren": true,
"settings": [
{
@ -1547,6 +1652,7 @@
"name": "Table",
"icon": "Table",
"styleable": true,
"illegalChildren": ["section"],
"hasChildren": true,
"showEmptyState": false,
"settings": [

View File

@ -33,6 +33,7 @@
"license": "MIT",
"gitHead": "d1836a898cab3f8ab80ee6d8f42be1a9eed7dcdc",
"dependencies": {
"@spectrum-css/link": "^3.1.3",
"@budibase/bbui": "^0.9.51",
"@spectrum-css/page": "^3.0.1",
"@spectrum-css/vars": "^3.0.1",

View File

@ -1,7 +1,7 @@
<script>
import { getContext } from "svelte"
const { styleable, transition } = getContext("sdk")
const { styleable } = getContext("sdk")
const component = getContext("component")
export let direction
@ -17,7 +17,6 @@
<div
class={[directionClass, hAlignClass, vAlignClass, sizeClass].join(" ")}
in:transition={{ type: $component.transition }}
use:styleable={$component.styles}
>
<slot />

View File

@ -0,0 +1,337 @@
<script>
import { getContext } from "svelte"
import { Heading, Icon } from "@budibase/bbui"
const { styleable, linkable } = getContext("sdk")
const component = getContext("component")
export let title
export let hideTitle = false
export let logoUrl
export let hideLogo = false
export let navigation = "Top"
export let sticky = false
export let links
export let width = "Large"
const navigationClasses = {
Top: "top",
Left: "left",
None: "none",
}
const widthClasses = {
Large: "l",
Medium: "m",
Small: "s",
}
$: validLinks = links?.filter(link => link.text && link.url) || []
$: typeClass = navigationClasses[navigation] || "none"
$: widthClass = widthClasses[width] || "l"
let mobileOpen = false
const isInternal = url => {
return url.startsWith("/")
}
const ensureExternal = url => {
return !url.startsWith("http") ? `http://${url}` : url
}
const close = () => {
mobileOpen = false
}
</script>
<div class="layout layout--{typeClass}" use:styleable={$component.styles}>
{#if typeClass !== "none"}
<div class="nav-wrapper" class:sticky>
<div class="nav nav--{typeClass} size--{widthClass}">
<div class="nav-header">
{#if validLinks?.length}
<div class="burger">
<Icon
hoverable
name="ShowMenu"
on:click={() => (mobileOpen = !mobileOpen)}
/>
</div>
{/if}
<div class="logo">
{#if !hideLogo}
<img
src={logoUrl || "https://i.imgur.com/Xhdt1YP.png"}
alt={title}
/>
{/if}
{#if !hideTitle && title}
<Heading size="S">{title}</Heading>
{/if}
</div>
<div class="portal">
<Icon
hoverable
name="Apps"
on:click={() => (window.location.href = "/builder/apps")}
/>
</div>
</div>
<div
class="mobile-click-handler"
class:visible={mobileOpen}
on:click={() => (mobileOpen = false)}
/>
{#if validLinks?.length}
<div class="links" class:visible={mobileOpen}>
{#each validLinks as { text, url }}
{#if isInternal(url)}
<a class="link" href={url} use:linkable on:click={close}>
{text}
</a>
{:else}
<a class="link" href={ensureExternal(url)} on:click={close}>
{text}
</a>
{/if}
{/each}
<div class="close">
<Icon
hoverable
name="Close"
on:click={() => (mobileOpen = false)}
/>
</div>
</div>
{/if}
</div>
</div>
{/if}
<div class="main-wrapper">
<div class="main size--{widthClass}">
<slot />
</div>
</div>
</div>
<style>
/* Main components */
.layout {
display: flex;
flex-direction: column;
justify-content: flex-start;
align-items: stretch;
height: 100%;
overflow: auto;
}
.nav-wrapper {
display: flex;
flex-direction: row;
justify-content: center;
align-items: stretch;
background: white;
z-index: 1;
box-shadow: 0 0 8px -1px rgba(0, 0, 0, 0.075);
}
.layout--top .nav-wrapper.sticky {
position: sticky;
top: 0;
left: 0;
}
.nav {
display: flex;
flex-direction: column;
justify-content: flex-start;
align-items: stretch;
padding: var(--spacing-xl) 32px;
max-width: 100%;
gap: var(--spacing-xl);
}
.nav-header {
flex: 0 0 auto;
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
gap: var(--spacing-xl);
}
.main-wrapper {
display: flex;
flex-direction: row;
justify-content: center;
align-items: stretch;
flex: 1 1 auto;
}
.main {
display: flex;
flex-direction: column;
justify-content: flex-start;
align-items: stretch;
max-width: 100%;
position: relative;
padding: 32px;
}
.size--s {
width: 800px;
}
.size--m {
width: 1100px;
}
.size--l {
width: 1400px;
}
/* Nav components */
.burger {
display: none;
}
.logo {
display: flex;
flex-direction: row;
justify-content: flex-start;
align-items: center;
gap: var(--spacing-m);
flex: 1 1 auto;
}
.logo img {
height: 36px;
}
.logo :global(h1) {
font-weight: 600;
flex: 1 1 auto;
width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.portal {
display: grid;
place-items: center;
}
.links {
display: flex;
flex-direction: row;
justify-content: flex-start;
align-items: center;
gap: var(--spacing-xl);
margin-top: var(--spacing-xl);
}
.link {
color: var(--spectrum-alias-text-color);
font-size: var(--spectrum-global-dimension-font-size-200);
font-weight: 600;
transition: color 130ms ease-out;
}
.link:hover {
color: var(--spectrum-global-color-blue-600);
}
.close {
display: none;
position: absolute;
top: var(--spacing-xl);
right: var(--spacing-xl);
}
.mobile-click-handler {
display: none;
}
/* Desktop nav overrides */
@media (min-width: 600px) {
.layout--left {
flex-direction: row;
overflow: hidden;
}
.layout--left .main-wrapper {
height: 100%;
overflow: auto;
}
.nav--left {
width: 250px;
padding: var(--spacing-xl);
}
.nav--left .links {
margin-top: var(--spacing-m);
flex-direction: column;
justify-content: flex-start;
align-items: stretch;
}
.nav--left .link {
font-size: var(--spectrum-global-dimension-font-size-150);
}
}
/* Mobile nav overrides */
@media (max-width: 600px) {
.nav-wrapper {
position: sticky;
top: 0;
left: 0;
box-shadow: 0 0 8px -1px rgba(0, 0, 0, 0.075);
}
/* Show close button in drawer */
.close {
display: block;
}
/* Force standard top bar */
.nav {
padding: var(--spacing-m) 16px;
}
.burger {
display: grid;
place-items: center;
}
.logo {
flex: 0 0 auto;
}
.logo :global(h1) {
display: none;
}
/* Reduce padding */
.main {
padding: 16px;
}
/* Transform links into drawer */
.links {
margin-top: 0;
position: fixed;
top: 0;
left: -250px;
transform: translateX(0);
width: 250px;
transition: transform 0.26s ease-in-out, opacity 0.26s ease-in-out;
height: 100vh;
opacity: 0;
background: white;
z-index: 999;
flex-direction: column;
justify-content: flex-start;
align-items: stretch;
padding: var(--spacing-xl);
}
.link {
width: calc(100% - 30px);
font-size: 120%;
}
.links.visible {
opacity: 1;
transform: translateX(250px);
box-shadow: 0 0 40px 20px rgba(0, 0, 0, 0.1);
}
.mobile-click-handler.visible {
position: fixed;
display: block;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
z-index: 998;
}
}
</style>

View File

@ -7,7 +7,7 @@
const component = getContext("component")
</script>
<div use:styleable={$component.styles}>
<div use:styleable={{ ...$component.styles, empty: true }}>
<h1>Screen Slot</h1>
<span>
The screens that you create will be displayed inside this box.
@ -20,13 +20,16 @@
div {
display: flex !important;
flex-direction: column !important;
align-items: center !important;
justify-content: center !important;
padding: 20px !important;
align-items: center !important;
padding: 32px !important;
text-align: center !important;
border-style: dashed !important;
border-width: 1px !important;
color: #000000 !important;
background-color: rgba(0, 0, 0, 0.05) !important;
}
h1 {
color: var(--spectrum-alias-text-color);
}
span {
font-size: var(--font-size-s);
color: var(--grey-6);
}
</style>

View File

@ -0,0 +1,73 @@
<script>
import { getContext } from "svelte"
import Placeholder from "./Placeholder.svelte"
const { styleable, builderStore } = getContext("sdk")
const component = getContext("component")
export let type = "mainSidebar"
export let minSize = 250
let layoutMap = {
mainSidebar: 2,
sidebarMain: 2,
twoColumns: 2,
threeColumns: 3,
}
let containerWidth
$: columnsDependingOnSize = calculateColumns(containerWidth)
function calculateColumns(parentWidth) {
const numberOfAllowedColumns = Math.floor(parentWidth / minSize) || 100
if (layoutMap[type] <= numberOfAllowedColumns) {
return false
} else if (layoutMap[type] > numberOfAllowedColumns) {
return numberOfAllowedColumns
}
}
</script>
<div
bind:clientWidth={containerWidth}
class="{type} columns-{columnsDependingOnSize}"
use:styleable={$component.styles}
>
<slot />
{#if layoutMap[type] - $component.children > 0}
{#each new Array(layoutMap[type] - $component.children) as _}
<div class:placeholder={$builderStore.inBuilder}>
<Placeholder />
</div>
{/each}
{/if}
</div>
<style>
div {
display: grid;
grid-gap: 16px;
}
.mainSidebar {
grid-template-columns: 3fr 1fr;
}
.sidebarMain {
grid-template-columns: 1fr 3fr;
}
.twoColumns {
grid-template-columns: 1fr 1fr;
}
.threeColumns {
grid-template-columns: 1fr 1fr 1fr;
}
.columns-1 {
grid-template-columns: 1fr;
}
.columns-2 {
grid-template-columns: 1fr 1fr;
}
.placeholder {
border: 2px dashed var(--grey-5);
padding: var(--spacing-l);
}
</style>

View File

@ -15,6 +15,7 @@ export { default as Placeholder } from "./Placeholder.svelte"
// User facing components
export { default as container } from "./Container.svelte"
export { default as section } from "./Section.svelte"
export { default as dataprovider } from "./DataProvider.svelte"
export { default as screenslot } from "./ScreenSlot.svelte"
export { default as button } from "./Button.svelte"
@ -23,6 +24,7 @@ export { default as stackedlist } from "./StackedList.svelte"
export { default as card } from "./Card.svelte"
export { default as text } from "./Text.svelte"
export { default as navigation } from "./Navigation.svelte"
export { default as layout } from "./Layout.svelte"
export { default as link } from "./Link.svelte"
export { default as heading } from "./Heading.svelte"
export { default as image } from "./Image.svelte"

View File

@ -7,10 +7,10 @@
resolved "https://registry.yarnpkg.com/@adobe/spectrum-css-workflow-icons/-/spectrum-css-workflow-icons-1.2.1.tgz#7e2cb3fcfb5c8b12d7275afafbb6ec44913551b4"
integrity sha512-uVgekyBXnOVkxp+CUssjN/gefARtudZC8duEn1vm0lBQFwGRZFlDEzU1QC+aIRWCrD1Z8OgRpmBYlSZ7QS003w==
"@budibase/bbui@^0.9.41":
version "0.9.45"
resolved "https://registry.yarnpkg.com/@budibase/bbui/-/bbui-0.9.45.tgz#c37e84ffbf99daf0ebaa5c19b9109e7222463591"
integrity sha512-+wojKap0kieYoeCqhah1V9EswVbR+Pg1k9Nlw1n/zTsuGtjA0kUH652yKjz7zNzZgFm+s5oYS1CoPieIMehhSw==
"@budibase/bbui@^0.9.47":
version "0.9.47"
resolved "https://registry.yarnpkg.com/@budibase/bbui/-/bbui-0.9.47.tgz#d8664a05203432d522cd91a0bad1cdd8518baf93"
integrity sha512-LXvJCgUSoc4EJKafBaKfUzU4GUOQGmts/8F4V6LTFtTyMZavgq2/KFAgPbR3QeYvidLsshtwop/pQfoszXTQnQ==
dependencies:
"@adobe/spectrum-css-workflow-icons" "^1.2.1"
"@spectrum-css/actionbutton" "^1.0.1"
@ -140,7 +140,7 @@
resolved "https://registry.yarnpkg.com/@spectrum-css/label/-/label-2.0.10.tgz#2368651d7636a19385b5d300cdf6272db1916001"
integrity sha512-xCbtEiQkZIlLdWFikuw7ifDCC21DOC/KMgVrrVJHXFc4KRQe9LTZSqmGF3tovm+CSq1adE59mYoTbojVQ9YuEQ==
"@spectrum-css/link@^3.1.1":
"@spectrum-css/link@^3.1.1", "@spectrum-css/link@^3.1.3":
version "3.1.3"
resolved "https://registry.yarnpkg.com/@spectrum-css/link/-/link-3.1.3.tgz#b0e560a7e0acdb4a2b329b6669696aa3438f5993"
integrity sha512-8Pmy5d73MwKcATTPaj+fSrZYnIw7GmfX40AvpC1xx5LauOxsbUb4AVNp1kn2H8rrOBmxF7czyhoBBhEiv66QUg==

File diff suppressed because it is too large Load Diff