BarcodeViewer komponent eklendi.
This commit is contained in:
parent
b31a651313
commit
660f6db268
9 changed files with 212 additions and 5 deletions
|
|
@ -1018,6 +1018,7 @@ alır):
|
|||
| `Rate` | `count` (`5`), `value`/`defaultValue`, `allowHalf` (`false`), `allowClear` (`true`), `color` (`amber`), `size` (`20`), `gap` (`4`), `tooltips[]`, `disabled`, `readOnly` |
|
||||
| `Upload` | `accept`, `multiple`, `draggable` (`false`), `uploadLimit`, `showList` (`true`), `tip`, `fileList[]`, `disabled` — dosyanın **gerçek** yüklenmesi `beforeUpload`/`onChange` script'inde `api.post` ile yapılır |
|
||||
| `BarcodeScanField` | `label`, `name` (`'barcode'`), `placeholder`, `buttonText`, `continuous` (`false` — açıkken diyalog okumadan sonra kapanmaz), `readOnly` (`true`), `className`; **olay**: `onScan(event)` — `event` okunan kodun metin hâli (Event workspace'te Input'un `onChange`'i gibi düzenlenir). Kamerayı `BarcodeDetector` (Android/Chromium) ya da lazy `zxing-wasm` (iOS/Firefox) ile okur — 1D + QR. **HTTPS şart**; iOS'ta yüklü PWA içinde yalnız 16.4+ çalışır, bileşen aksini kullanıcıya söyler |
|
||||
| `BarcodeViewer` | `value` (barkoda çevrilecek veri), `format` (`QRCode` \| `Code128` \| `Code39` \| `Code93` \| `Codabar` \| `EAN13` \| `EAN8` \| `UPCA` \| `UPCE` \| `ITF` \| `DataMatrix` \| `PDF417` \| `Aztec`), `label`, `showText` (`false` — kodun altına okunabilir metni de basar), `scale` (`4`, modül kalınlığı), `fitWidth` (`false` — açıkken kapsayıcının genişliğine yayılır), `className`. Veriyi **SVG** olarak çizer (lazy `zxing-wasm/writer`); karanlık temada da okunabilsin diye beyaz zemin üstünde. `Form` düğümünün içinde `value` bir kolona bağlanırsa kayıt değiştikçe kod yeniden çizilir — tıpkı bir `Input` gibi. Veri formata uymuyorsa (EAN13'e harf gibi) kod yerine zxing'in hata metni görünür |
|
||||
|
||||
**Gösterim/geri bildirim bileşenleri:**
|
||||
|
||||
|
|
|
|||
13
README.md
13
README.md
|
|
@ -1021,10 +1021,21 @@ dosyası yoktur; liste her `npm run build` / `npm start` ile tazelenir.
|
|||
bileşenler tek bir sözlükte durur (`RUNTIME_NODE_COMPONENTS`): kanvas onu `VisualCanvas` içinde,
|
||||
üretilen kod ise runtime kapsamı üzerinden kullanır — iki taraf aynı kaynaktan beslendiği için
|
||||
tasarım ve çalışma zamanı ayrışamaz. Sözlükte `PlatformIcon`, `BarcodeScanField`
|
||||
(`components/shared/BarcodeScanner`) ve public site parçaları (`components/publicSite`) bulunur. Yeni bir düğüm eklerken: bileşen `components/publicSite` (ya da
|
||||
(`components/shared/BarcodeScanner`), `BarcodeViewer` (`components/shared/BarcodeViewer.tsx`) ve
|
||||
public site parçaları (`components/publicSite`) bulunur. Yeni bir düğüm eklerken: bileşen `components/publicSite` (ya da
|
||||
uygun klasör) → `runtimeNodes.ts` sözlüğü → `catalog.ts` tanımı (`kind: 'custom'`) → `LanguagesData.json`
|
||||
açıklama anahtarı.
|
||||
|
||||
**Barkod.** Okuma ve çizme ayrı yollardır, ikisi de `utils/barcode.ts` içindedir ve `zxing-wasm`
|
||||
yalnızca gerektiğinde lazy iner. Okuma: Chromium'da yerleşik `BarcodeDetector`, olmayan tarayıcıda
|
||||
`zxing-wasm/reader` (`BarcodeScanField`, `BarcodeScannerDialog`). Çizme: `zxing-wasm/writer`
|
||||
(`writeBarcodeSvg` → `BarcodeViewer`); SVG üretilir, böylece büyütmede ve baskıda kırılmaz.
|
||||
Desteklenen çizim formatları `BARCODE_WRITE_FORMATS` listesindedir (QRCode, Code39, Code93,
|
||||
Code128, EAN13/8, UPCA/E, ITF, Codabar, DataMatrix, PDF417, Aztec) ve katalogdaki `format`
|
||||
seçenekleri de bu listeden üretilir — liste tek yerde değişir. `BarcodeViewer` bir `value` propu
|
||||
taşıdığı için tasarımcıdaki `Form` düğümünün içinde tıpkı bir `Input` gibi bir kolona bağlanabilir;
|
||||
kayıt değiştikçe kod yeniden çizilir.
|
||||
|
||||
Elle yazılmış bir bileşen Visual Designer'da **kod modunda** açılır: tasarımcı yalnızca kendi
|
||||
ürettiği kodu düğüm ağacına çevirir (`isDesignerGeneratedCode`), aksi hâlde tanımadığı JSX'i
|
||||
düşürüp kaydederken kodu yok ederdi.
|
||||
|
|
|
|||
|
|
@ -28986,6 +28986,18 @@
|
|||
"en": "Text field with a camera button that reads barcodes and QR codes.",
|
||||
"tr": "Barkod ve QR okuyan kamera butonlu metin alanı."
|
||||
},
|
||||
{
|
||||
"resourceName": "Platform",
|
||||
"key": "App.CatalogBarcodeViewer.BarcodeViewerDescription",
|
||||
"en": "Draws any value as a barcode or QR code; inside a Form the bound column is rendered.",
|
||||
"tr": "Herhangi bir değeri barkod ya da QR olarak çizer; Form içinde bağlı kolonu gösterir."
|
||||
},
|
||||
{
|
||||
"resourceName": "Platform",
|
||||
"key": "App.BarcodeViewer.NoValue",
|
||||
"en": "No value to draw as a barcode.",
|
||||
"tr": "Barkoda çevrilecek bir değer yok."
|
||||
},
|
||||
{
|
||||
"resourceName": "Platform",
|
||||
"key": "App.Geolocation.Unsupported",
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -1,6 +1,6 @@
|
|||
import type React from 'react'
|
||||
import PlatformIcon from './PlatformIcon'
|
||||
import { BarcodeScanField } from '@/components/shared'
|
||||
import { BarcodeScanField, BarcodeViewer } from '@/components/shared'
|
||||
import {
|
||||
PublicDemoButton,
|
||||
PublicHeaderBar,
|
||||
|
|
@ -19,6 +19,7 @@ import {
|
|||
export const RUNTIME_NODE_COMPONENTS = {
|
||||
PlatformIcon,
|
||||
BarcodeScanField,
|
||||
BarcodeViewer,
|
||||
PublicHeaderBar,
|
||||
PublicLogo,
|
||||
PublicLanguageSelector,
|
||||
|
|
|
|||
76
ui/src/components/shared/BarcodeViewer.tsx
Normal file
76
ui/src/components/shared/BarcodeViewer.tsx
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
import { useEffect, useState } from 'react'
|
||||
import classNames from 'classnames'
|
||||
import { useLocalization } from '@/utils/hooks/useLocalization'
|
||||
import { writeBarcodeSvg, type BarcodeWriteFormat } from '@/utils/barcode'
|
||||
|
||||
export type BarcodeViewerProps = {
|
||||
/** Barkoda çevrilecek veri. Form içinde kolona bağlanan alan budur. */
|
||||
value?: string
|
||||
format?: BarcodeWriteFormat
|
||||
label?: string
|
||||
/** Kodun altına okunabilir metni de basar. */
|
||||
showText?: boolean
|
||||
/** Modül (en ince çubuk) kalınlığı; büyüdükçe kod büyür. */
|
||||
scale?: number
|
||||
/** Kodu kapsayıcının genişliğine yayar; kapalıyken `scale` boyutu korunur. */
|
||||
fitWidth?: boolean
|
||||
className?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Herhangi bir veriyi barkod/QR olarak çizer. Tasarımcıdan sürüklenip bırakılan
|
||||
* düğüm ve üretilen kod aynı bileşeni kullanır; `value` bir Form'un kaydına
|
||||
* bağlandığında alan değiştikçe kod da yeniden çizilir.
|
||||
*
|
||||
* Kod SVG olarak üretilir: baskıda ve büyütmede kırılmaz, karanlık temada da
|
||||
* okunabilir kalsın diye beyaz zemin üstünde çizilir.
|
||||
*/
|
||||
const BarcodeViewer = ({
|
||||
value,
|
||||
format = 'QRCode',
|
||||
label,
|
||||
showText = false,
|
||||
scale = 4,
|
||||
fitWidth = false,
|
||||
className,
|
||||
}: BarcodeViewerProps) => {
|
||||
const { translate } = useLocalization()
|
||||
const [svg, setSvg] = useState('')
|
||||
const [error, setError] = useState('')
|
||||
const text = value === undefined || value === null ? '' : String(value)
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled = false
|
||||
void writeBarcodeSvg(text, { format, scale, showText }).then((result) => {
|
||||
if (cancelled) return
|
||||
setSvg(result.svg)
|
||||
setError(result.error)
|
||||
})
|
||||
return () => {
|
||||
cancelled = true
|
||||
}
|
||||
}, [text, format, scale, showText])
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
{label && <label className="mb-1 block text-sm font-semibold">{label}</label>}
|
||||
{!text ? (
|
||||
<span className="text-sm opacity-70">{translate('::App.BarcodeViewer.NoValue')}</span>
|
||||
) : error ? (
|
||||
<span className="text-sm text-red-500">{error}</span>
|
||||
) : (
|
||||
<div
|
||||
// zxing'in ürettiği SVG'dir, kullanıcı verisi değil: metin HRI içine
|
||||
// kaçışlanarak yazılır, bu yüzden gömmek güvenlidir.
|
||||
dangerouslySetInnerHTML={{ __html: svg }}
|
||||
className={classNames(
|
||||
'inline-block rounded bg-white p-2',
|
||||
fitWidth && 'block w-full [&>svg]:h-auto [&>svg]:w-full',
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default BarcodeViewer
|
||||
|
|
@ -3,6 +3,7 @@ export { default as AdaptableCard } from './AdaptableCard'
|
|||
export { default as Affix } from './Affix'
|
||||
export { default as AuthorityCheck } from './AuthorityCheck'
|
||||
export { BarcodeScanField, BarcodeScannerDialog, BarcodeScannerHost } from './BarcodeScanner'
|
||||
export { default as BarcodeViewer } from './BarcodeViewer'
|
||||
export { default as PermissionCheck } from './PermissionCheck'
|
||||
export { default as ConfirmDialog } from './ConfirmDialog'
|
||||
export { default as Container } from './Container'
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { CUSTOM_COMPONENTS, HTML_ELEMENTS } from '@/components/codeLayout/data/componentDefinitions'
|
||||
import generatedComponentProps from './generated/componentProps.json'
|
||||
import { BARCODE_WRITE_FORMATS } from '@/utils/barcode'
|
||||
import {
|
||||
DESIGNER_DATA_COMPONENT_NAMES,
|
||||
FORM_EVENTS,
|
||||
|
|
@ -1252,6 +1253,32 @@ export const DESIGNER_EXTRAS: DesignerComponentDefinition[] = [
|
|||
],
|
||||
hooks: [],
|
||||
},
|
||||
{
|
||||
// Veriyi barkod/QR olarak cizer. Bir Form'un icinde `value` bir kolona
|
||||
// baglanirsa alan degistikce kod da yeniden cizilir.
|
||||
name: 'BarcodeViewer',
|
||||
icon: 'QrCode',
|
||||
category: 'basic',
|
||||
kind: 'custom',
|
||||
toolboxGroup: 'ui',
|
||||
description: 'App.CatalogBarcodeViewer.BarcodeViewerDescription',
|
||||
properties: [
|
||||
{ name: 'label', type: 'string', value: '', category: 'properties' },
|
||||
{ name: 'value', type: 'string', value: '1234567890', category: 'properties' },
|
||||
{
|
||||
name: 'format',
|
||||
type: 'select',
|
||||
value: 'QRCode',
|
||||
options: [...BARCODE_WRITE_FORMATS],
|
||||
category: 'properties',
|
||||
},
|
||||
{ name: 'showText', type: 'boolean', value: false, category: 'properties' },
|
||||
{ name: 'scale', type: 'number', value: 4, category: 'styling' },
|
||||
{ name: 'fitWidth', type: 'boolean', value: false, category: 'styling' },
|
||||
{ name: 'className', type: 'string', value: '', category: 'styling' },
|
||||
],
|
||||
hooks: [],
|
||||
},
|
||||
{
|
||||
name: 'Spacer',
|
||||
icon: 'Space',
|
||||
|
|
|
|||
|
|
@ -117,3 +117,81 @@ export const stopCameraStream = (stream?: MediaStream | null) =>
|
|||
|
||||
/** Okuma geri bildirimi. Desteklemeyen cihazda sessizce atlanır. */
|
||||
export const vibrateOnScan = () => navigator.vibrate?.(60)
|
||||
|
||||
/**
|
||||
* Yazma (çizme) tarafı. Okuma yolundan bağımsızdır: `zxing-wasm/writer` yalnızca
|
||||
* ekranda bir barkod çizildiğinde iner, okuma yapan ekranlara yük bindirmez.
|
||||
*/
|
||||
export const BARCODE_WRITE_FORMATS = [
|
||||
'QRCode',
|
||||
'Code128',
|
||||
'Code39',
|
||||
'Code93',
|
||||
'Codabar',
|
||||
'EAN13',
|
||||
'EAN8',
|
||||
'UPCA',
|
||||
'UPCE',
|
||||
'ITF',
|
||||
'DataMatrix',
|
||||
'PDF417',
|
||||
'Aztec',
|
||||
] as const
|
||||
|
||||
export type BarcodeWriteFormat = (typeof BARCODE_WRITE_FORMATS)[number]
|
||||
|
||||
export interface BarcodeWriteOptions {
|
||||
format?: BarcodeWriteFormat
|
||||
/** Modül (en ince çubuk) kalınlığı. */
|
||||
scale?: number
|
||||
/** Kodun altına okunabilir metni de basar. */
|
||||
showText?: boolean
|
||||
}
|
||||
|
||||
type ZxingWriter = (
|
||||
value: string,
|
||||
options: BarcodeWriteOptions,
|
||||
) => Promise<{ svg: string; error: string }>
|
||||
|
||||
let zxingWriter: Promise<ZxingWriter> | undefined
|
||||
|
||||
const getZxingWriter = () => {
|
||||
zxingWriter ??= (async () => {
|
||||
const [{ prepareZXingModule, writeBarcode }, { default: wasmUrl }] = await Promise.all([
|
||||
import('zxing-wasm/writer'),
|
||||
import('zxing-wasm/writer/zxing_writer.wasm?url'),
|
||||
])
|
||||
|
||||
prepareZXingModule({ overrides: { locateFile: () => wasmUrl } })
|
||||
|
||||
return async (value: string, options: BarcodeWriteOptions) => {
|
||||
const result = await writeBarcode(value, {
|
||||
format: options.format ?? 'QRCode',
|
||||
scale: options.scale && options.scale > 0 ? options.scale : 4,
|
||||
addHRT: options.showText ?? false,
|
||||
})
|
||||
return { svg: result.svg, error: result.error }
|
||||
}
|
||||
})()
|
||||
return zxingWriter
|
||||
}
|
||||
|
||||
/**
|
||||
* Metni barkod SVG'sine çevirir. Veri formata uymuyorsa (EAN13'e harf gibi)
|
||||
* zxing boş SVG ve bir hata metni döner; çağıran taraf bunu kullanıcıya gösterir.
|
||||
* SVG'nin başındaki XML/DOCTYPE bildirimi ayıklanır — gömülü kullanımda geçersizdir.
|
||||
*/
|
||||
export const writeBarcodeSvg = async (
|
||||
value: string,
|
||||
options: BarcodeWriteOptions = {},
|
||||
): Promise<{ svg: string; error: string }> => {
|
||||
if (!value) return { svg: '', error: '' }
|
||||
try {
|
||||
const write = await getZxingWriter()
|
||||
const { svg, error } = await write(value, options)
|
||||
const start = svg.indexOf('<svg')
|
||||
return { svg: start < 0 ? '' : svg.slice(start), error }
|
||||
} catch (error) {
|
||||
return { svg: '', error: error instanceof Error ? error.message : String(error) }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue