1
0
Fork 0
mirror of synced 2024-08-23 05:51:29 +12:00

Merge branch 'master' into fix/remove-formula-column-subtype-check

This commit is contained in:
deanhannigan 2024-05-21 10:16:58 +01:00 committed by GitHub
commit 2494166cbc
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
162 changed files with 4724 additions and 1288 deletions

View file

@ -71,8 +71,8 @@ const handleMouseDown = e => {
// Clear any previous listeners in case of multiple down events, and register
// a single mouse up listener
document.removeEventListener("mouseup", handleMouseUp)
document.addEventListener("mouseup", handleMouseUp, true)
document.removeEventListener("click", handleMouseUp)
document.addEventListener("click", handleMouseUp, true)
}
// Global singleton listeners for our events

View file

@ -17,6 +17,8 @@
export let customPopoverHeight
export let open = false
export let loading
export let onOptionMouseenter = () => {}
export let onOptionMouseleave = () => {}
const dispatch = createEventDispatcher()
@ -97,4 +99,6 @@
{autoWidth}
{customPopoverHeight}
{loading}
{onOptionMouseenter}
{onOptionMouseleave}
/>

View file

@ -41,6 +41,8 @@
export let footer = null
export let customAnchor = null
export let loading
export let onOptionMouseenter = () => {}
export let onOptionMouseleave = () => {}
const dispatch = createEventDispatcher()
@ -199,6 +201,8 @@
aria-selected="true"
tabindex="0"
on:click={() => onSelectOption(getOptionValue(option, idx))}
on:mouseenter={e => onOptionMouseenter(e, option)}
on:mouseleave={e => onOptionMouseleave(e, option)}
class:is-disabled={!isOptionEnabled(option)}
>
{#if getOptionIcon(option, idx)}

View file

@ -26,6 +26,8 @@
export let tag = null
export let searchTerm = null
export let loading
export let onOptionMouseenter = () => {}
export let onOptionMouseleave = () => {}
const dispatch = createEventDispatcher()
@ -95,6 +97,8 @@
{autocomplete}
{sort}
{tag}
{onOptionMouseenter}
{onOptionMouseleave}
isPlaceholder={value == null || value === ""}
placeholderOption={placeholder === false ? null : placeholder}
isOptionSelected={option => compareOptionAndValue(option, value)}

View file

@ -0,0 +1,267 @@
<script>
import { onMount, createEventDispatcher } from "svelte"
import Atrament from "atrament"
import Icon from "../../Icon/Icon.svelte"
const dispatch = createEventDispatcher()
let last
export let value
export let disabled = false
export let editable = true
export let width = 400
export let height = 220
export let saveIcon = false
export let darkMode
export function toDataUrl() {
// PNG to preserve transparency
return canvasRef.toDataURL("image/png")
}
export function toFile() {
const data = canvasContext
.getImageData(0, 0, width, height)
.data.some(channel => channel !== 0)
if (!data) {
return
}
let dataURIParts = toDataUrl().split(",")
if (!dataURIParts.length) {
console.error("Could not retrieve signature data")
}
// Pull out the base64 encoded byte data
let binaryVal = atob(dataURIParts[1])
let blobArray = new Uint8Array(binaryVal.length)
let pos = 0
while (pos < binaryVal.length) {
blobArray[pos] = binaryVal.charCodeAt(pos)
pos++
}
const signatureBlob = new Blob([blobArray], {
type: "image/png",
})
return new File([signatureBlob], "signature.png", {
type: signatureBlob.type,
})
}
export function clearCanvas() {
return canvasContext.clearRect(0, 0, canvasWidth, canvasHeight)
}
let canvasRef
let canvasContext
let canvasWrap
let canvasWidth
let canvasHeight
let signature
let updated = false
let signatureFile
let urlFailed
$: if (value) {
signatureFile = value
}
$: if (signatureFile?.url) {
updated = false
}
$: if (last) {
dispatch("update")
}
onMount(() => {
if (!editable) {
return
}
const getPos = e => {
var rect = canvasRef.getBoundingClientRect()
const canvasX = e.offsetX || e.targetTouches?.[0].pageX - rect.left
const canvasY = e.offsetY || e.targetTouches?.[0].pageY - rect.top
return { x: canvasX, y: canvasY }
}
const checkUp = e => {
last = getPos(e)
}
canvasRef.addEventListener("pointerdown", e => {
const current = getPos(e)
//If the cursor didn't move at all, block the default pointerdown
if (last?.x === current?.x && last?.y === current?.y) {
e.preventDefault()
e.stopImmediatePropagation()
}
})
document.addEventListener("pointerup", checkUp)
signature = new Atrament(canvasRef, {
width,
height,
color: "white",
})
signature.weight = 4
signature.smoothing = 2
canvasWrap.style.width = `${width}px`
canvasWrap.style.height = `${height}px`
const { width: wrapWidth, height: wrapHeight } =
canvasWrap.getBoundingClientRect()
canvasHeight = wrapHeight
canvasWidth = wrapWidth
canvasContext = canvasRef.getContext("2d")
return () => {
signature.destroy()
document.removeEventListener("pointerup", checkUp)
}
})
</script>
<div class="signature" class:light={!darkMode} class:image-error={urlFailed}>
{#if !disabled}
<div class="overlay">
{#if updated && saveIcon}
<span class="save">
<Icon
name="Checkmark"
hoverable
tooltip={"Save"}
tooltipPosition={"top"}
tooltipType={"info"}
on:click={() => {
dispatch("change", toDataUrl())
}}
/>
</span>
{/if}
{#if signatureFile?.url && !updated}
<span class="delete">
<Icon
name="DeleteOutline"
hoverable
tooltip={"Delete"}
tooltipPosition={"top"}
tooltipType={"info"}
on:click={() => {
if (editable) {
clearCanvas()
}
dispatch("clear")
}}
/>
</span>
{/if}
</div>
{/if}
{#if !editable && signatureFile?.url}
<!-- svelte-ignore a11y-missing-attribute -->
{#if !urlFailed}
<img
src={signatureFile?.url}
on:error={() => {
urlFailed = true
}}
/>
{:else}
Could not load signature
{/if}
{:else}
<div bind:this={canvasWrap} class="canvas-wrap">
<canvas
id="signature-canvas"
bind:this={canvasRef}
style="--max-sig-width: {width}px; --max-sig-height: {height}px"
/>
{#if editable}
<div class="indicator-overlay">
<div class="sign-here">
<Icon name="Close" />
<hr />
</div>
</div>
{/if}
</div>
{/if}
</div>
<style>
.indicator-overlay {
position: absolute;
width: 100%;
height: 100%;
top: 0px;
display: flex;
flex-direction: column;
justify-content: end;
padding: var(--spectrum-global-dimension-size-150);
box-sizing: border-box;
pointer-events: none;
}
.sign-here {
display: flex;
align-items: center;
justify-content: center;
gap: var(--spectrum-global-dimension-size-150);
}
.sign-here hr {
border: 0;
border-top: 2px solid var(--spectrum-global-color-gray-200);
width: 100%;
}
.canvas-wrap {
position: relative;
margin: auto;
}
.signature img {
max-width: 100%;
}
#signature-canvas {
max-width: var(--max-sig-width);
max-height: var(--max-sig-height);
}
.signature.light img,
.signature.light #signature-canvas {
-webkit-filter: invert(100%);
filter: invert(100%);
}
.signature.image-error .overlay {
padding-top: 0px;
}
.signature {
width: 100%;
height: 100%;
position: relative;
text-align: center;
}
.overlay {
position: absolute;
width: 100%;
height: 100%;
pointer-events: none;
padding: var(--spectrum-global-dimension-size-150);
text-align: right;
z-index: 2;
box-sizing: border-box;
}
.save,
.delete {
display: inline-block;
}
</style>

View file

@ -16,3 +16,4 @@ export { default as CoreStepper } from "./Stepper.svelte"
export { default as CoreRichTextField } from "./RichTextField.svelte"
export { default as CoreSlider } from "./Slider.svelte"
export { default as CoreFile } from "./File.svelte"
export { default as CoreSignature } from "./Signature.svelte"

View file

@ -19,6 +19,8 @@
export let searchTerm = null
export let customPopoverHeight
export let helpText = null
export let onOptionMouseenter = () => {}
export let onOptionMouseleave = () => {}
const dispatch = createEventDispatcher()
const onChange = e => {
@ -41,6 +43,8 @@
{autoWidth}
{autocomplete}
{customPopoverHeight}
{onOptionMouseenter}
{onOptionMouseleave}
bind:searchTerm
on:change={onChange}
on:click

View file

@ -29,6 +29,9 @@
export let tag = null
export let helpText = null
export let compare
export let onOptionMouseenter = () => {}
export let onOptionMouseleave = () => {}
const dispatch = createEventDispatcher()
const onChange = e => {
value = e.detail
@ -67,6 +70,8 @@
{customPopoverHeight}
{tag}
{compare}
{onOptionMouseenter}
{onOptionMouseleave}
on:change={onChange}
on:click
/>

View file

@ -173,6 +173,7 @@
}
.spectrum-Modal {
border: 2px solid var(--spectrum-global-color-gray-200);
overflow: visible;
max-height: none;
margin: 40px 0;

View file

@ -27,6 +27,7 @@
export let secondaryButtonText = undefined
export let secondaryAction = undefined
export let secondaryButtonWarning = false
export let custom = false
const { hide, cancel } = getContext(Context.Modal)
let loading = false
@ -63,12 +64,13 @@
class:spectrum-Dialog--medium={size === "M"}
class:spectrum-Dialog--large={size === "L"}
class:spectrum-Dialog--extraLarge={size === "XL"}
class:no-grid={custom}
style="position: relative;"
role="dialog"
tabindex="-1"
aria-modal="true"
>
<div class="spectrum-Dialog-grid">
<div class="modal-core" class:spectrum-Dialog-grid={!custom}>
{#if title || $$slots.header}
<h1
class="spectrum-Dialog-heading spectrum-Dialog-heading--noHeader"
@ -153,6 +155,25 @@
.spectrum-Dialog-content {
overflow: visible;
}
.no-grid .spectrum-Dialog-content {
border-top: 2px solid var(--spectrum-global-color-gray-200);
border-bottom: 2px solid var(--spectrum-global-color-gray-200);
}
.no-grid .spectrum-Dialog-heading {
margin-top: 12px;
margin-left: 12px;
}
.spectrum-Dialog.no-grid {
width: 100%;
}
.spectrum-Dialog.no-grid .spectrum-Dialog-buttonGroup {
padding: 12px;
}
.spectrum-Dialog-heading {
font-family: var(--font-accent);
font-weight: 600;

View file

@ -1,40 +1,28 @@
<script>
import { getContext, onMount, createEventDispatcher } from "svelte"
import { getContext, onDestroy } from "svelte"
import Portal from "svelte-portal"
export let title
export let icon = ""
export let id
const dispatch = createEventDispatcher()
let selected = getContext("tab")
let tab_internal
let tabInfo
let observer
let ref
const setTabInfo = () => {
// If the tabs are being rendered inside a component which uses
// a svelte transition to enter, then this initial getBoundingClientRect
// will return an incorrect position.
// We just need to get this off the main thread to fix this, by using
// a 0ms timeout.
setTimeout(() => {
tabInfo = tab_internal?.getBoundingClientRect()
if (tabInfo && $selected.title === title) {
$selected.info = tabInfo
}
}, 0)
$: isSelected = $selected.title === title
$: {
if (isSelected && ref) {
observe()
} else {
stopObserving()
}
}
onMount(() => {
setTabInfo()
})
//Ensure that the underline is in the correct location
$: {
if ($selected.title === title && tab_internal) {
if ($selected.info?.left !== tab_internal.getBoundingClientRect().left) {
setTabInfo()
}
const setTabInfo = () => {
const tabInfo = ref?.getBoundingClientRect()
if (tabInfo) {
$selected.info = tabInfo
}
}
@ -42,10 +30,25 @@
$selected = {
...$selected,
title,
info: tab_internal.getBoundingClientRect(),
info: ref.getBoundingClientRect(),
}
dispatch("click")
}
const observe = () => {
if (!observer) {
observer = new ResizeObserver(setTabInfo)
observer.observe(ref)
}
}
const stopObserving = () => {
if (observer) {
observer.unobserve(ref)
observer = null
}
}
onDestroy(stopObserving)
</script>
<!-- svelte-ignore a11y-no-static-element-interactions -->
@ -53,11 +56,12 @@
<!-- svelte-ignore a11y-no-noninteractive-tabindex -->
<div
{id}
bind:this={tab_internal}
bind:this={ref}
on:click={onClick}
class:is-selected={$selected.title === title}
on:click
class="spectrum-Tabs-item"
class:emphasized={$selected.title === title && $selected.emphasized}
class:is-selected={isSelected}
class:emphasized={isSelected && $selected.emphasized}
tabindex="0"
>
{#if icon}
@ -72,7 +76,8 @@
{/if}
<span class="spectrum-Tabs-itemLabel">{title}</span>
</div>
{#if $selected.title === title}
{#if isSelected}
<Portal target=".spectrum-Tabs-content-{$selected.id}">
<slot />
</Portal>

View file

@ -0,0 +1,79 @@
<script>
import Portal from "svelte-portal"
import { getContext } from "svelte"
import Context from "../context"
export let anchor
export let visible = false
export let offset = 0
$: target = getContext(Context.PopoverRoot) || "#app"
let hovering = false
let tooltip
let x = 0
let y = 0
const updatePosition = (anchor, tooltip) => {
if (anchor == null || tooltip == null) {
return
}
requestAnimationFrame(() => {
const rect = anchor.getBoundingClientRect()
const windowOffset =
window.innerHeight - offset - (tooltip.clientHeight + rect.y)
const tooltipWidth = tooltip.clientWidth
x = rect.x - tooltipWidth - offset
y = windowOffset < 0 ? rect.y + windowOffset : rect.y
})
}
$: updatePosition(anchor, tooltip)
const handleMouseenter = () => {
hovering = true
}
const handleMouseleave = () => {
hovering = false
}
</script>
<Portal {target}>
<div
role="tooltip"
on:mouseenter={handleMouseenter}
on:mouseleave={handleMouseleave}
style:left={`${x}px`}
style:top={`${y}px`}
class="wrapper"
class:visible={visible || hovering}
>
<div bind:this={tooltip} class="tooltip">
<slot />
</div>
</div>
</Portal>
<style>
.wrapper {
background-color: var(--spectrum-global-color-gray-100);
box-shadow: 2px 2px 5px 0px rgba(0, 0, 0, 0.42);
opacity: 0;
overflow: hidden;
border-radius: 5px;
box-sizing: border-box;
border: 1px solid var(--grey-4);
position: absolute;
pointer-events: none;
z-index: 1000;
}
.visible {
opacity: 1;
pointer-events: auto;
}
</style>

View file

@ -53,6 +53,7 @@ export { default as Link } from "./Link/Link.svelte"
export { default as Tooltip } from "./Tooltip/Tooltip.svelte"
export { default as TempTooltip } from "./Tooltip/TempTooltip.svelte"
export { default as TooltipWrapper } from "./Tooltip/TooltipWrapper.svelte"
export { default as ContextTooltip } from "./Tooltip/Context.svelte"
export { default as Menu } from "./Menu/Menu.svelte"
export { default as MenuSection } from "./Menu/Section.svelte"
export { default as MenuSeparator } from "./Menu/Separator.svelte"

View file

@ -9,7 +9,7 @@
import TestDataModal from "./TestDataModal.svelte"
import { flip } from "svelte/animate"
import { fly } from "svelte/transition"
import { Icon, notifications, Modal } from "@budibase/bbui"
import { Icon, notifications, Modal, Toggle } from "@budibase/bbui"
import { ActionStepID } from "constants/backend/automations"
import UndoRedoControl from "components/common/UndoRedoControl.svelte"
@ -73,6 +73,16 @@
Test details
</div>
</div>
<div class="setting-spacing">
<Toggle
text={automation.disabled ? "Paused" : "Activated"}
on:change={automationStore.actions.toggleDisabled(
automation._id,
automation.disabled
)}
value={!automation.disabled}
/>
</div>
</div>
</div>
<div class="canvas" on:scroll={handleScroll}>

View file

@ -61,6 +61,7 @@
selected={automation._id === selectedAutomationId}
on:click={() => selectAutomation(automation._id)}
selectedBy={$userSelectedResourceMap[automation._id]}
disabled={automation.disabled}
>
<EditAutomationPopover {automation} />
</NavItem>

View file

@ -39,6 +39,15 @@
>Duplicate</MenuItem
>
<MenuItem icon="Edit" on:click={updateAutomationDialog.show}>Edit</MenuItem>
<MenuItem
icon={automation.disabled ? "CheckmarkCircle" : "Cancel"}
on:click={automationStore.actions.toggleDisabled(
automation._id,
automation.disabled
)}
>
{automation.disabled ? "Activate" : "Pause"}
</MenuItem>
<MenuItem icon="Delete" on:click={confirmDeleteDialog.show}>Delete</MenuItem>
</ActionMenu>

View file

@ -364,6 +364,7 @@
value.customType !== "cron" &&
value.customType !== "triggerSchema" &&
value.customType !== "automationFields" &&
value.type !== "signature_single" &&
value.type !== "attachment" &&
value.type !== "attachment_single"
)
@ -456,7 +457,7 @@
value={inputData[key]}
options={Object.keys(table?.schema || {})}
/>
{:else if value.type === "attachment"}
{:else if value.type === "attachment" || value.type === "signature_single"}
<div class="attachment-field-wrapper">
<div class="label-wrapper">
<Label>{label}</Label>

View file

@ -24,6 +24,11 @@
let table
let schemaFields
let attachmentTypes = [
FieldType.ATTACHMENTS,
FieldType.ATTACHMENT_SINGLE,
FieldType.SIGNATURE_SINGLE,
]
$: {
table = $tables.list.find(table => table._id === value?.tableId)
@ -120,15 +125,9 @@
{#if schemaFields.length}
{#each schemaFields as [field, schema]}
{#if !schema.autocolumn}
<div
class:schema-fields={schema.type !== FieldType.ATTACHMENTS &&
schema.type !== FieldType.ATTACHMENT_SINGLE}
>
<div class:schema-fields={!attachmentTypes.includes(schema.type)}>
<Label>{field}</Label>
<div
class:field-width={schema.type !== FieldType.ATTACHMENTS &&
schema.type !== FieldType.ATTACHMENT_SINGLE}
>
<div class:field-width={!attachmentTypes.includes(schema.type)}>
{#if isTestModal}
<RowSelectorTypes
{isTestModal}

View file

@ -21,6 +21,12 @@
return clone
})
let attachmentTypes = [
FieldType.ATTACHMENTS,
FieldType.ATTACHMENT_SINGLE,
FieldType.SIGNATURE_SINGLE,
]
function schemaHasOptions(schema) {
return !!schema.constraints?.inclusion?.length
}
@ -29,7 +35,8 @@
let params = {}
if (
schema.type === FieldType.ATTACHMENT_SINGLE &&
(schema.type === FieldType.ATTACHMENT_SINGLE ||
schema.type === FieldType.SIGNATURE_SINGLE) &&
Object.keys(keyValueObj).length === 0
) {
return []
@ -100,16 +107,20 @@
on:change={e => onChange(e, field)}
useLabel={false}
/>
{:else if schema.type === FieldType.ATTACHMENTS || schema.type === FieldType.ATTACHMENT_SINGLE}
{:else if attachmentTypes.includes(schema.type)}
<div class="attachment-field-spacinng">
<KeyValueBuilder
on:change={e =>
onChange(
{
detail:
schema.type === FieldType.ATTACHMENT_SINGLE
schema.type === FieldType.ATTACHMENT_SINGLE ||
schema.type === FieldType.SIGNATURE_SINGLE
? e.detail.length > 0
? { url: e.detail[0].name, filename: e.detail[0].value }
? {
url: e.detail[0].name,
filename: e.detail[0].value,
}
: {}
: e.detail.map(({ name, value }) => ({
url: name,
@ -125,7 +136,8 @@
customButtonText={"Add attachment"}
keyPlaceholder={"URL"}
valuePlaceholder={"Filename"}
actionButtonDisabled={schema.type === FieldType.ATTACHMENT_SINGLE &&
actionButtonDisabled={(schema.type === FieldType.ATTACHMENT_SINGLE ||
schema.type === FieldType.SIGNATURE) &&
Object.keys(value[field]).length >= 1}
/>
</div>

View file

@ -1,4 +1,5 @@
<script>
import { API } from "api"
import {
Input,
Select,
@ -8,11 +9,16 @@
Label,
RichTextField,
TextArea,
CoreSignature,
ActionButton,
notifications,
} from "@budibase/bbui"
import Dropzone from "components/common/Dropzone.svelte"
import { capitalise } from "helpers"
import LinkedRowSelector from "components/common/LinkedRowSelector.svelte"
import Editor from "../../integration/QueryEditor.svelte"
import { SignatureModal } from "@budibase/frontend-core/src/components"
import { themeStore } from "stores/portal"
export let meta
export let value
@ -38,8 +44,35 @@
const timeStamp = resolveTimeStamp(value)
const isTimeStamp = !!timeStamp || meta?.timeOnly
$: currentTheme = $themeStore?.theme
$: darkMode = !currentTheme.includes("light")
let signatureModal
</script>
<SignatureModal
{darkMode}
onConfirm={async sigCanvas => {
const signatureFile = sigCanvas.toFile()
let attachRequest = new FormData()
attachRequest.append("file", signatureFile)
try {
const uploadReq = await API.uploadBuilderAttachment(attachRequest)
const [signatureAttachment] = uploadReq
value = signatureAttachment
} catch (error) {
$notifications.error(error.message || "Failed to save signature")
value = []
}
}}
title={meta.name}
{value}
bind:this={signatureModal}
/>
{#if type === "options" && meta.constraints.inclusion.length !== 0}
<Select
{label}
@ -58,7 +91,51 @@
bind:value
/>
{:else if type === "attachment"}
<Dropzone {label} {error} bind:value />
<Dropzone
compact
{label}
{error}
{value}
on:change={e => {
value = e.detail
}}
/>
{:else if type === "attachment_single"}
<Dropzone
compact
{label}
{error}
value={value ? [value] : []}
on:change={e => {
value = e.detail?.[0]
}}
maximum={1}
/>
{:else if type === "signature_single"}
<div class="signature">
<Label>{label}</Label>
<div class="sig-wrap" class:display={value}>
{#if value}
<CoreSignature
{darkMode}
{value}
editable={false}
on:clear={() => {
value = null
}}
/>
{:else}
<ActionButton
fullWidth
on:click={() => {
signatureModal.show()
}}
>
Add signature
</ActionButton>
{/if}
</div>
</div>
{:else if type === "boolean"}
<Toggle text={label} {error} bind:value />
{:else if type === "array" && meta.constraints.inclusion.length !== 0}
@ -94,3 +171,22 @@
{:else}
<Input {label} {type} {error} bind:value disabled={readonly} />
{/if}
<style>
.signature :global(label.spectrum-FieldLabel) {
padding-top: var(--spectrum-fieldlabel-padding-top);
padding-bottom: var(--spectrum-fieldlabel-padding-bottom);
}
.sig-wrap.display {
min-height: 50px;
justify-content: center;
width: 100%;
display: flex;
flex-direction: column;
background-color: var(--spectrum-global-color-gray-50);
box-sizing: border-box;
border: var(--spectrum-alias-border-size-thin)
var(--spectrum-alias-border-color) solid;
border-radius: var(--spectrum-alias-border-radius-regular);
}
</style>

View file

@ -1,6 +1,6 @@
<script>
import { datasources, tables, integrations, appStore } from "stores/builder"
import { admin } from "stores/portal"
import { themeStore, admin } from "stores/portal"
import EditRolesButton from "./buttons/EditRolesButton.svelte"
import { TableNames } from "constants"
import { Grid } from "@budibase/frontend-core"
@ -38,6 +38,9 @@
})
$: relationshipsEnabled = relationshipSupport(tableDatasource)
$: currentTheme = $themeStore?.theme
$: darkMode = !currentTheme.includes("light")
const relationshipSupport = datasource => {
const integration = $integrations[datasource?.source]
return !isInternal && integration?.relationships !== false
@ -56,6 +59,7 @@
<div class="wrapper">
<Grid
{API}
{darkMode}
datasource={gridDatasource}
canAddRows={!isUsersTable}
canDeleteRows={!isUsersTable}

View file

@ -9,6 +9,7 @@ const MAX_DEPTH = 1
const TYPES_TO_SKIP = [
FieldType.FORMULA,
FieldType.LONGFORM,
FieldType.SIGNATURE_SINGLE,
FieldType.ATTACHMENTS,
//https://github.com/Budibase/budibase/issues/3030
FieldType.INTERNAL,

View file

@ -412,6 +412,7 @@
FIELDS.FORMULA,
FIELDS.JSON,
FIELDS.BARCODEQR,
FIELDS.SIGNATURE_SINGLE,
FIELDS.BIGINT,
FIELDS.AUTO,
]

View file

@ -54,6 +54,10 @@
label: "Attachment",
value: FieldType.ATTACHMENT_SINGLE,
},
{
label: "Signature",
value: FieldType.SIGNATURE_SINGLE,
},
{
label: "Attachment list",
value: FieldType.ATTACHMENTS,

View file

@ -54,6 +54,7 @@
export let autofocus = false
export let jsBindingWrapping = true
export let readonly = false
export let readonlyLineNumbers = false
const dispatch = createEventDispatcher()
@ -240,6 +241,9 @@
if (readonly) {
complete.push(EditorState.readOnly.of(true))
if (readonlyLineNumbers) {
complete.push(lineNumbers())
}
} else {
complete = [
...complete,

View file

@ -25,6 +25,7 @@
export let selectedBy = null
export let compact = false
export let hovering = false
export let disabled = false
const scrollApi = getContext("scroll")
const dispatch = createEventDispatcher()
@ -74,6 +75,7 @@
class:scrollable
class:highlighted
class:selectedBy
class:disabled
on:dragend
on:dragstart
on:dragover
@ -165,6 +167,9 @@
--avatars-background: var(--spectrum-global-color-gray-300);
color: var(--ink);
}
.nav-item.disabled span {
color: var(--spectrum-global-color-gray-700);
}
.nav-item:hover,
.hovering {
background-color: var(--spectrum-global-color-gray-200);

View file

@ -28,6 +28,12 @@
let bindingDrawer
let currentVal = value
let attachmentTypes = [
FieldType.ATTACHMENT_SINGLE,
FieldType.ATTACHMENTS,
FieldType.SIGNATURE_SINGLE,
]
$: readableValue = runtimeToReadableBinding(bindings, value)
$: tempValue = readableValue
$: isJS = isJSBinding(value)
@ -105,6 +111,7 @@
boolean: isValidBoolean,
attachment: false,
attachment_single: false,
signature_single: false,
}
const isValid = value => {
@ -126,6 +133,7 @@
"bigint",
"barcodeqr",
"attachment",
"signature_single",
"attachment_single",
].includes(type)
) {
@ -169,7 +177,7 @@
{updateOnChange}
/>
{/if}
{#if !disabled && type !== "formula" && !disabled && type !== FieldType.ATTACHMENTS && !disabled && type !== FieldType.ATTACHMENT_SINGLE}
{#if !disabled && type !== "formula" && !disabled && !attachmentTypes.includes(type)}
<div
class={`icon ${getIconClass(value, type)}`}
on:click={() => {

View file

@ -76,6 +76,7 @@ const componentMap = {
"field/array": FormFieldSelect,
"field/json": FormFieldSelect,
"field/barcodeqr": FormFieldSelect,
"field/signature_single": FormFieldSelect,
"field/bb_reference": FormFieldSelect,
// Some validation types are the same as others, so not all types are
// explicitly listed here. e.g. options uses string validation
@ -85,6 +86,8 @@ const componentMap = {
"validation/boolean": ValidationEditor,
"validation/datetime": ValidationEditor,
"validation/attachment": ValidationEditor,
"validation/attachment_single": ValidationEditor,
"validation/signature_single": ValidationEditor,
"validation/link": ValidationEditor,
"validation/bb_reference": ValidationEditor,
}

View file

@ -24,7 +24,9 @@
parameters
}
$: automations = $automationStore.automations
.filter(a => a.definition.trigger?.stepId === TriggerStepID.APP)
.filter(
a => a.definition.trigger?.stepId === TriggerStepID.APP && !a.disabled
)
.map(automation => {
const schema = Object.entries(
automation.definition.trigger.inputs.fields || {}

View file

@ -1,5 +1,5 @@
<script>
import EditComponentPopover from "../EditComponentPopover/EditComponentPopover.svelte"
import EditComponentPopover from "../EditComponentPopover.svelte"
import { Icon } from "@budibase/bbui"
import { runtimeToReadableBinding } from "dataBinding"
import { isJSBinding } from "@budibase/string-templates"

View file

@ -100,9 +100,6 @@
on:click={() => {
get(store).actions.select(draggableItem.id)
}}
on:mousedown={() => {
get(store).actions.select()
}}
bind:this={anchors[draggableItem.id]}
class:highlighted={draggableItem.id === $store.selected}
>

View file

@ -3,7 +3,6 @@
import { componentStore } from "stores/builder"
import { cloneDeep } from "lodash/fp"
import { createEventDispatcher, getContext } from "svelte"
import { customPositionHandler } from "."
import ComponentSettingsSection from "pages/builder/app/[application]/design/[screenId]/[componentId]/_components/Component/ComponentSettingsSection.svelte"
export let anchor
@ -18,76 +17,74 @@
let popover
let drawers = []
let open = false
let isOpen = false
// Auto hide the component when another item is selected
$: if (open && $draggable.selected !== componentInstance._id) {
popover.hide()
close()
}
// Open automatically if the component is marked as selected
$: if (!open && $draggable.selected === componentInstance._id && popover) {
popover.show()
open = true
open()
}
$: componentDef = componentStore.getDefinition(componentInstance._component)
$: parsedComponentDef = processComponentDefinitionSettings(componentDef)
const open = () => {
isOpen = true
drawers = []
$draggable.actions.select(componentInstance._id)
}
const close = () => {
// Slight delay allows us to be able to properly toggle open/close state by
// clicking again on the settings icon
setTimeout(() => {
isOpen = false
if ($draggable.selected === componentInstance._id) {
$draggable.actions.select()
}
}, 10)
}
const toggleOpen = () => {
if (isOpen) {
close()
} else {
open()
}
}
const processComponentDefinitionSettings = componentDef => {
if (!componentDef) {
return {}
}
const clone = cloneDeep(componentDef)
if (typeof parseSettings === "function") {
clone.settings = parseSettings(clone.settings)
}
return clone
}
const updateSetting = async (setting, value) => {
const nestedComponentInstance = cloneDeep(componentInstance)
const patchFn = componentStore.updateComponentSetting(setting.key, value)
patchFn(nestedComponentInstance)
dispatch("change", nestedComponentInstance)
}
</script>
<Icon
name="Settings"
hoverable
size="S"
on:click={() => {
if (!open) {
popover.show()
open = true
}
}}
/>
<Icon name="Settings" hoverable size="S" on:click={toggleOpen} />
<Popover
bind:this={popover}
on:open={() => {
drawers = []
$draggable.actions.select(componentInstance._id)
}}
on:close={() => {
open = false
if ($draggable.selected === componentInstance._id) {
$draggable.actions.select()
}
}}
open={isOpen}
on:close={close}
{anchor}
align="left-outside"
showPopover={drawers.length === 0}
clickOutsideOverride={drawers.length > 0}
maxHeight={600}
offset={18}
handlePostionUpdate={customPositionHandler}
>
<span class="popover-wrap">
<Layout noPadding noGap>

View file

@ -1,18 +0,0 @@
export const customPositionHandler = (anchorBounds, eleBounds, cfg) => {
let { left, top, offset } = cfg
let percentageOffset = 30
// left-outside
left = anchorBounds.left - eleBounds.width - (offset || 5)
// shift up from the anchor, if space allows
let offsetPos = Math.floor(eleBounds.height / 100) * percentageOffset
let defaultTop = anchorBounds.top - offsetPos
if (window.innerHeight - defaultTop < eleBounds.height) {
top = window.innerHeight - eleBounds.height - 5
} else {
top = anchorBounds.top - offsetPos
}
return { ...cfg, left, top }
}

View file

@ -0,0 +1,48 @@
<script>
import { ContextTooltip } from "@budibase/bbui"
import {
StringsAsDates,
NumbersAsDates,
ScalarJsonOnly,
Column,
Support,
NotRequired,
StringsAsNumbers,
DatesAsNumbers,
} from "./subjects"
import subjects from "../subjects"
export let anchor
export let schema
export let columnName
export let subject = subjects.none
</script>
<ContextTooltip visible={subject !== subjects.none} {anchor} offset={20}>
<div class="explanationModalContent">
{#if subject === subjects.column}
<Column {columnName} {schema} />
{:else if subject === subjects.support}
<Support />
{:else if subject === subjects.stringsAsNumbers}
<StringsAsNumbers />
{:else if subject === subjects.notRequired}
<NotRequired />
{:else if subject === subjects.datesAsNumbers}
<DatesAsNumbers />
{:else if subject === subjects.scalarJsonOnly}
<ScalarJsonOnly {columnName} {schema} />
{:else if subject === subjects.numbersAsDates}
<NumbersAsDates {columnName} />
{:else if subject === subjects.stringsAsDates}
<StringsAsDates {columnName} />
{/if}
</div>
</ContextTooltip>
<style>
.explanationModalContent {
max-width: 300px;
padding: 16px 12px 2px;
}
</style>

View file

@ -0,0 +1,147 @@
<script>
import { tables } from "stores/builder"
import {
BindingValue,
Block,
Subject,
JSONValue,
Property,
Section,
} from "./components"
export let schema
export let columnName
const parseDate = isoString => {
if ([null, undefined, ""].includes(isoString)) {
return "None"
}
const unixTime = Date.parse(isoString)
const date = new Date(unixTime)
return date.toLocaleString()
}
</script>
<Subject>
<div class="heading" slot="heading">
Column Overview for <Block>{columnName}</Block>
</div>
<Section>
{#if schema.type === "string"}
<Property
name="Max Length"
value={schema?.constraints?.length?.maximum ?? "None"}
/>
{:else if schema.type === "datetime"}
<Property
name="Earliest"
value={parseDate(schema?.constraints?.datetime?.earliest)}
/>
<Property
name="Latest"
value={parseDate(schema?.constraints?.datetime?.latest)}
/>
<Property
name="Ignore time zones"
value={schema?.ignoreTimeZones === true ? "Yes" : "No"}
/>
<Property
name="Date only"
value={schema?.dateOnly === true ? "Yes" : "No"}
/>
{:else if schema.type === "number"}
<Property
name="Min Value"
value={[null, undefined, ""].includes(
schema?.constraints?.numericality?.greaterThanOrEqualTo
)
? "None"
: schema?.constraints?.numericality?.greaterThanOrEqualTo}
/>
<Property
name="Max Value"
value={[null, undefined, ""].includes(
schema?.constraints?.numericality?.lessThanOrEqualTo
)
? "None"
: schema?.constraints?.numericality?.lessThanOrEqualTo}
/>
{:else if schema.type === "array"}
{#each schema?.constraints?.inclusion ?? [] as option, index}
<Property name={`Option ${index + 1}`} truncate>
<span
style:background-color={schema?.optionColors?.[option]}
class="optionCircle"
/>{option}
</Property>
{/each}
{:else if schema.type === "options"}
{#each schema?.constraints?.inclusion ?? [] as option, index}
<Property name={`Option ${index + 1}`} truncate>
<span
style:background-color={schema?.optionColors?.[option]}
class="optionCircle"
/>{option}
</Property>
{/each}
{:else if schema.type === "json"}
<Property name="Schema">
<JSONValue value={JSON.stringify(schema?.schema ?? {}, null, 2)} />
</Property>
{:else if schema.type === "formula"}
<Property name="Formula">
<BindingValue value={schema?.formula} />
</Property>
<Property
name="Formula type"
value={schema?.formulaType === "dynamic" ? "Dynamic" : "Static"}
/>
{:else if schema.type === "link"}
<Property name="Type" value={schema?.relationshipType} />
<Property
name="Related Table"
value={$tables?.list?.find(table => table._id === schema?.tableId)
?.name}
/>
<Property name="Column in Related Table" value={schema?.fieldName} />
{:else if schema.type === "bb_reference"}
<Property
name="Allow multiple users"
value={schema?.relationshipType === "many-to-many" ? "Yes" : "No"}
/>
{/if}
<Property
name="Required"
value={schema?.constraints?.presence?.allowEmpty === false ? "Yes" : "No"}
/>
</Section>
</Subject>
<style>
.heading {
display: flex;
align-items: center;
}
.heading > :global(.block) {
margin-left: 4px;
flex-grow: 0;
flex-shrink: 1;
display: inline-block;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.optionCircle {
display: inline-block;
background-color: hsla(0, 1%, 50%, 0.3);
border-radius: 100%;
width: 10px;
height: 10px;
vertical-align: middle;
margin-right: 5px;
}
</style>

View file

@ -0,0 +1,63 @@
<script>
import { onMount } from "svelte"
import {
ExampleSection,
ExampleLine,
Block,
Subject,
Section,
} from "./components"
let timestamp = Date.now()
onMount(() => {
let run = true
const updateTimeStamp = () => {
timestamp = Date.now()
if (run) {
setTimeout(updateTimeStamp, 200)
}
}
updateTimeStamp()
return () => {
run = false
}
})
</script>
<Subject heading="Dates as Numbers">
<Section>
A datetime value can be used in place of a numeric value, but it will be
converted to a <Block>UNIX time</Block> timestamp, which is the number of milliseconds
since Jan 1st 1970. A more recent moment in time will be a higher number.
</Section>
<ExampleSection heading="Examples:">
<ExampleLine>
<Block>
{new Date(946684800000).toLocaleString()}
</Block>
<span class="separator">{"->"} </span><Block>946684800000</Block>
</ExampleLine>
<ExampleLine>
<Block>
{new Date(1577836800000).toLocaleString()}
</Block>
<span class="separator">{"->"} </span><Block>1577836800000</Block>
</ExampleLine>
<ExampleLine>
<Block>Now</Block><span class="separator">{"->"} </span><Block
>{timestamp}</Block
>
</ExampleLine>
</ExampleSection>
</Subject>
<style>
.separator {
margin: 0 5px;
}
</style>

View file

@ -0,0 +1,11 @@
<script>
import { Block, Subject, Section } from "./components"
</script>
<Subject heading="Required Constraint">
<Section>
A <Block>required</Block> constraint can be applied to columns to ensure a value
is always present. If a column doesn't have this constraint, then its value for
a particular row could he missing.
</Section>
</Subject>

View file

@ -0,0 +1,65 @@
<script>
import { onMount } from "svelte"
import {
ExampleSection,
ExampleLine,
Block,
Subject,
Section,
} from "./components"
let timestamp = Date.now()
onMount(() => {
let run = true
const updateTimeStamp = () => {
timestamp = Date.now()
if (run) {
setTimeout(updateTimeStamp, 200)
}
}
updateTimeStamp()
return () => {
run = false
}
})
</script>
<Subject heading="Numbers as Dates">
<Section>
A number value can be used in place of a datetime value, but it will be
parsed as a <Block>UNIX time</Block> timestamp, which is the number of milliseconds
since Jan 1st 1970. A more recent moment in time will be a higher number.
</Section>
<ExampleSection heading="Examples:">
<ExampleLine>
<Block>946684800000</Block>
<span class="separator">{"->"}</span>
<Block>
{new Date(946684800000).toLocaleString()}
</Block>
</ExampleLine>
<ExampleLine>
<Block>1577836800000</Block>
<span class="separator">{"->"}</span>
<Block>
{new Date(1577836800000).toLocaleString()}
</Block>
</ExampleLine>
<ExampleLine>
<Block>{timestamp}</Block>
<span class="separator">{"->"}</span>
<Block>Now</Block>
</ExampleLine>
</ExampleSection>
</Subject>
<style>
.separator {
margin: 0 5px;
}
</style>

View file

@ -0,0 +1,71 @@
<script>
import {
ExampleSection,
ExampleLine,
Block,
Subject,
Section,
} from "./components"
export let schema
export let columnName
const maxScalarDescendantsToFind = 3
const getScalarDescendants = schema => {
const newScalarDescendants = []
const getScalarDescendantFromSchema = (path, schema) => {
if (newScalarDescendants.length >= maxScalarDescendantsToFind) {
return
}
if (["string", "number", "boolean"].includes(schema.type)) {
newScalarDescendants.push({ name: path.join("."), type: schema.type })
} else if (schema.type === "json") {
Object.entries(schema.schema ?? {}).forEach(
([childName, childSchema]) =>
getScalarDescendantFromSchema([...path, childName], childSchema)
)
}
}
Object.entries(schema?.schema ?? {}).forEach(([childName, childSchema]) =>
getScalarDescendantFromSchema([columnName, childName], childSchema)
)
return newScalarDescendants
}
$: scalarDescendants = getScalarDescendants(schema)
</script>
<Subject heading="Using Scalar JSON Values">
<Section>
<Block>JSON objects</Block> can't be used here, but any <Block>number</Block
>, <Block>string</Block> or <Block>boolean</Block> values nested within said
object can be if they are otherwise compatible with the input. These scalar values
can be selected from the same menu as this parent and take the form <Block
>parent.child</Block
>.
</Section>
{#if scalarDescendants.length > 0}
<ExampleSection heading="Examples scalar descendants of this object:">
{#each scalarDescendants as descendant}
<ExampleLine>
<Block truncate>{descendant.name}</Block><span class="separator"
>-</span
><Block truncate noShrink>{descendant.type}</Block>
</ExampleLine>
{/each}
</ExampleSection>
{/if}
</Subject>
<style>
.separator {
margin: 0 4px;
flex-shrink: 0;
}
</style>

View file

@ -0,0 +1,107 @@
<script>
import { onMount } from "svelte"
import {
ExampleSection,
ExampleLine,
Block,
Subject,
Section,
} from "./components"
let timestamp = Date.now()
$: iso = new Date(timestamp).toISOString()
onMount(() => {
let run = true
const updateTimeStamp = () => {
timestamp = Date.now()
if (run) {
setTimeout(updateTimeStamp, 200)
}
}
updateTimeStamp()
return () => {
run = false
}
})
</script>
<Subject heading="Strings as Dates">
<Section>
A string value can be used in place of a datetime value, but it will be
parsed as:
</Section>
<Section>
A <Block>UNIX time</Block> timestamp, which is the number of milliseconds since
Jan 1st 1970. A more recent moment in time will be a higher number.
</Section>
<ExampleSection heading="Examples:">
<ExampleLine>
<Block>946684800000</Block>
<span class="separator">{"->"}</span>
<Block>
{new Date(946684800000).toLocaleString()}
</Block>
</ExampleLine>
<ExampleLine>
<Block>1577836800000</Block>
<span class="separator">{"->"}</span>
<Block>
{new Date(1577836800000).toLocaleString()}
</Block>
</ExampleLine>
<ExampleLine>
<Block>{timestamp}</Block>
<span class="separator">{"->"}</span>
<Block>Now</Block>
</ExampleLine>
</ExampleSection>
<Section>
An <Block>ISO 8601</Block> datetime string, which represents an exact moment
in time as well as the potentional to store the timezone it occured in.
</Section>
<div class="isoExamples">
<ExampleSection heading="Examples:">
<ExampleLine>
<Block>2000-01-01T00:00:00.000Z</Block>
<span class="separator"></span>
<Block>
{new Date(946684800000).toLocaleString()}
</Block>
</ExampleLine>
<ExampleLine>
<Block>2000-01-01T00:00:00.000Z</Block>
<span class="separator"></span>
<Block>
{new Date(1577836800000).toLocaleString()}
</Block>
</ExampleLine>
<ExampleLine>
<Block>{iso}</Block>
<span class="separator"></span>
<Block>Now</Block>
</ExampleLine>
</ExampleSection>
</div>
</Subject>
<style>
.separator {
margin: 0 5px;
}
.isoExamples :global(.block) {
word-break: break-all;
}
.isoExamples :global(.exampleLine) {
align-items: center;
flex-direction: column;
margin-bottom: 16px;
width: 162px;
}
</style>

View file

@ -0,0 +1,56 @@
<script>
import {
ExampleSection,
ExampleLine,
Block,
Subject,
Section,
} from "./components"
</script>
<Subject heading="Text as Numbers">
<Section>
Text can be used in place of numbers in certain scenarios, but care needs to
be taken; if the value isn't purely numerical it may be converted in an
unexpected way.
</Section>
<ExampleSection heading="Examples:">
<ExampleLine>
<Block>"100"</Block><span class="separator">{"->"}</span><Block>100</Block
>
</ExampleLine>
<ExampleLine>
<Block>"100k"</Block><span class="separator">{"->"}</span><Block
>100</Block
>
</ExampleLine>
<ExampleLine>
<Block>"100,000"</Block><span class="separator">{"->"}</span><Block
>100</Block
>
</ExampleLine>
<ExampleLine>
<Block>"100 million"</Block><span class="separator">{"->"}</span><Block
>100</Block
>
</ExampleLine>
<ExampleLine>
<Block>"100.9"</Block><span class="separator">{"->"}</span><Block
>100.9</Block
>
</ExampleLine>
<ExampleLine>
<Block>"One hundred"</Block><span class="separator">{"->"}</span><Block
>Error</Block
>
</ExampleLine>
</ExampleSection>
</Subject>
<style>
.separator {
margin: 0 4px;
flex-shrink: 0;
}
</style>

View file

@ -0,0 +1,35 @@
<script>
import { InfoWord } from "../../typography"
import { Subject, Section } from "./components"
</script>
<Subject heading="Data/Component Compatibility">
<Section>
<InfoWord icon="CheckmarkCircle" color="var(--green)" text="Compatible" />
<span class="body"
>Fully compatible with the input as long as the data is present.</span
>
</Section>
<Section>
<InfoWord
icon="AlertCheck"
color="var(--yellow)"
text="Partially compatible"
/>
<span class="body"
>Partially compatible with the input, but beware of other caveats
mentioned.</span
>
</Section>
<Section>
<InfoWord icon="Alert" color="var(--red)" text="Not compatible" />
<span class="body">Incompatible with the component.</span>
</Section>
</Subject>
<style>
.body {
display: block;
margin-top: 5px;
}
</style>

View file

@ -0,0 +1,39 @@
<script>
import { decodeJSBinding } from "@budibase/string-templates"
import CodeEditor from "components/common/CodeEditor/CodeEditor.svelte"
import { EditorModes } from "components/common/CodeEditor"
import {
runtimeToReadableBinding,
getDatasourceForProvider,
} from "dataBinding"
import { tables, selectedScreen, selectedComponent } from "stores/builder"
import { getBindings } from "components/backend/DataTable/formula"
export let value
$: datasource = getDatasourceForProvider($selectedScreen, $selectedComponent)
$: tableId = datasource.tableId
$: table = $tables?.list?.find(table => table._id === tableId)
$: bindings = getBindings({ table })
$: readableBinding = runtimeToReadableBinding(bindings, value)
$: isJs = value?.startsWith?.("{{ js ")
</script>
<div class="editor">
<CodeEditor
readonly
readonlyLineNumbers
value={isJs ? decodeJSBinding(readableBinding) : readableBinding}
jsBindingWrapping={isJs}
mode={isJs ? EditorModes.JS : EditorModes.Handlebars}
/>
</div>
<style>
.editor {
border: 1px solid var(--grey-2);
border-radius: 2px;
overflow: hidden;
}
</style>

View file

@ -0,0 +1,30 @@
<script>
export let truncate = false
export let noShrink = false
</script>
<span class:truncate class:noShrink class="block">
<slot />
</span>
<style>
.block {
font-style: italic;
border-radius: 1px;
padding: 0px 5px 0px 3px;
border-radius: 1px;
background-color: var(--grey-3);
color: var(--ink);
word-break: break-word;
}
.truncate {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.noShrink {
flex-shrink: 0;
}
</style>

View file

@ -0,0 +1,12 @@
<li>
<div class="exampleLine">
<slot />
</div>
</li>
<style>
.exampleLine {
display: flex;
margin-bottom: 2px;
}
</style>

View file

@ -0,0 +1,32 @@
<script>
import Section from "./Section.svelte"
export let heading
</script>
<Section>
<span class="exampleSectionHeading">
<slot name="heading">
{heading}
</slot>
</span>
<ul>
<slot />
</ul>
</Section>
<style>
.exampleSectionHeading {
display: inline-block;
margin-bottom: 2px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 100%;
}
ul {
padding: 0 0 0 23px;
margin: 0;
}
</style>

View file

@ -0,0 +1,22 @@
<script>
export let value
</script>
<pre class="pre">
{value}
</pre>
<style>
.pre {
border: 1px solid var(--grey-2);
border-radius: 2px;
overflow: hidden;
margin: 0;
margin-top: 3px;
padding: 4px;
border-radius: 3px;
width: 250px;
box-sizing: border-box;
background-color: black;
}
</style>

View file

@ -0,0 +1,49 @@
<script>
export let name
export let value
export let truncate = false
</script>
<div class:truncate class="property">
<span class="propertyName">
<slot name="name">
{name}
</slot>
</span>
<span class="propertyDivider">-</span>
<span class="propertyValue">
<slot>
{value}
</slot>
</span>
</div>
<style>
.property {
max-width: 100%;
margin-bottom: 8px;
}
.truncate {
display: flex;
align-items: center;
overflow: hidden;
}
.propertyName {
font-weight: 600;
flex-shrink: 0;
}
.propertyDivider {
padding: 0 4px;
flex-shrink: 0;
}
.propertyValue {
word-break: break-word;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
</style>

View file

@ -0,0 +1,11 @@
<div class="section">
<slot />
</div>
<style>
.section {
line-height: 20px;
margin-bottom: 13px;
overflow: hidden;
}
</style>

View file

@ -0,0 +1,51 @@
<script>
import { onMount } from "svelte"
export let heading = ""
let body
const handleScroll = e => {
if (!body) return
body.scrollTo({ top: body.scrollTop + e.deltaY, behavior: "smooth" })
}
onMount(() => {
window.addEventListener("wheel", handleScroll)
return () => {
window.removeEventListener("wheel", handleScroll)
}
})
</script>
<div class="heading">
<span class="heading">
<slot name="heading">
{heading}
</slot>
</span>
</div>
<div class="divider" />
<div bind:this={body} class="body">
<slot />
</div>
<style>
.heading {
font-weight: 600;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.divider {
border-bottom: 1px solid var(--grey-4);
margin: 12px 0 12px;
}
.body {
max-height: 300px;
overflow-y: auto;
}
</style>

View file

@ -0,0 +1,8 @@
export { default as Subject } from "./Subject.svelte"
export { default as Property } from "./Property.svelte"
export { default as JSONValue } from "./JSONValue.svelte"
export { default as BindingValue } from "./BindingValue.svelte"
export { default as Section } from "./Section.svelte"
export { default as Block } from "./Block.svelte"
export { default as ExampleSection } from "./ExampleSection.svelte"
export { default as ExampleLine } from "./ExampleLine.svelte"

View file

@ -0,0 +1,8 @@
export { default as Column } from "./Column.svelte"
export { default as NotRequired } from "./NotRequired.svelte"
export { default as StringsAsNumbers } from "./StringsAsNumbers.svelte"
export { default as Support } from "./Support.svelte"
export { default as DatesAsNumbers } from "./DatesAsNumbers.svelte"
export { default as ScalarJsonOnly } from "./ScalarJsonOnly.svelte"
export { default as StringsAsDates } from "./StringsAsDates.svelte"
export { default as NumbersAsDates } from "./NumbersAsDates.svelte"

View file

@ -0,0 +1,102 @@
<script>
import DetailsModal from "./DetailsModal/index.svelte"
import {
messages as messageConstants,
getExplanationMessagesAndSupport,
getExplanationWithPresets,
} from "./explanation"
import {
StringAsDate,
NumberAsDate,
Column,
Support,
NotRequired,
StringAsNumber,
JSONPrimitivesOnly,
DateAsNumber,
} from "./lines"
import subjects from "./subjects"
import { appStore } from "stores/builder"
export let explanation
export let columnIcon
export let columnType
export let columnName
export let tableHref = () => {}
export let schema
$: explanationWithPresets = getExplanationWithPresets(
explanation,
$appStore.typeSupportPresets
)
let support
let messages = []
$: {
const explanationMessagesAndSupport = getExplanationMessagesAndSupport(
schema,
explanationWithPresets
)
support = explanationMessagesAndSupport.support
messages = explanationMessagesAndSupport.messages
}
let root = null
let detailsModalSubject = subjects.none
const setExplanationSubject = option => {
detailsModalSubject = option
root = root
}
</script>
<div bind:this={root} class="tooltipContents">
<Column
{columnName}
{columnIcon}
{columnType}
{tableHref}
{setExplanationSubject}
/>
<Support {support} {setExplanationSubject} />
{#if messages.includes(messageConstants.stringAsNumber)}
<StringAsNumber {setExplanationSubject} />
{/if}
{#if messages.includes(messageConstants.notRequired)}
<NotRequired {setExplanationSubject} />
{/if}
{#if messages.includes(messageConstants.jsonPrimitivesOnly)}
<JSONPrimitivesOnly {setExplanationSubject} />
{/if}
{#if messages.includes(messageConstants.dateAsNumber)}
<DateAsNumber {setExplanationSubject} />
{/if}
{#if messages.includes(messageConstants.numberAsDate)}
<NumberAsDate {setExplanationSubject} />
{/if}
{#if messages.includes(messageConstants.stringAsDate)}
<StringAsDate {setExplanationSubject} />
{/if}
</div>
{#if detailsModalSubject !== subjects.none}
<DetailsModal
{columnName}
anchor={root}
{schema}
subject={detailsModalSubject}
/>
{/if}
<style>
.tooltipContents {
max-width: 450px;
display: block;
padding: 20px 16px 10px;
border-radius: 5px;
box-sizing: border-box;
}
</style>

View file

@ -0,0 +1,100 @@
export const messages = {
jsonPrimitivesOnly: Symbol("explanation-json-primitives-only"),
stringAsNumber: Symbol("explanation-string-as-number"),
dateAsNumber: Symbol("explanation-date-as-number"),
numberAsDate: Symbol("explanation-number-as-date"),
stringAsDate: Symbol("explanation-string-as-date"),
notRequired: Symbol("explanation-not-required"),
contextError: Symbol("explanation-context-error"),
}
export const support = {
unsupported: Symbol("explanation-unsupported"),
partialSupport: Symbol("explanation-partialSupport"),
supported: Symbol("explanation-supported"),
}
const getSupport = (type, explanation) => {
if (!explanation?.typeSupport) {
return support.supported
}
if (
explanation?.typeSupport?.supported?.find(
mapping => mapping === type || mapping?.type === type
)
) {
return support.supported
}
if (
explanation?.typeSupport?.partialSupport?.find(
mapping => mapping === type || mapping?.type === type
)
) {
return support.partialSupport
}
return support.unsupported
}
const getSupportMessage = (type, explanation) => {
if (!explanation?.typeSupport) {
return null
}
const supported = explanation?.typeSupport?.supported?.find(
mapping => mapping?.type === type
)
if (supported) {
return messages[supported?.message]
}
const partialSupport = explanation?.typeSupport?.partialSupport?.find(
mapping => mapping?.type === type
)
if (partialSupport) {
return messages[partialSupport?.message]
}
const unsupported = explanation?.typeSupport?.unsupported?.find(
mapping => mapping?.type === type
)
if (unsupported) {
return messages[unsupported?.message]
}
return null
}
export const getExplanationMessagesAndSupport = (fieldSchema, explanation) => {
try {
const explanationMessagesAndSupport = {
support: getSupport(fieldSchema.type, explanation),
messages: [getSupportMessage(fieldSchema.type, explanation)],
}
const isRequired = fieldSchema?.constraints?.presence?.allowEmpty === false
if (!isRequired) {
explanationMessagesAndSupport.messages.push(messages.notRequired)
}
return explanationMessagesAndSupport
} catch (e) {
return {
support: support.partialSupport,
messages: [messages.contextError],
}
}
}
export const getExplanationWithPresets = (explanation, presets) => {
if (explanation?.typeSupport?.preset) {
return {
...explanation,
typeSupport: presets[explanation?.typeSupport?.preset],
}
}
return explanation
}

View file

@ -0,0 +1 @@
export { default as Explanation } from "./Explanation.svelte"

View file

@ -0,0 +1,84 @@
<script>
import {
Line,
InfoWord,
DocumentationLink,
Text,
Period,
} from "../typography"
import subjects from "../subjects"
export let columnName
export let columnIcon
export let columnType
export let tableHref
export let setExplanationSubject
const getDocLink = columnType => {
if (columnType === "Number") {
return "https://docs.budibase.com/docs/number"
}
if (columnType === "Text") {
return "https://docs.budibase.com/docs/text"
}
if (columnType === "Attachment") {
return "https://docs.budibase.com/docs/attachments"
}
if (columnType === "Multi-select") {
return "https://docs.budibase.com/docs/multi-select"
}
if (columnType === "JSON") {
return "https://docs.budibase.com/docs/json"
}
if (columnType === "Date/Time") {
return "https://docs.budibase.com/docs/datetime"
}
if (columnType === "User") {
return "https://docs.budibase.com/docs/user"
}
if (columnType === "QR") {
return "https://docs.budibase.com/docs/barcodeqr"
}
if (columnType === "Relationship") {
return "https://docs.budibase.com/docs/relationships"
}
if (columnType === "Formula") {
return "https://docs.budibase.com/docs/formula"
}
if (columnType === "Options") {
return "https://docs.budibase.com/docs/options"
}
if (columnType === "BigInt") {
// No BigInt docs
return null
}
if (columnType === "Boolean") {
return "https://docs.budibase.com/docs/boolean-truefalse"
}
if (columnType === "Signature") {
// No Signature docs
return null
}
return null
}
$: docLink = getDocLink(columnType)
</script>
<Line noWrap>
<InfoWord
on:mouseenter={() => setExplanationSubject(subjects.column)}
on:mouseleave={() => setExplanationSubject(subjects.none)}
href={tableHref}
text={columnName}
/>
<Text value=" is a " />
<DocumentationLink
disabled={docLink === null}
href={docLink}
icon={columnIcon}
text={`${columnType} column`}
/>
<Period />
</Line>

View file

@ -0,0 +1,16 @@
<script>
import { Line, InfoWord, Text, Period } from "../typography"
import subjects from "../subjects"
export let setExplanationSubject
</script>
<Line>
<Text value="Will be converted to a " />
<InfoWord
on:mouseenter={() => setExplanationSubject(subjects.datesAsNumbers)}
on:mouseleave={() => setExplanationSubject(subjects.none)}
text="UNIX time value"
/>
<Period />
</Line>

View file

@ -0,0 +1,21 @@
<script>
import { Line, InfoWord, Text, Period } from "../typography"
import subjects from "../subjects"
export let setExplanationSubject
</script>
<Line>
<InfoWord
on:mouseenter={() => setExplanationSubject(subjects.scalarJsonOnly)}
on:mouseleave={() => setExplanationSubject(subjects.none)}
>Scalar JSON values</InfoWord
>
<Text
value=" can be used with this input if their individual types are supported"
/>
<Period />
</Line>
<style>
</style>

View file

@ -0,0 +1,25 @@
<script>
import { Line, InfoWord, DocumentationLink, Space, Text } from "../typography"
import subjects from "../subjects"
export let setExplanationSubject
</script>
<Line>
<Text value="No " />
<InfoWord
on:mouseenter={() => setExplanationSubject(subjects.notRequired)}
on:mouseleave={() => setExplanationSubject(subjects.none)}
text="required"
/>
<Space />
<DocumentationLink
icon="DataUnavailable"
href="https://docs.budibase.com/docs/budibasedb#constraints"
text="Constraint"
/>
<Text value=", so values may be missing." />
</Line>
<style>
</style>

View file

@ -0,0 +1,16 @@
<script>
import { Line, InfoWord, Text, Period } from "../typography"
import subjects from "../subjects"
export let setExplanationSubject
</script>
<Line>
<Text value="Will be treated as a " />
<InfoWord
on:mouseenter={() => setExplanationSubject(subjects.numbersAsDates)}
on:mouseleave={() => setExplanationSubject(subjects.none)}
text="UNIX time value"
/>
<Period />
</Line>

View file

@ -0,0 +1,16 @@
<script>
import { Line, InfoWord, Text, Period } from "../typography"
import subjects from "../subjects"
export let setExplanationSubject
</script>
<Line>
<Text value="Will be treated as a " />
<InfoWord
on:mouseenter={() => setExplanationSubject(subjects.stringsAsDates)}
on:mouseleave={() => setExplanationSubject(subjects.none)}
text="UNIX time or ISO 8601 value"
/>
<Period />
</Line>

View file

@ -0,0 +1,16 @@
<script>
import { Line, InfoWord, Text, Period } from "../typography"
import subjects from "../subjects"
export let setExplanationSubject
</script>
<Line>
<Text value="Will be converted to a number, ignoring any " />
<InfoWord
on:mouseenter={() => setExplanationSubject(subjects.stringsAsNumbers)}
on:mouseleave={() => setExplanationSubject(subjects.none)}
text="non-numerical values"
/>
<Period />
</Line>

View file

@ -0,0 +1,59 @@
<script>
import { Line, InfoWord, DocumentationLink, Text } from "../typography"
import subjects from "../subjects"
import * as explanation from "../explanation"
export let setExplanationSubject
export let support
const getIcon = support => {
if (support === explanation.support.unsupported) {
return "Alert"
} else if (support === explanation.support.supported) {
return "CheckmarkCircle"
}
return "AlertCheck"
}
const getColor = support => {
if (support === explanation.support.unsupported) {
return "var(--red)"
} else if (support === explanation.support.supported) {
return "var(--green)"
}
return "var(--yellow)"
}
const getText = support => {
if (support === explanation.support.unsupported) {
return "Not compatible"
} else if (support === explanation.support.supported) {
return "Compatible"
}
return "Partially compatible"
}
$: icon = getIcon(support)
$: color = getColor(support)
$: text = getText(support)
</script>
<Line>
<InfoWord
on:mouseenter={() => setExplanationSubject(subjects.support)}
on:mouseleave={() => setExplanationSubject(subjects.none)}
{icon}
{color}
{text}
/>
<Text value=" with this " />
<DocumentationLink
href="https://docs.budibase.com/docs/chart"
icon="GraphPie"
text="Chart component"
/>
<Text value=" input." />
</Line>

View file

@ -0,0 +1,8 @@
export { default as Column } from "./Column.svelte"
export { default as NotRequired } from "./NotRequired.svelte"
export { default as StringAsNumber } from "./StringAsNumber.svelte"
export { default as Support } from "./Support.svelte"
export { default as JSONPrimitivesOnly } from "./JSONPrimitivesOnly.svelte"
export { default as DateAsNumber } from "./DateAsNumber.svelte"
export { default as NumberAsDate } from "./NumberAsDate.svelte"
export { default as StringAsDate } from "./StringAsDate.svelte"

View file

@ -0,0 +1,13 @@
const subjects = {
column: Symbol("details-modal-column"),
support: Symbol("details-modal-support"),
stringsAsNumbers: Symbol("details-modal-strings-as-numbers"),
datesAsNumbers: Symbol("details-modal-dates-as-numbers"),
numbersAsDates: Symbol("explanation-numbers-as-dates"),
stringsAsDates: Symbol("explanation-strings-as-dates"),
notRequired: Symbol("details-modal-not-required"),
scalarJsonOnly: Symbol("explanation-scalar-json-only"),
none: Symbol("details-modal-none"),
}
export default subjects

View file

@ -0,0 +1,14 @@
<span class="comma">,</span>
<style>
.comma {
color: var(--grey-6);
font-size: 17px;
display: inline block;
margin-left: 2px;
margin-right: 2px;
flex-shrink: 0;
position: relative;
bottom: 2px;
}
</style>

View file

@ -0,0 +1,66 @@
<script>
import { Icon } from "@budibase/bbui"
export let icon
export let text
export let href
export let disabled = false
</script>
<a
class:disabled
tabindex="0"
{href}
rel="noopener noreferrer"
target="_blank"
class="link"
>
<Icon size="XS" name={icon} />
<span class="text">
<slot>
{text}
</slot>
</span>
</a>
<style>
.text {
color: var(--ink);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.link {
display: inline-flex;
box-sizing: border-box;
padding: 1px 0 2px;
filter: brightness(100%);
align-items: center;
overflow: hidden;
flex-shrink: 0;
border-radius: 0;
border-bottom: 1px solid var(--blue);
transition: filter 300ms;
margin-right: 0px;
}
.link:hover {
filter: brightness(120%);
}
.disabled {
filter: brightness(100%);
border-bottom: 1px solid var(--grey-6);
}
.link :global(svg) {
margin-right: 3px;
color: var(--blue);
}
.disabled :global(svg) {
color: var(--grey-6);
}
</style>

View file

@ -0,0 +1,78 @@
<script>
import { Icon } from "@budibase/bbui"
export let icon = null
export let color = null
export let text
export let href = null
</script>
{#if href !== null}
<a
tabindex="0"
{href}
rel="noopener noreferrer"
target="_blank"
class="infoWord"
style:color
style:border-color={color}
on:mouseenter
on:mouseleave
>
{#if icon}
<Icon size="XS" name={icon} />
{/if}
<span class="text">
<slot>
{text}
</slot>
</span>
</a>
{:else}
<div
role="tooltip"
class="infoWord"
style:color
style:border-color={color}
on:mouseenter
on:mouseleave
>
{#if icon}
<Icon size="XS" name={icon} />
{/if}
<span class="text">
<slot>
{text}
</slot>
</span>
</div>
{/if}
<style>
.infoWord {
display: inline-flex;
box-sizing: border-box;
padding: 1px 0 2px;
filter: brightness(100%);
overflow: hidden;
transition: filter 300ms;
align-items: center;
border-bottom: 1px solid var(--grey-6);
}
.infoWord:hover {
filter: brightness(120%);
}
.infoWord :global(svg) {
color: inherit;
margin-right: 4px;
}
.text {
color: var(--ink);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
</style>

View file

@ -0,0 +1,41 @@
<script>
export let noWrap = false
</script>
<div class="line">
<span class="bullet"></span>
<div class="content" class:noWrap>
<slot />
</div>
</div>
<style>
.line {
color: var(--ink);
align-items: flex-start;
display: flex;
margin-bottom: 10px;
}
.bullet {
color: var(--grey-6);
font-size: 17px;
display: inline block;
margin-right: 10px;
}
.content {
line-height: 17px;
min-width: 0;
align-items: center;
display: flex;
flex-wrap: wrap;
row-gap: 3px;
}
.noWrap {
display: flex;
flex-wrap: nowrap;
}
</style>

View file

@ -0,0 +1,13 @@
<span class="period">.</span>
<style>
.period {
color: var(--grey-6);
font-size: 20px;
display: inline block;
margin-left: 2px;
flex-shrink: 0;
position: relative;
bottom: 2px;
}
</style>

View file

@ -0,0 +1,9 @@
<span class="space">{" "}</span>
<style>
.space {
white-space: pre;
width: 3px;
flex-shrink: 0;
}
</style>

View file

@ -0,0 +1,64 @@
<script>
import Comma from "./Comma.svelte"
import Period from "./Period.svelte"
import Space from "./Space.svelte"
export let value = null
const punctuation = [" ", ",", "."]
// TODO regex might work here now
const getWords = value => {
if (typeof value !== "string") {
return []
}
const newWords = []
let lastIndex = 0
const makeWord = i => {
// No word to make, multiple spaces, spaces at start of text etc
if (i - lastIndex > 0) {
newWords.push(value.substring(lastIndex, i))
}
lastIndex = i + 1
}
value.split("").forEach((character, i) => {
if (punctuation.includes(character)) {
makeWord(i)
newWords.push(character)
}
})
makeWord(value.length)
return newWords
}
$: words = getWords(value)
</script>
{#each words as word}
{#if word === " "}
<Space />
{:else if word === ","}
<Comma />
{:else if word === "."}
<Period />
{:else}
<span class="text">
{word}
</span>
{/if}
{/each}
<style>
.text {
/* invisible properties to match other inline text elements that do have borders. If we don't match here we run into subpixel issues */
box-sizing: border-box;
border-bottom: 1px solid transparent;
padding: 1px 0 2px;
}
</style>

View file

@ -0,0 +1,7 @@
export { default as Space } from "./Space.svelte"
export { default as Comma } from "./Comma.svelte"
export { default as Period } from "./Period.svelte"
export { default as Text } from "./Text.svelte"
export { default as InfoWord } from "./InfoWord.svelte"
export { default as DocumentationLink } from "./DocumentationLink.svelte"
export { default as Line } from "./Line.svelte"

View file

@ -1,5 +1,5 @@
<script>
import EditComponentPopover from "../EditComponentPopover/EditComponentPopover.svelte"
import EditComponentPopover from "../EditComponentPopover.svelte"
import { Toggle, Icon } from "@budibase/bbui"
import { createEventDispatcher } from "svelte"
import { cloneDeep } from "lodash/fp"

View file

@ -41,6 +41,7 @@ export const FieldTypeToComponentMap = {
[FieldType.BOOLEAN]: "booleanfield",
[FieldType.LONGFORM]: "longformfield",
[FieldType.DATETIME]: "datetimefield",
[FieldType.SIGNATURE_SINGLE]: "signaturesinglefield",
[FieldType.ATTACHMENTS]: "attachmentfield",
[FieldType.ATTACHMENT_SINGLE]: "attachmentsinglefield",
[FieldType.LINK]: "relationshipfield",

View file

@ -1,12 +1,22 @@
<script>
import { Select } from "@budibase/bbui"
import { Select, ContextTooltip } from "@budibase/bbui"
import { getDatasourceForProvider, getSchemaForDatasource } from "dataBinding"
import { selectedScreen } from "stores/builder"
import { createEventDispatcher } from "svelte"
import { Explanation } from "./Explanation"
import { debounce } from "lodash"
import { params } from "@roxi/routify"
import { Constants } from "@budibase/frontend-core"
import { FIELDS } from "constants/backend"
export let componentInstance = {}
export let value = ""
export let placeholder
export let explanation
let contextTooltipAnchor = null
let currentOption = null
let contextTooltipVisible = false
const dispatch = createEventDispatcher()
$: datasource = getDatasourceForProvider($selectedScreen, componentInstance)
@ -32,6 +42,77 @@
boundValue = getValidValue(value.detail, options)
dispatch("change", boundValue)
}
const updateTooltip = debounce((e, option) => {
if (option == null) {
contextTooltipVisible = false
} else {
contextTooltipAnchor = e?.target
currentOption = option
contextTooltipVisible = true
}
}, 200)
const onOptionMouseenter = (e, option) => {
updateTooltip(e, option)
}
const onOptionMouseleave = e => {
updateTooltip(e, null)
}
const getOptionIcon = optionKey => {
const option = schema[optionKey]
if (!option) return ""
if (option.autocolumn) {
return "MagicWand"
}
const { type, subtype } = option
const result =
typeof Constants.TypeIconMap[type] === "object" && subtype
? Constants.TypeIconMap[type][subtype]
: Constants.TypeIconMap[type]
return result || "Text"
}
const getOptionIconTooltip = optionKey => {
const option = schema[optionKey]
const type = option?.type
const field = Object.values(FIELDS).find(f => f.type === type)
if (field) {
return field.name
}
return ""
}
</script>
<Select {placeholder} value={boundValue} on:change={onChange} {options} />
<Select
{placeholder}
value={boundValue}
on:change={onChange}
{options}
{onOptionMouseenter}
{onOptionMouseleave}
/>
{#if explanation}
<ContextTooltip
visible={contextTooltipVisible}
anchor={contextTooltipAnchor}
offset={20}
>
<Explanation
tableHref={`/builder/app/${$params.application}/data/table/${datasource?.tableId}`}
schema={schema[currentOption]}
columnIcon={getOptionIcon(currentOption)}
columnName={currentOption}
columnType={getOptionIconTooltip(currentOption)}
{explanation}
/>
</ContextTooltip>
{/if}

View file

@ -1,5 +1,5 @@
<script>
import EditComponentPopover from "../EditComponentPopover/EditComponentPopover.svelte"
import EditComponentPopover from "../EditComponentPopover.svelte"
import { Toggle, Icon } from "@budibase/bbui"
import { createEventDispatcher } from "svelte"
import { cloneDeep } from "lodash/fp"

View file

@ -1,5 +1,5 @@
<script>
import EditComponentPopover from "../EditComponentPopover/EditComponentPopover.svelte"
import EditComponentPopover from "../EditComponentPopover.svelte"
import { Icon } from "@budibase/bbui"
import { setContext } from "svelte"
import { writable } from "svelte/store"

View file

@ -67,6 +67,7 @@ const toGridFormat = draggableListColumns => {
label: entry.label,
field: entry.field,
active: entry.active,
width: entry.width,
}))
}
@ -81,6 +82,7 @@ const toDraggableListFormat = (gridFormatColumns, createComponent, schema) => {
field: column.field,
label: column.label,
columnType: schema[column.field].type,
width: column.width,
},
{}
)

View file

@ -1,12 +1,22 @@
<script>
import { Multiselect } from "@budibase/bbui"
import { Multiselect, ContextTooltip } from "@budibase/bbui"
import { getDatasourceForProvider, getSchemaForDatasource } from "dataBinding"
import { selectedScreen } from "stores/builder"
import { createEventDispatcher } from "svelte"
import { Explanation } from "./Explanation"
import { FIELDS } from "constants/backend"
import { params } from "@roxi/routify"
import { debounce } from "lodash"
import { Constants } from "@budibase/frontend-core"
export let componentInstance = {}
export let value = ""
export let placeholder
export let explanation
let contextTooltipAnchor = null
let currentOption = null
let contextTooltipVisible = false
const dispatch = createEventDispatcher()
$: datasource = getDatasourceForProvider($selectedScreen, componentInstance)
@ -26,6 +36,84 @@
boundValue = getValidOptions(value.detail, options)
dispatch("change", boundValue)
}
const getOptionIcon = optionKey => {
const option = schema[optionKey]
if (!option) return ""
if (option.autocolumn) {
return "MagicWand"
}
const { type, subtype } = option
const result =
typeof Constants.TypeIconMap[type] === "object" && subtype
? Constants.TypeIconMap[type][subtype]
: Constants.TypeIconMap[type]
return result || "Text"
}
const getOptionIconTooltip = optionKey => {
const option = schema[optionKey]
const type = option?.type
const field = Object.values(FIELDS).find(f => f.type === type)
if (field) {
return field.name
} else if (type === "jsonarray") {
// `jsonarray` isn't present in the above FIELDS constant
return "JSON Array"
}
return ""
}
const updateTooltip = debounce((e, option) => {
if (option == null) {
contextTooltipVisible = false
} else {
contextTooltipAnchor = e?.target
currentOption = option
contextTooltipVisible = true
}
}, 200)
const onOptionMouseenter = (e, option) => {
updateTooltip(e, option)
}
const onOptionMouseleave = e => {
updateTooltip(e, null)
}
</script>
<Multiselect {placeholder} value={boundValue} on:change={setValue} {options} />
<Multiselect
iconPosition="right"
{placeholder}
value={boundValue}
on:change={setValue}
{options}
align="right"
{onOptionMouseenter}
{onOptionMouseleave}
/>
{#if explanation}
<ContextTooltip
visible={contextTooltipVisible}
anchor={contextTooltipAnchor}
offset={20}
>
<Explanation
tableHref={`/builder/app/${$params.application}/data/table/${datasource?.tableId}`}
schema={schema[currentOption]}
columnIcon={getOptionIcon(currentOption)}
columnName={currentOption}
columnType={getOptionIconTooltip(currentOption)}
{explanation}
/>
</ContextTooltip>
{/if}

View file

@ -108,6 +108,8 @@
Constraints.MaxFileSize,
Constraints.MaxUploadSize,
],
["attachment_single"]: [Constraints.Required, Constraints.MaxUploadSize],
["signature_single"]: [Constraints.Required],
["link"]: [
Constraints.Required,
Constraints.Contains,

View file

@ -10,7 +10,6 @@ import {
NewFormSteps,
} from "./steps"
import { API } from "api"
import { customPositionHandler } from "components/design/settings/controls/EditComponentPopover"
const ONBOARDING_EVENT_PREFIX = "onboarding"
@ -187,7 +186,6 @@ const getTours = () => {
tourEvent(TOUR_STEP_KEYS.BUILDER_FORM_CREATE_STEPS)
builderStore.highlightSetting("steps", "info")
},
positionHandler: customPositionHandler,
align: "left-outside",
},
],
@ -203,7 +201,6 @@ const getTours = () => {
tourEvent(TOUR_STEP_KEYS.BUILDER_FORM_ROW_ID)
builderStore.highlightSetting("rowId", "info")
},
positionHandler: customPositionHandler,
align: "left-outside",
},
{
@ -219,7 +216,6 @@ const getTours = () => {
tourEvent(TOUR_STEP_KEYS.BUILDER_FORM_VIEW_UPDATE_STEPS)
builderStore.highlightSetting("steps", "info")
},
positionHandler: customPositionHandler,
align: "left-outside",
scrollIntoView: true,
},

View file

@ -127,6 +127,14 @@ export const FIELDS = {
presence: false,
},
},
SIGNATURE_SINGLE: {
name: "Signature",
type: FieldType.SIGNATURE_SINGLE,
icon: "AnnotatePen",
constraints: {
presence: false,
},
},
LINK: {
name: "Relationship",
type: FieldType.LINK,

View file

@ -105,10 +105,6 @@
}
onMount(async () => {
document.fonts.onloadingdone = e => {
builderStore.loadFonts(e.fontfaces)
}
if (!hasSynced && application) {
try {
await API.syncApp(application)
@ -149,19 +145,17 @@
/>
</span>
<Tabs {selected} size="M">
{#key $builderStore?.fonts}
{#each $layout.children as { path, title }}
<TourWrap stepKeys={[`builder-${title}-section`]}>
<Tab
quiet
selected={$isActive(path)}
on:click={topItemNavigate(path)}
title={capitalise(title)}
id={`builder-${title}-tab`}
/>
</TourWrap>
{/each}
{/key}
{#each $layout.children as { path, title }}
<TourWrap stepKeys={[`builder-${title}-section`]}>
<Tab
quiet
selected={$isActive(path)}
on:click={topItemNavigate(path)}
title={capitalise(title)}
id={`builder-${title}-tab`}
/>
</TourWrap>
{/each}
</Tabs>
</div>
<div class="topcenternav">

View file

@ -191,6 +191,9 @@
// Number fields
min: setting.min ?? null,
max: setting.max ?? null,
// Field select settings
explanation: setting.explanation,
}}
{bindings}
{componentBindings}

View file

@ -71,6 +71,7 @@
"multifieldselect",
"s3upload",
"codescanner",
"signaturesinglefield",
"bbreferencesinglefield",
"bbreferencefield"
]

View file

@ -19,6 +19,7 @@ export const INITIAL_APP_META_STATE = {
showNotificationAction: false,
sidePanel: false,
},
typeSupportPresets: {},
features: {
componentValidation: false,
disableUserMetadata: false,
@ -79,6 +80,13 @@ export class AppMetaStore extends BudiStore {
}))
}
syncClientTypeSupportPresets(typeSupportPresets) {
this.update(state => ({
...state,
typeSupportPresets,
}))
}
async syncAppRoutes() {
const resp = await API.fetchAppRoutes()
this.update(state => ({

View file

@ -82,6 +82,7 @@ const automationActions = store => ({
steps: [],
trigger,
},
disabled: false,
}
const response = await store.actions.save(automation)
await store.actions.fetch()
@ -134,6 +135,28 @@ const automationActions = store => ({
})
await store.actions.fetch()
},
toggleDisabled: async automationId => {
let automation
try {
automation = store.actions.getDefinition(automationId)
if (!automation) {
return
}
automation.disabled = !automation.disabled
await store.actions.save(automation)
notifications.success(
`Automation ${
automation.disabled ? "enabled" : "disabled"
} successfully`
)
} catch (error) {
notifications.error(
`Error ${
automation && automation.disabled ? "enabling" : "disabling"
} automation`
)
}
},
updateBlockInputs: async (block, data) => {
// Create new modified block
let newBlock = {

View file

@ -14,7 +14,6 @@ export const INITIAL_BUILDER_STATE = {
tourKey: null,
tourStepKey: null,
hoveredComponentId: null,
fonts: null,
}
export class BuilderStore extends BudiStore {
@ -37,16 +36,6 @@ export class BuilderStore extends BudiStore {
this.websocket
}
loadFonts(fontFaces) {
const ff = fontFaces.map(
fontFace => `${fontFace.family}-${fontFace.weight}`
)
this.update(state => ({
...state,
fonts: [...(state.fonts || []), ...ff],
}))
}
init(app) {
if (!app?.appId) {
console.error("BuilderStore: No appId supplied for websocket")

View file

@ -108,6 +108,7 @@ export class ComponentStore extends BudiStore {
// Sync client features to app store
appStore.syncClientFeatures(components.features)
appStore.syncClientTypeSupportPresets(components?.typeSupportPresets ?? {})
return components
}

View file

@ -91,6 +91,14 @@ describe("Application Meta Store", () => {
})
})
it("Sync type support information to state", async ctx => {
ctx.test.appStore.syncClientTypeSupportPresets({ preset: "information" })
expect(ctx.test.store.typeSupportPresets).toStrictEqual({
preset: "information",
})
})
it("Sync component feature flags to state", async ctx => {
ctx.test.appStore.syncClientFeatures(clientFeaturesResp)

View file

@ -42,6 +42,7 @@ vi.mock("stores/builder", async () => {
update: mockAppStore.update,
set: mockAppStore.set,
syncClientFeatures: vi.fn(),
syncClientTypeSupportPresets: vi.fn(),
}
const mockTableStore = writable()
const tables = {

View file

@ -1,3 +1,4 @@
process.env.DISABLE_PINO_LOGGER = "1"
process.env.NO_JS = "1"
process.env.JS_BCRYPT = "1"
process.env.DISABLE_JWT_WARNING = "1"

View file

@ -1,5 +1,5 @@
#!/usr/bin/env node
process.env.DISABLE_PINO_LOGGER = "1"
// have to import this before anything else
import "./environment"
import { getCommands } from "./options"
import { Command } from "commander"

View file

@ -1,3 +1,3 @@
#!/bin/bash
dir="$(dirname -- "$(readlink -f "${BASH_SOURCE}")")"
${dir}/node_modules/ts-node/dist/bin.js ${dir}/src/index.ts $@
${dir}/../../node_modules/ts-node/dist/bin.js ${dir}/src/index.ts $@

View file

@ -13,6 +13,42 @@
"sidePanel": true,
"skeletonLoader": true
},
"typeSupportPresets": {
"numberLike": {
"supported": ["number", "boolean"],
"partialSupport": [
{ "type": "longform", "message": "stringAsNumber" },
{ "type": "string", "message": "stringAsNumber" },
{ "type": "bigint", "message": "stringAsNumber" },
{ "type": "options", "message": "stringAsNumber" },
{ "type": "formula", "message": "stringAsNumber" },
{ "type": "datetime", "message": "dateAsNumber"}
],
"unsupported": [
{ "type": "json", "message": "jsonPrimitivesOnly" }
]
},
"stringLike": {
"supported": ["string", "number", "bigint", "options", "longform", "boolean", "datetime"],
"unsupported": [
{ "type": "json", "message": "jsonPrimitivesOnly" }
]
},
"datetimeLike": {
"supported": ["datetime"],
"partialSupport": [
{ "type": "longform", "message": "stringAsDate" },
{ "type": "string", "message": "stringAsDate" },
{ "type": "options", "message": "stringAsDate" },
{ "type": "formula", "message": "stringAsDate" },
{ "type": "bigint", "message": "stringAsDate" },
{ "type": "number", "message": "numberAsDate"}
],
"unsupported": [
{ "type": "json", "message": "jsonPrimitivesOnly" }
]
}
},
"layout": {
"name": "Layout",
"description": "This component is specific only to layouts",
@ -1602,6 +1638,7 @@
]
},
"bar": {
"documentationLink": "https://docs.budibase.com/docs/bar-chart",
"name": "Bar Chart",
"description": "Bar chart",
"icon": "GraphBarVertical",
@ -1626,6 +1663,11 @@
"label": "Label column",
"key": "labelColumn",
"dependsOn": "dataProvider",
"explanation": {
"typeSupport": {
"preset": "stringLike"
}
},
"required": true
},
{
@ -1633,6 +1675,11 @@
"label": "Data columns",
"key": "valueColumns",
"dependsOn": "dataProvider",
"explanation": {
"typeSupport": {
"preset": "numberLike"
}
},
"required": true
},
{
@ -1760,6 +1807,7 @@
]
},
"line": {
"documentationLink": "https://docs.budibase.com/docs/line-chart",
"name": "Line Chart",
"description": "Line chart",
"icon": "GraphTrend",
@ -1784,6 +1832,11 @@
"label": "Label column",
"key": "labelColumn",
"dependsOn": "dataProvider",
"explanation": {
"typeSupport": {
"preset": "stringLike"
}
},
"required": true
},
{
@ -1791,6 +1844,11 @@
"label": "Data columns",
"key": "valueColumns",
"dependsOn": "dataProvider",
"explanation": {
"typeSupport": {
"preset": "numberLike"
}
},
"required": true
},
{
@ -1913,6 +1971,7 @@
]
},
"area": {
"documentationLink": "https://docs.budibase.com/docs/area-chart",
"name": "Area Chart",
"description": "Line chart",
"icon": "GraphAreaStacked",
@ -1937,6 +1996,11 @@
"label": "Label column",
"key": "labelColumn",
"dependsOn": "dataProvider",
"explanation": {
"typeSupport": {
"preset": "stringLike"
}
},
"required": true
},
{
@ -1944,6 +2008,11 @@
"label": "Data columns",
"key": "valueColumns",
"dependsOn": "dataProvider",
"explanation": {
"typeSupport": {
"preset": "numberLike"
}
},
"required": true
},
{
@ -2078,6 +2147,7 @@
]
},
"pie": {
"documentationLink": "https://docs.budibase.com/docs/pie-donut-chart",
"name": "Pie Chart",
"description": "Pie chart",
"icon": "GraphPie",
@ -2102,13 +2172,23 @@
"label": "Label column",
"key": "labelColumn",
"dependsOn": "dataProvider",
"explanation": {
"typeSupport": {
"preset": "stringLike"
}
},
"required": true
},
{
"type": "field",
"label": "Data columns",
"label": "Data column",
"key": "valueColumn",
"dependsOn": "dataProvider",
"explanation": {
"typeSupport": {
"preset": "numberLike"
}
},
"required": true
},
{
@ -2207,6 +2287,7 @@
]
},
"donut": {
"documentationLink": "https://docs.budibase.com/docs/pie-donut-chart",
"name": "Donut Chart",
"description": "Donut chart",
"icon": "GraphDonut",
@ -2231,6 +2312,11 @@
"label": "Label column",
"key": "labelColumn",
"dependsOn": "dataProvider",
"explanation": {
"typeSupport": {
"preset": "stringLike"
}
},
"required": true
},
{
@ -2238,6 +2324,11 @@
"label": "Data columns",
"key": "valueColumn",
"dependsOn": "dataProvider",
"explanation": {
"typeSupport": {
"preset": "numberLike"
}
},
"required": true
},
{
@ -2336,6 +2427,7 @@
]
},
"candlestick": {
"documentationLink": "https://docs.budibase.com/docs/candlestick-chart",
"name": "Candlestick Chart",
"description": "Candlestick chart",
"icon": "GraphBarVerticalStacked",
@ -2360,6 +2452,11 @@
"label": "Date column",
"key": "dateColumn",
"dependsOn": "dataProvider",
"explanation": {
"typeSupport": {
"preset": "datetimeLike"
}
},
"required": true
},
{
@ -2367,6 +2464,11 @@
"label": "Open column",
"key": "openColumn",
"dependsOn": "dataProvider",
"explanation": {
"typeSupport": {
"preset": "numberLike"
}
},
"required": true
},
{
@ -2374,6 +2476,11 @@
"label": "Close column",
"key": "closeColumn",
"dependsOn": "dataProvider",
"explanation": {
"typeSupport": {
"preset": "numberLike"
}
},
"required": true
},
{
@ -2381,6 +2488,11 @@
"label": "High column",
"key": "highColumn",
"dependsOn": "dataProvider",
"explanation": {
"typeSupport": {
"preset": "numberLike"
}
},
"required": true
},
{
@ -2388,6 +2500,11 @@
"label": "Low column",
"key": "lowColumn",
"dependsOn": "dataProvider",
"explanation": {
"typeSupport": {
"preset": "numberLike"
}
},
"required": true
},
{
@ -2427,6 +2544,7 @@
]
},
"histogram": {
"documentationLink": "https://docs.budibase.com/docs/histogram-chart",
"name": "Histogram Chart",
"description": "Histogram chart",
"icon": "Histogram",
@ -2434,7 +2552,6 @@
"width": 600,
"height": 400
},
"requiredAncestors": ["dataprovider"],
"settings": [
{
"type": "text",
@ -2452,6 +2569,11 @@
"label": "Data column",
"key": "valueColumn",
"dependsOn": "dataProvider",
"explanation": {
"typeSupport": {
"preset": "numberLike"
}
},
"required": true
},
{
@ -2746,6 +2868,14 @@
"type": "plainText",
"label": "Label",
"key": "label"
},
{
"type": "number",
"label": "Initial width",
"key": "width",
"placeholder": "Auto",
"min": 80,
"max": 9999
}
]
},
@ -4107,6 +4237,55 @@
}
]
},
"signaturesinglefield": {
"name": "Signature",
"icon": "AnnotatePen",
"styles": ["size"],
"size": {
"width": 400,
"height": 50
},
"settings": [
{
"type": "field/signature_single",
"label": "Field",
"key": "field",
"required": true
},
{
"type": "text",
"label": "Label",
"key": "label"
},
{
"type": "text",
"label": "Help text",
"key": "helpText"
},
{
"type": "boolean",
"label": "Disabled",
"key": "disabled",
"defaultValue": false
},
{
"type": "event",
"label": "On change",
"key": "onChange",
"context": [
{
"label": "Field Value",
"key": "value"
}
]
},
{
"type": "validation/signature_single",
"label": "Validation",
"key": "validation"
}
]
},
"embeddedmap": {
"name": "Embedded Map",
"icon": "Location",
@ -4372,7 +4551,7 @@
"defaultValue": false
},
{
"type": "validation/attachment",
"type": "validation/attachment_single",
"label": "Validation",
"key": "validation"
},
@ -5256,6 +5435,11 @@
"label": "Label column",
"key": "labelColumn",
"dependsOn": "dataSource",
"explanation": {
"typeSupport": {
"preset": "stringLike"
}
},
"required": true
},
{
@ -5263,6 +5447,11 @@
"label": "Data column",
"key": "valueColumn",
"dependsOn": "dataSource",
"explanation": {
"typeSupport": {
"preset": "numberLike"
}
},
"required": true
}
]
@ -5281,6 +5470,11 @@
"label": "Label column",
"key": "labelColumn",
"dependsOn": "dataSource",
"explanation": {
"typeSupport": {
"preset": "stringLike"
}
},
"required": true
},
{
@ -5288,6 +5482,11 @@
"label": "Data column",
"key": "valueColumn",
"dependsOn": "dataSource",
"explanation": {
"typeSupport": {
"preset": "numberLike"
}
},
"required": true
}
]
@ -5306,6 +5505,11 @@
"label": "Label column",
"key": "labelColumn",
"dependsOn": "dataSource",
"explanation": {
"typeSupport": {
"preset": "stringLike"
}
},
"required": true
},
{
@ -5313,6 +5517,11 @@
"label": "Data columns",
"key": "valueColumns",
"dependsOn": "dataSource",
"explanation": {
"typeSupport": {
"preset": "numberLike"
}
},
"required": true
},
{
@ -5360,6 +5569,11 @@
"label": "Value column",
"key": "valueColumn",
"dependsOn": "dataSource",
"explanation": {
"typeSupport": {
"preset": "numberLike"
}
},
"required": true
},
{
@ -5401,6 +5615,11 @@
"label": "Label column",
"key": "labelColumn",
"dependsOn": "dataSource",
"explanation": {
"typeSupport": {
"preset": "stringLike"
}
},
"required": true
},
{
@ -5408,6 +5627,11 @@
"label": "Data columns",
"key": "valueColumns",
"dependsOn": "dataSource",
"explanation": {
"typeSupport": {
"preset": "numberLike"
}
},
"required": true
},
{
@ -5450,6 +5674,11 @@
"label": "Label columns",
"key": "labelColumn",
"dependsOn": "dataSource",
"explanation": {
"typeSupport": {
"preset": "stringLike"
}
},
"required": true
},
{
@ -5457,6 +5686,11 @@
"label": "Data columns",
"key": "valueColumns",
"dependsOn": "dataSource",
"explanation": {
"typeSupport": {
"preset": "numberLike"
}
},
"required": true
},
{
@ -5511,6 +5745,11 @@
"label": "Date column",
"key": "dateColumn",
"dependsOn": "dataSource",
"explanation": {
"typeSupport": {
"preset": "datetimeLike"
}
},
"required": true
},
{
@ -5518,6 +5757,11 @@
"label": "Open column",
"key": "openColumn",
"dependsOn": "dataSource",
"explanation": {
"typeSupport": {
"preset": "numberLike"
}
},
"required": true
},
{
@ -5525,6 +5769,11 @@
"label": "Close column",
"key": "closeColumn",
"dependsOn": "dataSource",
"explanation": {
"typeSupport": {
"preset": "numberLike"
}
},
"required": true
},
{
@ -5532,6 +5781,11 @@
"label": "High column",
"key": "highColumn",
"dependsOn": "dataSource",
"explanation": {
"typeSupport": {
"preset": "numberLike"
}
},
"required": true
},
{
@ -5539,6 +5793,11 @@
"label": "Low column",
"key": "lowColumn",
"dependsOn": "dataSource",
"explanation": {
"typeSupport": {
"preset": "numberLike"
}
},
"required": true
},
{

View file

@ -25,7 +25,7 @@
"@budibase/string-templates": "0.0.0",
"@budibase/types": "0.0.0",
"@spectrum-css/card": "3.0.3",
"apexcharts": "^3.22.1",
"apexcharts": "^3.48.0",
"dayjs": "^1.10.8",
"downloadjs": "1.4.7",
"html5-qrcode": "^2.2.1",
@ -33,8 +33,8 @@
"sanitize-html": "^2.7.0",
"screenfull": "^6.0.1",
"shortid": "^2.2.15",
"svelte-apexcharts": "^1.0.2",
"svelte-spa-router": "^4.0.1"
"svelte-spa-router": "^4.0.1",
"atrament": "^4.3.0"
},
"devDependencies": {
"@rollup/plugin-alias": "^5.1.0",

View file

@ -193,6 +193,9 @@
$: pad = pad || (interactive && hasChildren && inDndPath)
$: $dndIsDragging, (pad = false)
$: currentTheme = $context?.device?.theme
$: darkMode = !currentTheme?.includes("light")
// Update component context
$: store.set({
id,
@ -222,6 +225,7 @@
parent: id,
ancestors: [...($component?.ancestors ?? []), instance._component],
path: [...($component?.path ?? []), id],
darkMode,
})
const initialise = (instance, force = false) => {
@ -283,10 +287,23 @@
const dependsOnKey = setting.dependsOn.setting || setting.dependsOn
const dependsOnValue = setting.dependsOn.value
const realDependentValue = instance[dependsOnKey]
const sectionDependsOnKey =
setting.sectionDependsOn?.setting || setting.sectionDependsOn
const sectionDependsOnValue = setting.sectionDependsOn?.value
const sectionRealDependentValue = instance[sectionDependsOnKey]
if (dependsOnValue == null && realDependentValue == null) {
return false
}
if (dependsOnValue !== realDependentValue) {
if (dependsOnValue != null && dependsOnValue !== realDependentValue) {
return false
}
if (
sectionDependsOnValue != null &&
sectionDependsOnValue !== sectionRealDependentValue
) {
return false
}
}

View file

@ -37,13 +37,16 @@
let grid
let gridContext
let minHeight
let minHeight = 0
$: currentTheme = $context?.device?.theme
$: darkMode = !currentTheme?.includes("light")
$: parsedColumns = getParsedColumns(columns)
$: columnWhitelist = parsedColumns.filter(x => x.active).map(x => x.field)
$: schemaOverrides = getSchemaOverrides(parsedColumns)
$: enrichedButtons = enrichButtons(buttons)
$: selectedRows = deriveSelectedRows(gridContext)
$: styles = patchStyles($component.styles, minHeight)
$: data = { selectedRows: $selectedRows }
$: actions = [
{
@ -84,9 +87,11 @@
const getSchemaOverrides = columns => {
let overrides = {}
columns.forEach(column => {
columns.forEach((column, idx) => {
overrides[column.field] = {
displayName: column.label,
width: column.width,
order: idx,
}
})
return overrides
@ -128,52 +133,55 @@
)
}
const patchStyles = (styles, minHeight) => {
return {
...styles,
normal: {
...styles?.normal,
"min-height": `${minHeight}px`,
},
}
}
onMount(() => {
gridContext = grid.getContext()
gridContext.minHeight.subscribe($height => (minHeight = $height))
})
</script>
<span style="--min-height:{minHeight}px">
<div
use:styleable={$component.styles}
class:in-builder={$builderStore.inBuilder}
>
<Grid
bind:this={grid}
datasource={table}
{API}
{stripeRows}
{quiet}
{initialFilter}
{initialSortColumn}
{initialSortOrder}
{fixedRowHeight}
{columnWhitelist}
{schemaOverrides}
canAddRows={allowAddRows}
canEditRows={allowEditRows}
canDeleteRows={allowDeleteRows}
canEditColumns={false}
canExpandRows={false}
canSaveSchema={false}
canSelectRows={true}
showControls={false}
notifySuccess={notificationStore.actions.success}
notifyError={notificationStore.actions.error}
buttons={enrichedButtons}
isCloud={$environmentStore.cloud}
on:rowclick={e => onRowClick?.({ row: e.detail })}
/>
</div>
</span>
<div use:styleable={styles} class:in-builder={$builderStore.inBuilder}>
<Grid
bind:this={grid}
datasource={table}
{API}
{stripeRows}
{quiet}
{darkMode}
{initialFilter}
{initialSortColumn}
{initialSortOrder}
{fixedRowHeight}
{columnWhitelist}
{schemaOverrides}
canAddRows={allowAddRows}
canEditRows={allowEditRows}
canDeleteRows={allowDeleteRows}
canEditColumns={false}
canExpandRows={false}
canSaveSchema={false}
canSelectRows={true}
showControls={false}
notifySuccess={notificationStore.actions.success}
notifyError={notificationStore.actions.error}
buttons={enrichedButtons}
isCloud={$environmentStore.cloud}
on:rowclick={e => onRowClick?.({ row: e.detail })}
/>
</div>
<Provider {data} {actions} />
<style>
span {
display: contents;
}
div {
display: flex;
flex-direction: column;
@ -182,7 +190,6 @@
border-radius: 4px;
overflow: hidden;
height: 410px;
min-height: var(--min-height);
}
div.in-builder :global(*) {
pointer-events: none;

View file

@ -32,7 +32,7 @@
// Bar/Line/Area
export let valueColumns
export let yAxisUnits
export let valueUnits
export let yAxisLabel
export let xAxisLabel
export let curve
@ -51,8 +51,6 @@
export let bucketCount
let dataProviderId
$: colors = c1 && c2 && c3 && c4 && c5 ? [c1, c2, c3, c4, c5] : null
</script>
<Block>
@ -84,8 +82,7 @@
dataLabels,
legend,
animate,
...colors,
yAxisUnits,
valueUnits,
yAxisLabel,
xAxisLabel,
stacked,
@ -98,6 +95,11 @@
lowColumn,
dateColumn,
bucketCount,
c1,
c2,
c3,
c4,
c5,
}}
/>
{/if}

Some files were not shown because too many files have changed in this diff Show more