Full support for signature field type and some feedback changes
This commit is contained in:
parent
fc35bfd83b
commit
1e5506b8c3
|
@ -1,65 +1,80 @@
|
||||||
<!--
|
|
||||||
|
|
||||||
Should this just be Canvas.svelte?
|
|
||||||
A drawing zone?
|
|
||||||
Shift it somewhere else?
|
|
||||||
|
|
||||||
width, height, toBase64, toFileObj
|
|
||||||
-->
|
|
||||||
<script>
|
<script>
|
||||||
import { onMount } from "svelte"
|
import { onMount } from "svelte"
|
||||||
|
import Icon from "../../Icon/Icon.svelte"
|
||||||
|
import { createEventDispatcher } from "svelte"
|
||||||
|
|
||||||
export let canvasWidth
|
const dispatch = createEventDispatcher()
|
||||||
export let canvasHeight
|
|
||||||
|
|
||||||
let canvasRef
|
export let value
|
||||||
let canvas
|
export let disabled = false
|
||||||
let mouseDown = false
|
export let editable = true
|
||||||
let lastOffsetX, lastOffsetY
|
export let width = 400
|
||||||
|
export let height = 220
|
||||||
let touching = false
|
export let saveIcon = false
|
||||||
let touchmove = false
|
export let isDark
|
||||||
|
|
||||||
let debug = true
|
|
||||||
let debugData
|
|
||||||
|
|
||||||
export function toDataUrl() {
|
export function toDataUrl() {
|
||||||
return canvasRef.toDataURL()
|
// PNG to preserve transparency
|
||||||
|
return canvasRef.toDataURL("image/png")
|
||||||
}
|
}
|
||||||
|
|
||||||
export function clear() {
|
export function toFile() {
|
||||||
return canvas.clearRect(0, 0, canvas.width, canvas.height)
|
const data = canvas
|
||||||
|
.getImageData(0, 0, width, height)
|
||||||
|
.data.some(channel => channel !== 0)
|
||||||
|
|
||||||
|
if (!data) {
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const updated = (x, y) => {
|
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 canvas.clearRect(0, 0, canvasWidth, canvasHeight)
|
||||||
|
}
|
||||||
|
|
||||||
|
const updatedPos = (x, y) => {
|
||||||
return lastOffsetX != x || lastOffsetY != y
|
return lastOffsetX != x || lastOffsetY != y
|
||||||
}
|
}
|
||||||
|
|
||||||
// Needs touch handling
|
|
||||||
const handleDraw = e => {
|
const handleDraw = e => {
|
||||||
// e.touches[0] there should ol
|
|
||||||
|
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
|
if (disabled || !editable) {
|
||||||
|
return
|
||||||
|
}
|
||||||
var rect = canvasRef.getBoundingClientRect()
|
var rect = canvasRef.getBoundingClientRect()
|
||||||
const canvasX = e.offsetX || e.targetTouches?.[0].pageX - rect.left
|
const canvasX = e.offsetX || e.targetTouches?.[0].pageX - rect.left
|
||||||
const canvasY = e.offsetY || e.targetTouches?.[0].pageY - rect.top
|
const canvasY = e.offsetY || e.targetTouches?.[0].pageY - rect.top
|
||||||
|
|
||||||
const coords = { x: Math.round(canvasX), y: Math.round(canvasY) }
|
const coords = { x: Math.round(canvasX), y: Math.round(canvasY) }
|
||||||
draw(coords.x, coords.y)
|
draw(coords.x, coords.y)
|
||||||
debugData = {
|
|
||||||
coords,
|
|
||||||
t0x: Math.round(e.touches?.[0].clientX),
|
|
||||||
t0y: Math.round(e.touches?.[0].clientY),
|
|
||||||
mouseOffx: e.offsetX,
|
|
||||||
mouseOffy: e.offsetY,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const draw = (xPos, yPos) => {
|
const draw = (xPos, yPos) => {
|
||||||
if (mouseDown && updated(xPos, yPos)) {
|
if (drawing && updatedPos(xPos, yPos)) {
|
||||||
canvas.miterLimit = 3
|
canvas.miterLimit = 2
|
||||||
canvas.lineWidth = 3
|
canvas.lineWidth = 2
|
||||||
canvas.lineJoin = "round"
|
canvas.lineJoin = "round"
|
||||||
canvas.lineCap = "round"
|
canvas.lineCap = "round"
|
||||||
canvas.strokeStyle = "white"
|
canvas.strokeStyle = "white"
|
||||||
|
@ -72,69 +87,199 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
const stopTracking = () => {
|
const stopTracking = () => {
|
||||||
mouseDown = false
|
if (!canvas) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
canvas.closePath()
|
||||||
|
drawing = false
|
||||||
lastOffsetX = null
|
lastOffsetX = null
|
||||||
lastOffsetY = null
|
lastOffsetY = null
|
||||||
}
|
}
|
||||||
|
|
||||||
const canvasMouseDown = e => {
|
const canvasContact = e => {
|
||||||
// if (e.button != 0) {
|
if (disabled || !editable || (!e.targetTouches && e.button != 0)) {
|
||||||
// return
|
return
|
||||||
// }
|
} else if (!updated) {
|
||||||
mouseDown = true
|
updated = true
|
||||||
canvas.moveTo(e.offsetX, e.offsetY)
|
clearCanvas()
|
||||||
|
}
|
||||||
|
drawing = true
|
||||||
canvas.beginPath()
|
canvas.beginPath()
|
||||||
|
canvas.moveTo(e.offsetX, e.offsetY)
|
||||||
|
}
|
||||||
|
|
||||||
|
let canvasRef
|
||||||
|
let canvas
|
||||||
|
let canvasWrap
|
||||||
|
let drawing = false
|
||||||
|
let updated = false
|
||||||
|
let lastOffsetX, lastOffsetY
|
||||||
|
let canvasWidth
|
||||||
|
let canvasHeight
|
||||||
|
let signature
|
||||||
|
let urlFailed
|
||||||
|
|
||||||
|
$: if (value) {
|
||||||
|
const [attachment] = value || []
|
||||||
|
signature = attachment
|
||||||
|
}
|
||||||
|
|
||||||
|
$: if (signature?.url) {
|
||||||
|
updated = false
|
||||||
}
|
}
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
|
if (!editable) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
canvasWrap.style.width = `${width}px`
|
||||||
|
canvasWrap.style.height = `${height}px`
|
||||||
|
|
||||||
|
const { width: wrapWidth, height: wrapHeight } =
|
||||||
|
canvasWrap.getBoundingClientRect()
|
||||||
|
|
||||||
|
canvasHeight = wrapHeight
|
||||||
|
canvasWidth = wrapWidth
|
||||||
|
|
||||||
canvas = canvasRef.getContext("2d")
|
canvas = canvasRef.getContext("2d")
|
||||||
canvas.imageSmoothingEnabled = true
|
canvas.imageSmoothingEnabled = true
|
||||||
canvas.imageSmoothingQuality = "high"
|
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div>
|
<div class="signature" class:light={!isDark} class:image-error={urlFailed}>
|
||||||
<div>{JSON.stringify(debugData, null, 2)}</div>
|
{#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 signature?.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 && signature?.url}
|
||||||
|
<!-- svelte-ignore a11y-missing-attribute -->
|
||||||
|
{#if !urlFailed}
|
||||||
|
<img
|
||||||
|
src={signature?.url}
|
||||||
|
on:error={() => {
|
||||||
|
urlFailed = true
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{:else}
|
||||||
|
Could not load signature
|
||||||
|
{/if}
|
||||||
|
{:else}
|
||||||
|
<div bind:this={canvasWrap} class="canvas-wrap">
|
||||||
<canvas
|
<canvas
|
||||||
id="canvas"
|
id="canvas"
|
||||||
width={200}
|
width={canvasWidth}
|
||||||
height={220}
|
height={canvasHeight}
|
||||||
bind:this={canvasRef}
|
bind:this={canvasRef}
|
||||||
on:mousemove={handleDraw}
|
on:mousemove={handleDraw}
|
||||||
on:mousedown={canvasMouseDown}
|
on:mousedown={canvasContact}
|
||||||
on:mouseup={stopTracking}
|
on:mouseup={stopTracking}
|
||||||
on:mouseleave={stopTracking}
|
on:mouseleave={stopTracking}
|
||||||
on:touchstart={e => {
|
on:touchstart={canvasContact}
|
||||||
touching = true
|
on:touchend={stopTracking}
|
||||||
canvasMouseDown(e)
|
on:touchmove={handleDraw}
|
||||||
}}
|
on:touchleave={stopTracking}
|
||||||
on:touchend={e => {
|
|
||||||
touching = false
|
|
||||||
touchmove = false
|
|
||||||
stopTracking(e)
|
|
||||||
}}
|
|
||||||
on:touchmove={e => {
|
|
||||||
touchmove = true
|
|
||||||
handleDraw(e)
|
|
||||||
}}
|
|
||||||
on:touchleave={e => {
|
|
||||||
touching = false
|
|
||||||
touchmove = false
|
|
||||||
stopTracking(e)
|
|
||||||
}}
|
|
||||||
class:touching={touching && debug}
|
|
||||||
class:touchmove={touchmove && debug}
|
|
||||||
/>
|
/>
|
||||||
|
{#if editable}
|
||||||
|
<div class="indicator-overlay">
|
||||||
|
<div class="sign-here">
|
||||||
|
<Icon name="Close" />
|
||||||
|
<hr />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
#canvas {
|
.indicator-overlay {
|
||||||
border: 1px solid blueviolet;
|
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;
|
||||||
}
|
}
|
||||||
#canvas.touching {
|
.sign-here {
|
||||||
border-color: aquamarine;
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: var(--spectrum-global-dimension-size-150);
|
||||||
}
|
}
|
||||||
#canvas.touchmove {
|
.sign-here hr {
|
||||||
border-color: rgb(227, 102, 68);
|
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.light img,
|
||||||
|
.signature.light #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>
|
</style>
|
||||||
|
|
|
@ -173,6 +173,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.spectrum-Modal {
|
.spectrum-Modal {
|
||||||
|
border: 2px solid var(--spectrum-global-color-gray-200);
|
||||||
overflow: visible;
|
overflow: visible;
|
||||||
max-height: none;
|
max-height: none;
|
||||||
margin: 40px 0;
|
margin: 40px 0;
|
||||||
|
|
|
@ -27,6 +27,7 @@
|
||||||
export let secondaryButtonText = undefined
|
export let secondaryButtonText = undefined
|
||||||
export let secondaryAction = undefined
|
export let secondaryAction = undefined
|
||||||
export let secondaryButtonWarning = false
|
export let secondaryButtonWarning = false
|
||||||
|
export let enableGrid = true
|
||||||
|
|
||||||
const { hide, cancel } = getContext(Context.Modal)
|
const { hide, cancel } = getContext(Context.Modal)
|
||||||
let loading = false
|
let loading = false
|
||||||
|
@ -63,12 +64,13 @@
|
||||||
class:spectrum-Dialog--medium={size === "M"}
|
class:spectrum-Dialog--medium={size === "M"}
|
||||||
class:spectrum-Dialog--large={size === "L"}
|
class:spectrum-Dialog--large={size === "L"}
|
||||||
class:spectrum-Dialog--extraLarge={size === "XL"}
|
class:spectrum-Dialog--extraLarge={size === "XL"}
|
||||||
|
class:no-grid={!enableGrid}
|
||||||
style="position: relative;"
|
style="position: relative;"
|
||||||
role="dialog"
|
role="dialog"
|
||||||
tabindex="-1"
|
tabindex="-1"
|
||||||
aria-modal="true"
|
aria-modal="true"
|
||||||
>
|
>
|
||||||
<div class="spectrum-Dialog-grid">
|
<div class="modal-core" class:spectrum-Dialog-grid={enableGrid}>
|
||||||
{#if title || $$slots.header}
|
{#if title || $$slots.header}
|
||||||
<h1
|
<h1
|
||||||
class="spectrum-Dialog-heading spectrum-Dialog-heading--noHeader"
|
class="spectrum-Dialog-heading spectrum-Dialog-heading--noHeader"
|
||||||
|
@ -153,6 +155,25 @@
|
||||||
.spectrum-Dialog-content {
|
.spectrum-Dialog-content {
|
||||||
overflow: visible;
|
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 {
|
.spectrum-Dialog-heading {
|
||||||
font-family: var(--font-accent);
|
font-family: var(--font-accent);
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
<script>
|
<script>
|
||||||
import { datasources, tables, integrations, appStore } from "stores/builder"
|
import { datasources, tables, integrations, appStore } from "stores/builder"
|
||||||
|
import { themeStore } from "stores/portal"
|
||||||
import EditRolesButton from "./buttons/EditRolesButton.svelte"
|
import EditRolesButton from "./buttons/EditRolesButton.svelte"
|
||||||
import { TableNames } from "constants"
|
import { TableNames } from "constants"
|
||||||
import { Grid } from "@budibase/frontend-core"
|
import { Grid } from "@budibase/frontend-core"
|
||||||
|
@ -37,6 +38,9 @@
|
||||||
})
|
})
|
||||||
$: relationshipsEnabled = relationshipSupport(tableDatasource)
|
$: relationshipsEnabled = relationshipSupport(tableDatasource)
|
||||||
|
|
||||||
|
$: currentTheme = $themeStore?.theme
|
||||||
|
$: isDark = !currentTheme.includes("light")
|
||||||
|
|
||||||
const relationshipSupport = datasource => {
|
const relationshipSupport = datasource => {
|
||||||
const integration = $integrations[datasource?.source]
|
const integration = $integrations[datasource?.source]
|
||||||
return !isInternal && integration?.relationships !== false
|
return !isInternal && integration?.relationships !== false
|
||||||
|
@ -55,6 +59,7 @@
|
||||||
<div class="wrapper">
|
<div class="wrapper">
|
||||||
<Grid
|
<Grid
|
||||||
{API}
|
{API}
|
||||||
|
{isDark}
|
||||||
datasource={gridDatasource}
|
datasource={gridDatasource}
|
||||||
canAddRows={!isUsersTable}
|
canAddRows={!isUsersTable}
|
||||||
canDeleteRows={!isUsersTable}
|
canDeleteRows={!isUsersTable}
|
||||||
|
|
|
@ -12,6 +12,7 @@ const TYPES_TO_SKIP = [
|
||||||
FIELDS.FORMULA.type,
|
FIELDS.FORMULA.type,
|
||||||
FIELDS.LONGFORM.type,
|
FIELDS.LONGFORM.type,
|
||||||
FIELDS.ATTACHMENT.type,
|
FIELDS.ATTACHMENT.type,
|
||||||
|
FIELDS.SIGNATURE.type,
|
||||||
internalType,
|
internalType,
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -82,6 +82,7 @@ const componentMap = {
|
||||||
"validation/boolean": ValidationEditor,
|
"validation/boolean": ValidationEditor,
|
||||||
"validation/datetime": ValidationEditor,
|
"validation/datetime": ValidationEditor,
|
||||||
"validation/attachment": ValidationEditor,
|
"validation/attachment": ValidationEditor,
|
||||||
|
"validation/signature": ValidationEditor,
|
||||||
"validation/link": ValidationEditor,
|
"validation/link": ValidationEditor,
|
||||||
"validation/bb_reference": ValidationEditor,
|
"validation/bb_reference": ValidationEditor,
|
||||||
}
|
}
|
||||||
|
|
|
@ -108,6 +108,7 @@
|
||||||
Constraints.MaxFileSize,
|
Constraints.MaxFileSize,
|
||||||
Constraints.MaxUploadSize,
|
Constraints.MaxUploadSize,
|
||||||
],
|
],
|
||||||
|
["signature"]: [Constraints.Required],
|
||||||
["link"]: [
|
["link"]: [
|
||||||
Constraints.Required,
|
Constraints.Required,
|
||||||
Constraints.Contains,
|
Constraints.Contains,
|
||||||
|
|
|
@ -114,7 +114,6 @@ export const FIELDS = {
|
||||||
presence: false,
|
presence: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
// USE the single approach like Adrias update
|
|
||||||
SIGNATURE: {
|
SIGNATURE: {
|
||||||
name: "Signature",
|
name: "Signature",
|
||||||
type: FieldType.SIGNATURE,
|
type: FieldType.SIGNATURE,
|
||||||
|
|
|
@ -4156,7 +4156,7 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "validation/string",
|
"type": "validation/signature",
|
||||||
"label": "Validation",
|
"label": "Validation",
|
||||||
"key": "validation"
|
"key": "validation"
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,6 +23,7 @@
|
||||||
appStore,
|
appStore,
|
||||||
dndComponentPath,
|
dndComponentPath,
|
||||||
dndIsDragging,
|
dndIsDragging,
|
||||||
|
themeStore,
|
||||||
} from "stores"
|
} from "stores"
|
||||||
import { Helpers } from "@budibase/bbui"
|
import { Helpers } from "@budibase/bbui"
|
||||||
import { getActiveConditions, reduceConditionActions } from "utils/conditions"
|
import { getActiveConditions, reduceConditionActions } from "utils/conditions"
|
||||||
|
@ -192,6 +193,7 @@
|
||||||
let pad = false
|
let pad = false
|
||||||
$: pad = pad || (interactive && hasChildren && inDndPath)
|
$: pad = pad || (interactive && hasChildren && inDndPath)
|
||||||
$: $dndIsDragging, (pad = false)
|
$: $dndIsDragging, (pad = false)
|
||||||
|
$: isDark = !$themeStore.theme?.includes("light")
|
||||||
|
|
||||||
// Update component context
|
// Update component context
|
||||||
$: store.set({
|
$: store.set({
|
||||||
|
@ -222,6 +224,7 @@
|
||||||
parent: id,
|
parent: id,
|
||||||
ancestors: [...($component?.ancestors ?? []), instance._component],
|
ancestors: [...($component?.ancestors ?? []), instance._component],
|
||||||
path: [...($component?.path ?? []), id],
|
path: [...($component?.path ?? []), id],
|
||||||
|
isDark,
|
||||||
})
|
})
|
||||||
|
|
||||||
const initialise = (instance, force = false) => {
|
const initialise = (instance, force = false) => {
|
||||||
|
|
|
@ -35,6 +35,7 @@
|
||||||
|
|
||||||
let grid
|
let grid
|
||||||
|
|
||||||
|
$: isDark = $component.isDark
|
||||||
$: columnWhitelist = parsedColumns
|
$: columnWhitelist = parsedColumns
|
||||||
?.filter(col => col.active)
|
?.filter(col => col.active)
|
||||||
?.map(col => col.field)
|
?.map(col => col.field)
|
||||||
|
@ -114,6 +115,7 @@
|
||||||
<Grid
|
<Grid
|
||||||
bind:this={grid}
|
bind:this={grid}
|
||||||
datasource={table}
|
datasource={table}
|
||||||
|
{isDark}
|
||||||
{API}
|
{API}
|
||||||
{stripeRows}
|
{stripeRows}
|
||||||
{initialFilter}
|
{initialFilter}
|
||||||
|
|
|
@ -1,8 +0,0 @@
|
||||||
<script>
|
|
||||||
import SignatureField from "./SignatureField.svelte"
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
Sig Wrap
|
|
||||||
<SignatureField />
|
|
||||||
</div>
|
|
|
@ -1,6 +1,140 @@
|
||||||
<script>
|
<script>
|
||||||
import { CoreSignature } from "@budibase/bbui"
|
import { CoreSignature, ActionButton } from "@budibase/bbui"
|
||||||
|
import { getContext } from "svelte"
|
||||||
|
import Field from "./Field.svelte"
|
||||||
|
import { SignatureModal } from "@budibase/frontend-core/src/components"
|
||||||
|
|
||||||
|
export let field
|
||||||
|
export let label
|
||||||
|
export let disabled = false
|
||||||
|
export let readonly = false
|
||||||
|
export let validation
|
||||||
|
export let onChange
|
||||||
|
export let span
|
||||||
|
export let helpText = null
|
||||||
|
|
||||||
|
let fieldState
|
||||||
|
let fieldApi
|
||||||
|
let fieldSchema
|
||||||
|
let modal
|
||||||
|
|
||||||
|
const { API, notificationStore, builderStore } = getContext("sdk")
|
||||||
|
const context = getContext("context")
|
||||||
|
const formContext = getContext("form")
|
||||||
|
|
||||||
|
const saveSignature = async canvas => {
|
||||||
|
try {
|
||||||
|
const signatureFile = canvas.toFile()
|
||||||
|
let updateValue
|
||||||
|
|
||||||
|
if (signatureFile) {
|
||||||
|
let attachRequest = new FormData()
|
||||||
|
attachRequest.append("file", signatureFile)
|
||||||
|
|
||||||
|
const resp = await API.uploadAttachment({
|
||||||
|
data: attachRequest,
|
||||||
|
tableId: formContext?.dataSource?.tableId,
|
||||||
|
})
|
||||||
|
|
||||||
|
updateValue = resp
|
||||||
|
} else {
|
||||||
|
updateValue = []
|
||||||
|
}
|
||||||
|
|
||||||
|
const changed = fieldApi.setValue(updateValue)
|
||||||
|
if (onChange && changed) {
|
||||||
|
onChange({ value: updateValue })
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
notificationStore.actions.error(
|
||||||
|
`There was a problem saving your signature`
|
||||||
|
)
|
||||||
|
console.error(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const deleteSignature = async () => {
|
||||||
|
const deleteRequest = fieldState?.value.map(item => item.key)
|
||||||
|
|
||||||
|
const changed = fieldApi.setValue([])
|
||||||
|
if (onChange && changed) {
|
||||||
|
onChange({ value: [] })
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await API.deleteAttachments({
|
||||||
|
keys: deleteRequest,
|
||||||
|
tableId: formContext?.dataSource?.tableId,
|
||||||
|
})
|
||||||
|
} catch (error) {
|
||||||
|
notificationStore.actions.error(
|
||||||
|
`There was a problem deleting your signature`
|
||||||
|
)
|
||||||
|
console.error(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$: currentTheme = $context?.device?.theme
|
||||||
|
$: isDark = !currentTheme?.includes("light")
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div>SignatureField</div>
|
<SignatureModal
|
||||||
<CoreSignature />
|
onConfirm={saveSignature}
|
||||||
|
title={fieldSchema?.name}
|
||||||
|
value={fieldState?.value}
|
||||||
|
{isDark}
|
||||||
|
bind:this={modal}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Field
|
||||||
|
{label}
|
||||||
|
{field}
|
||||||
|
disabled={$builderStore.inBuilder || disabled}
|
||||||
|
{readonly}
|
||||||
|
{validation}
|
||||||
|
{span}
|
||||||
|
{helpText}
|
||||||
|
type="signature"
|
||||||
|
bind:fieldState
|
||||||
|
bind:fieldApi
|
||||||
|
bind:fieldSchema
|
||||||
|
defaultValue={[]}
|
||||||
|
>
|
||||||
|
{#if fieldState}
|
||||||
|
{#if (Array.isArray(fieldState?.value) && !fieldState?.value?.length) || !fieldState?.value}
|
||||||
|
<ActionButton
|
||||||
|
fullWidth
|
||||||
|
on:click={() => {
|
||||||
|
modal.show()
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Add signature
|
||||||
|
</ActionButton>
|
||||||
|
{:else}
|
||||||
|
<div class="signature-field">
|
||||||
|
<CoreSignature
|
||||||
|
{isDark}
|
||||||
|
disabled={$builderStore.inBuilder || disabled}
|
||||||
|
editable={false}
|
||||||
|
value={fieldState?.value}
|
||||||
|
on:clear={deleteSignature}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
{/if}
|
||||||
|
</Field>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.signature-field {
|
||||||
|
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>
|
||||||
|
|
|
@ -191,8 +191,8 @@ const parseType = (value, type) => {
|
||||||
return value === true
|
return value === true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse attachments, treating no elements as null
|
// Parse attachments/signatures, treating no elements as null
|
||||||
if (type === FieldTypes.ATTACHMENT) {
|
if (type === FieldTypes.ATTACHMENT || type === FieldTypes.SIGNATURE) {
|
||||||
if (!Array.isArray(value) || !value.length) {
|
if (!Array.isArray(value) || !value.length) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,48 @@
|
||||||
|
<script>
|
||||||
|
import { Modal, ModalContent, Body, CoreSignature } from "@budibase/bbui"
|
||||||
|
|
||||||
|
export let onConfirm = () => {}
|
||||||
|
export let value
|
||||||
|
export let title
|
||||||
|
export let isDark
|
||||||
|
|
||||||
|
export const show = () => {
|
||||||
|
modal.show()
|
||||||
|
}
|
||||||
|
|
||||||
|
let modal
|
||||||
|
let canvas
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Modal bind:this={modal}>
|
||||||
|
<ModalContent
|
||||||
|
showConfirmButton
|
||||||
|
showCancelButton={false}
|
||||||
|
showCloseIcon={false}
|
||||||
|
enableGrid={false}
|
||||||
|
showDivider={false}
|
||||||
|
onConfirm={() => {
|
||||||
|
onConfirm(canvas)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div slot="header">
|
||||||
|
<Body>{title}</Body>
|
||||||
|
</div>
|
||||||
|
<div class="signature-wrap modal">
|
||||||
|
<CoreSignature {isDark} {value} saveIcon={false} bind:this={canvas} />
|
||||||
|
</div>
|
||||||
|
</ModalContent>
|
||||||
|
</Modal>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.signature-wrap {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: flex-start;
|
||||||
|
align-items: stretch;
|
||||||
|
background-color: var(--spectrum-global-color-gray-50);
|
||||||
|
color: var(--spectrum-alias-text-color);
|
||||||
|
box-sizing: border-box;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -1,7 +1,9 @@
|
||||||
<script>
|
<script>
|
||||||
import { onMount, getContext } from "svelte"
|
import { onMount, getContext } from "svelte"
|
||||||
import { CoreSignature } from "@budibase/bbui"
|
import { SignatureModal } from "@budibase/frontend-core/src/components"
|
||||||
|
import { CoreSignature, ActionButton } from "@budibase/bbui"
|
||||||
|
|
||||||
|
export let schema
|
||||||
export let value
|
export let value
|
||||||
export let focused = false
|
export let focused = false
|
||||||
export let onChange
|
export let onChange
|
||||||
|
@ -9,12 +11,18 @@
|
||||||
export let api
|
export let api
|
||||||
export let invertX = false
|
export let invertX = false
|
||||||
export let invertY = false
|
export let invertY = false
|
||||||
// export let schema
|
|
||||||
|
|
||||||
const { API, notifications } = getContext("grid")
|
const { API, notifications, props } = getContext("grid")
|
||||||
|
|
||||||
let isOpen = false
|
let isOpen = false
|
||||||
let sigCanvas
|
let editing = false
|
||||||
|
let signature
|
||||||
|
let modal
|
||||||
|
|
||||||
|
$: if (value) {
|
||||||
|
const [attachment] = value
|
||||||
|
signature = attachment
|
||||||
|
}
|
||||||
|
|
||||||
$: editable = focused && !readonly
|
$: editable = focused && !readonly
|
||||||
$: {
|
$: {
|
||||||
|
@ -35,6 +43,31 @@
|
||||||
isOpen = false
|
isOpen = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const deleteSignature = async () => {
|
||||||
|
onChange([])
|
||||||
|
const deleteRequest = value.map(item => item.key)
|
||||||
|
try {
|
||||||
|
await API.deleteBuilderAttachments(deleteRequest)
|
||||||
|
} catch (e) {
|
||||||
|
$notifications.error(error.message || "Failed to delete signature")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const saveSignature = async sigCanvas => {
|
||||||
|
const signatureFile = sigCanvas.toFile()
|
||||||
|
|
||||||
|
let attachRequest = new FormData()
|
||||||
|
attachRequest.append("file", signatureFile)
|
||||||
|
|
||||||
|
try {
|
||||||
|
const uploadReq = await API.uploadBuilderAttachment(attachRequest)
|
||||||
|
onChange(uploadReq)
|
||||||
|
} catch (error) {
|
||||||
|
$notifications.error(error.message || "Failed to upload attachment")
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
api = {
|
api = {
|
||||||
focus: () => open(),
|
focus: () => open(),
|
||||||
|
@ -47,44 +80,75 @@
|
||||||
|
|
||||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||||
<div class="signature-cell" class:editable on:click={editable ? open : null}>
|
<div
|
||||||
signature cell: open {isOpen}
|
class="signature-cell"
|
||||||
|
class:light={!($props?.isDark || undefined)}
|
||||||
|
class:editable
|
||||||
|
on:click={editable ? open : null}
|
||||||
|
>
|
||||||
|
{#if signature?.url}
|
||||||
|
<!-- svelte-ignore a11y-missing-attribute -->
|
||||||
|
<img src={signature?.url} />
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<SignatureModal
|
||||||
|
onConfirm={saveSignature}
|
||||||
|
title={schema?.name}
|
||||||
|
{value}
|
||||||
|
isDark={$props.isDark}
|
||||||
|
bind:this={modal}
|
||||||
|
/>
|
||||||
|
|
||||||
{#if isOpen}
|
{#if isOpen}
|
||||||
<div class="signature" class:invertX class:invertY>
|
<div class="signature" class:invertX class:invertY class:empty={!signature}>
|
||||||
<button
|
{#if signature?.key}
|
||||||
on:click={() => {
|
<div class="signature-wrap">
|
||||||
console.log(sigCanvas.toDataUrl())
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
check
|
|
||||||
</button>
|
|
||||||
<div class="field-wrap">
|
|
||||||
<CoreSignature
|
<CoreSignature
|
||||||
bind:this={sigCanvas}
|
isDark={$props.isDark}
|
||||||
on:change={() => {
|
editable={false}
|
||||||
console.log("cell change")
|
{value}
|
||||||
}}
|
on:change={saveSignature}
|
||||||
|
on:clear={deleteSignature}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
{:else}
|
||||||
|
<div class="add-signature">
|
||||||
|
<ActionButton
|
||||||
|
fullWidth
|
||||||
|
on:click={() => {
|
||||||
|
editing = true
|
||||||
|
modal.show()
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Add signature
|
||||||
|
</ActionButton>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
.signature {
|
||||||
|
min-width: 320px;
|
||||||
|
}
|
||||||
|
.signature.empty {
|
||||||
|
width: 100%;
|
||||||
|
min-width: unset;
|
||||||
|
}
|
||||||
|
.signature-cell.light img {
|
||||||
|
-webkit-filter: invert(100%);
|
||||||
|
filter: invert(100%);
|
||||||
|
}
|
||||||
.signature-cell {
|
.signature-cell {
|
||||||
/* display: flex;
|
|
||||||
padding: var(--cell-padding);
|
|
||||||
overflow: hidden;
|
|
||||||
user-select: none;
|
|
||||||
position: relative; */
|
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
align-items: stretch;
|
align-items: stretch;
|
||||||
padding: var(--cell-padding);
|
max-width: 320px;
|
||||||
|
padding-left: var(--cell-padding);
|
||||||
|
padding-right: var(--cell-padding);
|
||||||
flex-wrap: nowrap;
|
flex-wrap: nowrap;
|
||||||
gap: var(--cell-spacing);
|
|
||||||
align-self: stretch;
|
align-self: stretch;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
|
@ -96,29 +160,20 @@
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 100%;
|
top: 100%;
|
||||||
left: 0;
|
left: 0;
|
||||||
width: 320px;
|
|
||||||
background: var(--grid-background-alt);
|
background: var(--grid-background-alt);
|
||||||
border: var(--cell-border);
|
border: var(--cell-border);
|
||||||
padding: var(--cell-padding);
|
padding: var(--cell-padding);
|
||||||
box-shadow: 0 0 20px -4px rgba(0, 0, 0, 0.15);
|
|
||||||
border-bottom-left-radius: 2px;
|
|
||||||
border-bottom-right-radius: 2px;
|
|
||||||
}
|
}
|
||||||
.field-wrap {
|
.signature-wrap {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
align-items: stretch;
|
align-items: stretch;
|
||||||
background-color: var(--spectrum-global-color-gray-50);
|
background-color: var(--spectrum-global-color-gray-50);
|
||||||
color: var(--spectrum-alias-text-color);
|
color: var(--spectrum-alias-text-color);
|
||||||
/* font-size: var(--spectrum-alias-item-text-size-m); */
|
|
||||||
box-sizing: border-box;
|
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);
|
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.signature.invertX {
|
.signature.invertX {
|
||||||
left: auto;
|
left: auto;
|
||||||
right: 0;
|
right: 0;
|
||||||
|
@ -127,17 +182,4 @@
|
||||||
transform: translateY(-100%);
|
transform: translateY(-100%);
|
||||||
top: 0;
|
top: 0;
|
||||||
}
|
}
|
||||||
/* .attachment-cell {
|
|
||||||
flex: 1 1 auto;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
align-items: stretch;
|
|
||||||
padding: var(--cell-padding);
|
|
||||||
flex-wrap: nowrap;
|
|
||||||
gap: var(--cell-spacing);
|
|
||||||
align-self: stretch;
|
|
||||||
overflow: hidden;
|
|
||||||
user-select: none;
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -49,6 +49,7 @@
|
||||||
export let notifySuccess = null
|
export let notifySuccess = null
|
||||||
export let notifyError = null
|
export let notifyError = null
|
||||||
export let buttons = null
|
export let buttons = null
|
||||||
|
export let isDark
|
||||||
|
|
||||||
// Unique identifier for DOM nodes inside this instance
|
// Unique identifier for DOM nodes inside this instance
|
||||||
const rand = Math.random()
|
const rand = Math.random()
|
||||||
|
@ -101,6 +102,7 @@
|
||||||
notifySuccess,
|
notifySuccess,
|
||||||
notifyError,
|
notifyError,
|
||||||
buttons,
|
buttons,
|
||||||
|
isDark,
|
||||||
})
|
})
|
||||||
|
|
||||||
// Set context for children to consume
|
// Set context for children to consume
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
export { default as SplitPage } from "./SplitPage.svelte"
|
export { default as SplitPage } from "./SplitPage.svelte"
|
||||||
export { default as TestimonialPage } from "./TestimonialPage.svelte"
|
export { default as TestimonialPage } from "./TestimonialPage.svelte"
|
||||||
|
export { default as SignatureModal } from "./SignatureModal.svelte"
|
||||||
export { default as Testimonial } from "./Testimonial.svelte"
|
export { default as Testimonial } from "./Testimonial.svelte"
|
||||||
export { default as UserAvatar } from "./UserAvatar.svelte"
|
export { default as UserAvatar } from "./UserAvatar.svelte"
|
||||||
export { default as UserAvatars } from "./UserAvatars.svelte"
|
export { default as UserAvatars } from "./UserAvatars.svelte"
|
||||||
|
|
|
@ -175,13 +175,15 @@ export async function validate({
|
||||||
errors[fieldName] = [`${fieldName} is required`]
|
errors[fieldName] = [`${fieldName} is required`]
|
||||||
}
|
}
|
||||||
} else if (
|
} else if (
|
||||||
(type === FieldType.ATTACHMENT || type === FieldType.JSON) &&
|
(type === FieldType.ATTACHMENT ||
|
||||||
|
type === FieldType.SIGNATURE ||
|
||||||
|
type === FieldType.JSON) &&
|
||||||
typeof row[fieldName] === "string"
|
typeof row[fieldName] === "string"
|
||||||
) {
|
) {
|
||||||
// this should only happen if there is an error
|
// this should only happen if there is an error
|
||||||
try {
|
try {
|
||||||
const json = JSON.parse(row[fieldName])
|
const json = JSON.parse(row[fieldName])
|
||||||
if (type === FieldType.ATTACHMENT) {
|
if (type === FieldType.ATTACHMENT || type === FieldType.SIGNATURE) {
|
||||||
if (Array.isArray(json)) {
|
if (Array.isArray(json)) {
|
||||||
row[fieldName] = json
|
row[fieldName] = json
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -34,7 +34,10 @@ export class AttachmentCleanup {
|
||||||
let files: string[] = []
|
let files: string[] = []
|
||||||
const tableSchema = opts.oldTable?.schema || table.schema
|
const tableSchema = opts.oldTable?.schema || table.schema
|
||||||
for (let [key, schema] of Object.entries(tableSchema)) {
|
for (let [key, schema] of Object.entries(tableSchema)) {
|
||||||
if (schema.type !== FieldType.ATTACHMENT) {
|
if (
|
||||||
|
schema.type !== FieldType.ATTACHMENT &&
|
||||||
|
schema.type !== FieldType.SIGNATURE
|
||||||
|
) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
const columnRemoved = opts.oldTable && !table.schema[key]
|
const columnRemoved = opts.oldTable && !table.schema[key]
|
||||||
|
@ -68,9 +71,13 @@ export class AttachmentCleanup {
|
||||||
return AttachmentCleanup.coreCleanup(() => {
|
return AttachmentCleanup.coreCleanup(() => {
|
||||||
let files: string[] = []
|
let files: string[] = []
|
||||||
for (let [key, schema] of Object.entries(table.schema)) {
|
for (let [key, schema] of Object.entries(table.schema)) {
|
||||||
if (schema.type !== FieldType.ATTACHMENT) {
|
if (
|
||||||
|
schema.type !== FieldType.ATTACHMENT &&
|
||||||
|
schema.type !== FieldType.SIGNATURE
|
||||||
|
) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
rows.forEach(row => {
|
rows.forEach(row => {
|
||||||
if (!Array.isArray(row[key])) {
|
if (!Array.isArray(row[key])) {
|
||||||
return
|
return
|
||||||
|
|
|
@ -147,8 +147,11 @@ export async function inputProcessing(
|
||||||
clonedRow[key] = coerce(value, field.type)
|
clonedRow[key] = coerce(value, field.type)
|
||||||
}
|
}
|
||||||
|
|
||||||
// remove any attachment urls, they are generated on read
|
// remove any attachment/signature urls, they are generated on read
|
||||||
if (field.type === FieldType.ATTACHMENT) {
|
if (
|
||||||
|
field.type === FieldType.ATTACHMENT ||
|
||||||
|
field.type === FieldType.SIGNATURE
|
||||||
|
) {
|
||||||
const attachments = clonedRow[key]
|
const attachments = clonedRow[key]
|
||||||
if (attachments?.length) {
|
if (attachments?.length) {
|
||||||
attachments.forEach((attachment: RowAttachment) => {
|
attachments.forEach((attachment: RowAttachment) => {
|
||||||
|
@ -216,7 +219,10 @@ export async function outputProcessing<T extends Row[] | Row>(
|
||||||
|
|
||||||
// process complex types: attachements, bb references...
|
// process complex types: attachements, bb references...
|
||||||
for (let [property, column] of Object.entries(table.schema)) {
|
for (let [property, column] of Object.entries(table.schema)) {
|
||||||
if (column.type === FieldType.ATTACHMENT) {
|
if (
|
||||||
|
column.type === FieldType.ATTACHMENT ||
|
||||||
|
column.type === FieldType.SIGNATURE
|
||||||
|
) {
|
||||||
for (let row of enriched) {
|
for (let row of enriched) {
|
||||||
if (row[property] == null || !Array.isArray(row[property])) {
|
if (row[property] == null || !Array.isArray(row[property])) {
|
||||||
continue
|
continue
|
||||||
|
|
Loading…
Reference in New Issue