Merge branch 'plugins-dev-experience' of github.com:Budibase/budibase into plugins-dev-experience

This commit is contained in:
mike12345567 2022-08-17 23:21:58 +01:00
commit 6a64c23999
38 changed files with 682 additions and 2269 deletions

View File

@ -11,6 +11,10 @@ if [[ "${TARGETBUILD}" = "aas" ]]; then
apt-get install -y openssh-server
sed -i "s/#Port 22/Port 2222/" /etc/ssh/sshd_config
/etc/init.d/ssh restart
fi
sed -i "s#DATA_DIR#/home#g" /opt/clouseau/clouseau.ini
sed -i "s#DATA_DIR#/home#g" /opt/couchdb/etc/local.ini
else
sed -i "s#DATA_DIR#/data#g" /opt/clouseau/clouseau.ini
sed -i "s#DATA_DIR#/data#g" /opt/couchdb/etc/local.ini
sed -i 's#DATA_DIR#$DATA_DIR#' /opt/clouseau/clouseau.ini /opt/couchdb/etc/local.ini
fi

View File

@ -1,5 +1,5 @@
; CouchDB Configuration Settings
[couchdb]
database_dir = DATA_DIR/couch/dbs
view_index_dir = DATA_DIR/couch/views
database_dir = DATA_DIR/couchdb/dbs
view_index_dir = DATA_DIR/couchdb/views

View File

@ -3,6 +3,11 @@ healthy=true
if [ -f "/data/.env" ]; then
export $(cat /data/.env | xargs)
elif [ -f "/home/.env" ]; then
export $(cat /home/.env | xargs)
else
echo "No .env file found"
healthy=false
fi
if [[ $(curl -Lfk -s -w "%{http_code}\n" http://localhost/ -o /dev/null) -ne 200 ]]; then

View File

@ -1,5 +1,5 @@
{
"version": "1.2.40",
"version": "1.2.41-alpha.0",
"npmClient": "yarn",
"packages": [
"packages/*"

View File

@ -1,6 +1,6 @@
{
"name": "@budibase/backend-core",
"version": "1.2.40",
"version": "1.2.41-alpha.0",
"description": "Budibase backend core libraries used in server and worker",
"main": "dist/src/index.js",
"types": "dist/src/index.d.ts",
@ -20,7 +20,7 @@
"test:watch": "jest --watchAll"
},
"dependencies": {
"@budibase/types": "1.2.40",
"@budibase/types": "1.2.41-alpha.0",
"@techpass/passport-openidconnect": "0.3.2",
"aws-sdk": "2.1030.0",
"bcrypt": "5.0.1",

View File

@ -1,7 +1,7 @@
{
"name": "@budibase/bbui",
"description": "A UI solution used in the different Budibase projects.",
"version": "1.2.40",
"version": "1.2.41-alpha.0",
"license": "MPL-2.0",
"svelte": "src/index.js",
"module": "dist/bbui.es.js",
@ -38,7 +38,7 @@
],
"dependencies": {
"@adobe/spectrum-css-workflow-icons": "^1.2.1",
"@budibase/string-templates": "1.2.40",
"@budibase/string-templates": "1.2.41-alpha.0",
"@spectrum-css/actionbutton": "^1.0.1",
"@spectrum-css/actiongroup": "^1.0.1",
"@spectrum-css/avatar": "^3.0.2",

View File

@ -83,4 +83,9 @@
transform: translateX(-50%);
text-align: center;
}
.spectrum-Icon--sizeXS {
width: 10px;
height: 10px;
}
</style>

View File

@ -1,5 +1,6 @@
<script>
import { createEventDispatcher, getContext } from "svelte"
import Icon from "../Icon/Icon.svelte"
const dispatch = createEventDispatcher()
const actionMenu = getContext("actionMenu")
@ -8,6 +9,22 @@
export let icon = undefined
export let disabled = undefined
export let noClose = false
export let keyBind = undefined
$: keys = getKeys(keyBind)
const getKeys = keyBind => {
let keys = keyBind?.split("+") || []
for (let i = 0; i < keys.length; i++) {
if (
keys[i].toLowerCase() === "ctrl" &&
navigator.platform.startsWith("Mac")
) {
keys[i] = "⌘"
}
}
return keys
}
const onClick = () => {
if (actionMenu && !noClose) {
@ -26,20 +43,54 @@
tabindex="0"
>
{#if icon}
<svg
class="spectrum-Icon spectrum-Icon--sizeS spectrum-Menu-itemIcon"
focusable="false"
aria-hidden="true"
aria-label={icon}
>
<use xlink:href="#spectrum-icon-18-{icon}" />
</svg>
<div class="icon">
<Icon name={icon} size="S" />
</div>
{/if}
<span class="spectrum-Menu-itemLabel"><slot /></span>
{#if keys?.length}
<div class="keys">
{#each keys as key}
<div class="key">
{#if key.startsWith("!")}
<Icon size="XS" name={key.split("!")[1]} />
{:else}
{key}
{/if}
</div>
{/each}
</div>
{/if}
</li>
<style>
.spectrum-Menu-itemIcon {
.icon {
align-self: center;
margin-right: var(--spacing-s);
}
.keys {
margin-left: 30px;
display: flex;
flex-direction: row;
justify-content: flex-end;
align-items: center;
gap: 4px;
}
.key {
color: var(--spectrum-global-color-gray-900);
padding: 2px 4px;
font-size: 12px;
font-weight: 600;
background-color: var(--spectrum-global-color-gray-300);
border-radius: 4px;
min-width: 12px;
height: 16px;
text-align: center;
margin: -1px 0;
display: grid;
place-items: center;
}
.is-disabled .key {
color: var(--spectrum-global-color-gray-600);
}
</style>

View File

@ -11,6 +11,8 @@
const dispatch = createEventDispatcher()
let visible = fixed || inline
let modal
$: dispatch(visible ? "show" : "hide")
export function show() {
@ -41,12 +43,22 @@
}
}
async function focusFirstInput(node) {
async function focusModal(node) {
await tick()
// Try to focus first input
const inputs = node.querySelectorAll("input")
if (inputs?.length) {
await tick()
inputs[0].focus()
}
// Otherwise try to focus confirmation button
else if (modal) {
const confirm = modal.querySelector(".confirm-wrap .spectrum-Button")
if (confirm) {
confirm.focus()
}
}
}
setContext(Context.Modal, { show, hide, cancel })
@ -56,7 +68,7 @@
{#if inline}
{#if visible}
<div use:focusFirstInput class="spectrum-Modal inline is-open">
<div use:focusModal bind:this={modal} class="spectrum-Modal inline is-open">
<slot />
</div>
{/if}
@ -70,17 +82,18 @@
-->
<Portal target=".modal-container">
{#if visible}
<div
class="spectrum-Underlay is-open"
in:fade={{ duration: 200 }}
out:fade|local={{ duration: 200 }}
on:mousedown|self={cancel}
>
<div class="spectrum-Underlay is-open" on:mousedown|self={cancel}>
<div
class="background"
in:fade={{ duration: 200 }}
out:fade|local={{ duration: 200 }}
/>
<div class="modal-wrapper" on:mousedown|self={cancel}>
<div class="modal-inner-wrapper" on:mousedown|self={cancel}>
<slot name="outside" />
<div
use:focusFirstInput
use:focusModal
bind:this={modal}
class="spectrum-Modal is-open"
in:fly={{ y: 30, duration: 200 }}
out:fly|local={{ y: 30, duration: 200 }}
@ -103,7 +116,17 @@
z-index: 999;
overflow: auto;
overflow-x: hidden;
background: rgba(0, 0, 0, 0.75);
background: transparent;
}
.background {
background: var(--modal-background, rgba(0, 0, 0, 0.75));
position: fixed;
top: 0;
left: 0;
height: 100vh;
width: 100vw;
opacity: 0.65;
pointer-events: none;
}
.modal-wrapper {

View File

@ -1,6 +1,6 @@
{
"name": "@budibase/builder",
"version": "1.2.40",
"version": "1.2.41-alpha.0",
"license": "GPL-3.0",
"private": true,
"scripts": {
@ -69,10 +69,10 @@
}
},
"dependencies": {
"@budibase/bbui": "1.2.40",
"@budibase/client": "1.2.40",
"@budibase/frontend-core": "1.2.40",
"@budibase/string-templates": "1.2.40",
"@budibase/bbui": "1.2.41-alpha.0",
"@budibase/client": "1.2.41-alpha.0",
"@budibase/frontend-core": "1.2.41-alpha.0",
"@budibase/string-templates": "1.2.41-alpha.0",
"@sentry/browser": "5.19.1",
"@spectrum-css/page": "^3.0.1",
"@spectrum-css/vars": "^3.0.1",

View File

@ -552,7 +552,16 @@ export const getFrontendStore = () => {
if (!component) {
return
}
let parentId
// Determine the next component to select after deletion
const state = get(store)
let nextSelectedComponentId
if (state.selectedComponentId === component._id) {
nextSelectedComponentId = store.actions.components.getNext()
if (!nextSelectedComponentId) {
nextSelectedComponentId = store.actions.components.getPrevious()
}
}
// Patch screen
await store.actions.screens.patch(screen => {
@ -567,17 +576,18 @@ export const getFrontendStore = () => {
if (!parent) {
return false
}
parentId = parent._id
parent._children = parent._children.filter(
child => child._id !== component._id
)
})
// Select the deleted component's parent
store.update(state => {
state.selectedComponentId = parentId
return state
})
// Update selected component if required
if (nextSelectedComponentId) {
store.update(state => {
state.selectedComponentId = nextSelectedComponentId
return state
})
}
},
copy: (component, cut = false, selectParent = true) => {
// Update store with copied component
@ -636,6 +646,16 @@ export const getFrontendStore = () => {
}
}
// Check inside is valid
if (mode === "inside") {
const definition = store.actions.components.getDefinition(
targetComponent._component
)
if (!definition.hasChildren) {
mode = "below"
}
}
// Paste new component
if (mode === "inside") {
// Paste inside target component if chosen
@ -672,46 +692,193 @@ export const getFrontendStore = () => {
return state
})
},
getPrevious: () => {
const state = get(store)
const componentId = state.selectedComponentId
const screen = get(selectedScreen)
const parent = findComponentParent(screen.props, componentId)
// Check we aren't right at the top of the tree
const index = parent?._children.findIndex(x => x._id === componentId)
if (!parent || componentId === screen.props._id) {
return null
}
// If we have siblings above us, choose the sibling or a descendant
if (index > 0) {
// If sibling before us accepts children, select a descendant
const previousSibling = parent._children[index - 1]
if (previousSibling._children?.length) {
let target = previousSibling
while (target._children?.length) {
target = target._children[target._children.length - 1]
}
return target._id
}
// Otherwise just select sibling
return previousSibling._id
}
// If no siblings above us, select the parent
return parent._id
},
getNext: () => {
const component = get(selectedComponent)
const componentId = component?._id
const screen = get(selectedScreen)
const parent = findComponentParent(screen.props, componentId)
const index = parent?._children.findIndex(x => x._id === componentId)
// If we have children, select first child
if (component._children?.length) {
return component._children[0]._id
} else if (!parent) {
return null
}
// Otherwise select the next sibling if we have one
if (index < parent._children.length - 1) {
const nextSibling = parent._children[index + 1]
return nextSibling._id
}
// Last child, select our parents next sibling
let target = parent
let targetParent = findComponentParent(screen.props, target._id)
let targetIndex = targetParent?._children.findIndex(
child => child._id === target._id
)
while (
targetParent != null &&
targetIndex === targetParent._children?.length - 1
) {
target = targetParent
targetParent = findComponentParent(screen.props, target._id)
targetIndex = targetParent?._children.findIndex(
child => child._id === target._id
)
}
if (targetParent) {
return targetParent._children[targetIndex + 1]._id
} else {
return null
}
},
selectPrevious: () => {
const previousId = store.actions.components.getPrevious()
if (previousId) {
store.update(state => {
state.selectedComponentId = previousId
return state
})
}
},
selectNext: () => {
const nextId = store.actions.components.getNext()
if (nextId) {
store.update(state => {
state.selectedComponentId = nextId
return state
})
}
},
moveUp: async component => {
await store.actions.screens.patch(screen => {
const componentId = component?._id
const parent = findComponentParent(screen.props, componentId)
if (!parent?._children?.length) {
return false
// Check we aren't right at the top of the tree
const index = parent?._children.findIndex(x => x._id === componentId)
if (!parent || (index === 0 && parent._id === screen.props._id)) {
return
}
const currentIndex = parent._children.findIndex(
child => child._id === componentId
)
if (currentIndex === 0) {
return false
}
const originalComponent = cloneDeep(parent._children[currentIndex])
const newChildren = parent._children.filter(
// Copy original component and remove it from the parent
const originalComponent = cloneDeep(parent._children[index])
parent._children = parent._children.filter(
component => component._id !== componentId
)
newChildren.splice(currentIndex - 1, 0, originalComponent)
parent._children = newChildren
// If we have siblings above us, move up
if (index > 0) {
// If sibling before us accepts children, move to last child of
// sibling
const previousSibling = parent._children[index - 1]
const definition = store.actions.components.getDefinition(
previousSibling._component
)
if (definition.hasChildren) {
previousSibling._children.push(originalComponent)
}
// Otherwise just move component above sibling
else {
parent._children.splice(index - 1, 0, originalComponent)
}
}
// If no siblings above us, go above the parent as long as it isn't
// the screen
else if (parent._id !== screen.props._id) {
const grandParent = findComponentParent(screen.props, parent._id)
const parentIndex = grandParent._children.findIndex(
child => child._id === parent._id
)
grandParent._children.splice(parentIndex, 0, originalComponent)
}
})
},
moveDown: async component => {
await store.actions.screens.patch(screen => {
const componentId = component?._id
const parent = findComponentParent(screen.props, componentId)
// Sanity check parent is found
if (!parent?._children?.length) {
return false
}
const currentIndex = parent._children.findIndex(
child => child._id === componentId
)
if (currentIndex === parent._children.length - 1) {
return false
// Check we aren't right at the bottom of the tree
const index = parent._children.findIndex(x => x._id === componentId)
if (
index === parent._children.length - 1 &&
parent._id === screen.props._id
) {
return
}
const originalComponent = cloneDeep(parent._children[currentIndex])
const newChildren = parent._children.filter(
// Copy the original component and remove from parent
const originalComponent = cloneDeep(parent._children[index])
parent._children = parent._children.filter(
component => component._id !== componentId
)
newChildren.splice(currentIndex + 1, 0, originalComponent)
parent._children = newChildren
// Move below the next sibling if we are not the last sibling
if (index < parent._children.length) {
// If the next sibling has children, become the first child
const nextSibling = parent._children[index]
const definition = store.actions.components.getDefinition(
nextSibling._component
)
if (definition.hasChildren) {
nextSibling._children.splice(0, 0, originalComponent)
}
// Otherwise move below next sibling
else {
parent._children.splice(index + 1, 0, originalComponent)
}
}
// Last child, so move below our parent
else {
const grandParent = findComponentParent(screen.props, parent._id)
const parentIndex = grandParent._children.findIndex(
child => child._id === parent._id
)
grandParent._children.splice(parentIndex + 1, 0, originalComponent)
}
})
},
updateStyle: async (name, value) => {

View File

@ -162,7 +162,7 @@
width="28px"
height="28px"
class="spectrum-Icon"
style="color:grey;"
style="color:var(--spectrum-global-color-gray-700);"
focusable="false"
>
<use xlink:href="#spectrum-icon-18-Reuse" />

View File

@ -64,7 +64,7 @@
width="28px"
height="28px"
class="spectrum-Icon"
style="color:grey;"
style="color:var(--spectrum-global-color-gray-700);"
focusable="false"
>
<use xlink:href="#spectrum-icon-18-{block.icon}" />

View File

@ -152,7 +152,11 @@
} else if (type === "update-prop") {
await store.actions.components.updateSetting(data.prop, data.value)
} else if (type === "delete-component" && data.id) {
// Legacy type, can be deleted in future
confirmDeleteComponent(data.id)
} else if (type === "key-down") {
const { key, ctrlKey } = data
document.dispatchEvent(new KeyboardEvent("keydown", { key, ctrlKey }))
} else if (type === "duplicate-component" && data.id) {
const rootComponent = get(currentAsset).props
const component = findComponent(rootComponent, data.id)

View File

@ -1,117 +1,69 @@
<script>
import { store } from "builderStore"
import ConfirmDialog from "components/common/ConfirmDialog.svelte"
import { ActionMenu, MenuItem, Icon, notifications } from "@budibase/bbui"
import { ActionMenu, MenuItem, Icon } from "@budibase/bbui"
export let component
let confirmDeleteDialog
$: definition = store.actions.components.getDefinition(component?._component)
$: noChildrenAllowed = !component || !definition?.hasChildren
$: noPaste = !$store.componentToPaste
// "editable" has been repurposed for inline text editing.
// It remains here for legacy compatibility.
// Future components should define "static": true for indicate they should
// not show a context menu.
$: showMenu = definition?.editable !== false && definition?.static !== true
const moveUpComponent = async () => {
try {
await store.actions.components.moveUp(component)
} catch (error) {
notifications.error("Error moving component up")
}
}
const moveDownComponent = async () => {
try {
await store.actions.components.moveDown(component)
} catch (error) {
notifications.error("Error moving component down")
}
}
const duplicateComponent = () => {
storeComponentForCopy(false)
pasteComponent("below")
}
const deleteComponent = async () => {
try {
await store.actions.components.delete(component)
} catch (error) {
notifications.error("Error deleting component")
}
}
const storeComponentForCopy = (cut = false) => {
store.actions.components.copy(component, cut)
}
const pasteComponent = mode => {
try {
store.actions.components.paste(component, mode)
} catch (error) {
notifications.error("Error saving component")
}
const keyboardEvent = (key, ctrlKey = false) => {
document.dispatchEvent(new KeyboardEvent("keydown", { key, ctrlKey }))
}
</script>
{#if showMenu}
<ActionMenu>
<div slot="control" class="icon">
<Icon size="S" hoverable name="MoreSmallList" />
</div>
<MenuItem icon="Delete" on:click={confirmDeleteDialog.show}>
Delete
</MenuItem>
<MenuItem noClose icon="ChevronUp" on:click={moveUpComponent}>
Move up
</MenuItem>
<MenuItem noClose icon="ChevronDown" on:click={moveDownComponent}>
Move down
</MenuItem>
<MenuItem noClose icon="Duplicate" on:click={duplicateComponent}>
Duplicate
</MenuItem>
<MenuItem icon="Cut" on:click={() => storeComponentForCopy(true)}>
Cut
</MenuItem>
<MenuItem icon="Copy" on:click={() => storeComponentForCopy(false)}>
Copy
</MenuItem>
<MenuItem
icon="LayersBringToFront"
on:click={() => pasteComponent("above")}
disabled={noPaste}
>
Paste above
</MenuItem>
<MenuItem
icon="LayersSendToBack"
on:click={() => pasteComponent("below")}
disabled={noPaste}
>
Paste below
</MenuItem>
<MenuItem
icon="ShowOneLayer"
on:click={() => pasteComponent("inside")}
disabled={noPaste || noChildrenAllowed}
>
Paste inside
</MenuItem>
</ActionMenu>
<ConfirmDialog
bind:this={confirmDeleteDialog}
title="Confirm Deletion"
body={`Are you sure you wish to delete this '${definition?.name}' component?`}
okText="Delete Component"
onOk={deleteComponent}
/>
{/if}
<ActionMenu>
<div slot="control" class="icon">
<Icon size="S" hoverable name="MoreSmallList" />
</div>
<MenuItem
icon="Delete"
keyBind="!BackAndroid"
on:click={() => keyboardEvent("Delete")}
>
Delete
</MenuItem>
<MenuItem
icon="ChevronUp"
keyBind="Ctrl+!ArrowUp"
on:click={() => keyboardEvent("ArrowUp", true)}
>
Move up
</MenuItem>
<MenuItem
icon="ChevronDown"
keyBind="Ctrl+!ArrowDown"
on:click={() => keyboardEvent("ArrowDown", true)}
>
Move down
</MenuItem>
<MenuItem
icon="Duplicate"
keyBind="Ctrl+D"
on:click={() => keyboardEvent("d", true)}
>
Duplicate
</MenuItem>
<MenuItem
icon="Cut"
keyBind="Ctrl+X"
on:click={() => keyboardEvent("x", true)}
>
Cut
</MenuItem>
<MenuItem
icon="Copy"
keyBind="Ctrl+C"
on:click={() => keyboardEvent("c", true)}
>
Copy
</MenuItem>
<MenuItem
icon="LayersSendToBack"
keyBind="Ctrl+V"
on:click={() => keyboardEvent("v", true)}
disabled={noPaste}
>
Paste
</MenuItem>
</ActionMenu>
<style>
.icon {

View File

@ -2,16 +2,19 @@
import Panel from "components/design/Panel.svelte"
import ComponentTree from "./ComponentTree.svelte"
import { dndStore } from "./dndStore.js"
import { goto } from "@roxi/routify"
import { store, selectedScreen } from "builderStore"
import { goto, isActive } from "@roxi/routify"
import { store, selectedScreen, selectedComponent } from "builderStore"
import NavItem from "components/common/NavItem.svelte"
import ScreenslotDropdownMenu from "./ScreenslotDropdownMenu.svelte"
import { setContext } from "svelte"
import { setContext, onMount } from "svelte"
import { get } from "svelte/store"
import DNDPositionIndicator from "./DNDPositionIndicator.svelte"
import { DropPosition } from "./dndStore"
import ConfirmDialog from "components/common/ConfirmDialog.svelte"
import { notifications, Button } from "@budibase/bbui"
let scrollRef
let confirmDeleteDialog
const scrollTo = bounds => {
if (!bounds) {
@ -69,6 +72,76 @@
setContext("scroll", {
scrollTo,
})
const deleteComponent = async () => {
await store.actions.components.delete(get(selectedComponent))
}
const handleKeyPress = async e => {
// Ignore repeating events
if (e.repeat) {
return
}
// Ignore events when typing
const activeTag = document.activeElement?.tagName.toLowerCase()
if (["input", "textarea"].indexOf(activeTag) !== -1 && e.key !== "Escape") {
return
}
const component = get(selectedComponent)
try {
if (e.ctrlKey || e.metaKey) {
if (e.key === "ArrowUp") {
e.preventDefault()
await store.actions.components.moveUp(component)
} else if (e.key === "ArrowDown") {
e.preventDefault()
await store.actions.components.moveDown(component)
} else if (e.key === "c") {
e.preventDefault()
await store.actions.components.copy(component, false)
} else if (e.key === "x") {
e.preventDefault()
store.actions.components.copy(component, true)
} else if (e.key === "v") {
e.preventDefault()
await store.actions.components.paste(component, "inside")
} else if (e.key === "d") {
e.preventDefault()
await store.actions.components.copy(component)
await store.actions.components.paste(component, "below")
} else if (e.key === "Enter") {
e.preventDefault()
$goto("./new")
}
} else if (e.key === "Backspace" || e.key === "Delete") {
// Don't show confirmation for the screen itself
if (component._id === get(selectedScreen).props._id) {
return
}
e.preventDefault()
confirmDeleteDialog.show()
} else if (e.key === "ArrowUp") {
e.preventDefault()
await store.actions.components.selectPrevious()
} else if (e.key === "ArrowDown") {
e.preventDefault()
await store.actions.components.selectNext()
} else if (e.key === "Escape" && $isActive("./new")) {
e.preventDefault()
$goto("./")
}
} catch (error) {
console.log(error)
notifications.error("Error handling key press")
}
}
onMount(() => {
document.addEventListener("keydown", handleKeyPress)
return () => {
document.removeEventListener("keydown", handleKeyPress)
}
})
</script>
<Panel title="Components" showExpandIcon borderRight>
@ -116,6 +189,13 @@
</ul>
</div>
</Panel>
<ConfirmDialog
bind:this={confirmDeleteDialog}
title="Confirm Deletion"
body={`Are you sure you want to delete "${$selectedComponent?._instanceName}"?`}
okText="Delete Component"
onOk={deleteComponent}
/>
<style>
.add-component {

View File

@ -31,15 +31,20 @@
<div slot="control" class="icon">
<Icon size="S" hoverable name="MoreSmallList" />
</div>
<MenuItem icon="Copy" on:click={() => storeComponentForCopy(false)}>
<MenuItem
icon="Copy"
keyBind="Ctrl+C"
on:click={() => storeComponentForCopy(false)}
>
Copy
</MenuItem>
<MenuItem
icon="ShowOneLayer"
icon="LayersSendToBack"
keyBind="Ctrl+V"
on:click={() => pasteComponent("inside")}
disabled={noPaste}
>
Paste inside
Paste
</MenuItem>
</ActionMenu>
{/if}

View File

@ -1,20 +1,11 @@
<script>
import { notifications, Slider, Icon } from "@budibase/bbui"
import { notifications } from "@budibase/bbui"
import { store } from "builderStore"
import { Constants } from "@budibase/frontend-core"
const ThemeOptions = [
"spectrum--darkest",
"spectrum--dark",
"spectrum--light",
"spectrum--lightest",
]
$: themeIndex = ThemeOptions.indexOf($store.theme) ?? 2
const onChangeTheme = async e => {
const onChangeTheme = async theme => {
try {
const theme = ThemeOptions[e.detail] ?? ThemeOptions[2]
await store.actions.theme.save(theme)
await store.actions.theme.save(`spectrum--${theme}`)
} catch (error) {
notifications.error("Error updating theme")
}
@ -22,26 +13,52 @@
</script>
<div class="container">
<Icon name="Moon" />
<Slider
min={0}
max={3}
step={1}
value={themeIndex}
on:change={onChangeTheme}
/>
<Icon name="Light" />
{#each Constants.Themes as theme}
<div
class="theme"
class:selected={`spectrum--${theme.class}` === $store.theme}
on:click={() => onChangeTheme(theme.class)}
>
<div
style="background: {theme.preview}"
class="color spectrum--{theme.class}"
/>
{theme.name}
</div>
{/each}
</div>
<style>
div {
.container {
display: flex;
flex-direction: column;
justify-content: flex-start;
align-items: stretch;
gap: var(--spacing-xs);
}
.color {
width: 20px;
height: 20px;
border-radius: 50px;
background: var(--spectrum-global-color-gray-200);
border: 1px solid rgba(0, 0, 0, 0.1);
}
.theme {
border-radius: 4px;
padding: var(--spacing-s) var(--spacing-m);
display: flex;
flex-direction: row;
justify-content: flex-start;
align-items: center;
gap: var(--spacing-m);
gap: var(--spacing-xl);
transition: background 130ms ease-out;
font-weight: 600;
color: var(--spectrum-global-color-gray-900);
}
div :global(.spectrum-Form-item) {
flex: 1 1 auto;
.theme:hover {
cursor: pointer;
}
.theme.selected,
.theme:hover {
background: var(--spectrum-global-color-gray-50);
}
</style>

View File

@ -0,0 +1,38 @@
<script>
import { createEventDispatcher } from "svelte"
import { Slider, Button } from "@budibase/bbui"
export let customTheme
const dispatch = createEventDispatcher()
const options = ["0", "4px", "8px", "16px"]
$: index = options.indexOf(customTheme.buttonBorderRadius) ?? 2
const onChange = async e => {
dispatch("change", options[e.detail])
}
</script>
<div class="container">
<Slider min={0} max={3} step={1} value={index} on:change={onChange} />
<div class="button" style="--radius: {customTheme.buttonBorderRadius};">
<Button primary newStyles>Button</Button>
</div>
</div>
<style>
.container {
display: flex;
flex-direction: row;
justify-content: flex-start;
align-items: center;
gap: var(--spacing-xl);
}
.container :global(.spectrum-Form-item) {
flex: 1 1 auto;
}
.button :global(.spectrum-Button) {
border-radius: var(--radius) !important;
}
</style>

View File

@ -1,35 +1,11 @@
<script>
import Panel from "components/design/Panel.svelte"
import {
Layout,
Label,
ColorPicker,
Button,
notifications,
} from "@budibase/bbui"
import { Layout, Label, ColorPicker, 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",
},
]
import ButtonRoundnessSelect from "./ButtonRoundnessSelect.svelte"
$: customTheme = $store.customTheme || {}
@ -52,22 +28,11 @@
<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>
<Label>Button roundness</Label>
<ButtonRoundnessSelect
{customTheme}
on:change={e => update("buttonBorderRadius", e.detail)}
/>
</Layout>
<Layout noPadding gap="XS">
<Label>Accent color</Label>
@ -88,29 +53,3 @@
</Layout>
</Layout>
</Panel>
<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>

View File

@ -1,6 +1,6 @@
{
"name": "@budibase/cli",
"version": "1.2.40",
"version": "1.2.41-alpha.0",
"description": "Budibase CLI, for developers, self hosting and migrations.",
"main": "src/index.js",
"bin": {
@ -26,9 +26,9 @@
"outputPath": "build"
},
"dependencies": {
"@budibase/backend-core": "1.2.40",
"@budibase/string-templates": "1.2.40",
"@budibase/types": "1.2.40",
"@budibase/backend-core": "1.2.41-alpha.0",
"@budibase/string-templates": "1.2.41-alpha.0",
"@budibase/types": "1.2.41-alpha.0",
"axios": "0.21.2",
"chalk": "4.1.0",
"cli-progress": "3.11.2",

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{
"name": "@budibase/client",
"version": "1.2.40",
"version": "1.2.41-alpha.0",
"license": "MPL-2.0",
"module": "dist/budibase-client.js",
"main": "dist/budibase-client.js",
@ -19,9 +19,9 @@
"dev:builder": "rollup -cw"
},
"dependencies": {
"@budibase/bbui": "1.2.40",
"@budibase/frontend-core": "1.2.40",
"@budibase/string-templates": "1.2.40",
"@budibase/bbui": "1.2.41-alpha.0",
"@budibase/frontend-core": "1.2.41-alpha.0",
"@budibase/string-templates": "1.2.41-alpha.0",
"@spectrum-css/button": "^3.0.3",
"@spectrum-css/card": "^3.0.3",
"@spectrum-css/divider": "^1.0.3",

View File

@ -102,7 +102,7 @@
id="spectrum-root"
lang="en"
dir="ltr"
class="spectrum spectrum--medium spectrum--darkest {$themeStore.theme}"
class="spectrum spectrum--medium {$themeStore.baseTheme} {$themeStore.theme}"
>
<DeviceBindingsProvider>
<UserBindingsProvider>

View File

@ -17,10 +17,16 @@
--spectrum-semantic-cta-color-background-default: var(--primaryColor);
--spectrum-semantic-cta-color-background-hover: var(--primaryColorHover);
--spectrum-semantic-cta-color-background-down: var(--primaryColorHover);
--spectrum-button-primary-s-border-radius: var(--buttonBorderRadius);
--spectrum-button-primary-s-border-radius: calc(
var(--buttonBorderRadius) * 0.9
);
--spectrum-button-primary-m-border-radius: var(--buttonBorderRadius);
--spectrum-button-primary-l-border-radius: var(--buttonBorderRadius);
--spectrum-button-primary-xl-border-radius: var(--buttonBorderRadius);
--spectrum-button-primary-l-border-radius: calc(
var(--buttonBorderRadius) * 1.25
);
--spectrum-button-primary-xl-border-radius: calc(
var(--buttonBorderRadius) * 1.5
);
/* Loading spinners */
--spectrum-progresscircle-medium-track-fill-color: var(--primaryColor);

View File

@ -5,6 +5,8 @@ import "@spectrum-css/vars/dist/spectrum-darkest.css"
import "@spectrum-css/vars/dist/spectrum-dark.css"
import "@spectrum-css/vars/dist/spectrum-light.css"
import "@spectrum-css/vars/dist/spectrum-lightest.css"
import "@budibase/frontend-core/src/themes/nord.css"
import "@budibase/frontend-core/src/themes/midnight.css"
import "@spectrum-css/page/dist/index-vars.css"
// Non user-facing components

View File

@ -16,20 +16,20 @@
})
const onKeyDown = e => {
if (e.key === "Delete" || e.key === "Backspace") {
deleteSelectedComponent()
}
}
const deleteSelectedComponent = () => {
const state = get(builderStore)
if (!state.inBuilder || !state.selectedComponentId || state.editMode) {
if (!state.inBuilder || state.editMode) {
return
}
const activeTag = document.activeElement?.tagName.toLowerCase()
if (["input", "textarea"].indexOf(activeTag) !== -1) {
return
}
builderStore.actions.deleteComponent(state.selectedComponentId)
// Need to manually block certain keys from propagating to the browser
if (e.ctrlKey && e.key === "d") {
e.preventDefault()
}
builderStore.actions.keyDown(e.key, e.ctrlKey)
}
</script>

View File

@ -40,8 +40,8 @@ const createBuilderStore = () => {
updateProp: (prop, value) => {
dispatchEvent("update-prop", { prop, value })
},
deleteComponent: id => {
dispatchEvent("delete-component", { id })
keyDown: (key, ctrlKey) => {
dispatchEvent("key-down", { key, ctrlKey })
},
duplicateComponent: id => {
dispatchEvent("duplicate-component", { id })

View File

@ -1,6 +1,7 @@
import { derived } from "svelte/store"
import { appStore } from "./app"
import { builderStore } from "./builder"
import { Constants } from "@budibase/frontend-core"
// This is the good old acorn bug where having the word "g l o b a l" makes it
// think that this is not ES6 compatible and starts throwing errors when using
@ -28,6 +29,13 @@ const createThemeStore = () => {
// Ensure theme is set
theme = theme || defaultTheme
// Get base theme
let base =
Constants.Themes.find(x => `spectrum--${x.class}` === theme)?.base || ""
if (base) {
base = `spectrum--${base}`
}
// Delete and nullish keys from the custom theme
if (customTheme) {
Object.entries(customTheme).forEach(([key, value]) => {
@ -51,6 +59,7 @@ const createThemeStore = () => {
return {
theme,
baseTheme: base,
customTheme,
customThemeCss,
}

View File

@ -1,12 +1,12 @@
{
"name": "@budibase/frontend-core",
"version": "1.2.40",
"version": "1.2.41-alpha.0",
"description": "Budibase frontend core libraries used in builder and client",
"author": "Budibase",
"license": "MPL-2.0",
"svelte": "src/index.js",
"dependencies": {
"@budibase/bbui": "1.2.40",
"@budibase/bbui": "1.2.41-alpha.0",
"lodash": "^4.17.21",
"svelte": "^3.46.2"
}

View File

@ -12,5 +12,7 @@
--spectrum-global-color-gray-700: hsl(var(--hue), var(--sat), 70%);
--spectrum-global-color-gray-800: hsl(var(--hue), var(--sat), 80%);
--spectrum-global-color-gray-900: hsl(var(--hue), var(--sat), 95%);
--modal-background: var(--spectrum-global-color-gray-50);
}

View File

@ -43,4 +43,7 @@
--spectrum-alias-highlight-hover: rgba(169, 177, 193, 0.1);
--spectrum-alias-highlight-active: rgba(169, 177, 193, 0.1);
--spectrum-alias-background-color-hover-overlay: rgba(169, 177, 193, 0.1);
--modal-background: var(--spectrum-global-color-gray-50);
}

View File

@ -1,7 +1,7 @@
{
"name": "@budibase/server",
"email": "hi@budibase.com",
"version": "1.2.40",
"version": "1.2.41-alpha.0",
"description": "Budibase Web Server",
"main": "src/index.ts",
"repository": {
@ -77,11 +77,11 @@
"license": "GPL-3.0",
"dependencies": {
"@apidevtools/swagger-parser": "10.0.3",
"@budibase/backend-core": "1.2.40",
"@budibase/client": "1.2.40",
"@budibase/pro": "1.2.40",
"@budibase/string-templates": "1.2.40",
"@budibase/types": "^1.2.40",
"@budibase/backend-core": "1.2.41-alpha.0",
"@budibase/client": "1.2.41-alpha.0",
"@budibase/pro": "1.2.41-alpha.0",
"@budibase/string-templates": "1.2.41-alpha.0",
"@budibase/types": "1.2.41-alpha.0",
"@bull-board/api": "3.7.0",
"@bull-board/koa": "3.9.4",
"@elastic/elasticsearch": "7.10.0",

View File

@ -1094,18 +1094,19 @@
resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"
integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==
"@budibase/backend-core@1.2.40":
version "1.2.40"
resolved "https://registry.yarnpkg.com/@budibase/backend-core/-/backend-core-1.2.40.tgz#692062478f310eaee780adbc55e266fc3b6649b6"
integrity sha512-HUw6IQgKkP345vnQcTok6X+inqYe0WpUL7uFJnm3XERvsAWX4FYWbgDoGJGS0+jZjdPjvARkt4+uZuTAEUFLiQ==
"@budibase/backend-core@1.2.41-alpha.0":
version "1.2.41-alpha.0"
resolved "https://registry.yarnpkg.com/@budibase/backend-core/-/backend-core-1.2.41-alpha.0.tgz#ee3bc032ae89e347b9fb220303a9d040df6f5823"
integrity sha512-x6wJpBBFj6a1pGu2OR1vBPHiA4LvdQpVxr6f1lZa2+nAhuXVtEhrdnvBVzK2hJ52dOTTTGaw2A5tTkbf7861CA==
dependencies:
"@budibase/types" "^1.2.40"
"@budibase/types" "1.2.41-alpha.0"
"@techpass/passport-openidconnect" "0.3.2"
aws-sdk "2.1030.0"
bcrypt "5.0.1"
dotenv "16.0.1"
emitter-listener "1.1.2"
ioredis "4.28.0"
joi "17.6.0"
jsonwebtoken "8.5.1"
koa-passport "4.1.4"
lodash "4.17.21"
@ -1177,13 +1178,13 @@
svelte-flatpickr "^3.2.3"
svelte-portal "^1.0.0"
"@budibase/pro@1.2.40":
version "1.2.40"
resolved "https://registry.yarnpkg.com/@budibase/pro/-/pro-1.2.40.tgz#007cad4d8341698104498ae7d1578a7e26760dd7"
integrity sha512-lGB0/O8l63FtpP1dOzxowFUjgj7SrRSVsODygPB7oScfUBtbqHyZKC4EafrVpJT4A8hbdh1ijQZ8PLJE/YdRkQ==
"@budibase/pro@1.2.41-alpha.0":
version "1.2.41-alpha.0"
resolved "https://registry.yarnpkg.com/@budibase/pro/-/pro-1.2.41-alpha.0.tgz#4f27dbeee24ddc58e2a4e75cd90b53543f742722"
integrity sha512-LiKuO7/9GGzf3xfMmNTZQl2r4jmGgqnUf7fA5ub/MRyWYM7ZjPWEKW1V9OAk3vXwv9a+4V6FUxJQ1sDUZ3DT/Q==
dependencies:
"@budibase/backend-core" "1.2.40"
"@budibase/types" "1.2.40"
"@budibase/backend-core" "1.2.41-alpha.0"
"@budibase/types" "1.2.41-alpha.0"
"@koa/router" "8.0.8"
joi "17.6.0"
node-fetch "^2.6.1"
@ -1206,10 +1207,10 @@
svelte-apexcharts "^1.0.2"
svelte-flatpickr "^3.1.0"
"@budibase/types@1.2.40", "@budibase/types@^1.2.40":
version "1.2.40"
resolved "https://registry.yarnpkg.com/@budibase/types/-/types-1.2.40.tgz#c1456728b61966cab7fe3689bd044bf415faa138"
integrity sha512-eNZFG0C/GBog87y6StKBjdF/aDJJtUHbAM8NWLUNNqTAiMxO2s3XhDN1dE6t74NBCRFzJo2sAD+34pJHhKFanQ==
"@budibase/types@1.2.41-alpha.0":
version "1.2.41-alpha.0"
resolved "https://registry.yarnpkg.com/@budibase/types/-/types-1.2.41-alpha.0.tgz#01071ce6ecef6799b04b1defce2b38e81b615f9f"
integrity sha512-2jkeToXsujYUXb6P2DFTFUOFv56GgImJ4webP8xr5cPGL0/xmhWGMd/lLxLt96RtMQp43UmVWZm1BA7TZw4fJg==
"@bull-board/api@3.7.0":
version "3.7.0"

View File

@ -1,6 +1,6 @@
{
"name": "@budibase/string-templates",
"version": "1.2.40",
"version": "1.2.41-alpha.0",
"description": "Handlebars wrapper for Budibase templating.",
"main": "src/index.cjs",
"module": "dist/bundle.mjs",

View File

@ -1,6 +1,6 @@
{
"name": "@budibase/types",
"version": "1.2.40",
"version": "1.2.41-alpha.0",
"description": "Budibase types",
"main": "dist/index.js",
"types": "dist/index.d.ts",

View File

@ -1,7 +1,7 @@
{
"name": "@budibase/worker",
"email": "hi@budibase.com",
"version": "1.2.40",
"version": "1.2.41-alpha.0",
"description": "Budibase background service",
"main": "src/index.ts",
"repository": {
@ -35,10 +35,10 @@
"author": "Budibase",
"license": "GPL-3.0",
"dependencies": {
"@budibase/backend-core": "1.2.40",
"@budibase/pro": "1.2.40",
"@budibase/string-templates": "1.2.40",
"@budibase/types": "1.2.40",
"@budibase/backend-core": "1.2.41-alpha.0",
"@budibase/pro": "1.2.41-alpha.0",
"@budibase/string-templates": "1.2.41-alpha.0",
"@budibase/types": "1.2.41-alpha.0",
"@koa/router": "8.0.8",
"@sentry/node": "6.17.7",
"@techpass/passport-openidconnect": "0.3.2",

View File

@ -291,18 +291,19 @@
resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"
integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==
"@budibase/backend-core@1.2.40":
version "1.2.40"
resolved "https://registry.yarnpkg.com/@budibase/backend-core/-/backend-core-1.2.40.tgz#692062478f310eaee780adbc55e266fc3b6649b6"
integrity sha512-HUw6IQgKkP345vnQcTok6X+inqYe0WpUL7uFJnm3XERvsAWX4FYWbgDoGJGS0+jZjdPjvARkt4+uZuTAEUFLiQ==
"@budibase/backend-core@1.2.41-alpha.0":
version "1.2.41-alpha.0"
resolved "https://registry.yarnpkg.com/@budibase/backend-core/-/backend-core-1.2.41-alpha.0.tgz#ee3bc032ae89e347b9fb220303a9d040df6f5823"
integrity sha512-x6wJpBBFj6a1pGu2OR1vBPHiA4LvdQpVxr6f1lZa2+nAhuXVtEhrdnvBVzK2hJ52dOTTTGaw2A5tTkbf7861CA==
dependencies:
"@budibase/types" "^1.2.40"
"@budibase/types" "1.2.41-alpha.0"
"@techpass/passport-openidconnect" "0.3.2"
aws-sdk "2.1030.0"
bcrypt "5.0.1"
dotenv "16.0.1"
emitter-listener "1.1.2"
ioredis "4.28.0"
joi "17.6.0"
jsonwebtoken "8.5.1"
koa-passport "4.1.4"
lodash "4.17.21"
@ -324,21 +325,21 @@
uuid "8.3.2"
zlib "1.0.5"
"@budibase/pro@1.2.40":
version "1.2.40"
resolved "https://registry.yarnpkg.com/@budibase/pro/-/pro-1.2.40.tgz#007cad4d8341698104498ae7d1578a7e26760dd7"
integrity sha512-lGB0/O8l63FtpP1dOzxowFUjgj7SrRSVsODygPB7oScfUBtbqHyZKC4EafrVpJT4A8hbdh1ijQZ8PLJE/YdRkQ==
"@budibase/pro@1.2.41-alpha.0":
version "1.2.41-alpha.0"
resolved "https://registry.yarnpkg.com/@budibase/pro/-/pro-1.2.41-alpha.0.tgz#4f27dbeee24ddc58e2a4e75cd90b53543f742722"
integrity sha512-LiKuO7/9GGzf3xfMmNTZQl2r4jmGgqnUf7fA5ub/MRyWYM7ZjPWEKW1V9OAk3vXwv9a+4V6FUxJQ1sDUZ3DT/Q==
dependencies:
"@budibase/backend-core" "1.2.40"
"@budibase/types" "1.2.40"
"@budibase/backend-core" "1.2.41-alpha.0"
"@budibase/types" "1.2.41-alpha.0"
"@koa/router" "8.0.8"
joi "17.6.0"
node-fetch "^2.6.1"
"@budibase/types@1.2.40", "@budibase/types@^1.2.40":
version "1.2.40"
resolved "https://registry.yarnpkg.com/@budibase/types/-/types-1.2.40.tgz#c1456728b61966cab7fe3689bd044bf415faa138"
integrity sha512-eNZFG0C/GBog87y6StKBjdF/aDJJtUHbAM8NWLUNNqTAiMxO2s3XhDN1dE6t74NBCRFzJo2sAD+34pJHhKFanQ==
"@budibase/types@1.2.41-alpha.0":
version "1.2.41-alpha.0"
resolved "https://registry.yarnpkg.com/@budibase/types/-/types-1.2.41-alpha.0.tgz#01071ce6ecef6799b04b1defce2b38e81b615f9f"
integrity sha512-2jkeToXsujYUXb6P2DFTFUOFv56GgImJ4webP8xr5cPGL0/xmhWGMd/lLxLt96RtMQp43UmVWZm1BA7TZw4fJg==
"@cspotcode/source-map-consumer@0.8.0":
version "0.8.0"