Initial commit of QR Reader field
This commit is contained in:
parent
41ea9b9c2a
commit
5836c2b3c4
|
@ -138,6 +138,7 @@ const fieldTypeToComponentMap = {
|
|||
attachment: "attachmentfield",
|
||||
link: "relationshipfield",
|
||||
json: "jsonfield",
|
||||
code: "codescanner",
|
||||
}
|
||||
|
||||
export function makeDatasourceFormComponents(datasource) {
|
||||
|
|
|
@ -261,6 +261,7 @@
|
|||
} else {
|
||||
return [
|
||||
FIELDS.STRING,
|
||||
FIELDS.CODE,
|
||||
FIELDS.LONGFORM,
|
||||
FIELDS.OPTIONS,
|
||||
FIELDS.DATETIME,
|
||||
|
|
|
@ -124,6 +124,10 @@
|
|||
label: "Multi-select",
|
||||
value: FIELDS.ARRAY.type,
|
||||
},
|
||||
{
|
||||
label: "Code",
|
||||
value: FIELDS.CODE.type,
|
||||
},
|
||||
]
|
||||
</script>
|
||||
|
||||
|
|
|
@ -51,6 +51,7 @@ const componentMap = {
|
|||
"field/link": FormFieldSelect,
|
||||
"field/array": FormFieldSelect,
|
||||
"field/json": FormFieldSelect,
|
||||
"field/code": FormFieldSelect,
|
||||
// Some validation types are the same as others, so not all types are
|
||||
// explicitly listed here. e.g. options uses string validation
|
||||
"validation/string": ValidationEditor,
|
||||
|
|
|
@ -26,7 +26,7 @@
|
|||
let entries = Object.entries(schema ?? {})
|
||||
|
||||
let types = []
|
||||
if (type === "field/options") {
|
||||
if ((type === "field/options", type === "field/code")) {
|
||||
// allow options to be used on both options and string fields
|
||||
types = [type, "field/string"]
|
||||
} else {
|
||||
|
|
|
@ -8,6 +8,15 @@ export const FIELDS = {
|
|||
presence: false,
|
||||
},
|
||||
},
|
||||
CODE: {
|
||||
name: "Code",
|
||||
type: "code",
|
||||
constraints: {
|
||||
type: "string",
|
||||
length: {},
|
||||
presence: false,
|
||||
},
|
||||
},
|
||||
LONGFORM: {
|
||||
name: "Long Form Text",
|
||||
type: "longform",
|
||||
|
@ -148,6 +157,7 @@ export const ALLOWABLE_STRING_OPTIONS = [
|
|||
FIELDS.STRING,
|
||||
FIELDS.OPTIONS,
|
||||
FIELDS.LONGFORM,
|
||||
FIELDS.CODE,
|
||||
]
|
||||
export const ALLOWABLE_STRING_TYPES = ALLOWABLE_STRING_OPTIONS.map(
|
||||
opt => opt.type
|
||||
|
|
|
@ -65,7 +65,8 @@
|
|||
"relationshipfield",
|
||||
"datetimefield",
|
||||
"multifieldselect",
|
||||
"s3upload"
|
||||
"s3upload",
|
||||
"codescanner"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
|
|
@ -3157,6 +3157,42 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
"codescanner": {
|
||||
"name": "Code Scanner",
|
||||
"icon": "Camera",
|
||||
"styles": [
|
||||
"size"
|
||||
],
|
||||
"draggable": true,
|
||||
"illegalChildren": [
|
||||
"section"
|
||||
],
|
||||
"settings": [
|
||||
{
|
||||
"type": "text",
|
||||
"label": "Label",
|
||||
"key": "label",
|
||||
"defaultValue": true
|
||||
},
|
||||
{
|
||||
"type": "field/code",
|
||||
"label": "Field",
|
||||
"key": "field",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "boolean",
|
||||
"label": "Disabled",
|
||||
"key": "disabled",
|
||||
"defaultValue": false
|
||||
},
|
||||
{
|
||||
"type": "validation/string",
|
||||
"label": "Validation",
|
||||
"key": "validation"
|
||||
}
|
||||
]
|
||||
},
|
||||
"embeddedmap": {
|
||||
"name": "Embedded Map",
|
||||
"icon": "Location",
|
||||
|
|
|
@ -33,6 +33,7 @@
|
|||
"apexcharts": "^3.22.1",
|
||||
"dayjs": "^1.10.5",
|
||||
"downloadjs": "1.4.7",
|
||||
"html5-qrcode": "^2.2.1",
|
||||
"leaflet": "^1.7.1",
|
||||
"regexparam": "^1.3.0",
|
||||
"sanitize-html": "^2.7.0",
|
||||
|
|
|
@ -0,0 +1,141 @@
|
|||
<script>
|
||||
import { ModalContent, Modal, Select } from "@budibase/bbui"
|
||||
import { Input, Button, StatusLight } from "@budibase/bbui"
|
||||
import { Html5Qrcode } from "html5-qrcode"
|
||||
|
||||
export let code = ""
|
||||
|
||||
let videoEle
|
||||
let camModal
|
||||
let manualMode = false
|
||||
let enabled = false
|
||||
let cameraInit = false
|
||||
let html5QrCode
|
||||
let cameraId
|
||||
let cameraDevices = []
|
||||
let selectedCam
|
||||
|
||||
const checkCamera = async () => {
|
||||
return new Promise(resolve => {
|
||||
Html5Qrcode.getCameras()
|
||||
.then(devices => {
|
||||
if (devices && devices.length) {
|
||||
cameraDevices = devices
|
||||
cameraId = devices[0].id
|
||||
resolve({ enabled: true })
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
resolve({ enabled: false })
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
$: if (enabled && videoEle && !cameraInit) {
|
||||
html5QrCode = new Html5Qrcode("reader")
|
||||
html5QrCode
|
||||
.start(
|
||||
cameraId,
|
||||
{
|
||||
fps: 25,
|
||||
qrbox: { width: 250, height: 250 },
|
||||
},
|
||||
(decodedText, decodedResult) => {
|
||||
code = decodedText
|
||||
console.log(decodedText, decodedResult)
|
||||
}
|
||||
)
|
||||
.catch(err => {
|
||||
console.log("There was a problem scanning the image", err)
|
||||
})
|
||||
}
|
||||
|
||||
const showReaderModal = async () => {
|
||||
camModal.show()
|
||||
const camStatus = await checkCamera()
|
||||
enabled = camStatus.enabled
|
||||
}
|
||||
|
||||
const hideReaderModal = async () => {
|
||||
camModal.hide()
|
||||
await html5QrCode.stop()
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="scanner-video-wrapper">
|
||||
{#if code}
|
||||
<div class="scanner-value">
|
||||
<StatusLight positive />
|
||||
{code}
|
||||
</div>
|
||||
{/if}
|
||||
<Button primary icon="Camera" on:click={showReaderModal}>Scan Code</Button>
|
||||
</div>
|
||||
|
||||
<div class="modal-wrap">
|
||||
<Select
|
||||
on:change={e => console.log(e)}
|
||||
value={selectedCam}
|
||||
options={cameraDevices}
|
||||
getOptionLabel={() => cameraDevices}
|
||||
/>
|
||||
|
||||
<Modal bind:this={camModal} on:hide={hideReaderModal}>
|
||||
<ModalContent
|
||||
title="Scan Code"
|
||||
showCancelButton={false}
|
||||
showConfirmButton={false}
|
||||
>
|
||||
<div id="reader" bind:this={videoEle} />
|
||||
<div class="code-wrap">
|
||||
{#if manualMode}
|
||||
<Input label="Enter" bind:value={code} />
|
||||
{/if}
|
||||
{#if code}
|
||||
<div class="scanner-value">
|
||||
<StatusLight positive />
|
||||
{code}
|
||||
</div>
|
||||
{/if}
|
||||
{#if !code && enabled && videoEle && cameraInit}
|
||||
<div class="scanner-value">
|
||||
<StatusLight neutral />
|
||||
Searching for code...
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
<div slot="footer">
|
||||
<div class="footer-buttons">
|
||||
<Button
|
||||
group
|
||||
secondary
|
||||
newStyles
|
||||
on:click={() => {
|
||||
manualMode = !manualMode
|
||||
}}
|
||||
>
|
||||
Enter Manually
|
||||
</Button>
|
||||
|
||||
<Button group cta disabled={!code}>Confirm</Button>
|
||||
</div>
|
||||
</div>
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.footer-buttons {
|
||||
display: flex;
|
||||
grid-area: buttonGroup;
|
||||
gap: var(--spectrum-global-dimension-static-size-200);
|
||||
}
|
||||
.scanner-value {
|
||||
padding-top: var(
|
||||
--spectrum-fieldlabel-side-m-padding-top,
|
||||
var(--spectrum-global-dimension-size-100)
|
||||
);
|
||||
display: flex;
|
||||
margin-bottom: var(--spacing-m);
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,55 @@
|
|||
<script>
|
||||
import Field from "./Field.svelte"
|
||||
import CodeScanner from "../CodeScanner.svelte"
|
||||
|
||||
export let field
|
||||
export let label
|
||||
export let type = "code"
|
||||
export let disabled = false
|
||||
export let validation
|
||||
export let defaultValue = ""
|
||||
export let onChange
|
||||
|
||||
let fieldState
|
||||
let fieldApi
|
||||
|
||||
let scannedCode
|
||||
let loaded = false
|
||||
|
||||
const handleInput = () => {
|
||||
const changed = fieldApi.setValue(scannedCode)
|
||||
if (onChange && changed) {
|
||||
onChange({ value: scannedCode })
|
||||
}
|
||||
}
|
||||
|
||||
$: if (!loaded && !scannedCode && fieldState?.value) {
|
||||
scannedCode = fieldState.value + ""
|
||||
loaded = true
|
||||
}
|
||||
|
||||
/*
|
||||
QR Nimiq has rollup issues?
|
||||
QR qrcodejs 12b bundle?
|
||||
https://github.com/davidshimjs/qrcodejs
|
||||
BOTH html5-qrcode has a 330k bundle
|
||||
https://github.com/mebjas/html5-qrcode
|
||||
BOTH zxing 360k bundle size
|
||||
https://github.com/zxing-js/library
|
||||
*/
|
||||
</script>
|
||||
|
||||
<Field
|
||||
{label}
|
||||
{field}
|
||||
{disabled}
|
||||
{validation}
|
||||
{defaultValue}
|
||||
{type}
|
||||
bind:fieldState
|
||||
bind:fieldApi
|
||||
>
|
||||
{#if fieldState}
|
||||
<CodeScanner bind:code={scannedCode} on:input={handleInput} />
|
||||
{/if}
|
||||
</Field>
|
|
@ -13,3 +13,4 @@ export { default as passwordfield } from "./PasswordField.svelte"
|
|||
export { default as formstep } from "./FormStep.svelte"
|
||||
export { default as jsonfield } from "./JSONField.svelte"
|
||||
export { default as s3upload } from "./S3Upload.svelte"
|
||||
export { default as codescanner } from "./CodeScannerField.svelte"
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
export const FieldTypes = {
|
||||
STRING: "string",
|
||||
CODE: "code",
|
||||
LONGFORM: "longform",
|
||||
OPTIONS: "options",
|
||||
NUMBER: "number",
|
||||
|
|
|
@ -31,6 +31,7 @@ exports.NoEmptyFilterStrings = [
|
|||
|
||||
exports.FieldTypes = {
|
||||
STRING: "string",
|
||||
CODE: "code",
|
||||
LONGFORM: "longform",
|
||||
OPTIONS: "options",
|
||||
NUMBER: "number",
|
||||
|
@ -51,6 +52,7 @@ exports.CanSwitchTypes = [
|
|||
exports.FieldTypes.STRING,
|
||||
exports.FieldTypes.OPTIONS,
|
||||
exports.FieldTypes.LONGFORM,
|
||||
exports.FieldTypes.CODE,
|
||||
],
|
||||
[exports.FieldTypes.BOOLEAN, exports.FieldTypes.NUMBER],
|
||||
]
|
||||
|
|
|
@ -40,6 +40,7 @@ function generateSchema(
|
|||
case FieldTypes.STRING:
|
||||
case FieldTypes.OPTIONS:
|
||||
case FieldTypes.LONGFORM:
|
||||
case FieldTypes.CODE:
|
||||
schema.text(key)
|
||||
break
|
||||
case FieldTypes.NUMBER:
|
||||
|
|
|
@ -4,6 +4,7 @@ const { FieldTypes } = require("../constants")
|
|||
const VALIDATORS = {
|
||||
[FieldTypes.STRING]: () => true,
|
||||
[FieldTypes.OPTIONS]: () => true,
|
||||
[FieldTypes.CODE]: () => true,
|
||||
[FieldTypes.NUMBER]: attribute => {
|
||||
// allow not to be present
|
||||
if (!attribute) {
|
||||
|
|
|
@ -48,6 +48,11 @@ const TYPE_TRANSFORM_MAP = {
|
|||
[null]: "",
|
||||
[undefined]: undefined,
|
||||
},
|
||||
[FieldTypes.CODE]: {
|
||||
"": "",
|
||||
[null]: "",
|
||||
[undefined]: undefined,
|
||||
},
|
||||
[FieldTypes.FORMULA]: {
|
||||
"": "",
|
||||
[null]: "",
|
||||
|
|
|
@ -24,6 +24,7 @@ export enum QueryType {
|
|||
|
||||
export enum DatasourceFieldType {
|
||||
STRING = "string",
|
||||
CODE = "code",
|
||||
LONGFORM = "longForm",
|
||||
BOOLEAN = "boolean",
|
||||
NUMBER = "number",
|
||||
|
|
Loading…
Reference in New Issue