-
Notifications
You must be signed in to change notification settings - Fork 70
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #24 from FRC2713/cleanup
Cleanup directory structure, add text preview to QR Code
- Loading branch information
Showing
31 changed files
with
413 additions
and
223 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> | ||
</> | ||
)} | ||
</> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export * from './QRModal'; |
Oops, something went wrong.