Merge branch 'master' into fix/remove-formula-column-subtype-check
This commit is contained in:
commit
2494166cbc
162 changed files with 4724 additions and 1288 deletions
|
@ -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
|
||||
|
|
|
@ -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}
|
||||
/>
|
||||
|
|
|
@ -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)}
|
||||
|
|
|
@ -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)}
|
||||
|
|
267
packages/bbui/src/Form/Core/Signature.svelte
Normal file
267
packages/bbui/src/Form/Core/Signature.svelte
Normal 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>
|
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
/>
|
||||
|
|
|
@ -173,6 +173,7 @@
|
|||
}
|
||||
|
||||
.spectrum-Modal {
|
||||
border: 2px solid var(--spectrum-global-color-gray-200);
|
||||
overflow: visible;
|
||||
max-height: none;
|
||||
margin: 40px 0;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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>
|
||||
|
|
79
packages/bbui/src/Tooltip/Context.svelte
Normal file
79
packages/bbui/src/Tooltip/Context.svelte
Normal 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>
|
|
@ -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"
|
||||
|
|
|
@ -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}>
|
||||
|
|
|
@ -61,6 +61,7 @@
|
|||
selected={automation._id === selectedAutomationId}
|
||||
on:click={() => selectAutomation(automation._id)}
|
||||
selectedBy={$userSelectedResourceMap[automation._id]}
|
||||
disabled={automation.disabled}
|
||||
>
|
||||
<EditAutomationPopover {automation} />
|
||||
</NavItem>
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -412,6 +412,7 @@
|
|||
FIELDS.FORMULA,
|
||||
FIELDS.JSON,
|
||||
FIELDS.BARCODEQR,
|
||||
FIELDS.SIGNATURE_SINGLE,
|
||||
FIELDS.BIGINT,
|
||||
FIELDS.AUTO,
|
||||
]
|
||||
|
|
|
@ -54,6 +54,10 @@
|
|||
label: "Attachment",
|
||||
value: FieldType.ATTACHMENT_SINGLE,
|
||||
},
|
||||
{
|
||||
label: "Signature",
|
||||
value: FieldType.SIGNATURE_SINGLE,
|
||||
},
|
||||
{
|
||||
label: "Attachment list",
|
||||
value: FieldType.ATTACHMENTS,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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={() => {
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
|
|
@ -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 || {}
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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}
|
||||
>
|
||||
|
|
|
@ -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>
|
|
@ -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 }
|
||||
}
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -0,0 +1,12 @@
|
|||
<li>
|
||||
<div class="exampleLine">
|
||||
<slot />
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<style>
|
||||
.exampleLine {
|
||||
display: flex;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
</style>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -0,0 +1,11 @@
|
|||
<div class="section">
|
||||
<slot />
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.section {
|
||||
line-height: 20px;
|
||||
margin-bottom: 13px;
|
||||
overflow: hidden;
|
||||
}
|
||||
</style>
|
|
@ -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>
|
|
@ -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"
|
|
@ -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"
|
|
@ -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>
|
|
@ -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
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
export { default as Explanation } from "./Explanation.svelte"
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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"
|
|
@ -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
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -0,0 +1,9 @@
|
|||
<span class="space">{" "}</span>
|
||||
|
||||
<style>
|
||||
.space {
|
||||
white-space: pre;
|
||||
width: 3px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
</style>
|
|
@ -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>
|
|
@ -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"
|
|
@ -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"
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
{}
|
||||
)
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -108,6 +108,8 @@
|
|||
Constraints.MaxFileSize,
|
||||
Constraints.MaxUploadSize,
|
||||
],
|
||||
["attachment_single"]: [Constraints.Required, Constraints.MaxUploadSize],
|
||||
["signature_single"]: [Constraints.Required],
|
||||
["link"]: [
|
||||
Constraints.Required,
|
||||
Constraints.Contains,
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -191,6 +191,9 @@
|
|||
// Number fields
|
||||
min: setting.min ?? null,
|
||||
max: setting.max ?? null,
|
||||
|
||||
// Field select settings
|
||||
explanation: setting.explanation,
|
||||
}}
|
||||
{bindings}
|
||||
{componentBindings}
|
||||
|
|
|
@ -71,6 +71,7 @@
|
|||
"multifieldselect",
|
||||
"s3upload",
|
||||
"codescanner",
|
||||
"signaturesinglefield",
|
||||
"bbreferencesinglefield",
|
||||
"bbreferencefield"
|
||||
]
|
||||
|
|
|
@ -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 => ({
|
||||
|
|
|
@ -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 = {
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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 = {
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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 $@
|
||||
|
|
|
@ -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
|
||||
},
|
||||
{
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
Loading…
Reference in a new issue