1
0
Fork 0
mirror of synced 2024-09-17 01:38:40 +12:00

Merge branch 'master' of github.com:Budibase/budibase into global-bindings

This commit is contained in:
Andrew Kingston 2023-11-29 09:09:11 +00:00
commit 8bc0aec2c1
12 changed files with 261 additions and 103 deletions

View file

@ -20,7 +20,7 @@
let focus = false let focus = false
const updateValue = newValue => { const updateValue = newValue => {
if (readonly) { if (readonly || disabled) {
return return
} }
if (type === "number") { if (type === "number") {
@ -31,14 +31,14 @@
} }
const onFocus = () => { const onFocus = () => {
if (readonly) { if (readonly || disabled) {
return return
} }
focus = true focus = true
} }
const onBlur = event => { const onBlur = event => {
if (readonly) { if (readonly || disabled) {
return return
} }
focus = false focus = false
@ -46,14 +46,14 @@
} }
const onInput = event => { const onInput = event => {
if (readonly || !updateOnChange) { if (readonly || !updateOnChange || disabled) {
return return
} }
updateValue(event.target.value) updateValue(event.target.value)
} }
const updateValueOnEnter = event => { const updateValueOnEnter = event => {
if (readonly) { if (readonly || disabled) {
return return
} }
if (event.key === "Enter") { if (event.key === "Enter") {
@ -69,6 +69,7 @@
} }
onMount(() => { onMount(() => {
if (disabled) return
focus = autofocus focus = autofocus
if (focus) field.focus() if (focus) field.focus()
}) })
@ -108,4 +109,16 @@
.spectrum-Textfield { .spectrum-Textfield {
width: 100%; width: 100%;
} }
input::placeholder {
color: var(--grey-7);
}
input:hover::placeholder {
color: var(--grey-7) !important;
}
input:focus::placeholder {
color: var(--grey-7) !important;
}
</style> </style>

View file

@ -1,4 +1,5 @@
import { store } from "./index" import { store } from "./index"
import { get } from "svelte/store"
import { Helpers } from "@budibase/bbui" import { Helpers } from "@budibase/bbui"
import { import {
decodeJSBinding, decodeJSBinding,
@ -245,6 +246,10 @@ export const makeComponentUnique = component => {
} }
export const getComponentText = component => { export const getComponentText = component => {
if (component == null) {
return ""
}
if (component?._instanceName) { if (component?._instanceName) {
return component._instanceName return component._instanceName
} }
@ -253,3 +258,16 @@ export const getComponentText = component => {
"component" "component"
return capitalise(type) return capitalise(type)
} }
export const getComponentName = component => {
if (component == null) {
return ""
}
const components = get(store)?.components || {}
const componentDefinition = components[component._component] || {}
const name =
componentDefinition.friendlyName || componentDefinition.name || ""
return name
}

View file

@ -4,7 +4,7 @@ import { getTemporalStore } from "./store/temporal"
import { getThemeStore } from "./store/theme" import { getThemeStore } from "./store/theme"
import { getUserStore } from "./store/users" import { getUserStore } from "./store/users"
import { getDeploymentStore } from "./store/deployments" import { getDeploymentStore } from "./store/deployments"
import { derived, writable, get } from "svelte/store" import { derived, get } from "svelte/store"
import { findComponent, findComponentPath } from "./componentUtils" import { findComponent, findComponentPath } from "./componentUtils"
import { RoleUtils } from "@budibase/frontend-core" import { RoleUtils } from "@budibase/frontend-core"
import { createHistoryStore } from "builderStore/store/history" import { createHistoryStore } from "builderStore/store/history"
@ -146,5 +146,3 @@ export const userSelectedResourceMap = derived(userStore, $userStore => {
export const isOnlyUser = derived(userStore, $userStore => { export const isOnlyUser = derived(userStore, $userStore => {
return $userStore.length < 2 return $userStore.length < 2
}) })
export const screensHeight = writable("210px")

View file

@ -1,10 +1,11 @@
<script> <script>
import { Icon } from "@budibase/bbui" import { AbsTooltip, Icon } from "@budibase/bbui"
import { createEventDispatcher, getContext } from "svelte" import { createEventDispatcher, getContext } from "svelte"
import { helpers } from "@budibase/shared-core" import { helpers } from "@budibase/shared-core"
import { UserAvatars } from "@budibase/frontend-core" import { UserAvatars } from "@budibase/frontend-core"
export let icon export let icon
export let iconTooltip
export let withArrow = false export let withArrow = false
export let withActions = true export let withActions = true
export let indentLevel = 0 export let indentLevel = 0
@ -77,7 +78,11 @@
{style} {style}
{draggable} {draggable}
> >
<div class="nav-item-content" bind:this={contentRef}> <div
class="nav-item-content"
bind:this={contentRef}
class:right={rightAlignIcon}
>
{#if withArrow} {#if withArrow}
<div <div
class:opened class:opened
@ -98,7 +103,9 @@
</div> </div>
{:else if icon} {:else if icon}
<div class="icon" class:right={rightAlignIcon}> <div class="icon" class:right={rightAlignIcon}>
<Icon color={iconColor} size="S" name={icon} /> <AbsTooltip type="info" position="right" text={iconTooltip}>
<Icon color={iconColor} size="S" name={icon} />
</AbsTooltip>
</div> </div>
{/if} {/if}
<div class="text" title={showTooltip ? text : null}> <div class="text" title={showTooltip ? text : null}>
@ -166,6 +173,11 @@
width: max-content; width: max-content;
position: relative; position: relative;
padding-left: var(--spacing-l); padding-left: var(--spacing-l);
box-sizing: border-box;
}
.nav-item-content.right {
width: 100%;
} }
/* Needed to fully display the actions icon */ /* Needed to fully display the actions icon */
@ -264,6 +276,7 @@
} }
.right { .right {
margin-left: auto;
order: 10; order: 10;
} }
</style> </style>

View file

@ -0,0 +1,119 @@
const getResizeActions = (
cssProperty,
mouseMoveEventProperty,
elementProperty,
initialValue,
setValue = () => {}
) => {
let element = null
const elementAction = node => {
element = node
if (initialValue != null) {
element.style[cssProperty] = `${initialValue}px`
}
return {
destroy() {
element = null
},
}
}
const dragHandleAction = node => {
let startProperty = null
let startPosition = null
const handleMouseMove = e => {
e.preventDefault() // Prevent highlighting while dragging
const change = e[mouseMoveEventProperty] - startPosition
element.style[cssProperty] = `${startProperty + change}px`
}
const handleMouseUp = e => {
e.preventDefault() // Prevent highlighting while dragging
window.removeEventListener("mousemove", handleMouseMove)
window.removeEventListener("mouseup", handleMouseUp)
element.style.removeProperty("transition") // remove temporary transition override
for (let item of document.getElementsByTagName("iframe")) {
item.style.removeProperty("pointer-events")
}
setValue(element[elementProperty])
}
const handleMouseDown = e => {
if (e.detail > 1) {
// e.detail is the number of rapid clicks, so e.detail = 2 is
// a double click. We want to prevent default behaviour in
// this case as it highlights nearby selectable elements, which
// then interferes with the resizing mousemove.
// Putting this on the double click handler doesn't seem to
// work, so it must go here.
e.preventDefault()
}
if (
e.target.hasAttribute("disabled") &&
e.target.getAttribute("disabled") !== "false"
) {
return
}
element.style.transition = `${cssProperty} 0ms` // temporarily override any height transitions
// iframes swallow mouseup events if your cursor ends up over it during a drag, so make them
// temporarily non-interactive
for (let item of document.getElementsByTagName("iframe")) {
item.style.pointerEvents = "none"
}
startProperty = element[elementProperty]
startPosition = e[mouseMoveEventProperty]
window.addEventListener("mousemove", handleMouseMove)
window.addEventListener("mouseup", handleMouseUp)
}
const handleDoubleClick = () => {
element.style.removeProperty(cssProperty)
}
node.addEventListener("mousedown", handleMouseDown)
node.addEventListener("dblclick", handleDoubleClick)
return {
destroy() {
node.removeEventListener("mousedown", handleMouseDown)
node.removeEventListener("dblclick", handleDoubleClick)
},
}
}
return [elementAction, dragHandleAction]
}
export const getVerticalResizeActions = (initialValue, setValue = () => {}) => {
return getResizeActions(
"height",
"pageY",
"clientHeight",
initialValue,
setValue
)
}
export const getHorizontalResizeActions = (
initialValue,
setValue = () => {}
) => {
return getResizeActions(
"width",
"pageX",
"clientWidth",
initialValue,
setValue
)
}

View file

@ -1,8 +1,9 @@
<script> <script>
import { Icon, Body } from "@budibase/bbui" import { AbsTooltip, Icon, Body } from "@budibase/bbui"
export let title export let title
export let icon export let icon
export let iconTooltip
export let showAddButton = false export let showAddButton = false
export let showBackButton = false export let showBackButton = false
export let showCloseButton = false export let showCloseButton = false
@ -36,7 +37,9 @@
<Icon name="ArrowLeft" hoverable on:click={onClickBackButton} /> <Icon name="ArrowLeft" hoverable on:click={onClickBackButton} />
{/if} {/if}
{#if icon} {#if icon}
<Icon name={icon} /> <AbsTooltip type="info" text={iconTooltip}>
<Icon name={icon} />
</AbsTooltip>
{/if} {/if}
<div class="title"> <div class="title">
{#if customTitleContent} {#if customTitleContent}
@ -68,6 +71,7 @@
<style> <style>
.panel { .panel {
min-width: 260px;
width: 260px; width: 260px;
flex: 0 0 260px; flex: 0 0 260px;
background: var(--background); background: var(--background);
@ -85,6 +89,7 @@
border-right: var(--border-light); border-right: var(--border-light);
} }
.panel.wide { .panel.wide {
min-width: 310px;
width: 310px; width: 310px;
flex: 0 0 310px; flex: 0 0 310px;
} }

View file

@ -1,7 +1,7 @@
<script> <script>
import Panel from "components/design/Panel.svelte" import Panel from "components/design/Panel.svelte"
import { store, selectedComponent, selectedScreen } from "builderStore" import { store, selectedComponent, selectedScreen } from "builderStore"
import { getComponentText } from "builderStore/componentUtils" import { getComponentName } from "builderStore/componentUtils"
import ComponentSettingsSection from "./ComponentSettingsSection.svelte" import ComponentSettingsSection from "./ComponentSettingsSection.svelte"
import DesignSection from "./DesignSection.svelte" import DesignSection from "./DesignSection.svelte"
import CustomStylesSection from "./CustomStylesSection.svelte" import CustomStylesSection from "./CustomStylesSection.svelte"
@ -43,17 +43,25 @@
$: id = $selectedComponent?._id $: id = $selectedComponent?._id
$: id, (section = tabs[0]) $: id, (section = tabs[0])
$: componentName = getComponentName(componentInstance)
</script> </script>
{#if $selectedComponent} {#if $selectedComponent}
{#key $selectedComponent._id} {#key $selectedComponent._id}
<Panel {title} icon={componentDefinition?.icon} borderLeft wide> <Panel
{title}
icon={componentDefinition?.icon}
iconTooltip={componentName}
borderLeft
wide
>
<span class="panel-title-content" slot="panel-title-content"> <span class="panel-title-content" slot="panel-title-content">
<input <input
class="input" class="input"
value={title} value={title}
{title} {title}
placeholder={getComponentText(componentInstance)} placeholder={componentName}
on:keypress={e => { on:keypress={e => {
if (e.key.toLowerCase() === "enter") { if (e.key.toLowerCase() === "enter") {
e.target.blur() e.target.blur()

View file

@ -25,6 +25,7 @@
<style> <style>
.app-panel { .app-panel {
min-width: 410px;
flex: 1 1 auto; flex: 1 1 auto;
overflow-y: auto; overflow-y: auto;
display: flex; display: flex;

View file

@ -12,6 +12,7 @@
import { import {
findComponentPath, findComponentPath,
getComponentText, getComponentText,
getComponentName,
} from "builderStore/componentUtils" } from "builderStore/componentUtils"
import { get } from "svelte/store" import { get } from "svelte/store"
import { dndStore } from "./dndStore" import { dndStore } from "./dndStore"
@ -110,6 +111,7 @@
on:drop={onDrop} on:drop={onDrop}
text={getComponentText(component)} text={getComponentText(component)}
icon={getComponentIcon(component)} icon={getComponentIcon(component)}
iconTooltip={getComponentName(component)}
withArrow={componentHasChildren(component)} withArrow={componentHasChildren(component)}
indentLevel={level} indentLevel={level}
selected={$store.selectedComponentId === component._id} selected={$store.selectedComponentId === component._id}

View file

@ -1,21 +1,55 @@
<script> <script>
import ScreenList from "./ScreenList/index.svelte" import ScreenList from "./ScreenList/index.svelte"
import ComponentList from "./ComponentList/index.svelte" import ComponentList from "./ComponentList/index.svelte"
import { getHorizontalResizeActions } from "components/common/resizable"
const [resizable, resizableHandle] = getHorizontalResizeActions()
</script> </script>
<div class="panel"> <div class="panel" use:resizable>
<ScreenList /> <div class="content">
<ComponentList /> <ScreenList />
<ComponentList />
</div>
<div class="divider">
<div class="dividerClickExtender" role="separator" use:resizableHandle />
</div>
</div> </div>
<style> <style>
.panel { .panel {
display: flex;
min-width: 270px;
width: 310px; width: 310px;
height: 100%; height: 100%;
border-right: var(--border-light); }
.content {
overflow: hidden;
flex-grow: 1;
height: 100%;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
background: var(--background); background: var(--background);
position: relative; position: relative;
} }
.divider {
position: relative;
height: 100%;
width: 2px;
background: var(--spectrum-global-color-gray-200);
transition: background 130ms ease-out;
}
.divider:hover {
background: var(--spectrum-global-color-gray-300);
cursor: row-resize;
}
.dividerClickExtender {
position: absolute;
cursor: col-resize;
height: 100%;
width: 12px;
}
</style> </style>

View file

@ -1,108 +1,50 @@
<script> <script>
import { Layout } from "@budibase/bbui" import { Layout } from "@budibase/bbui"
import { import { store, sortedScreens, userSelectedResourceMap } from "builderStore"
store,
sortedScreens,
userSelectedResourceMap,
screensHeight,
} from "builderStore"
import NavItem from "components/common/NavItem.svelte" import NavItem from "components/common/NavItem.svelte"
import RoleIndicator from "./RoleIndicator.svelte" import RoleIndicator from "./RoleIndicator.svelte"
import DropdownMenu from "./DropdownMenu.svelte" import DropdownMenu from "./DropdownMenu.svelte"
import { onMount } from "svelte"
import { goto } from "@roxi/routify" import { goto } from "@roxi/routify"
import { getVerticalResizeActions } from "components/common/resizable"
import NavHeader from "components/common/NavHeader.svelte" import NavHeader from "components/common/NavHeader.svelte"
let search = false const [resizable, resizableHandle] = getVerticalResizeActions()
let resizing = false
let searchValue = ""
let container let searching = false
let searchValue = ""
let screensContainer let screensContainer
let scrolling = false let scrolling = false
let previousHeight = null
let dragOffset
$: filteredScreens = getFilteredScreens($sortedScreens, searchValue) $: filteredScreens = getFilteredScreens($sortedScreens, searchValue)
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms)) const handleOpenSearch = async () => {
$: search ? openSearch() : closeSearch()
const openSearch = async () => {
screensContainer.scroll({ top: 0, behavior: "smooth" }) screensContainer.scroll({ top: 0, behavior: "smooth" })
previousHeight = $screensHeight
$screensHeight = "calc(100% + 1px)"
} }
const closeSearch = async () => { $: {
if (previousHeight) { if (searching) {
// Restore previous height and wait for animation handleOpenSearch()
$screensHeight = previousHeight
previousHeight = null
await sleep(300)
} }
} }
const getFilteredScreens = (screens, search) => { const getFilteredScreens = (screens, searchValue) => {
return screens.filter(screen => { return screens.filter(screen => {
return !search || screen.routing.route.includes(search) return !searchValue || screen.routing.route.includes(searchValue)
}) })
} }
const handleScroll = e => { const handleScroll = e => {
scrolling = e.target.scrollTop !== 0 scrolling = e.target.scrollTop !== 0
} }
const startResizing = e => {
// Reset the height store to match the true height
$screensHeight = `${container.getBoundingClientRect().height}px`
// Store an offset to easily compute new height when moving the mouse
dragOffset = parseInt($screensHeight) - e.clientY
// Add event listeners
resizing = true
document.addEventListener("mousemove", resize)
document.addEventListener("mouseup", stopResizing)
}
const resize = e => {
// Prevent negative heights as this screws with layout
const newHeight = Math.max(0, e.clientY + dragOffset)
if (newHeight == null || isNaN(newHeight)) {
return
}
$screensHeight = `${newHeight}px`
}
const stopResizing = () => {
resizing = false
document.removeEventListener("mousemove", resize)
}
onMount(() => {
// Ensure we aren't stuck at 100% height from leaving while searching
if ($screensHeight == null || isNaN(parseInt($screensHeight))) {
$screensHeight = "210px"
}
})
</script> </script>
<svelte:window /> <div class="screens" class:searching use:resizable>
<div
class="screens"
class:search
class:resizing
style={`height:${$screensHeight};`}
bind:this={container}
>
<div class="header" class:scrolling> <div class="header" class:scrolling>
<NavHeader <NavHeader
title="Screens" title="Screens"
placeholder="Search for screens" placeholder="Search for screens"
bind:value={searchValue} bind:value={searchValue}
bind:search bind:search={searching}
onAdd={() => $goto("../new")} onAdd={() => $goto("../new")}
/> />
</div> </div>
@ -110,6 +52,7 @@
{#if filteredScreens?.length} {#if filteredScreens?.length}
{#each filteredScreens as screen (screen._id)} {#each filteredScreens as screen (screen._id)}
<NavItem <NavItem
scrollable
icon={screen.routing.homeScreen ? "Home" : null} icon={screen.routing.homeScreen ? "Home" : null}
indentLevel={0} indentLevel={0}
selected={$store.selectedScreenId === screen._id} selected={$store.selectedScreenId === screen._id}
@ -135,9 +78,11 @@
</div> </div>
<div <div
role="separator"
disabled={searching}
class="divider" class="divider"
on:mousedown={startResizing} class:disabled={searching}
on:dblclick={() => screensHeight.set("210px")} use:resizableHandle
/> />
</div> </div>
@ -148,14 +93,12 @@
min-height: 147px; min-height: 147px;
max-height: calc(100% - 147px); max-height: calc(100% - 147px);
position: relative; position: relative;
transition: height 300ms ease-out; transition: height 300ms ease-out, max-height 300ms ease-out;
height: 210px;
} }
.screens.search { .screens.searching {
max-height: none; max-height: 100%;
} height: 100% !important;
.screens.resizing {
user-select: none;
cursor: row-resize;
} }
.header { .header {
@ -177,9 +120,6 @@
overflow: auto; overflow: auto;
flex-grow: 1; flex-grow: 1;
} }
.screens.resizing .content {
pointer-events: none;
}
.screens :global(.nav-item) { .screens :global(.nav-item) {
padding-right: 8px !important; padding-right: 8px !important;
@ -217,4 +157,10 @@
.divider:hover:after { .divider:hover:after {
background: var(--spectrum-global-color-gray-300); background: var(--spectrum-global-color-gray-300);
} }
.divider.disabled {
cursor: auto;
}
.divider.disabled:after {
background: var(--spectrum-global-color-gray-200);
}
</style> </style>

View file

@ -40,6 +40,7 @@
} }
.content { .content {
width: 100vw;
display: flex; display: flex;
flex-direction: row; flex-direction: row;
justify-content: flex-start; justify-content: flex-start;