Skip to content

Commit

Permalink
Merge pull request #35 from FRC2713/settings
Browse files Browse the repository at this point in the history
add settings modal
  • Loading branch information
tytremblay authored Mar 5, 2024
2 parents da5e040 + a3e498a commit c089965
Show file tree
Hide file tree
Showing 11 changed files with 234 additions and 152 deletions.
31 changes: 20 additions & 11 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"preview": "vite preview"
},
"dependencies": {
"@heroicons/react": "^2.1.1",
"immer": "^10.0.3",
"next-themes": "^0.2.1",
"preact": "^10.19.3",
Expand Down
35 changes: 9 additions & 26 deletions src/components/QR/QRModal.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import { useMemo, useRef } from 'preact/hooks';
import { useMemo } from 'preact/hooks';
import QRCode from 'qrcode.react';
import { useOnClickOutside } from '../../hooks/useOnClickOutside';
import { getFieldValue, useQRScoutState } from '../../store/store';
import { Modal } from '../core/Modal';
import { Config } from '../inputs/BaseInputProps';
import { CloseButton } from './CloseButton';
import { PreviewText } from './PreviewText';

export interface QRModalProps {
Expand All @@ -20,36 +19,20 @@ export function getQRCodeData(formData: Config): string {
}

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 shadow-lg w-96"
>
<div className="flex flex-col items-center pt-8 ">
<CloseButton onClick={props.onDismiss} />
<QRCode className="m-2 mt-4" size={256} value={qrCodeData} />
<h1 className="text-3xl text-gray-800 font-rhr-ns ">{title}</h1>
<PreviewText data={qrCodeData} />
</div>
</div>
</>
)}
</>
<Modal show={props.show} onDismiss={props.onDismiss}>
<div className="flex flex-col items-center pt-8 px-4 bg-white rounded-md">
<QRCode className="m-2 mt-4" size={256} value={qrCodeData} />
<h1 className="text-3xl text-gray-800 font-rhr-ns ">{title}</h1>
<PreviewText data={qrCodeData} />
</div>
</Modal>
);
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { useMemo } from 'preact/hooks';
import { useQRScoutState } from '../../../store/store';
import { Section } from '../../core/Section';
import { CommitButton } from './CommitButton';
import { ResetButton } from './ResetButton';

Expand All @@ -23,12 +24,12 @@ export function CommitAndResetSection({
}, [formData]);

return (
<div className="mb-4 flex flex-col justify-center rounded bg-white py-2 shadow-md dark:bg-gray-600">
<Section>
<CommitButton
disabled={missingRequiredFields.length > 0}
onClick={onCommit}
/>
<ResetButton />
</div>
</Section>
);
}
96 changes: 18 additions & 78 deletions src/components/Sections/ConfigSection/ConfigSection.tsx
Original file line number Diff line number Diff line change
@@ -1,84 +1,24 @@
import {
resetToDefaultConfig,
uploadConfig,
useQRScoutState,
} from '../../../store/store';
import { Cog6ToothIcon } from '@heroicons/react/20/solid';
import { useState } from 'preact/hooks';
import Button, { Variant } from '../../core/Button';
import { Config } from '../../inputs/BaseInputProps';
import { ThemeSelector } from './ThemeSelector';

/**
* Download a text file
* @param filename The name of the file
* @param text The text to put in the file
*/
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);
}

/**
* Download the current form data as a json file
* @param formData The form data to download
*/
function downloadConfig(formData: Config) {
const configDownload = { ...formData };

configDownload.sections.forEach(s =>
s.fields.forEach(f => (f.value = undefined)),
);
download('QRScout_config.json', JSON.stringify(configDownload));
}
import { Section } from '../../core/Section';
import { SettingsModal } from './SettingsModal';

export function ConfigSection() {
const formData = useQRScoutState(state => state.formData);
return (
<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(formData)}
>
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>
<ThemeSelector />
const [showModal, setShowModal] = useState(false);

<Button variant={Variant.Danger} onClick={() => resetToDefaultConfig()}>
Reset Config to Default
</Button>
</div>
return (
<Section>
<div className="flex flex-col items-center justify-center pt-4">
<SettingsModal show={showModal} onDismiss={() => setShowModal(false)} />
<Button
icon={<Cog6ToothIcon className="h-5 w-5" />}
variant={Variant.Transparent}
onClick={() => setShowModal(true)}
>
Settings
</Button>
</div>
</Section>
);
}
92 changes: 92 additions & 0 deletions src/components/Sections/ConfigSection/SettingsModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import {
resetToDefaultConfig,
uploadConfig,
useQRScoutState,
} from '../../../store/store';
import Button, { Variant } from '../../core/Button';
import { Modal } from '../../core/Modal';
import { Config } from '../../inputs/BaseInputProps';
import { ThemeSelector } from './ThemeSelector';

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

/**
* Download a text file
* @param filename The name of the file
* @param text The text to put in the file
*/
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);
}

/**
* Download the current form data as a json file
* @param formData The form data to download
*/
function downloadConfig(formData: Config) {
const configDownload = { ...formData };

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

export function SettingsModal(props: ModalProps) {
const formData = useQRScoutState(state => state.formData);
return (
<Modal show={props.show} onDismiss={props.onDismiss}>
<div className="flex flex-col justify-start rounded bg-white 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(formData)}
>
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>

<ThemeSelector />
<Button variant={Variant.Danger} onClick={() => resetToDefaultConfig()}>
Reset Config to Default
</Button>
</div>
</Modal>
);
}
37 changes: 13 additions & 24 deletions src/components/Sections/FormSection.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { useQRScoutState } from '../../store/store';
import { Section } from '../core/Section';
import { InputProps } from '../inputs/BaseInputProps';
import ConfigurableInput from '../inputs/ConfigurableInput';
import InputCard from '../inputs/InputCard';
Expand All @@ -11,29 +12,17 @@ export default function FormSection(props: SectionProps) {
const formData = useQRScoutState(state => state.formData);
const inputs = formData.sections.find(s => s.name === props.name)?.fields;
return (
<div
className="mb-4 rounded bg-gray-100 shadow-md dark:bg-gray-600"
key={props.name}
>
<div className="mb-2 rounded-t bg-red-rhr p-1 shadow-md">
<h2 className="font-rhr-ns text-2xl uppercase text-white dark:text-black">
{props.name}
</h2>
</div>
<div className="flex flex-col justify-start gap-2">
{inputs?.map((e: InputProps) => (
<InputCard
title={e.title}
required={e.required}
hasValue={
e.value !== null && e.value !== undefined && e.value !== ''
}
key={`${props.name}_${e.title}`}
>
<ConfigurableInput section={props.name} code={e.code} />
</InputCard>
))}
</div>
</div>
<Section title={props.name}>
{inputs?.map((e: InputProps) => (
<InputCard
title={e.title}
required={e.required}
hasValue={e.value !== null && e.value !== undefined && e.value !== ''}
key={`${props.name}_${e.title}`}
>
<ConfigurableInput section={props.name} code={e.code} />
</InputCard>
))}
</Section>
);
}
Loading

0 comments on commit c089965

Please sign in to comment.