Skip to content

Commit

Permalink
chore: added qr-code
Browse files Browse the repository at this point in the history
  • Loading branch information
stephane-segning committed Jul 25, 2024
1 parent 9341a56 commit 243e6a1
Show file tree
Hide file tree
Showing 18 changed files with 314 additions and 95 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"@reduxjs/toolkit": "^2.2.6",
"@sentry/react": "^7.114.0",
"@sentry/tracing": "^7.114.0",
"@yudiel/react-qr-scanner": "^2.0.4",
"autoprefixer": "^10.4.19",
"i18next": "^23.11.4",
"md-to-pdf": "^5.2.4",
Expand Down
16 changes: 16 additions & 0 deletions src/components/config-scan.button.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { useCallback } from 'react';
import { Button } from 'react-daisyui';
import { useNavigate } from 'react-router-dom';

export default function ConfigScanButton() {
const navigate = useNavigate();
const scanConfigAndPersist = useCallback(() => {
navigate('/config/scan');
}, [navigate]);

return (
<Button fullWidth onClick={scanConfigAndPersist} color="primary">
Scan
</Button>
);
}
15 changes: 13 additions & 2 deletions src/components/floating-config.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { Button, Divider, Dropdown } from 'react-daisyui';
import { useCallback } from 'react';
import { useCallback, useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import { BarChart2, Globe, Menu, Settings } from 'react-feather';
import { BarChart2, Globe, List, Menu, Settings } from 'react-feather';
import { Link } from 'react-router-dom';
import { themeChange } from 'theme-change';

interface ThemeButtonProps {
themeName: 'valantine' | 'light' | 'dark';
Expand All @@ -22,6 +23,10 @@ function ThemeButton({ themeName }: ThemeButtonProps) {
}

export function FloatingConfig() {
useEffect(() => {
themeChange(false);
}, []);

const { i18n } = useTranslation();

const changeLanguageHandler = useCallback(
Expand Down Expand Up @@ -70,6 +75,12 @@ export function FloatingConfig() {
<span>Config</span>
</Link>
</Dropdown.Item>
<Dropdown.Item anchor={false}>
<Link to="/scans">
<List />
<span>Scans</span>
</Link>
</Dropdown.Item>
</Dropdown.Menu>
</Dropdown>
</div>
Expand Down
50 changes: 50 additions & 0 deletions src/components/header.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { Button } from 'react-daisyui';
import { ArrowLeft, Icon } from 'react-feather';
import { useNavigate } from 'react-router-dom';
import { ReactNode } from 'react';

interface HeaderProps {
title: string;
Icon?: Icon;
onIconClick: () => void;
back: false;
}

interface BackHeaderProps {
title: string;
back: true;
}

interface RawTrail {
trailing?: ReactNode;
}

export function Header({
trailing,
title,
back,
...rest
}: RawTrail & (HeaderProps | BackHeaderProps)) {
const nav = useNavigate();
const { Icon, onIconClick } = rest as HeaderProps;
const navBack = () => {
nav('..');
};
return (
<div className={`flex items-center ${back ? '' : 'justify-between'}`}>
{back && (
<Button color="ghost" onClick={navBack} shape="circle">
<ArrowLeft />
</Button>
)}
<h1 className="text-2xl">{title}</h1>
<div className="mx-auto" />
{!back && Icon && (
<Button color="ghost" onClick={onIconClick} shape="circle">
<Icon />
</Button>
)}
{trailing}
</div>
);
}
38 changes: 18 additions & 20 deletions src/components/notification.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {
selectNotification,
removeNotification,
selectNotifications,
useAppDispatch,
useAppSelector,
} from '../store';
Expand All @@ -9,31 +9,29 @@ import { useCallback } from 'react';
import { X } from 'react-feather';

export function Notification() {
const notifications = useAppSelector(selectNotifications);
const notifications = useAppSelector(selectNotification);
const dispatch = useAppDispatch();
const remove = useCallback(
(msg: string) => () => {
dispatch(removeNotification(msg));
(id: string) => () => {
dispatch(removeNotification(id));
},
[dispatch]
);
return (
<div className="">
{notifications.map((message, index) => (
<Toast horizontal="start" vertical="bottom" key={index}>
<Alert status="error">
<Button
onClick={remove(message)}
color="ghost"
size="sm"
shape="circle"
>
<X />
</Button>
<span>{message}</span>
</Alert>
</Toast>
<Toast horizontal="start" vertical="bottom">
{notifications.map((notification, index) => (
<Alert key={index} status="error">
<Button
onClick={remove(notification.id)}
size="sm"
color="ghost"
shape="circle"
>
<X />
</Button>
<span>{notification.message}</span>
</Alert>
))}
</div>
</Toast>
);
}
10 changes: 0 additions & 10 deletions src/components/theme-wrapper.tsx

This file was deleted.

16 changes: 16 additions & 0 deletions src/components/to-list-scan.button.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { useCallback } from 'react';
import { Button } from 'react-daisyui';
import { ArrowRight } from 'react-feather';
import { useNavigate } from 'react-router-dom';

export default function ToListScanButton() {
const navigate = useNavigate();
const toScans = useCallback(() => navigate('/scans'), [navigate]);

return (
<Button fullWidth color="primary" onClick={toScans}>
<span>To Scans</span>
<ArrowRight />
</Button>
);
}
5 changes: 1 addition & 4 deletions src/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import i18n from './i18n';
import './index.scss';
import { Provider } from 'react-redux';
import { store } from './store';
import { ThemeWrapper } from './components/theme-wrapper.tsx';
import { isElectron } from './shared/constants.ts';

Sentry.init({
Expand Down Expand Up @@ -36,9 +35,7 @@ root.render(
<React.StrictMode>
<I18nextProvider i18n={i18n}>
<Provider store={store}>
<ThemeWrapper>
<App />
</ThemeWrapper>
<App />
</Provider>
</I18nextProvider>
</React.StrictMode>
Expand Down
19 changes: 16 additions & 3 deletions src/router.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,19 @@ export const router = createBrowserRouter([
element: (
<div>
<FloatingConfig />
<Outlet />
<div className="flex flex-col gap-2 md:gap-4">
<Outlet />
</div>
</div>
),
children: [
{
path: '/scans',
element: (
<div className="p-4">
<Outlet />
<div className="flex flex-col gap-2 md:gap-4">
<Outlet />
</div>
</div>
),
children: [
Expand All @@ -30,7 +34,16 @@ export const router = createBrowserRouter([
},
{
path: '/config',
lazy: () => import('./screens/app-config.screen'),
children: [
{
path: '',
lazy: () => import('./screens/app-config.screen'),
},
{
path: 'scan',
lazy: () => import('./screens/scan-config.screen'),
},
],
},
],
},
Expand Down
26 changes: 9 additions & 17 deletions src/screens/app-config.screen.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import React, { lazy, useCallback, useEffect } from 'react';
import React, { lazy, useEffect } from 'react';
import { t } from 'i18next';
import { Button, Card } from 'react-daisyui';
import { Card } from 'react-daisyui';
import { useNavigate } from 'react-router-dom';
import { isElectron } from '../shared/constants.ts';
import { ArrowRight } from 'react-feather';

const ConfigQrCode = lazy(() => import('../components/config.qr-code'));
const ConfigScanButton = lazy(() => import('../components/config-scan.button'));
const ToListScanButton = lazy(
() => import('../components/to-list-scan.button.tsx')
);

const configKey = 'lynx:config';

Expand All @@ -18,10 +21,6 @@ export const Component: React.FC = () => {
return JSON.parse(item);
}
};
const scanConfigAndPersist = useCallback(() => {
console.log('scanConfigAndPersist');
}, []);
const toScans = useCallback(() => navigate('/scans'), [navigate]);

useEffect(() => {
const config = checkConfig();
Expand All @@ -30,22 +29,15 @@ export const Component: React.FC = () => {
}
}, [navigate]);
return (
<div className="flex justify-center items-center h-[100vh] p-4">
<div className="flex justify-center h-[100vh] items-center p-4">
<Card className="max-w-sm border-0 sm:border-2 sm:bg-base-200">
{isElectron && <ConfigQrCode />}
<Card.Body>
<Card.Title>{t('config.page')}</Card.Title>
<p>{t('config.description')}</p>
<Card.Actions>
{isElectron && (
<Button onClick={scanConfigAndPersist} color="primary">
Scan
</Button>
)}
<Button fullWidth color="primary" onClick={toScans}>
<span>To Scans</span>
<ArrowRight />
</Button>
{!isElectron && <ConfigScanButton />}
{isElectron && <ToListScanButton />}
</Card.Actions>
</Card.Body>
</Card>
Expand Down
85 changes: 85 additions & 0 deletions src/screens/scan-config.screen.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import React, { useState } from 'react';
import { Header } from '../components/header.tsx';
import {
IDetectedBarcode,
Scanner,
useDevices,
} from '@yudiel/react-qr-scanner';
import { Button, Dropdown } from 'react-daisyui';
import { Camera } from 'react-feather';
import { setUrlConfig, useAppDispatch } from '../store';
import { useNavigate } from 'react-router-dom';

export const Component: React.FC = () => {
const dispatch = useAppDispatch();
const navigate = useNavigate();
const devices = useDevices();
const [deviceId, setDeviceId] = useState(
devices.length > 0 ? devices[0].deviceId : ''
);
const onScan = (detectedCodes: IDetectedBarcode[]) => {
for (const { rawValue, format } of detectedCodes) {
if (format in ['qr_code', 'rm_qr_code', 'micro_qr_code']) {
const config = JSON.parse(rawValue) as Record<string, string>;
dispatch(setUrlConfig(config.url));
navigate('..');
}
}
};
return (
<div className="flex flex-col">
<div className="p-4">
<Header
title="Configs"
back={true}
trailing={
<Dropdown horizontal="left">
<Dropdown.Toggle button={false}>
<Button shape="circle">
<Camera />
</Button>
</Dropdown.Toggle>
<Dropdown.Menu className="w-72 z-50">
{devices.map(({ deviceId, label }) => (
<Dropdown.Item anchor={false} key={deviceId}>
<button onClick={() => setDeviceId(deviceId)}>
<Camera />
{label}
</button>
</Dropdown.Item>
))}
</Dropdown.Menu>
</Dropdown>
}
/>
</div>
<div className="bg-primary h-[calc(100vh-80px)]">
<Scanner
components={{
audio: false,
finder: false,
tracker: (detectedCodes, ctx) => {
for (const detectedCode of detectedCodes) {
const { cornerPoints } = detectedCode;
ctx.strokeStyle = '#f5f';
ctx.lineWidth = 4;
ctx.beginPath();
ctx.moveTo(cornerPoints[0].x, cornerPoints[0].y);
ctx.lineTo(cornerPoints[1].x, cornerPoints[1].y);
ctx.lineTo(cornerPoints[2].x, cornerPoints[2].y);
ctx.lineTo(cornerPoints[3].x, cornerPoints[3].y);
ctx.lineTo(cornerPoints[0].x, cornerPoints[0].y);
ctx.stroke();
}
},
}}
classNames={{ video: 'object-cover' }}
onScan={onScan}
constraints={{
deviceId,
}}
/>
</div>
</div>
);
};
Loading

0 comments on commit 243e6a1

Please sign in to comment.