Merge pull request #8153 from Budibase/feature/qr-barcode-reader
Feature/qr barcode reader
This commit is contained in:
commit
aa1293f15a
|
@ -138,6 +138,7 @@ const fieldTypeToComponentMap = {
|
||||||
attachment: "attachmentfield",
|
attachment: "attachmentfield",
|
||||||
link: "relationshipfield",
|
link: "relationshipfield",
|
||||||
json: "jsonfield",
|
json: "jsonfield",
|
||||||
|
barcodeqr: "codescanner",
|
||||||
}
|
}
|
||||||
|
|
||||||
export function makeDatasourceFormComponents(datasource) {
|
export function makeDatasourceFormComponents(datasource) {
|
||||||
|
|
|
@ -261,6 +261,7 @@
|
||||||
} else {
|
} else {
|
||||||
return [
|
return [
|
||||||
FIELDS.STRING,
|
FIELDS.STRING,
|
||||||
|
FIELDS.BARCODEQR,
|
||||||
FIELDS.LONGFORM,
|
FIELDS.LONGFORM,
|
||||||
FIELDS.OPTIONS,
|
FIELDS.OPTIONS,
|
||||||
FIELDS.DATETIME,
|
FIELDS.DATETIME,
|
||||||
|
|
|
@ -124,6 +124,14 @@
|
||||||
label: "Multi-select",
|
label: "Multi-select",
|
||||||
value: FIELDS.ARRAY.type,
|
value: FIELDS.ARRAY.type,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: "Barcode/QR",
|
||||||
|
value: FIELDS.BARCODEQR.type,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Long Form Text",
|
||||||
|
value: FIELDS.LONGFORM.type,
|
||||||
|
},
|
||||||
]
|
]
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -51,6 +51,7 @@ const componentMap = {
|
||||||
"field/link": FormFieldSelect,
|
"field/link": FormFieldSelect,
|
||||||
"field/array": FormFieldSelect,
|
"field/array": FormFieldSelect,
|
||||||
"field/json": FormFieldSelect,
|
"field/json": FormFieldSelect,
|
||||||
|
"field/barcode/qr": FormFieldSelect,
|
||||||
// Some validation types are the same as others, so not all types are
|
// Some validation types are the same as others, so not all types are
|
||||||
// explicitly listed here. e.g. options uses string validation
|
// explicitly listed here. e.g. options uses string validation
|
||||||
"validation/string": ValidationEditor,
|
"validation/string": ValidationEditor,
|
||||||
|
|
|
@ -24,18 +24,17 @@
|
||||||
|
|
||||||
const getOptions = (schema, type) => {
|
const getOptions = (schema, type) => {
|
||||||
let entries = Object.entries(schema ?? {})
|
let entries = Object.entries(schema ?? {})
|
||||||
|
|
||||||
let types = []
|
let types = []
|
||||||
if (type === "field/options") {
|
if (type === "field/options" || type === "field/barcode/qr") {
|
||||||
// allow options to be used on both options and string fields
|
// allow options to be used on both options and string fields
|
||||||
types = [type, "field/string"]
|
types = [type, "field/string"]
|
||||||
} else {
|
} else {
|
||||||
types = [type]
|
types = [type]
|
||||||
}
|
}
|
||||||
|
|
||||||
types = types.map(type => type.split("/")[1])
|
types = types.map(type => type.slice(type.indexOf("/") + 1))
|
||||||
entries = entries.filter(entry => types.includes(entry[1].type))
|
|
||||||
|
|
||||||
|
entries = entries.filter(entry => types.includes(entry[1].type))
|
||||||
return entries.map(entry => entry[0])
|
return entries.map(entry => entry[0])
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -8,6 +8,15 @@ export const FIELDS = {
|
||||||
presence: false,
|
presence: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
BARCODEQR: {
|
||||||
|
name: "Barcode/QR",
|
||||||
|
type: "barcodeqr",
|
||||||
|
constraints: {
|
||||||
|
type: "string",
|
||||||
|
length: {},
|
||||||
|
presence: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
LONGFORM: {
|
LONGFORM: {
|
||||||
name: "Long Form Text",
|
name: "Long Form Text",
|
||||||
type: "longform",
|
type: "longform",
|
||||||
|
@ -148,6 +157,7 @@ export const ALLOWABLE_STRING_OPTIONS = [
|
||||||
FIELDS.STRING,
|
FIELDS.STRING,
|
||||||
FIELDS.OPTIONS,
|
FIELDS.OPTIONS,
|
||||||
FIELDS.LONGFORM,
|
FIELDS.LONGFORM,
|
||||||
|
FIELDS.BARCODEQR,
|
||||||
]
|
]
|
||||||
export const ALLOWABLE_STRING_TYPES = ALLOWABLE_STRING_OPTIONS.map(
|
export const ALLOWABLE_STRING_TYPES = ALLOWABLE_STRING_OPTIONS.map(
|
||||||
opt => opt.type
|
opt => opt.type
|
||||||
|
|
|
@ -66,7 +66,8 @@
|
||||||
"relationshipfield",
|
"relationshipfield",
|
||||||
"datetimefield",
|
"datetimefield",
|
||||||
"multifieldselect",
|
"multifieldselect",
|
||||||
"s3upload"
|
"s3upload",
|
||||||
|
"codescanner"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -3157,6 +3157,56 @@
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"codescanner": {
|
||||||
|
"name": "Barcode/QR Scanner",
|
||||||
|
"icon": "Camera",
|
||||||
|
"styles": [
|
||||||
|
"size"
|
||||||
|
],
|
||||||
|
"illegalChildren": [
|
||||||
|
"section"
|
||||||
|
],
|
||||||
|
"settings": [
|
||||||
|
{
|
||||||
|
"type": "field/barcode/qr",
|
||||||
|
"label": "Field",
|
||||||
|
"key": "field",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"label": "Label",
|
||||||
|
"key": "label"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"label": "Button text",
|
||||||
|
"key": "scanButtonText"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"label": "Default value",
|
||||||
|
"key": "defaultValue"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "boolean",
|
||||||
|
"label": "Disabled",
|
||||||
|
"key": "disabled",
|
||||||
|
"defaultValue": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "boolean",
|
||||||
|
"label": "Allow manual entry",
|
||||||
|
"key": "allowManualEntry",
|
||||||
|
"defaultValue": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "validation/string",
|
||||||
|
"label": "Validation",
|
||||||
|
"key": "validation"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
"embeddedmap": {
|
"embeddedmap": {
|
||||||
"name": "Embedded Map",
|
"name": "Embedded Map",
|
||||||
"icon": "Location",
|
"icon": "Location",
|
||||||
|
@ -4399,7 +4449,9 @@
|
||||||
"formblock": {
|
"formblock": {
|
||||||
"name": "Form Block",
|
"name": "Form Block",
|
||||||
"icon": "Form",
|
"icon": "Form",
|
||||||
"styles": ["size"],
|
"styles": [
|
||||||
|
"size"
|
||||||
|
],
|
||||||
"block": true,
|
"block": true,
|
||||||
"info": "Form blocks are only compatible with internal or SQL tables",
|
"info": "Form blocks are only compatible with internal or SQL tables",
|
||||||
"settings": [
|
"settings": [
|
||||||
|
@ -4407,7 +4459,11 @@
|
||||||
"type": "select",
|
"type": "select",
|
||||||
"label": "Type",
|
"label": "Type",
|
||||||
"key": "actionType",
|
"key": "actionType",
|
||||||
"options": ["Create", "Update", "View"],
|
"options": [
|
||||||
|
"Create",
|
||||||
|
"Update",
|
||||||
|
"View"
|
||||||
|
],
|
||||||
"defaultValue": "Create"
|
"defaultValue": "Create"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -33,6 +33,7 @@
|
||||||
"apexcharts": "^3.22.1",
|
"apexcharts": "^3.22.1",
|
||||||
"dayjs": "^1.10.5",
|
"dayjs": "^1.10.5",
|
||||||
"downloadjs": "1.4.7",
|
"downloadjs": "1.4.7",
|
||||||
|
"html5-qrcode": "^2.2.1",
|
||||||
"leaflet": "^1.7.1",
|
"leaflet": "^1.7.1",
|
||||||
"regexparam": "^1.3.0",
|
"regexparam": "^1.3.0",
|
||||||
"sanitize-html": "^2.7.0",
|
"sanitize-html": "^2.7.0",
|
||||||
|
|
|
@ -27,6 +27,15 @@ export default {
|
||||||
file: `./dist/budibase-client.js`,
|
file: `./dist/budibase-client.js`,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
onwarn(warning, warn) {
|
||||||
|
if (
|
||||||
|
warning.code === "THIS_IS_UNDEFINED" ||
|
||||||
|
warning.code === "CIRCULAR_DEPENDENCY"
|
||||||
|
) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
warn(warning)
|
||||||
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
alias({
|
alias({
|
||||||
entries: [
|
entries: [
|
||||||
|
|
|
@ -0,0 +1,234 @@
|
||||||
|
<script>
|
||||||
|
import { ModalContent, Modal, Icon, ActionButton } from "@budibase/bbui"
|
||||||
|
import { Input, Button, StatusLight } from "@budibase/bbui"
|
||||||
|
import { Html5Qrcode } from "html5-qrcode"
|
||||||
|
import { createEventDispatcher } from "svelte"
|
||||||
|
|
||||||
|
export let value
|
||||||
|
export let disabled = false
|
||||||
|
export let allowManualEntry = false
|
||||||
|
export let scanButtonText = "Scan code"
|
||||||
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
|
let videoEle
|
||||||
|
let camModal
|
||||||
|
let manualMode = false
|
||||||
|
let cameraEnabled
|
||||||
|
let cameraStarted = false
|
||||||
|
let html5QrCode
|
||||||
|
let cameraSetting = { facingMode: "environment" }
|
||||||
|
let cameraConfig = {
|
||||||
|
fps: 25,
|
||||||
|
qrbox: { width: 250, height: 250 },
|
||||||
|
}
|
||||||
|
const onScanSuccess = decodedText => {
|
||||||
|
if (value != decodedText) {
|
||||||
|
dispatch("change", decodedText)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const initReader = async () => {
|
||||||
|
if (html5QrCode) {
|
||||||
|
html5QrCode.stop()
|
||||||
|
}
|
||||||
|
html5QrCode = new Html5Qrcode("reader")
|
||||||
|
return new Promise(resolve => {
|
||||||
|
html5QrCode
|
||||||
|
.start(cameraSetting, cameraConfig, onScanSuccess)
|
||||||
|
.then(() => {
|
||||||
|
resolve({ initialised: true })
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
console.log("There was a problem scanning the image", err)
|
||||||
|
resolve({ initialised: false })
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const checkCamera = async () => {
|
||||||
|
return new Promise(resolve => {
|
||||||
|
Html5Qrcode.getCameras()
|
||||||
|
.then(devices => {
|
||||||
|
if (devices && devices.length) {
|
||||||
|
resolve({ enabled: true })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(e => {
|
||||||
|
console.error(e)
|
||||||
|
resolve({ enabled: false })
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const start = async () => {
|
||||||
|
const status = await initReader()
|
||||||
|
cameraStarted = status.initialised
|
||||||
|
}
|
||||||
|
|
||||||
|
$: if (cameraEnabled && videoEle && !cameraStarted) {
|
||||||
|
start()
|
||||||
|
}
|
||||||
|
|
||||||
|
const showReaderModal = async () => {
|
||||||
|
camModal.show()
|
||||||
|
const camStatus = await checkCamera()
|
||||||
|
cameraEnabled = camStatus.enabled
|
||||||
|
}
|
||||||
|
|
||||||
|
const hideReaderModal = async () => {
|
||||||
|
cameraEnabled = undefined
|
||||||
|
cameraStarted = false
|
||||||
|
if (html5QrCode) {
|
||||||
|
await html5QrCode.stop()
|
||||||
|
html5QrCode = undefined
|
||||||
|
}
|
||||||
|
camModal.hide()
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="scanner-video-wrapper">
|
||||||
|
{#if value && !manualMode}
|
||||||
|
<div class="scanner-value field-display">
|
||||||
|
<StatusLight positive />
|
||||||
|
{value}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if allowManualEntry && manualMode}
|
||||||
|
<div class="manual-input">
|
||||||
|
<Input
|
||||||
|
bind:value
|
||||||
|
on:change={() => {
|
||||||
|
dispatch("change", value)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if value}
|
||||||
|
<ActionButton
|
||||||
|
on:click={() => {
|
||||||
|
dispatch("change", "")
|
||||||
|
}}
|
||||||
|
{disabled}
|
||||||
|
>
|
||||||
|
Clear
|
||||||
|
</ActionButton>
|
||||||
|
{:else}
|
||||||
|
<ActionButton
|
||||||
|
icon="Camera"
|
||||||
|
on:click={() => {
|
||||||
|
showReaderModal()
|
||||||
|
}}
|
||||||
|
{disabled}
|
||||||
|
>
|
||||||
|
{scanButtonText}
|
||||||
|
</ActionButton>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal-wrap">
|
||||||
|
<Modal bind:this={camModal} on:hide={hideReaderModal}>
|
||||||
|
<ModalContent
|
||||||
|
title={scanButtonText}
|
||||||
|
showConfirmButton={false}
|
||||||
|
showCancelButton={false}
|
||||||
|
>
|
||||||
|
<div id="reader" class="container" bind:this={videoEle}>
|
||||||
|
<div class="camera-placeholder">
|
||||||
|
<Icon size="XXL" name="Camera" />
|
||||||
|
{#if cameraEnabled === false}
|
||||||
|
<div>Your camera is disabled.</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{#if cameraEnabled === true}
|
||||||
|
<div class="code-wrap">
|
||||||
|
{#if value}
|
||||||
|
<div class="scanner-value">
|
||||||
|
<StatusLight positive />
|
||||||
|
{value}
|
||||||
|
</div>
|
||||||
|
{:else}
|
||||||
|
<div class="scanner-value">
|
||||||
|
<StatusLight neutral />
|
||||||
|
Searching for code...
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<div slot="footer">
|
||||||
|
<div class="footer-buttons">
|
||||||
|
{#if allowManualEntry && !manualMode}
|
||||||
|
<Button
|
||||||
|
group
|
||||||
|
secondary
|
||||||
|
newStyles
|
||||||
|
on:click={() => {
|
||||||
|
manualMode = true
|
||||||
|
camModal.hide()
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Enter manually
|
||||||
|
</Button>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<Button
|
||||||
|
group
|
||||||
|
cta
|
||||||
|
on:click={() => {
|
||||||
|
camModal.hide()
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Confirm
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ModalContent>
|
||||||
|
</Modal>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
#reader :global(video) {
|
||||||
|
border-radius: 4px;
|
||||||
|
border: 2px solid var(--spectrum-global-color-gray-300);
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.field-display :global(.spectrum-Tags-item) {
|
||||||
|
margin: 0px;
|
||||||
|
}
|
||||||
|
.footer-buttons {
|
||||||
|
display: flex;
|
||||||
|
grid-area: buttonGroup;
|
||||||
|
gap: var(--spectrum-global-dimension-static-size-200);
|
||||||
|
}
|
||||||
|
.scanner-value {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
.field-display {
|
||||||
|
padding-top: var(
|
||||||
|
--spectrum-fieldlabel-side-m-padding-top,
|
||||||
|
var(--spectrum-global-dimension-size-100)
|
||||||
|
);
|
||||||
|
margin-bottom: var(--spacing-m);
|
||||||
|
}
|
||||||
|
.camera-placeholder {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border-radius: 4px;
|
||||||
|
border: 2px solid var(--spectrum-global-color-gray-300);
|
||||||
|
background-color: var(--spectrum-global-color-gray-200);
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--spectrum-global-dimension-static-size-200);
|
||||||
|
}
|
||||||
|
.container,
|
||||||
|
.camera-placeholder {
|
||||||
|
width: 100%;
|
||||||
|
min-height: 240px;
|
||||||
|
}
|
||||||
|
.manual-input {
|
||||||
|
padding-bottom: var(--spacing-m);
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,47 @@
|
||||||
|
<script>
|
||||||
|
import Field from "./Field.svelte"
|
||||||
|
import CodeScanner from "./CodeScanner.svelte"
|
||||||
|
|
||||||
|
export let field
|
||||||
|
export let label
|
||||||
|
export let type = "barcodeqr"
|
||||||
|
export let disabled = false
|
||||||
|
export let validation
|
||||||
|
export let defaultValue = ""
|
||||||
|
export let onChange
|
||||||
|
export let allowManualEntry
|
||||||
|
export let scanButtonText
|
||||||
|
|
||||||
|
let fieldState
|
||||||
|
let fieldApi
|
||||||
|
|
||||||
|
$: scanText = scanButtonText || "Scan code"
|
||||||
|
|
||||||
|
const handleUpdate = e => {
|
||||||
|
const changed = fieldApi.setValue(e.detail)
|
||||||
|
if (onChange && changed) {
|
||||||
|
onChange({ value: e.detail })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Field
|
||||||
|
{label}
|
||||||
|
{field}
|
||||||
|
{disabled}
|
||||||
|
{validation}
|
||||||
|
{defaultValue}
|
||||||
|
{type}
|
||||||
|
bind:fieldState
|
||||||
|
bind:fieldApi
|
||||||
|
>
|
||||||
|
{#if fieldState}
|
||||||
|
<CodeScanner
|
||||||
|
value={fieldState.value}
|
||||||
|
on:change={handleUpdate}
|
||||||
|
disabled={fieldState.disabled}
|
||||||
|
{allowManualEntry}
|
||||||
|
scanButtonText={scanText}
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
|
</Field>
|
|
@ -13,3 +13,4 @@ export { default as passwordfield } from "./PasswordField.svelte"
|
||||||
export { default as formstep } from "./FormStep.svelte"
|
export { default as formstep } from "./FormStep.svelte"
|
||||||
export { default as jsonfield } from "./JSONField.svelte"
|
export { default as jsonfield } from "./JSONField.svelte"
|
||||||
export { default as s3upload } from "./S3Upload.svelte"
|
export { default as s3upload } from "./S3Upload.svelte"
|
||||||
|
export { default as codescanner } from "./CodeScannerField.svelte"
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
export const FieldTypes = {
|
export const FieldTypes = {
|
||||||
STRING: "string",
|
STRING: "string",
|
||||||
|
BARCODEQR: "barcodeqr",
|
||||||
LONGFORM: "longform",
|
LONGFORM: "longform",
|
||||||
OPTIONS: "options",
|
OPTIONS: "options",
|
||||||
NUMBER: "number",
|
NUMBER: "number",
|
||||||
|
|
|
@ -31,6 +31,7 @@ exports.NoEmptyFilterStrings = [
|
||||||
|
|
||||||
exports.FieldTypes = {
|
exports.FieldTypes = {
|
||||||
STRING: "string",
|
STRING: "string",
|
||||||
|
BARCODEQR: "barcodeqr",
|
||||||
LONGFORM: "longform",
|
LONGFORM: "longform",
|
||||||
OPTIONS: "options",
|
OPTIONS: "options",
|
||||||
NUMBER: "number",
|
NUMBER: "number",
|
||||||
|
@ -51,6 +52,7 @@ exports.CanSwitchTypes = [
|
||||||
exports.FieldTypes.STRING,
|
exports.FieldTypes.STRING,
|
||||||
exports.FieldTypes.OPTIONS,
|
exports.FieldTypes.OPTIONS,
|
||||||
exports.FieldTypes.LONGFORM,
|
exports.FieldTypes.LONGFORM,
|
||||||
|
exports.FieldTypes.BARCODEQR,
|
||||||
],
|
],
|
||||||
[exports.FieldTypes.BOOLEAN, exports.FieldTypes.NUMBER],
|
[exports.FieldTypes.BOOLEAN, exports.FieldTypes.NUMBER],
|
||||||
]
|
]
|
||||||
|
|
|
@ -40,6 +40,7 @@ function generateSchema(
|
||||||
case FieldTypes.STRING:
|
case FieldTypes.STRING:
|
||||||
case FieldTypes.OPTIONS:
|
case FieldTypes.OPTIONS:
|
||||||
case FieldTypes.LONGFORM:
|
case FieldTypes.LONGFORM:
|
||||||
|
case FieldTypes.BARCODEQR:
|
||||||
schema.text(key)
|
schema.text(key)
|
||||||
break
|
break
|
||||||
case FieldTypes.NUMBER:
|
case FieldTypes.NUMBER:
|
||||||
|
|
|
@ -4,6 +4,7 @@ const { FieldTypes } = require("../constants")
|
||||||
const VALIDATORS = {
|
const VALIDATORS = {
|
||||||
[FieldTypes.STRING]: () => true,
|
[FieldTypes.STRING]: () => true,
|
||||||
[FieldTypes.OPTIONS]: () => true,
|
[FieldTypes.OPTIONS]: () => true,
|
||||||
|
[FieldTypes.BARCODEQR]: () => true,
|
||||||
[FieldTypes.NUMBER]: attribute => {
|
[FieldTypes.NUMBER]: attribute => {
|
||||||
// allow not to be present
|
// allow not to be present
|
||||||
if (!attribute) {
|
if (!attribute) {
|
||||||
|
|
|
@ -48,6 +48,11 @@ const TYPE_TRANSFORM_MAP = {
|
||||||
[null]: "",
|
[null]: "",
|
||||||
[undefined]: undefined,
|
[undefined]: undefined,
|
||||||
},
|
},
|
||||||
|
[FieldTypes.BARCODEQR]: {
|
||||||
|
"": "",
|
||||||
|
[null]: "",
|
||||||
|
[undefined]: undefined,
|
||||||
|
},
|
||||||
[FieldTypes.FORMULA]: {
|
[FieldTypes.FORMULA]: {
|
||||||
"": "",
|
"": "",
|
||||||
[null]: "",
|
[null]: "",
|
||||||
|
|
|
@ -24,6 +24,7 @@ export enum QueryType {
|
||||||
|
|
||||||
export enum DatasourceFieldType {
|
export enum DatasourceFieldType {
|
||||||
STRING = "string",
|
STRING = "string",
|
||||||
|
CODE = "code",
|
||||||
LONGFORM = "longForm",
|
LONGFORM = "longForm",
|
||||||
BOOLEAN = "boolean",
|
BOOLEAN = "boolean",
|
||||||
NUMBER = "number",
|
NUMBER = "number",
|
||||||
|
|
Loading…
Reference in New Issue