Skip to content

Commit

Permalink
Merge pull request #24 from FRC2713/cleanup
Browse files Browse the repository at this point in the history
Cleanup directory structure, add text preview to QR Code
  • Loading branch information
tytremblay authored Feb 23, 2024
2 parents 9813aca + e472384 commit 2994874
Show file tree
Hide file tree
Showing 31 changed files with 413 additions and 223 deletions.
170 changes: 14 additions & 156 deletions src/app.tsx
Original file line number Diff line number Diff line change
@@ -1,176 +1,34 @@
import { useTheme } from 'next-themes';
import { useMemo, useState } from 'preact/hooks';
import { Logo } from './components/Logo';
import QRModal from './components/QRModal';
import Section from './components/Section';
import Button, { Variant } from './components/core/Button';
import {
getQRCodeData,
resetSections,
resetToDefaultConfig,
uploadConfig,
useQRScoutState,
} from './store/store';
import { useState } from 'preact/hooks';
import { Footer } from './components/Footer';
import { Header } from './components/Header';
import { QRModal } from './components/QR';
import { Sections } from './components/Sections';
import { CommitAndResetSection } from './components/Sections/CommitAndResetSection/CommitAndResetSection';
import { ConfigSection } from './components/Sections/ConfigSection';
import { useQRScoutState } from './store/store';

export function App() {
const { theme, setTheme } = useTheme();

const formData = useQRScoutState(state => state.formData);

const [showQR, setShowQR] = useState(false);

const missingRequiredFields = useMemo(() => {
return formData.sections
.map(s => s.fields)
.flat()
.filter(
f =>
f.required &&
(f.value === null || f.value === undefined || f.value === ``),
);
}, [formData]);

function getFieldValue(code: string): any {
return formData.sections
.map(s => s.fields)
.flat()
.find(f => f.code === code)?.value;
}

function download(filename: string, text: string) {
var element = document.createElement('a');
element.setAttribute(
'href',
'data:text/plain;charset=utf-8,' + encodeURIComponent(text),
);
element.setAttribute('download', filename);

element.style.display = 'none';
document.body.appendChild(element);

element.click();

document.body.removeChild(element);
}

function downloadConfig() {
const configDownload = { ...formData };

configDownload.sections.forEach(s =>
s.fields.forEach(f => (f.value = undefined)),
);
download('QRScout_config.json', JSON.stringify(configDownload));
}

return (
<div className="min-h-screen py-2 dark:bg-gray-700">
<head>
<title>QRScout|{formData.title}</title>
<link rel="icon" href="/favicon.ico" />
</head>

<Header />
<main className="flex flex-1 flex-col items-center justify-center px-4 text-center">
<h1 className="font-sans text-6xl font-bold">
<div className={`font-rhr text-red-rhr`}>{formData.page_title}</div>
</h1>
<QRModal
show={showQR}
title={`${getFieldValue('robot')} - ${getFieldValue('matchNumber')}`}
data={getQRCodeData()}
onDismiss={() => setShowQR(false)}
/>
<QRModal show={showQR} onDismiss={() => setShowQR(false)} />

<form className="w-full px-4">
<div className="mt-4 grid grid-cols-1 gap-4 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5">
{formData.sections.map(section => {
return <Section key={section.name} name={section.name} />;
})}

<div className="mb-4 flex flex-col justify-center rounded bg-white py-2 shadow-md dark:bg-gray-600">
<button
className="focus:shadow-outline mx-2 rounded bg-gray-700 py-6 px-6 font-bold uppercase text-white hover:bg-gray-700 focus:shadow-lg focus:outline-none disabled:bg-gray-300 dark:bg-red-rhr"
type="button"
onClick={() => setShowQR(true)}
disabled={missingRequiredFields.length > 0}
>
Commit
</button>
<button
className="focus:shadow-outline mx-2 my-6 rounded border border-red-rhr bg-white py-2 font-bold uppercase text-red-rhr hover:bg-red-200 focus:outline-none dark:bg-gray-500 dark:text-white dark:hover:bg-gray-700"
type="button"
onClick={() => resetSections()}
>
Reset
</button>
</div>
<div className="mb-4 flex flex-col justify-center rounded bg-white shadow-md dark:bg-gray-600 gap-2 p-2">
<Button
variant={Variant.Secondary}
onClick={() =>
navigator.clipboard.writeText(
formData.sections
.map(s => s.fields)
.flat()
.map(f => f.title)
.join('\t'),
)
}
>
Copy Column Names
</Button>
<Button
variant={Variant.Secondary}
onClick={() => downloadConfig()}
>
Download Config
</Button>
<label className="mx-2 flex cursor-pointer flex-row justify-center rounded bg-gray-500 py-2 text-center font-bold text-white shadow-sm hover:bg-gray-600">
<span className="text-base leading-normal">Upload Config</span>
<input
type="file"
className="hidden"
accept=".json"
onChange={e => uploadConfig(e)}
/>
</label>
<div className="mx-2 flex flex-col justify-start bg-gray-500 p-2 rounded">
<div className="rounded-t pb-2 text-left font-bold text-white">
Theme
</div>
<select
className="rounded bg-white px-4 py-2 dark:bg-gray-700 dark:text-white"
name="Theme"
id="theme"
onInput={v => setTheme(v.currentTarget.value)}
value={theme}
>
<option key={'system'} value={'system'}>
System
</option>
<option key={'dark'} value={'dark'}>
Dark
</option>
<option key={'light'} value={'light'}>
Light
</option>
</select>
</div>

<Button
variant={Variant.Secondary}
onClick={() => resetToDefaultConfig()}
>
Reset To Default Config
</Button>
</div>
<Sections />
<CommitAndResetSection onCommit={() => setShowQR(true)} />
<ConfigSection />
</div>
</form>
</main>
<footer>
<div className="mt-8 flex h-24 flex-col items-center justify-center p-2">
<Logo />
</div>
</footer>
<Footer />
</div>
);
}
11 changes: 11 additions & 0 deletions src/components/Footer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { Logo } from './Logo';

export function Footer() {
return (
<footer>
<div className="mt-8 flex h-24 flex-col items-center justify-center p-2">
<Logo />
</div>
</footer>
);
}
11 changes: 11 additions & 0 deletions src/components/Header.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { useQRScoutState } from '../store/store';

export function Header() {
const title = useQRScoutState(state => state.formData.title);
return (
<head>
<title>QRScout|{title}</title>
<link rel="icon" href="/favicon.ico" />
</head>
);
}
28 changes: 28 additions & 0 deletions src/components/QR/CloseButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
export type CloseButtonProps = {
onClick: () => void;
};

export function CloseButton(props: CloseButtonProps) {
return (
<button
className="focus:shadow-outline rounded-full text-gray-800 absolute top-0 right-0 m-2 p-2 hover:bg-gray-200 "
type="button"
onClick={props.onClick}
>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
strokeWidth={2}
stroke="currentColor"
className="w-6 h-6"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M6 18 18 6M6 6l12 12"
/>
</svg>
</button>
);
}
26 changes: 26 additions & 0 deletions src/components/QR/CopyButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
export type CopyButtonProps = {
onCopy: () => void;
className?: string;
};

export function CopyButton(props: CopyButtonProps) {
return (
<div onClick={props.onCopy} className={props.className}>
<svg
className="text-gray-500 hover:text-gray-800 "
width="24"
height="24"
viewBox="0 0 24 24"
strokeWidth="2"
stroke="currentColor"
fill="none"
strokeLinecap="round"
strokeLinejoin="round"
>
<path stroke="none" d="M0 0h24v24H0z" />
<rect x="8" y="8" width="12" height="12" rx="2" />
<path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2" />
</svg>
</div>
);
}
27 changes: 27 additions & 0 deletions src/components/QR/PreviewText.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { CopyButton } from './CopyButton';

export type PreviewTextProps = {
data: string;
};
export function PreviewText(props: PreviewTextProps) {
const chunks = props.data.split('\t');
return (
<div className="flex flex-col items-end gap-2">
<div className="text-left p-2 bg-gray-100 rounded-md shadow-md dark:bg-gray-600 mt-2">
<p className=" font-mono text-wrap break-all text-gray-800 dark:text-gray-200">
{chunks.map((c, i) => (
<>
<span key={i + c}>{c}</span>

<span key={i + c + 'tab'} className="text-gray-500">
{i !== chunks.length - 1 ? '|' : ' ↵'}
</span>
</>
))}
</p>
</div>

<CopyButton onCopy={() => navigator.clipboard.writeText(props.data)} />
</div>
);
}
56 changes: 56 additions & 0 deletions src/components/QR/QRModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { useMemo, useRef } from 'preact/hooks';
import QRCode from 'qrcode.react';
import { useOnClickOutside } from '../../hooks/useOnClickOutside';
import { getFieldValue, useQRScoutState } from '../../store/store';
import { Config } from '../inputs/BaseInputProps';
import { CloseButton } from './CloseButton';
import { PreviewText } from './PreviewText';

export interface QRModalProps {
show: boolean;
onDismiss: () => void;
}

export function getQRCodeData(formData: Config): string {
return formData.sections
.map(s => s.fields)
.flat()
.map(v => `${v.value}`.replace(/\n/g, ' '))
.join('\t');
}

export function QRModal(props: QRModalProps) {
const modalRef = useRef(null);
const formData = useQRScoutState(state => state.formData);
useOnClickOutside(modalRef, props.onDismiss);

const title = `${getFieldValue('robot')} - M${getFieldValue(
'matchNumber',
)}`.toUpperCase();

const qrCodeData = useMemo(() => getQRCodeData(formData), [formData]);
return (
<>
{props.show && (
<>
<div
className="fixed inset-0 h-full w-full overflow-y-auto bg-gray-600 bg-opacity-50 dark:bg-opacity-70 backdrop-blur-sm "
id="my-modal"
/>
<div
ref={modalRef}
className="fixed top-20 rounded-md bg-white border p-5 shadow-lg w-96"
>
<div className="flex flex-col items-center ">
<h1 className="text-4xl text-black font-mono ">{title}</h1>
<CloseButton onClick={props.onDismiss} />
<QRCode className="m-2 mt-4" size={256} value={qrCodeData} />
<div className="h-1 w-full border-t border-gray-800 my-2" />
<PreviewText data={qrCodeData} />
</div>
</div>
</>
)}
</>
);
}
1 change: 1 addition & 0 deletions src/components/QR/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './QRModal';
Loading

0 comments on commit 2994874

Please sign in to comment.