Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 69 additions & 0 deletions docs/tdev/gallery/tools/qr-scanner/index.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
---
page_id: e831b10f-8331-4e6f-a6d5-8503cbffec54
tags: ["tools"]
---

import Scanner from '@tdev/qr-scanner';
import BrowserWindow from '@tdev-components/BrowserWindow';

# QR Code Scanner

QR Codes sind praktisch um Inhalte wie URLs oder anderes schnell zu teilen. Mit dem QR Code Scanner können Sie QR Codes scannen und die darin enthaltenen Informationen anzeigen.

```mdx
import Scanner from '@tdev/qr-scanner';

<Scanner />
```

<BrowserWindow>
<Scanner />
</BrowserWindow>


## Links direkt öffnen

```mdx
import Scanner from '@tdev-components/shared/QR-Code/Scanner';

<Scanner redirect inNewTab />
```

<BrowserWindow>
<Scanner redirect inNewTab />
</BrowserWindow>

## Optionen

`onScan`
: Callback Funktion, die aufgerufen wird, wenn ein QR Code gescannt wurde.
:::dd
```tsx
<Scanner onScan={(content: sring) => console.log(content)} />
```
:::
`hideOpenLinkButton`
: Versteckt den Button zum Öffnen des Inhalts als Link.
: `boolean`
: Standard: `false`
`redirect`
: Der Inhalt des QR Codes als URL behandelt und direkt geöffnet.
: `boolean`
: Standard: `false`
:::dd
`inNewTab`
: Der Inhalt des QR Codes wird in einem neuen Tab geöffnet.
: `boolean`
: Standard: `false`
:::
## Installation

:::info[Code]
- `packages/tdev/qr-scanner`
:::

:::info[npm]
```bash
yarn add @yudiel/react-qr-scanner
```
:::
85 changes: 85 additions & 0 deletions packages/tdev/qr-scanner/README.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
---
page_id: e831b10f-8331-4e6f-a6d5-8503cbffec54
tags: ['tools']
---

import { mdiEarth } from '@mdi/js';

import QrCode from '@tdev-scanner';

import BrowserWindow from '@tdev-components/BrowserWindow';

# QR Codes

QR Codes sind praktisch um Inhalte wie URLs oder anderes schnell zu teilen.

```mdx
import QrCode from '@tdev-components/shared/QrCode';

<QrCode text="Hello World" />
```

<BrowserWindow>
<QrCode text="Hello World" />
</BrowserWindow>

## Inhalt anzeigen

```mdx
<QrCode showText text="Hello World" />
```

<BrowserWindow>
<QrCode showText text="Hello World" />
</BrowserWindow>

## Mit Texteingabe

```mdx
<QrCode withInput size="20em" text="Hello World" />
```

<BrowserWindow>
<QrCode withInput size="20em" text="Hello World" />
</BrowserWindow>

## Mit Icon

Es können auch MdiIcons im QR Code eingebettet werden.

:::warning[Grösse und Farbe] Da das Icon als SVG eingebettet wird, muss die Grösse als Zahl (in Pixel) und die Farbe als HTML-Farbe angegeben werden. CSS-Variablen oder -Grössen funktionieren nicht. :::

```mdx
<QrCode icon={mdiEarth} text="Hello World" />
<QrCode icon={mdiEarth} iconSize="48" iconColor="#01f0bc" text="Hello World in Cyan" download />
```

<BrowserWindow>
:::flex{justifyContent="space-around"}
<QrCode icon={mdiEarth} text="Hello World" />

::br

<QrCode icon={mdiEarth} iconSize="48" iconColor="#01f0bc" text="Hello World in Cyan" download/>

:::

</BrowserWindow>

## Optionen

`text` : Der Text, der im QR Code codiert wird. : `string` `showText` : Zeigt den Text unter dem QR Code an. : `boolean` : Standard: `false` `isLink` : Der Text wird als Link interpretiert und als `<Link>`-Tag dargestellt. `linkText` : Linktext der anstelle des `text`-Feldes angezeigt werden soll. : Standard: die URL im `text` Feld `withInput` : Zeigt ein Input Feld um den Text zu ändern. : `boolean` : Standard: `false` `size` : Die Grösse des QR Codes. : `string` - CSS Grösse, z.B. `20em` : Standard: `15em` (im css gesetzt) `className` : Eine CSS Klasse für den umgebenden `div`-Container. : `string` `icon` : Ein MdiIcon (oder ein SVG-Path), das im QR Code eingebettet wird. : `string` `download` : Es wird ein Download-Button angezeigt und der QR-Code kann als `.png` heruntergeladen werden. : Standard: `false` `iconSize` : Die Grösse des Icons. : `number` - eine Zahl in Pixel (kann nicht als CSS-Grösse angegeben werden!) : Standard: `32` `iconColor` : Die Farbe des Icons. : `string` - eine HTML-Farbe (kann nicht als CSS-Variable angegeben werden!) : Standard: <span className="badge badge--primary" style={{['--ifm-badge-background-color']: '#306cce'}}>#306cce</span> `qrProps` : Weitere Props für das `QRCode`-Element. : [QRCode.React](https://www.npmjs.com/package/qrcode.react#props)

## Installation

:::info[Code]

- `src/components/shared/QrCode` :::

:::info[npm]

```bash
yarn add qrcode.react
```

:::
181 changes: 181 additions & 0 deletions packages/tdev/qr-scanner/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
import React from 'react';
import clsx from 'clsx';
import styles from './styles.module.scss';
import Loader from '@tdev-components/Loader';
import Button from '@tdev-components/shared/Button';
import { useClientLib } from '@tdev-hooks/useClientLib';
import type { default as QrScannerLib } from '@yudiel/react-qr-scanner';
import Storage from '@tdev-stores/utils/Storage';
import { mdiCameraFlipOutline } from '@mdi/js';
import SelectInput from '@tdev-components/shared/SelectInput';
import CodeBlockWrapper from '@tdev/theme/CodeBlock';

interface Props {
redirect?: boolean;
inNewTab?: boolean;
hideOpenLinkButton?: boolean;
onScan?: (qr: string) => void;
}
const Scanner = (props: Props) => {
const [reloadKey, setReloadKey] = React.useState(1);
const deviceId = React.useRef<string | undefined>(Storage.get('QrScannerDeviceId'));
const Lib = useClientLib<typeof QrScannerLib>(
() => import('@yudiel/react-qr-scanner'),
'@yudiel/react-qr-scanner'
);
if (!Lib) {
return <Loader />;
}
return (
<ScannerComponent
key={reloadKey}
Lib={Lib}
onChangeSrc={(id) => {
if (deviceId.current === id && reloadKey > 5) {
setReloadKey(1);
Storage.remove('QrScannerDeviceId');
} else {
setReloadKey((prev) => prev + 1);
}
deviceId.current = id;
}}
{...props}
/>
);
};

const ScannerComponent = (
props: { Lib: typeof QrScannerLib; onChangeSrc: (id?: string) => void } & Props
) => {
const { Lib } = props;
const [error, setError] = React.useState<string | undefined>();
const [qr, setQr] = React.useState('');
const [deviceId, setDeviceId] = React.useState<string | undefined>(
Storage.get('QrScannerDeviceId', undefined)
);
const devices = Lib.useDevices();
React.useEffect(() => {
if (devices.length > 0 && deviceId) {
if (!devices.find((d) => d.deviceId === deviceId)) {
Storage.remove('QrScannerDeviceId');
setDeviceId(undefined);
}
}
}, [devices]);
const clearErrorMessage = React.useCallback(() => {
setError(undefined);
}, []);
const deviceIdx = devices.findIndex((d) => d.deviceId === deviceId);
const showFooter = qr || devices.length > 1 || error;
return (
<div className={clsx('card', styles.qr)}>
<div className={clsx(styles.scanner, 'card__body')}>
{devices.length > 0 ? (
<Lib.Scanner
paused={!!qr}
onScan={(result) => {
clearErrorMessage();
setQr(result[0].rawValue);
if (props.onScan) {
props.onScan(result[0].rawValue);
}
if (props.redirect) {
try {
const url = new URL(result[0].rawValue);
if (props.inNewTab) {
window.open(url.href, '_blank');
} else {
window.location.href = url.href;
}
} catch (e) {
setError(`Invalide URL: "${result[0].rawValue}"`);
}
}
}}
onError={(err) => {
setTimeout(() => {
props.onChangeSrc(deviceId);
}, 30);
setError('Die Kamera konnte nicht gestartet werden.');
}}
constraints={{
deviceId: deviceId
}}
allowMultiple={false}
components={{
torch: true,
finder: true,
zoom: true,
onOff: true
}}
sound={false}
/>
) : (
<Loader />
)}
</div>
{showFooter && (
<div className="card__footer">
{devices.length > 1 && (
<SelectInput
value={deviceId || ''}
onChange={(id) => {
Storage.set('QrScannerDeviceId', id);
props.onChangeSrc(id);
}}
options={devices.map((d) => d.deviceId)}
labels={devices.map((d, idx) => d.label || `${d.kind}-${idx}`)}
/>
// <Button
// icon={mdiCameraFlipOutline}
// text={
// devices.length > 2
// ? `${(deviceIdx >= 0 ? deviceIdx : 0) + 1}/${devices.length}`
// : ''
// }
// onClick={() => {
// const nextDeviceIdx = ((deviceIdx >= 0 ? deviceIdx : 0) + 1) % devices.length;
// const deviceId = devices[nextDeviceIdx].deviceId;
// Storage.set('QrScannerDeviceId', deviceId);
// props.onChangeSrc(deviceId);
// }}
// iconSide="left"
// />
)}
{qr && (
<>
<small>{qr}</small>
<div className="button-group button-group--block">
<Button
className={clsx('button--block')}
text="Neu scannen"
color="secondary"
onClick={() => setQr('')}
/>
{!props.hideOpenLinkButton && (
<Button
className={clsx('button--block')}
color="primary"
text="Besuchen"
href={qr}
/>
)}
</div>
</>
)}
{error && (
<CodeBlockWrapper
title="Fehlermeldung"
metastring="{1-99}"
className={clsx(styles.error)}
>
{error}
</CodeBlockWrapper>
)}
</div>
)}
</div>
);
};

export default Scanner;
19 changes: 19 additions & 0 deletions packages/tdev/qr-scanner/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"name": "@tdev/qr-scanner",
"version": "1.0.0",
"main": "index.tsx",
"types": "index.tsx",
"dependencies": {
"@yudiel/react-qr-scanner": "^2.3.1"
},
"devDependencies": {
"vitest": "*",
"@docusaurus/module-type-aliases": "*",
"@docusaurus/core": "*",
"@docusaurus/theme-classic": "*",
"@types/node": "*"
},
"peerDependencies": {
"@tdev/core": "1.0.0"
}
}
14 changes: 14 additions & 0 deletions packages/tdev/qr-scanner/styles.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
.qr {
width: 15em;
.scanner {
padding: 0;
height: 15em;
}
}

.error {
:global(.theme-code-block-highlighted-line) {
color: var(--ifm-color-danger-darker) !important;
background: transparent;
}
}
3 changes: 3 additions & 0 deletions packages/tdev/qr-scanner/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"extends": "../../../tsconfig.json"
}
Loading