reactstrap#Modal TypeScript Examples
The following examples show how to use
reactstrap#Modal.
You can vote up the ones you like or vote down the ones you don't like,
and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: SiopApproval.tsx From health-cards-tests with MIT License | 6 votes |
SiopApprovalModal: React.FC<SiopApprovalProps | null> = (props) => {
if (!props.onApproval) {
return null;
}
const focusCallback = useCallback(b => {
setTimeout(() => b && b.focus());
}, []);
return <>
<Modal isOpen={true}>
<ModalHeader>Share with {props.with}?</ModalHeader>
<ModalBody>The following details will be shared:
<ul>
<li><b>Your ID Card:</b> allows secure communications</li>
{props.share.includes('https://smarthealth.cards#covid19') && <li>
<b>Your COVID Card:</b> labs and vaccines for COVID-19
</li>}
{props.share.includes('https://smarthealth.cards#immunization') && <li>
<b>Your Immunizations Card:</b> full vaccine history
</li>}
</ul>
</ModalBody>
<ModalFooter>
<Button color="danger" onClick={props.onDenial}>Do not share</Button>
<Button innerRef={focusCallback} color="success" onClick={props.onApproval}>
Share {props.share.length > 0 ? "these cards" : "this card"}
</Button>
</ModalFooter>
</Modal>
</>;
}
Example #2
Source File: Modals.tsx From health-cards-tests with MIT License | 6 votes |
QRPresentationModal: React.FC<QrPresentationState> = ({ healthCard, dispatch }) => {
const done = () => {
dispatch({ type: 'end-qr-presentation' });
}
const SMALLEST_B64_CHAR_CODE = 45; // "-".charCodeAt(0) === 45
const encodeToNumeric = (jws: string): number[] => jws
.split("")
.map(c => c.charCodeAt(0) - SMALLEST_B64_CHAR_CODE)
.flatMap(c => [
Math.floor(c / 10),
c % 10
]);
return <>
<Modal isOpen={true}>
<ModalHeader>Present Health Card</ModalHeader>
<ModalBody><QrDisplay numeric={encodeToNumeric(healthCard.vcSigned)} noLink={true}></QrDisplay></ModalBody>
<ModalFooter className="json"><pre>{JSON.stringify(healthCard.vcPayload, null, 2)}</pre></ModalFooter>
<ModalFooter>
<Button color="success" onClick={e => done()}>Done</Button>
</ModalFooter>
</Modal>
</>
}
Example #3
Source File: ErrorPopup.tsx From nextclade with MIT License | 6 votes |
export function ErrorPopup() {
const { t } = useTranslationSafe()
const error = useRecoilValue(globalErrorAtom)
const dismissError = useResetRecoilState(globalErrorAtom)
const reload = useReloadPage('/')
if (!error) {
return null
}
return (
<Modal centered isOpen backdrop="static" toggle={dismissError} fade={false} size="lg">
<ModalHeader toggle={dismissError} tag="div">
<h3 className="text-center text-danger">{t('Error')}</h3>
</ModalHeader>
<ModalBody>
<ErrorContent error={error} />
<ErrorContentExplanation />
</ModalBody>
<ModalFooter>
<div className="ml-auto">
<Button type="button" color="danger" title={t('Reload the page and start Nextclade fresh')} onClick={reload}>
{t('Restart Nextclade')}
</Button>
<ButtonOk type="button" color="secondary" title={t('Close this dialog window')} onClick={dismissError}>
{t('Dismiss')}
</ButtonOk>
</div>
</ModalFooter>
</Modal>
)
}
Example #4
Source File: SiopApproval.tsx From health-cards-tests with MIT License | 5 votes |
SiopRequestReceiver: React.FC<SiopRequestReceiverProps> = (props) => {
const [currentCode, setCurrentCode] = useState("")
const runningQrScanner = useRef(null)
let qrScanner; // scope bound to callback
const videoCallback = useCallback((videoElement) => {
if (!videoElement) {
if (qrScanner) {
qrScanner.destroy()
}
return;
}
qrScanner = new QrScanner(videoElement, result => {
if (result.length)
props.onReady(result)
});
runningQrScanner.current = qrScanner
qrScanner.start();
}, [])
useEffect(() => {
if (props.redirectMode === "window-open") {
const onMessage = ({ data, source }) => {
props.onReady(data)
}
const registered = window.addEventListener("message", onMessage)
const opened = window.open(props.startUrl)
return () => {
window.removeEventListener("message", onMessage)
}
}
}, [])
return props.redirectMode === "qr" ? <>
<Modal isOpen={true}>
<ModalHeader>Connect to {props.label}</ModalHeader>
<ModalBody>
<div>Scan a QR Code</div>
<video ref={videoCallback} style={{ maxWidth: "100%", maxHeight: "30vh" }} />
</ModalBody>
<ModalFooter >
<InputGroup>
<Input placeholder="Or paste a URL directly..." type="text" autoFocus={true} value={currentCode} onChange={e => setCurrentCode(e.target.value)}>
</Input>
</InputGroup>
{props.onCancel ?
<Button color="error" onClick={e => props.onCancel()}>
Cancel
</Button> : ""
}
<Button color="success" onClick={e => props.onReady(currentCode)}>
Connect
</Button>
</ModalFooter>
</Modal>
</> : <>
<span>Waiting for redirect...</span>
</>
}
Example #5
Source File: MeasurementInterval.tsx From mops-vida-pm-watchdog with MIT License | 5 votes |
MeasurementInterval: React.FC = () => {
const [open, setOpen] = React.useState(false);
const dispatch = useDispatch();
const connected = useSelector((state) => state.report.connected);
const interval = useSelector((state) => state.report.latest.measurementInterval);
const enabled = useSelector((state) => state.report.latest.measurementIntervalEnabled);
const onToggle = () => setOpen(connected && !open);
const onChange = async (value: number) => {
setOpen(false);
await dispatch(setMeasurementInterval(value));
await dispatch(setMeasurementEnable(true));
};
const onDisable = async () => {
setOpen(false);
dispatch(setMeasurementEnable(false));
};
return (
<>
<span onClick={onToggle}>{enabled ? `${interval} minutes` : 'Disabled'}</span>
<Modal placement='bottom' isOpen={open} toggle={onToggle}>
<ModalHeader>Measurement interval</ModalHeader>
<ModalBody>
<p>Current Interval: {interval} minutes</p>
<ListGroup>
{steps.map((step) => (
<ListGroupItem onClick={() => onChange(step)} key={step} active={step === interval}>
{step} minutes
</ListGroupItem>
))}
</ListGroup>
</ModalBody>
<ModalFooter>
<Button color='primary' onClick={onDisable} hidden={!enabled}>
Disable
</Button>
<Button color='secondary' onClick={onToggle}>
Cancel
</Button>
</ModalFooter>
</Modal>
</>
);
}
Example #6
Source File: chart_embed.tsx From website with Apache License 2.0 | 5 votes |
public render(): JSX.Element {
return (
<Modal
isOpen={this.state.modal}
toggle={this.toggle}
className="modal-dialog-centered modal-lg"
onOpened={this.onOpened}
id={this.modalId}
>
<ModalHeader toggle={this.toggle}>
{intl.formatMessage({
id: "embed_export_chart_link",
defaultMessage: "Export this chart",
description:
"Text for the hyperlink text that will let users export data and export charts.",
})}
</ModalHeader>
<ModalBody>
<div
ref={this.svgContainerElement}
className="modal-chart-container"
></div>
<textarea
className="copy-svg mt-3"
value={this.state.dataCsv}
readOnly
ref={this.textareaElement}
onClick={this.onClickTextarea}
></textarea>
</ModalBody>
<ModalFooter>
<Button color="primary" onClick={this.onDownloadSvg}>
{intl.formatMessage({
id: "embed_download_chart_link",
defaultMessage: "Download Chart Image",
description:
"Text for the hyperlink text that will download the chart image.",
})}
</Button>{" "}
<Button color="primary" onClick={this.onDownloadData}>
{intl.formatMessage({
id: "embed_download_csv_link",
defaultMessage: "Download Data as CSV",
description:
"Text for the hyperlink text that will download the data as a CSV.",
})}
</Button>
</ModalFooter>
</Modal>
);
}
Example #7
Source File: source_selector.tsx From website with Apache License 2.0 | 5 votes |
export function SourceSelector(props: SourceSelectorPropType): JSX.Element {
const [modalOpen, setModalOpen] = useState(false);
const [modalSelections, setModalSelections] = useState(
getModalSelections(props.svInfoList)
);
useEffect(() => {
// If modal is closed without updating sources, we want to reset the
// selections in the modal.
if (!modalOpen) {
setModalSelections(getModalSelections(props.svInfoList));
}
}, [props.svInfoList, modalOpen]);
return (
<>
<Button
className={`${SELECTOR_PREFIX}-open-modal-button`}
size="sm"
color="light"
onClick={() => setModalOpen(true)}
>
Select {props.svInfoList.length > 1 ? "Sources" : "Source"}
</Button>
<Modal
isOpen={modalOpen}
className={`${SELECTOR_PREFIX}-modal`}
style={{ maxWidth: MODAL_MAX_WIDTH }}
>
<ModalHeader toggle={() => setModalOpen(false)}>
Source Selector
</ModalHeader>
<ModalBody>
{props.svInfoList.map((svInfo) => {
const selectedMetahash = modalSelections[svInfo.dcid];
return (
<Collapsible
key={svInfo.dcid}
trigger={getSVTriggerJsx(false, svInfo, selectedMetahash)}
triggerWhenOpen={getSVTriggerJsx(
true,
svInfo,
selectedMetahash
)}
open={props.svInfoList.length < 2}
>
<div className={`${SELECTOR_PREFIX}-options-section`}>
{getSourceOptionJsx(
svInfo,
"",
modalSelections,
setModalSelections
)}
{getSourceOptionSectionJsx(
svInfo,
modalSelections,
setModalSelections
)}
</div>
</Collapsible>
);
})}
</ModalBody>
<ModalFooter>
<Button color="primary" onClick={onConfirm}>
Update
</Button>
</ModalFooter>
</Modal>
</>
);
function onConfirm(): void {
props.onSvMetahashUpdated(modalSelections);
setModalOpen(false);
}
}
Example #8
Source File: Modals.tsx From health-cards-tests with MIT License | 5 votes |
ConfigEditModal: React.FC<{
defaultUiState: UiState;
uiState: UiState;
dispatch: any;
}> = ({ defaultUiState, uiState, dispatch }) => {
const onSave = ui => {
window.location.hash = JSON.stringify({ ...ui, editingConfig: undefined }, null, 0)
dispatch({ type: 'save-ui-state', newState: ui })
}
const onDiscard = () => {
dispatch({ type: 'toggle-editing-config' });
}
const [ui, setUi] = useState(uiState);
return <>
<Modal isOpen={true}>
<ModalHeader>Edit Config Settings</ModalHeader>
<ModalBody>
<ConfigEditOption
title="FHIR Server"
default={defaultUiState.fhirClient.server}
value={ui.fhirClient.server}
onChange={v => setUi({ ...ui, fhirClient: { ...ui.fhirClient, server: v } })} />
<ConfigEditOption
title="Client ID"
default={defaultUiState.fhirClient.client_id}
value={ui.fhirClient.client_id}
onChange={v => setUi({ ...ui, fhirClient: { ...ui.fhirClient, client_id: v } })} />
<ConfigEditOption
title="Client Secret"
default={defaultUiState.fhirClient.client_secret}
value={ui.fhirClient.client_secret}
onChange={v => setUi({ ...ui, fhirClient: { ...ui.fhirClient, client_secret: v } })} />
<ConfigEditOption
title="Scopes"
default={defaultUiState.fhirClient.scope}
value={ui.fhirClient.scope}
onChange={v => setUi({ ...ui, fhirClient: { ...ui.fhirClient, scope: v } })} />
</ModalBody>
<ModalFooter>
<Button color="danger" onClick={e => onDiscard()}>Cancel</Button>
<Button color="success" onClick={e => onSave(ui)}>Save to Bookmarkable URL</Button>
</ModalFooter>
</Modal>
</>;
}
Example #9
Source File: Payment.tsx From reference-merchant with Apache License 2.0 | 5 votes |
export default function Payment({ product, isOpen, demoMode, onClose }: PaymentProps) {
const [paymentProcessingDetails, setPaymentProcessingDetails] = useState<
PaymentProcessingDetails | undefined
>();
type PaymentState = "inactive" | "fetchingProcessingDetails" | "paying" | "paymentCleared";
const [paymentState, setPaymentState] = useState<PaymentState>("inactive");
const [showOrderDetails, setShowOrderDetails] = useState<boolean>(false);
if (paymentState === "inactive" && isOpen && !!product) {
setPaymentState("fetchingProcessingDetails");
}
// Initiates payment
useEffect(() => {
let isOutdated = false;
const fetchPaymentUrl = async () => {
try {
if (paymentState !== "fetchingProcessingDetails") return;
const payment = await new BackendClient().checkoutOne(product!.gtin);
if (!isOutdated) {
setPaymentProcessingDetails(payment);
setPaymentState("paying");
}
} catch (e) {
console.error("Unexpected error", e);
}
};
// noinspection JSIgnoredPromiseFromCall
fetchPaymentUrl();
return () => {
isOutdated = true;
};
}, [paymentState, product]);
const timeoutRef = useRef<NodeJS.Timeout>();
const onModalClosed = () => {
setPaymentState("inactive");
onClose();
};
return (
<Modal isOpen={isOpen} centered={true} size="md" toggle={onModalClosed} fade={true}>
<ModalHeader toggle={onModalClosed}>{product?.name}</ModalHeader>
<ModalBody className="p-0">
{paymentState === "fetchingProcessingDetails" && (
<div className="d-flex justify-content-center my-5">
<Spinner color="primary" />
</div>
)}
{paymentState === "paying" && (
<iframe
title="Checkout form"
height="560"
src={
demoMode
? `${paymentProcessingDetails?.paymentFormUrl}&demoMode=True`
: paymentProcessingDetails?.paymentFormUrl
}
frameBorder="0"
allowFullScreen
/>
)}
{paymentState === "paymentCleared" && (
<h4 className="my-5 text-success text-center">
<i className="fa fa-check" /> Paid successfully!
</h4>
)}
</ModalBody>
</Modal>
);
}
Example #10
Source File: Payment.tsx From reference-merchant with Apache License 2.0 | 5 votes |
function OrderDetailsModal({ orderId, isOpen, onClose }: OrderDetailsModalProps) {
return (
<Modal isOpen={isOpen} centered={true} size="md" toggle={onClose} fade={true}>
<ModalHeader toggle={onClose} />
<ModalBody className="p-0">{orderId && <OrderDetails orderId={orderId} />}</ModalBody>
</Modal>
);
}
Example #11
Source File: stat_var_widget.tsx From website with Apache License 2.0 | 4 votes |
export function StatVarWidget(props: StatVarWidgetPropsType): JSX.Element {
// Set up refs for sv widget modal. Widget is tied to the LHS menu but
// reattached to the modal when it is opened on small screens.
const svHierarchyModalRef = createRef<HTMLDivElement>();
const svHierarchyContainerRef = createRef<HTMLDivElement>();
function onSvModalOpened(): void {
if (svHierarchyModalRef.current && svHierarchyContainerRef.current) {
svHierarchyModalRef.current.appendChild(svHierarchyContainerRef.current);
}
}
function onSvModalClosed(): void {
document
.getElementById("explore")
.appendChild(svHierarchyContainerRef.current);
}
useEffect(() => {
if (!_.isEmpty(props.samplePlaces) && !_.isEmpty(props.selectedSVs)) {
axios
.post("/api/place/stat-vars/union", {
dcids: props.samplePlaces.map((place) => place.dcid),
statVars: Object.keys(props.selectedSVs),
})
.then((resp) => {
const availableSVs = resp.data;
const unavailableSVs = [];
for (const sv in props.selectedSVs) {
if (availableSVs.indexOf(sv) === -1) {
unavailableSVs.push(sv);
}
}
if (!_.isEmpty(unavailableSVs)) {
props.deselectSVs(unavailableSVs);
alert(
`Sorry, the selected variable${
unavailableSVs.length > 1 ? "s" : ""
} [${unavailableSVs
.map((sv) => props.selectedSVs[sv].title || sv)
.join(", ")}] ` +
`${
unavailableSVs.length > 1 ? "are" : "is"
} not available for the chosen place${
props.samplePlaces.length > 1 ? "s" : ""
}.`
);
}
});
}
}, [props.samplePlaces]);
return (
<>
<div className="d-none d-lg-flex explore-menu-container" id="explore">
{props.collapsible && (
<DrawerToggle
collapseElemId="explore"
visibleElemId="stat-var-hierarchy-section"
/>
)}
<div ref={svHierarchyContainerRef} className="full-size">
<StatVarHierarchy
type={props.svHierarchyType}
places={props.samplePlaces}
selectedSVs={Object.keys(props.selectedSVs)}
selectSV={props.selectSV}
searchLabel={"Statistical Variables"}
deselectSV={(sv) => props.deselectSVs([sv])}
/>
</div>
</div>
<Modal
isOpen={props.openSvHierarchyModal}
toggle={props.openSvHierarchyModalCallback}
className="modal-dialog-centered modal-lg"
contentClassName="modal-sv-widget"
onOpened={onSvModalOpened}
onClosed={onSvModalClosed}
scrollable={true}
>
<ModalHeader toggle={props.openSvHierarchyModalCallback}>
Select Variables
</ModalHeader>
<ModalBody>
<div ref={svHierarchyModalRef} className="full-size"></div>
</ModalBody>
<ModalFooter>
<Button color="primary" onClick={props.openSvHierarchyModalCallback}>
Done
</Button>
</ModalFooter>
</Modal>
</>
);
}
Example #12
Source File: ExportDialogButton.tsx From nextclade with MIT License | 4 votes |
export function ExportDialogButton() {
const { t } = useTranslationSafe()
const [isOpen, setIsOpen] = useState<boolean>(false)
const toggleOpen = useCallback(() => setIsOpen((isOpen) => !isOpen), [])
const open = useCallback(() => setIsOpen(true), [])
const close = useCallback(() => setIsOpen(false), [])
const canDownload = useRecoilValue(canDownloadAtom)
// TODO: We could probably use a map and then iterate over it, to reduce duplication
const exportZip = useExportZip()
const exportFasta = useExportFasta()
const exportCsv = useExportCsv()
const exportTsv = useExportTsv()
const exportJson = useExportJson()
const exportNdjson = useExportNdjson()
const exportPeptides = useExportPeptides()
const exportTree = useExportTree()
const exportInsertionsCsv = useExportInsertionsCsv()
const exportErrorsCsv = useExportErrorsCsv()
const exportParams = useMemo(() => DEFAULT_EXPORT_PARAMS, [])
return (
<>
<PanelButton type="button" onClick={open} title={t('Download results')} disabled={!canDownload}>
<DownloadIcon />
</PanelButton>
<Modal centered isOpen={isOpen} toggle={toggleOpen} fade={false} size="lg">
<ModalHeader toggle={close} tag="div" className="d-flex">
<h4 className="mx-auto">
<DownloadIcon />
{t('Download results')}
</h4>
</ModalHeader>
<ModalBody>
<Row>
<Col>
<Card>
<ListGroup flush>
<ExportFileElement
Icon={FileIconJson}
filename={exportParams.filenameJson}
HelpMain={t('Results of the analysis in JSON format.')}
HelpDetails={t(
'Contains detailed results of the analysis, such as clades, mutations, QC metrics etc., in JSON format. Convenient for further automated processing.',
)}
HelpDownload={t('Download results of the analysis in JSON format.')}
onDownload={exportJson}
/>
<ExportFileElement
Icon={FileIconNdjson}
filename={exportParams.filenameNdjson}
HelpMain={t('Results of the analysis in NDJSON format (newline-delimited JSON).')}
HelpDetails={t(
'Contains detailed results of the analysis, such as clades, mutations, QC metrics etc., in NDJSON format. Convenient for further automated processing.',
)}
HelpDownload={t('Download results of the analysis in NDJSON format.')}
onDownload={exportNdjson}
/>
<ExportFileElement
Icon={FileIconCsv}
filename={exportParams.filenameCsv}
HelpMain={t('Summarized results of the analysis in CSV format.')}
HelpDetails={t(
'Contains summarized results of the analysis, such as clades, mutations, QC metrics etc., in tabular format. Convenient for further review and processing using spreadsheets or data-science tools.',
)}
HelpDownload={t('Download summarized results in CSV format')}
onDownload={exportCsv}
/>
<ExportFileElement
Icon={FileIconTsv}
filename={exportParams.filenameTsv}
HelpMain={t('Summarized results of the analysis in TSV format.')}
HelpDetails={t(
'Contains summarized results of the analysis, such as clades, mutations, QC metrics etc in tabular format. Convenient for further review and processing using spreadsheets or data-science tools.',
)}
HelpDownload={t('Download summarized results in TSV format')}
onDownload={exportTsv}
/>
<ExportFileElement
Icon={FileIconJson}
filename={exportParams.filenameTree}
HelpMain={t('Phylogenetic tree with sequenced placed onto it.')}
HelpDetails={
<>
{t('The tree is in Nextstrain format.')}{' '}
{t('Can be viewed locally with Nextstrain Auspice or in {{auspice_us}}')}
<LinkExternal url="https://auspice.us">{'auspice.us'}</LinkExternal>
{'.'}
</>
}
HelpDownload={t(
'Download phylogenetic tree with sequenced placed onto it, in Auspice JSON v2 format.',
)}
onDownload={exportTree}
/>
<ExportFileElement
Icon={FileIconFasta}
filename={exportParams.filenameFasta}
HelpMain={t('Aligned sequences in FASTA format.')}
HelpDetails={t('Contains aligned sequences in FASTA format.')}
HelpDownload={t('Download aligned sequences in FASTA format.')}
onDownload={exportFasta}
/>
<ExportFileElement
Icon={FileIconZip}
filename={exportParams.filenamePeptidesZip}
HelpMain={t('Aligned peptides in FASTA format, zipped')}
HelpDetails={t(
'Contains results of translation of your sequences. One FASTA file per gene, all in a zip archive.',
)}
HelpDownload={t(
'Download aligned peptides in FASTA format, one file per gene, all in a zip archive.',
)}
onDownload={exportPeptides}
/>
<ExportFileElement
Icon={FileIconCsv}
filename={exportParams.filenameInsertionsCsv}
HelpMain={t('Insertions in CSV format.')}
HelpDetails={t('Contains insertions stripped from aligned sequences.')}
HelpDownload={t('Download insertions in CSV format')}
onDownload={exportInsertionsCsv}
/>
<ExportFileElement
Icon={FileIconCsv}
filename={exportParams.filenameErrorsCsv}
HelpMain={t('Errors, warnings, and failed genes in CSV format.')}
HelpDetails={t(
'Contains a list of errors, a list of warnings and a list of genes that failed processing, per sequence, in CSV format.',
)}
HelpDownload={t('Download warnings, and failed genes in CSV format')}
onDownload={exportErrorsCsv}
/>
<ExportFileElement
Icon={FileIconZip}
filename={exportParams.filenameZip}
HelpMain={t('All files in a zip archive.')}
HelpDetails={t('Contains all of the above files in a single zip file.')}
HelpDownload={t('Download all in zip archive')}
onDownload={exportZip}
/>
</ListGroup>
</Card>
</Col>
</Row>
</ModalBody>
<ModalFooter>
<Button type="button" onClick={close} title={t('Close')}>
<div>{t('Close')}</div>
</Button>
</ModalFooter>
</Modal>
</>
)
}
Example #13
Source File: DweetCard.tsx From dwitter-frontend with Apache License 2.0 | 4 votes |
DweetCard: React.FC<Props> = (props) => {
const [iframeContainer, setIframeContainer] = useState<HTMLDivElement | null>(
null
);
const [iframe, setIframe] = useState<HTMLIFrameElement | null>(null);
const [shouldExpandComments, setShouldExpandComments] = useState(false);
const [updatedDweet, setUpdatedDweet] = useState<Dweet | null>(null);
const [replyText, setReplyText] = useState('');
const [originalShouldShowIframe, setShouldShowIframe] = useState(false);
const [remixText, setRemixText] = useState('');
const [hasDweetChanged, setHasDweetChanged] = useState(false);
const [hasCodeBeenFocused, setHasCodeBeenFocused] = useState(false);
const [newDweetRedirect, setNewDweetRedirect] = useState<string | null>(null);
const [context] = useContext(Context);
const isEmptyStateDweet = props.dweet === null;
const shouldShowIframe = originalShouldShowIframe && !isEmptyStateDweet;
useEffect(() => {
if (!iframeContainer) {
return;
}
const observer = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (shouldShowIframe !== entry.isIntersecting) {
setShouldShowIframe(entry.isIntersecting);
}
});
});
observer.observe(iframeContainer);
}, [iframeContainer, shouldShowIframe]);
useEffect(() => {
const handler = (event: MessageEvent) => {
if (event.source !== iframe?.contentWindow) {
return;
}
if (
event.origin !== 'http://dweet.localhost:8000' &&
event.origin !== 'https://dweet.dwitter.net'
) {
return;
}
if (event.data.type === 'error') {
setError(event.data.error);
}
};
window.addEventListener('message', handler);
// clean up
return () => window.removeEventListener('message', handler);
}, [iframe]);
const [showShareModal, setShowShareModal] = useState(false);
const [showRemixListModal, setShowRemixListModal] = useState(false);
const inputRef = useRef<HTMLInputElement>(null);
const dweet: Dweet = (updatedDweet ? updatedDweet : props.dweet) || {
id: -1,
deleted: false,
code: '',
awesome_count: 0,
author: {
id: -1,
username: '',
avatar: '',
},
posted: '',
link: '',
has_user_awesomed: false,
comments: [],
remix_of: null,
remixes: [],
};
const [code, setCode] = useState(dweet?.code || '');
// eslint-disable-next-line react-hooks/exhaustive-deps
const originalCode = useMemo(() => dweet?.code || '', []);
const isOriginalCodeCompressed = useMemo(
() => isCodeCompressed(originalCode),
[originalCode]
);
const [error, setError] = useState('');
const shouldStickyFirstComment =
dweet.comments.length > 0 &&
dweet.comments[0].author.id === dweet.author.id;
const isNSFW = dweet.comments.some(x=>x.text.toUpperCase().indexOf('#NSFW') >= 0);
const shouldCollapseComments =
dweet.comments.length > 6 && !shouldExpandComments;
const comments = shouldCollapseComments
? dweet.comments.slice(dweet.comments.length - 5)
: dweet.comments;
const getCompressedCode = useCallback(() => {
if (isOriginalCodeCompressed && !hasDweetChanged) {
// if the dweet has not been edited, recover original compressed code
return originalCode;
}
let compressedCode = compressCode(code);
try {
encodeURIComponent(compressedCode);
setError('');
return compressedCode;
} catch (e) {
setError('An error occurred while trying to compress the code');
return code;
}
}, [code, originalCode, hasDweetChanged, isOriginalCodeCompressed]);
const encodedCode = useMemo(() => {
try {
const ec = encodeURIComponent(code);
return ec;
} catch (e) {
setError('Encoding error');
return '';
}
}, [code, setError, error]);
if (dweet.deleted) {
return (
<div className="card p-3 mb-3">
<div className="d-flex align-items-center mb-3">
<span style={{ textAlign: 'left' }}>
[ this dweet no longer exists ]
</span>
<div style={{ flex: 1 }} />
<RemixOf dweet={dweet} />
</div>
</div>
);
}
return (
<div className="card p-3 mb-3">
<div
style={{
padding: '0 0 56.25%',
position: 'relative',
height: 0,
width: '100%',
marginBottom: 16,
}}
>
<div
ref={setIframeContainer}
style={{
position: 'absolute',
top: 0,
left: 0,
right: 0,
bottom: 0,
}}
>
{shouldShowIframe && (
<iframe
ref={setIframe}
title={'dweet_' + dweet.id}
src={
`${process.env.REACT_APP_API_EMBED_BASE_URL}id/${dweet.id}` +
`?${isNSFW ? 'spoiler' : 'autoplay'}=true&code=${encodedCode}`
}
sandbox="allow-same-origin allow-scripts"
style={{
width: '100%',
height: '100%',
border: 0,
borderRadius: 2,
}}
/>
)}
</div>
</div>
<div
style={{
display: 'flex',
alignItems: 'center',
marginBottom: 32,
}}
>
<div style={{ flex: 1 }}>
<div
style={{
display: 'flex',
alignItems: 'stretch',
}}
>
<AwesomeButton dweet={dweet} onUpdate={setUpdatedDweet} />
</div>
</div>
{context.user && dweet.author.id == context?.user.id ? (
// Swap report button with delete for your own dweet
<DeleteButton
dweetId={dweet.id}
isEmptyStateDweet={isEmptyStateDweet}
onDeleted={() => {
setUpdatedDweet({ ...dweet, deleted: true });
}}
/>
) : (
<>
{context.user &&
context?.user.is_staff && ( // Extra delete button for mods
<span>
<DeleteButton
dweetId={dweet.id}
isEmptyStateDweet={isEmptyStateDweet}
onDeleted={() => {
setUpdatedDweet({ ...dweet, deleted: true });
}}
/>
</span>
)}
<div style={{ marginLeft: 16 }}>
<ReportButton
dweetId={dweet.id}
isEmptyStateDweet={isEmptyStateDweet}
/>
</div>
</>
)}
<a
href="#"
style={{
marginLeft: 16,
...(isEmptyStateDweet
? { background: 'var(--blue)', borderRadius: 4, opacity: 0.33 }
: {}),
}}
onClick={(e) => {
e.preventDefault();
if (isEmptyStateDweet) {
return;
}
setShowShareModal(true);
}}
>
share
</a>
{dweet.remixes.length > 0 && (
<a
href="#"
style={{
marginLeft: 16,
...(isEmptyStateDweet
? { background: 'var(--blue)', borderRadius: 4, opacity: 0.33 }
: {}),
}}
onClick={(e) => {
e.preventDefault();
if (isEmptyStateDweet) {
return;
}
setShowRemixListModal(true);
}}
>
{dweet.remixes.length} remix{dweet.remixes.length > 1 && 'es'}
</a>
)}
<a
href="#"
style={{
marginLeft: 16,
...(isEmptyStateDweet
? { background: 'var(--blue)', borderRadius: 4, opacity: 0.33 }
: {}),
}}
onClick={(e) => {
e.preventDefault();
if (isEmptyStateDweet) {
return;
}
iframeContainer?.requestFullscreen();
}}
>
fullscreen
</a>
</div>
<div className="d-flex align-items-center mb-3">
<UserView user={dweet.author} />
<div style={{ flex: 1 }} />
<RemixOf dweet={dweet} />
</div>
<div
style={{
background: context.theme.codeEditorBackgroundColor,
marginLeft: -16,
marginRight: -16,
padding: 16,
color: 'gray',
fontFamily: 'monospace',
...(isEmptyStateDweet ? { color: '#0000' } : {}),
}}
>
function u(t){' {'}
{dweet.id !== -1 &&
(isEmptyStateDweet ||
isCodeCompressed(originalCode) ||
hasCodeBeenFocused) && (
<label
style={{ float: 'right', margin: 0, cursor: 'pointer' }}
className="d-flex align-items-center"
>
<span className="mr-3">Compressed</span>
<Switch
onColor={context.theme.primary}
offColor={context.theme.switchOffBackgroundColor}
height={16}
width={40}
checkedIcon={<span />}
uncheckedIcon={<span />}
checked={isCodeCompressed(code)}
onChange={() =>
setCode((old) =>
isCodeCompressed(old)
? getUncompressedCode(old, setError)
: getCompressedCode()
)
}
/>
</label>
)}
<div
className="d-flex flex-column"
style={{ height: 64 + 16, marginBottom: 16, marginTop: 8 }}
>
{!isEmptyStateDweet && (
<AceEditor
showGutter={false}
wrapEnabled={true}
showPrintMargin={false}
highlightActiveLine={false}
fontSize={14}
value={code}
mode="javascript"
theme="monokai"
onFocus={() => {
setHasCodeBeenFocused(true);
}}
onChange={(code) => {
setCode(code);
setError('');
setHasDweetChanged(code !== originalCode);
}}
style={{
width: '100%',
flex: 1,
border: 0,
background: 'transparent',
color: 'white',
}}
/>
)}
</div>
{'} //'}{' '}
<span
style={{
color: getDweetLength(code) > 140 ? 'red' : 'inherit',
}}
>
{getDweetLength(code)}/140
</span>
<div style={{ float: 'right' }}>
<a
className="no-link-styling"
href={'https://dwitter.net/d/' + dweet.id}
>
{new Date(dweet.posted).toLocaleString()}
</a>
</div>
<div>
<pre style={{ color: 'red' }}>{error}</pre>
</div>
<div>
<form
style={{ marginTop: 32 }}
onSubmit={async (e) => {
e.preventDefault();
try {
await context.requireLogin({
reason:
'You need to be logged in to post dweets. Please log in now and your remix will immediately publish.',
nextAction: 'post remix',
});
const new_dweet = await postDweet(code, remixText, dweet.id);
setNewDweetRedirect('/d/' + new_dweet.id);
} catch {
console.log('Failed to post remix');
}
}}
>
{newDweetRedirect && <Redirect to={newDweetRedirect} />}
<div
className={remixText ? 'shadow-primary border-radius' : ''}
style={{
position: 'relative',
display: hasCodeBeenFocused ? 'block' : 'none',
}}
>
<input
type="text"
disabled={isEmptyStateDweet}
ref={inputRef}
style={{
paddingRight: 64,
background: 'transparent',
color: 'white',
borderColor: 'gray',
border: '1px solid',
}}
placeholder={
hasDweetChanged
? 'Add a caption here...'
: 'Change the code...'
}
className="form-control"
value={remixText}
onChange={(e) => setRemixText(e.target.value)}
/>
<div
style={{ position: 'absolute', right: 0, top: 0, bottom: 0 }}
>
<button
style={{
borderTopLeftRadius: 0,
borderBottomLeftRadius: 0,
...(isEmptyStateDweet ? { color: '#0000' } : {}),
}}
className={
'btn ' + (hasDweetChanged ? 'btn-primary' : 'btn-secondary')
}
disabled={
!hasDweetChanged ||
isEmptyStateDweet ||
getDweetLength(code) > 140
}
>
Post as Remix
</button>
</div>
</div>
</form>
</div>
</div>
{shouldStickyFirstComment && (
<div
style={{
padding: 16,
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
fontWeight: 'bold',
borderBottom:
dweet.comments.length > 1
? `1px solid ${context.theme.secondaryBorderColor}`
: 'none',
}}
>
<Linkify
componentDecorator={(decoratedHref, decoratedText, key) => (
<a target="_blank" href={decoratedHref} key={key}>
{decoratedText}
</a>
)}
>
{dweet.comments[0].text}
</Linkify>
</div>
)}
{shouldCollapseComments && (
<div style={{ padding: 16, textAlign: 'center' }}>
<a
href="#"
onClick={(e) => {
e.preventDefault();
setShouldExpandComments(true);
}}
>
Show more comments...
</a>
</div>
)}
{comments.slice(shouldStickyFirstComment ? 1 : 0).map((comment) => {
return (
<DweetCommentView
comment={comment}
deletePermission={
context.user && comment.author.id == context?.user.id
}
moderator={context?.user ? context?.user.is_staff : false}
isEmptyStateDweet={isEmptyStateDweet}
/>
);
})}
<form
style={{ marginTop: 32 }}
onSubmit={async (e) => {
e.preventDefault();
try {
await context.requireLogin({
reason:
'You need to log in in order to post this comment! Log in now, and this comment will be posted: ' +
replyText,
nextAction: 'post comment',
});
} catch {
return;
}
try {
setUpdatedDweet(await addComment(dweet.id, replyText));
setReplyText('');
inputRef.current?.blur();
} catch (e) {
alert('Error posting comment');
}
}}
>
<div
className={replyText ? 'shadow-primary border-radius' : ''}
style={{ position: 'relative' }}
>
<input
type="text"
disabled={isEmptyStateDweet}
ref={inputRef}
style={{ paddingRight: 64 }}
className="form-control"
value={replyText}
onChange={(e) => setReplyText(e.target.value)}
/>
<div style={{ position: 'absolute', right: 0, top: 0, bottom: 0 }}>
<button
style={{
borderTopLeftRadius: 0,
borderBottomLeftRadius: 0,
...(isEmptyStateDweet ? { color: '#0000' } : {}),
}}
className={
'btn ' +
(replyText.length === 0 ? 'btn-secondary' : 'btn-primary')
}
disabled={replyText.length === 0 || isEmptyStateDweet}
>
Comment
</button>
</div>
</div>
</form>
<Modal
isOpen={showRemixListModal}
toggle={() => setShowRemixListModal(!showRemixListModal)}
>
<div className="p-3">
<div
className="mb-3"
style={{
fontWeight: 'bold',
textAlign: 'center',
position: 'relative',
}}
>
<a
href="#"
style={{
position: 'absolute',
right: 0,
top: 0,
fontWeight: 'normal',
}}
onClick={(e) => {
e.preventDefault();
setShowRemixListModal(false);
}}
>
close
</a>
Remixes of d/{dweet.id}
</div>
<DweetList dweet_ids={dweet.remixes} />
</div>
</Modal>
<Modal
isOpen={showShareModal}
keyboard={true}
toggle={() => setShowShareModal(!showShareModal)}
>
<div className="p-3">
<div
className="mb-3"
style={{
fontWeight: 'bold',
textAlign: 'center',
position: 'relative',
}}
>
<a
href="#"
style={{
position: 'absolute',
right: 0,
top: 0,
fontWeight: 'normal',
}}
onClick={(e) => {
e.preventDefault();
setShowShareModal(false);
}}
>
close
</a>
Share d/{dweet.id}
</div>
<label>Permalink</label>
<input
className="form-control mb-3"
type="text"
readOnly={true}
value={'https://dwitter.net/d/' + dweet.id}
/>
<label>Iframe embed</label>
<input
className="form-control"
type="text"
readOnly={true}
value={`<iframe width=500 height=570 frameBorder="0" src="https://www.dwitter.net/e/${dweet.id}" allowFullScreen="true"></iframe>`}
/>
</div>
</Modal>
</div>
);
}
Example #14
Source File: App.tsx From dwitter-frontend with Apache License 2.0 | 4 votes |
function App() {
const [currentLoginRequest, setCurrentLoginRequest] =
useState<LoginRequest | null>(null);
let themeMode = localStorage.getItem('themeMode') || 'automatic';
if (themeMode === 'automatic') {
themeMode = window.matchMedia('(prefers-colors-scheme: dark)').matches
? 'dark'
: 'light';
}
const [context, setContext] = useState<AppContext>({
theme: themes[themeMode],
user: JSON.parse(localStorage.getItem('user') || 'null'),
requireLogin: async (options: { reason: string; nextAction: string }) => {
if (localStorage.getItem('user')) {
return;
}
if (currentLoginRequest) {
return await currentLoginRequest.promise;
}
const loginPromise: Partial<LoginRequest> = { ...options };
loginPromise.promise = new Promise((resolve, reject) => {
loginPromise.resolve = resolve;
loginPromise.reject = reject;
});
setCurrentLoginRequest(loginPromise as LoginRequest);
try {
await loginPromise.promise;
} catch {
setCurrentLoginRequest(null);
throw new Error();
}
if (currentLoginRequest === loginPromise) {
setCurrentLoginRequest(null);
}
},
});
const faviconFolder = '/favicon-' + context.theme.key + '/';
return (
<Context.Provider
value={[context, (partial) => setContext({ ...context, ...partial })]}
>
<style>
{`:root {${Object.entries(context.theme)
.map(([key, value]) => '--' + key + ': ' + value)
.join(';')}}`}
</style>
<Router>
<div
className="App"
style={{
background: context.theme.pageBackgroundColor,
color: context.theme.mainTextColor,
}}
>
<Helmet titleTemplate={'%s | Dwitter.net'}>
<title>Dwitter.net - Javascript demos in 140 characters</title>
<link
rel="apple-touch-icon"
sizes="180x180"
href={faviconFolder + 'apple-touch-icon.png'}
/>
<link
rel="icon"
type="image/png"
sizes="32x32"
href={faviconFolder + 'favicon-32x32.png'}
/>
<link
rel="icon"
type="image/png"
sizes="16x16"
href={faviconFolder + 'favicon-16x16.png'}
/>
</Helmet>
<Header />
<Switch>
<Route
path="/accounts/login/"
exact={true}
component={Login}
></Route>
<Route
path="/accounts/register/"
exact={true}
component={Register}
></Route>
<Route path="/create" exact={true} component={Create}></Route>
{context.user && (
<Route
path={'/' + context.user.username + '/settings'}
exact={true}
component={Settings}
/>
)}
<Route path="/d/:id" exact={true} component={SingleDweet} />
<Route path="/new" exact={true} component={NewFeed} />
<Route
path="/top/week"
exact={true}
component={(props: RouteComponentProps) => (
<TopFeed period="week" {...props} />
)}
/>
<Route
path="/top/month"
exact={true}
component={(props: RouteComponentProps) => (
<TopFeed period="month" {...props} />
)}
/>
<Route
path="/top/year"
exact={true}
component={(props: RouteComponentProps) => (
<TopFeed period="year" {...props} />
)}
/>
<Route
path="/top/all"
exact={true}
component={(props: RouteComponentProps) => (
<TopFeed period="all" {...props} />
)}
/>
<Route path="/random" exact={true} component={RandomFeed} />
<Route
path="/h/:hashtag/top"
exact={true}
component={TopHashtagFeed}
/>
<Route
path="/h/:hashtag/hot"
exact={true}
component={HotHashtagFeed}
/>
<Route path="/h/:hashtag" exact={true} component={HotHashtagFeed} />
<Route
path="/h/:hashtag/new"
exact={true}
component={NewHashtagFeed}
/>
<Route
path="/u/:username/top"
exact={true}
component={TopUserFeed}
/>
<Route
path="/u/:username/hot"
exact={true}
component={HotUserFeed}
/>
<Route path="/u/:username" exact={true} component={HotUserFeed} />
<Route
path="/u/:username/new"
exact={true}
component={NewUserFeed}
/>
<Route path="/about" exact={true} component={About} />
<Route path="" exact={true} component={HotFeed} />
</Switch>
</div>
<Modal
isOpen={!!currentLoginRequest}
keyboard={true}
toggle={() => currentLoginRequest && currentLoginRequest.reject()}
>
<div className="p-3">
<div className="d-flex justify-content-center align-items-center flex-column">
<div className="d-flex justify-content-center align-items-stretch flex-column">
<div
className="alert alert-info"
style={{
marginTop: 16,
marginBottom: 32,
maxWidth: 16 * 18,
}}
>
{currentLoginRequest && currentLoginRequest.reason}
</div>
{currentLoginRequest && (
<LoginForm
nextAction={currentLoginRequest.nextAction}
onLogin={() => {
currentLoginRequest.resolve();
setCurrentLoginRequest(null);
}}
/>
)}
</div>
</div>
</div>
</Modal>
</Router>
</Context.Provider>
);
}
Example #15
Source File: FeedbackForm.tsx From TutorBase with MIT License | 4 votes |
export default function FeedbackForm({apptTutorId}: IProps) {
const [formOpen, setFormOpen] = useState(false);
const clientData = useSelector(selectClientData);
const [feedbackMessage, setFeedbackMessage] = useState("");
const [rating, setRating] = useState(0);
const submitFeedback = async () => {
let clientId = clientData.clientId;
let tutorId = apptTutorId;
setFormOpen(false)
await api.SubmitFeedback({
clientId: clientId,
tutorId: tutorId,
message: feedbackMessage,
rating: rating
});
// TODO: Show some Toast UI confirming that the rating was submitted
}
return (
<Container>
<Button onClick={(e) => {
setFormOpen(true);
e.stopPropagation();
}}
style={{
minWidth: '60px',
lineHeight: '1',
position: "relative",
}}>
<div style={{
fontSize: "clamp(8px, 1vw, 12px)"
}}>
Rate
<FontAwesomeIcon icon={faStar} style={{marginLeft: '5px'}}/>
</div>
</Button>
<Modal
isOpen={formOpen}
toggle={() => setFormOpen(!formOpen)}
>
<ModalHeader toggle={() => setFormOpen(!formOpen)}>
Please give feedback on your session below.
</ModalHeader>
<StyledBody>
<Label for="exampleText">
What did you think of your session?
</Label>
<Input
id="exampleText"
name="text"
type="textarea"
value={feedbackMessage}
onChange={(element) => setFeedbackMessage(element.target.value)}
/>
<Label style={{marginTop: '1em'}}>
How would you rate your session?
</Label>
<div style={{lineHeight: '0.75'}}>
<ReactStars size={40} value={rating} onChange={new_rating => setRating(new_rating)}/>
</div>
</StyledBody>
<ModalFooter>
<Button
color="primary"
onClick={() => submitFeedback()}
>
Submit
</Button>
{' '}
<Button onClick={() => setFormOpen(false)}>
Cancel
</Button>
</ModalFooter>
</Modal>
</Container>
)
}
Example #16
Source File: bq_modal.tsx From website with Apache License 2.0 | 4 votes |
export function BqModal(props: BqModalPropType): JSX.Element {
const textAreaRef = useRef<HTMLTextAreaElement>();
const [modalOpen, setModalOpen] = useState(false);
const [query, setQuery] = useState("");
const bqLink = useRef(
document.getElementById("bq-link") as HTMLAnchorElement
);
if (bqLink.current) {
bqLink.current.onclick = () => {
const query = `${props.getSqlQuery()}`;
setQuery(query);
setModalOpen(true);
};
}
useEffect(() => {
if (bqLink.current) {
bqLink.current.style.display = props.showButton ? "inline-block" : "none";
}
return () => {
if (bqLink.current) {
bqLink.current.style.display = "none";
}
};
});
const textAreaOnClick = () => {
if (textAreaRef.current) {
textAreaRef.current.focus();
textAreaRef.current.setSelectionRange(
0,
textAreaRef.current.value.length
);
}
};
return (
<Modal
isOpen={modalOpen}
className="big-query-modal modal-dialog-centered modal-lg"
>
<ModalHeader toggle={() => setModalOpen(false)}>
Analyze this data in BigQuery
</ModalHeader>
<ModalBody>
<div className="big-query-sql-instructions">
<p>To run this query:</p>
<ol>
<li>
<a
href={
"https://console.cloud.google.com/bigquery/analytics-hub/exchanges(analyticshub:projects/841968438789/locations/us/dataExchanges/data_commons_17d0b72b0b2/listings/data_commons_1803e67fbc9)"
}
>
Link Data Commons to your Google Cloud Project
</a>
.
</li>
<li>
Copy and paste the query into the{" "}
<a
href={
"https://console.cloud.google.com/bigquery;create-new-query-tab"
}
>
BigQuery Editor
</a>
.
</li>
</ol>
<p>
For more information on querying Data Commons, see{" "}
<a href={"https://docs.datacommons.org/bigquery/"}>this guide</a>.
</p>
</div>
<textarea
className="copy-svg"
value={query}
readOnly
ref={textAreaRef}
onClick={textAreaOnClick}
/>
</ModalBody>
</Modal>
);
}
Example #17
Source File: statvar.tsx From website with Apache License 2.0 | 4 votes |
export function StatVarChooser(props: StatVarChooserProps): JSX.Element {
const { x, y, place } = useContext(Context);
// Temporary variable for storing an extra statvar.
const [thirdStatVar, setThirdStatVar] = useState(emptyStatVar);
// Records which two of the three statvars are wanted if a third statvar is selected.
const [modalSelected, setModalSelected] = useState(defaultModalSelected);
const [modalOpen, setModalOpen] = useState(false);
const [samplePlaces, setSamplePlaces] = useState(
getSamplePlaces(
place.value.enclosingPlace.dcid,
place.value.enclosedPlaceType,
place.value.enclosedPlaces
)
);
useEffect(() => {
const samplePlaces = getSamplePlaces(
place.value.enclosingPlace.dcid,
place.value.enclosedPlaceType,
place.value.enclosedPlaces
);
setSamplePlaces(samplePlaces);
}, [place.value.enclosedPlaces]);
const closeModal = () => {
setThirdStatVar(emptyStatVar);
setModalOpen(false);
};
useEffect(() => {
const statVarsToGetInfo = [];
if (!_.isEmpty(x.value.statVarDcid) && _.isNull(x.value.statVarInfo)) {
statVarsToGetInfo.push(x.value.statVarDcid);
}
if (!_.isEmpty(y.value.statVarDcid) && _.isNull(y.value.statVarInfo)) {
statVarsToGetInfo.push(y.value.statVarDcid);
}
if (_.isEmpty(statVarsToGetInfo)) {
return;
}
getStatVarInfo(statVarsToGetInfo)
.then((info) => {
statVarsToGetInfo.forEach((sv) => {
const svInfo = sv in info ? info[sv] : {};
if (sv === x.value.statVarDcid) {
x.setStatVarInfo(svInfo);
} else {
y.setStatVarInfo(svInfo);
}
});
})
.catch(() => {
if (statVarsToGetInfo.indexOf(x.value.statVarDcid) > -1) {
x.setStatVarInfo({});
}
if (statVarsToGetInfo.indexOf(y.value.statVarDcid) > -1) {
y.setStatVarInfo({});
}
});
}, [x.value, y.value]);
let yTitle = y.value.statVarDcid;
if (y.value.statVarInfo && y.value.statVarInfo.title) {
yTitle = y.value.statVarInfo.title;
}
let xTitle = x.value.statVarDcid;
if (x.value.statVarInfo && x.value.statVarInfo.title) {
xTitle = x.value.statVarInfo.title;
}
const selectedSvs = {};
if (!_.isEmpty(x.value.statVarDcid)) {
selectedSvs[x.value.statVarDcid] = x.value.statVarInfo;
}
if (!_.isEmpty(y.value.statVarDcid)) {
selectedSvs[y.value.statVarDcid] = y.value.statVarInfo;
}
if (!_.isEmpty(thirdStatVar.dcid)) {
selectedSvs[thirdStatVar.dcid] = thirdStatVar.info;
}
return (
<>
<StatVarWidget
openSvHierarchyModal={props.openSvHierarchyModal}
openSvHierarchyModalCallback={props.openSvHierarchyModalCallback}
collapsible={true}
svHierarchyType={StatVarHierarchyType.SCATTER}
samplePlaces={samplePlaces}
deselectSVs={(svList: string[]) =>
svList.forEach((sv) => {
removeStatVar(x, y, sv);
})
}
selectedSVs={selectedSvs}
selectSV={(sv) => addStatVar(x, y, sv, setThirdStatVar, setModalOpen)}
/>
{/* Modal for selecting 2 stat vars when a third is selected */}
<Modal isOpen={modalOpen} backdrop="static" id="statvar-modal">
<ModalHeader toggle={closeModal}>
Only Two Statistical Variables Supported
</ModalHeader>
<ModalBody>
<Container>
<div>
You selected:{" "}
<b>{thirdStatVar.info.title || thirdStatVar.dcid}</b>
</div>
<div className="radio-selection-label">
Please choose 1 more statistical variable to keep:
</div>
<div className="radio-selection-section">
<FormGroup radio="true" row>
<Label radio="true">
<Input
id="x-radio-button"
type="radio"
name="statvar"
defaultChecked={modalSelected.x}
onClick={() => setModalSelected({ x: true, y: false })}
/>
{xTitle}
</Label>
</FormGroup>
<FormGroup radio="true" row>
<Label radio="true">
<Input
id="y-radio-button"
type="radio"
name="statvar"
defaultChecked={modalSelected.y}
onClick={() => setModalSelected({ x: false, y: true })}
/>
{yTitle}
</Label>
</FormGroup>
</div>
</Container>
</ModalBody>
<ModalFooter>
<Button
color="primary"
onClick={() =>
confirmStatVars(
x,
y,
thirdStatVar,
setThirdStatVar,
modalSelected,
setModalSelected,
setModalOpen
)
}
>
Confirm
</Button>
</ModalFooter>
</Modal>
</>
);
}
Example #18
Source File: stat_var_chooser.tsx From website with Apache License 2.0 | 4 votes |
export function StatVarChooser(props: StatVarChooserProps): JSX.Element {
const [samplePlaces, setSamplePlaces] = useState([]);
// extraStatVar holds a stat var that is selected after the max number of
// selected stat vars has been reached. This stat var will either be removed
// or used to replace another selected stat var.
const [extraStatVar, setExtraStatVar] = useState(EMPTY_SV_AND_INFO);
const [modalSelection, setModalSelection] = useState("");
const [modalOpen, setModalOpen] = useState(false);
const modalSvOrder = useRef(Object.keys(props.statVars));
useEffect(() => {
modalSvOrder.current = Object.keys(props.statVars);
}, [props.statVars]);
useEffect(() => {
if (!props.placeDcid || !props.enclosedPlaceType) {
setSamplePlaces([]);
return;
}
getEnclosedPlacesPromise(props.placeDcid, props.enclosedPlaceType)
.then((enclosedPlaces) => {
const samplePlaces = getSamplePlaces(
props.placeDcid,
props.enclosedPlaceType,
enclosedPlaces
);
setSamplePlaces(samplePlaces);
})
.catch(() => {
setSamplePlaces([]);
});
}, [props.placeDcid, props.enclosedPlaceType]);
const selectedSVs = { ...props.statVars };
// although we don't propagate the extra stat var selection to the rest of the
// tool, we do need to pass it to the widget because the StatVarHierarchy has
// it showing as selected.
if (!_.isEmpty(extraStatVar.dcid)) {
selectedSVs[extraStatVar.dcid] = extraStatVar.info;
}
return (
<>
<StatVarWidget
openSvHierarchyModal={props.openSvHierarchyModal}
openSvHierarchyModalCallback={props.openSvHierarchyModalCallback}
collapsible={false}
svHierarchyType={StatVarHierarchyType.DOWNLOAD}
samplePlaces={samplePlaces}
deselectSVs={(svList: string[]) =>
svList.forEach((sv) => {
props.onStatVarRemoved(sv);
})
}
selectedSVs={selectedSVs}
selectSV={(sv) => selectSV(sv)}
/>
{/* Modal for selecting stat var to replace when too many are selected */}
<Modal isOpen={modalOpen} backdrop="static" id="statvar-modal">
<ModalHeader toggle={closeModal}>
Only 5 Statistical Variables Supported
</ModalHeader>
<ModalBody>
<Container>
<div>
You selected:{" "}
<b>{extraStatVar.info.title || extraStatVar.dcid}</b>
</div>
<div className="radio-selection-label">
Please choose a statistical variable to replace:
</div>
<div className="radio-selection-section">
{modalSvOrder.current.map((sv, idx) => {
return (
<FormGroup key={sv} radio="true" row>
<Label radio="true">
<Input
type="radio"
name="statvar"
defaultChecked={
(_.isEmpty(modalSelection) && idx === 0) ||
modalSelection === sv
}
onClick={() => setModalSelection(sv)}
/>
{sv in props.statVars
? props.statVars[sv].title || sv
: sv}
</Label>
</FormGroup>
);
})}
</div>
</Container>
</ModalBody>
<ModalFooter>
<Button color="primary" onClick={() => confirmStatVars()}>
Confirm
</Button>
</ModalFooter>
</Modal>
</>
);
/**
* Close the modal
*/
function closeModal(): void {
setExtraStatVar(EMPTY_SV_AND_INFO);
setModalSelection("");
setModalOpen(false);
}
/**
* Confirms the variable to replace in the modal.
*/
function confirmStatVars(): void {
const svToRemove = _.isEmpty(modalSelection)
? modalSvOrder.current[0]
: modalSelection;
props.onStatVarRemoved(svToRemove);
props.onStatVarSelected(extraStatVar.dcid, extraStatVar.info);
closeModal();
}
/**
* Select a variable.
*/
function selectSV(sv: string): void {
getStatVarInfo([sv])
.then((svInfo) => {
const selectedSVInfo = svInfo[sv] || {};
if (Object.keys(props.statVars).length >= MAX_SV) {
setExtraStatVar({ dcid: sv, info: selectedSVInfo });
setModalOpen(true);
} else {
props.onStatVarSelected(sv, selectedSVInfo);
}
})
.catch(() => {
if (Object.keys(props.statVars).length >= MAX_SV) {
setExtraStatVar({ dcid: sv, info: {} });
setModalOpen(true);
} else {
props.onStatVarSelected(sv, {});
}
});
}
}
Example #19
Source File: TutorPanelSignup.tsx From TutorBase with MIT License | 4 votes |
Panel = (props: IProps) => {
let dispatch = useDispatch();
let id = useSelector(selectClientData).clientId;
const [modalOpen, setModalOpen] = useState(false);
let params : string = useLocation().pathname;
const [selectedSubjects, setSelectedSubjects] = useState(new Set<string>());
const [RIN, setRIN] = useState("");
const [validRIN, setValidRIN] = useState(false);
const [cohort, setCohort] = useState("");
const [comments, setComments] = useState("");
const [footerMessage, setFooterMessage] = useState("");
const [rate, setRate] = useState(0);
let subjects = [];
let selectedSubjectsOutput = [];
const [subjectsList, setSubjectsList] = useState(new Array<Subject>());
function checkRIN(value: string) {
if (value.length !== 9) {
setValidRIN(false);
}
else {
setValidRIN(true);
}
setRIN(value);
}
function submit() {
if (!validRIN
|| cohort === ""
|| cohort === "Select"
|| selectedSubjects.size === 0) {
setFooterMessage("Please complete required fields.");
return;
}
let subs: Array<String> = Array.from(selectedSubjects.keys());
api.TutorSignup(id, RIN, subs, comments, rate).then(res =>{
res ?
setFooterMessage("Application submitted.")
: setFooterMessage("Error submitting. Please try again.");
}).catch(err => {
setFooterMessage("Error submitting. Please try again.")
});
}
useEffect(() => {
// Get all avaliable subjects from API
const getSubjects = async () => {
return (await api.GetSubjects()).data;
}
getSubjects().then(value => {
setSubjectsList(value);
}
)
}, []);
for (let i = 0; i < subjectsList.length; i++) {
let name: string = subjectsList[i].id;
let color = SubjectToColor(name);
subjects.push(
(<Button
style={{background: color}}
onClick={() => setSelectedSubjects(SelectedSubjectsHandler(selectedSubjects, name))}
>
{name}
</Button>
)
);
}
let selectedSubs:Array<string> = Array.from(selectedSubjects.keys());
for (let i = 0; i < selectedSubs.length; i++) {
let name: string = selectedSubs[i];
let color = SubjectToColor(name);
selectedSubjectsOutput.push(
(
<Badge
style={{
backgroundColor: color,
cursor:'default',
color: "black",
minWidth: '6em',
display: "flex",
flexDirection:'row',
alignItems: 'center',
marginRight: '0.5em'
}}
pill
>
<div style={{
display: "flex",
flex: '50%',
}}>
{name + ' '}
</div>
<Button
close
style={{
display: "flex",
flex: '50%',
alignItems: 'center'
}}
onClick={() => setSelectedSubjects(SelectedSubjectsHandler(selectedSubjects, name))} />
</Badge>
)
);
}
return (
<div id="panel-wrapper">
<Navbar className={classNames("navbar-expand-lg", "navbar-light", "bg-light", "border-bottom", "shadow")}>
<Button className="btn-red" id="menu-toggle" onClick={() => {
dispatch(actions.toggleSidebar());
}}>☰</Button>
</Navbar>
<Container fluid className="background" style={{marginBottom:'10em'}}>
<hr></hr>
<Row xs="2" className="parent">
</Row>
<div style={{display:'flex', flexDirection:'column', flexWrap:'wrap', alignContent:'center'}}>
{props.isLoading ? (
<div style={{display:'flex', flexDirection:'row', flex:'1 1 0px', flexWrap:'wrap', justifyContent:'center', marginTop:'10em'}}>
<Spinner style={{color:'#E66064'}}></Spinner>
</div>)
: (
<div>
<div style={{display:'flex', flexDirection:'row', flex:'1 1 0px', flexWrap:'wrap', justifyContent:'center', marginTop:'10em'}}>
<h5>You are not currently signed up as a tutor. This dashboard is for tutors only. You can apply to be a TutorBase tutor below!
</h5></div>
<div style={{display:'flex', flexDirection:'row', flex:'1 1 0px', flexWrap:'wrap', justifyContent:'center', marginTop:'1em'}}>
<Button
className="btn-red"
style={{height:'4em', width:'10em', borderRadius:'20em'}}
onClick={() => setModalOpen(true)}
>
Sign up as tutor
</Button>
<Modal
centered={true}
scrollable={true}
isOpen={modalOpen}
>
<ModalHeader toggle={() => setModalOpen(!modalOpen)}>
Tutor Application Form
</ModalHeader>
<ModalBody>
<h5>RIN</h5>
<Input
defaultValue={RIN}
onChange={(e) => checkRIN(e.target.value)}
valid={validRIN}
invalid={!validRIN}
/>
<p />
<h5>Cohort</h5>
<Input
type="select"
onChange={(e) => setCohort(e.target.value)}
initialValue="Select"
invalid={cohort === "" || cohort === "Select"}>
<option>
Select
</option>
<option>
Freshman
</option>
<option>
Sophomore
</option>
<option>
Junior
</option>
<option>
Senior
</option>
<option>
Graduate
</option>
</Input>
<p />
<h5>Select Subjects to tutor</h5>
<ButtonGroup>
{subjects}
</ButtonGroup>
<p>
Selected:
<Card
outline={selectedSubjects.size === 0}
color= {selectedSubjects.size === 0 ? "danger" : ""}>
<CardBody
style={{
display: "flex",
background: "lightgray",
minHeight: "4em",
flexWrap: 'wrap'
}}>
{selectedSubjectsOutput}
</CardBody></Card>
</p>
<p>
<h5>Hourly Rate ($) (optional)</h5>
<Input
type="number"
onChange={(e) => setRate(+(e.target.value))} />
</p>
<h5>Comments (optional)</h5>
<Input
type="textarea"
onChange={(e) => setComments(e.target.value)} />
</ModalBody>
<ModalFooter>
<p style={{color: footerMessage === "Application submitted." ? 'green' : 'red'}}>
{footerMessage}
</p>
<Button
color="primary"
onClick={() => submit()}
>
Submit
</Button>
{' '}
<Button onClick={() => setModalOpen(false)}>
Cancel
</Button>
</ModalFooter>
</Modal>
</div>
</div>
)}
</div>
</Container>
</div>
);
}
Example #20
Source File: ClientSettings.tsx From TutorBase with MIT License | 4 votes |
ClientSettings = () => {
let clientData = useSelector(selectClientData);
let dispatch = useDispatch();
let [nameModalOpen, setNameModalOpen] = useState<boolean>(false);
let [imgModalOpen, setImgModalOpen] = useState<boolean>(false);
let [tempName, setTempName] = useState<Name>({
first_name: "",
last_name: ""
});
let [clientName, setClientName] = useState<Name>({
first_name: "",
last_name: ""
});
let [croppedImg, setCroppedImg] = useState<string>("");
let [clientImg, setClientImg] = useState<string>("");
const saveNameChange = async () => {
let name: Name = {first_name: tempName.first_name, last_name: tempName.last_name};
await api.SetClientName(name, clientData.clientId);
setClientName(name);
dispatch(clientDataActions.setFirstName(tempName.first_name));
dispatch(clientDataActions.setLastName(tempName.last_name));
setNameModalOpen(false);
}
const handleImageSave = async (img: string) => {
await api.SetClientProfileImage(img, clientData.clientId);
setClientImg(img);
dispatch(clientDataActions.setProfileImage(img));
}
const saveImgChange = async () => {
if(croppedImg.toString() !== "") {
CompressAndSaveImg(croppedImg, clientData.first_name + clientData.last_name + "-photo", handleImageSave);
} else {
handleImageSave(croppedImg);
}
setImgModalOpen(false);
}
const cancelNameChange = () => {
setNameModalOpen(false);
setTempName(clientName);
}
const cancelImgChange = () => {
setCroppedImg("");
setImgModalOpen(false);
}
useEffect(() => {
const getUser = async () => {
return (await api.GetUserById(clientData.clientId)).data;
}
getUser().then(value => {
setTempName({first_name: value[0].first_name, last_name: value[0].last_name});
setClientName({first_name: value[0].first_name, last_name: value[0].last_name});
setClientImg(value[0].profile_img);
dispatch(clientDataActions.setFirstName(value[0].first_name));
dispatch(clientDataActions.setLastName(value[0].last_name));
dispatch(clientDataActions.setProfileImage(value[0].profile_img));
});
}, [clientData.clientId, dispatch]);
return (
<Container className="settings" fluid>
<Row className="title" style={{ marginTop: '25px'}}>
<div className="profile-text">Settings</div>
</Row>
<hr></hr>
<Row>
<ListGroup>
<ListGroupItem className="img-item">
<img src={clientImg === "" ? defaultUser : clientImg} width="200px"/>
<a href="#" className="modal-link" onClick={() => setImgModalOpen(true)}>
<span className="heading-item"><FontAwesomeIcon icon={faEdit} className="font-adj"/></span>
</a>
<Modal isOpen={imgModalOpen} fade={false} toggle={() => {setImgModalOpen(!imgModalOpen)}} className="img-modal">
<ModalHeader toggle={() => {cancelImgChange()}}>Edit Profile Photo</ModalHeader>
<ModalBody>
Change your profile photo here.
<hr/>
<Avatar
width={250}
height={250}
cropColor="#E66064"
closeIconColor="#E66064"
onCrop={(img) => setCroppedImg(img)}
onClose={() => {setCroppedImg("")}}
onBeforeFileLoad={() => {}}
src={clientImg === "" ? defaultUser : clientImg}
/>
</ModalBody>
<ModalFooter>
<Button className="btn-red" onClick={() => {saveImgChange()}}>Save</Button>
<Button color="secondary" onClick={() => {cancelImgChange()}}>Cancel</Button>
</ModalFooter>
</Modal>
</ListGroupItem>
<ListGroupItem className="name-item">
<span className="heading-item">{clientName.first_name} {clientName.last_name}</span>
<a href="#" className="modal-link" onClick={() => {setNameModalOpen(true)}}>
<span className="heading-item"><FontAwesomeIcon icon={faEdit} className="font-adj"/></span>
</a>
<Modal isOpen={nameModalOpen} fade={false} toggle={() => {setNameModalOpen(!nameModalOpen)}} className="name-modal">
<ModalHeader toggle={() => {cancelNameChange()}}>Edit Name</ModalHeader>
<ModalBody>
Change your name here.
<hr/>
<InputGroup>
First Name:<Input id="first-name" value={tempName.first_name} onChange={(value) => setTempName({first_name: value.target.value, last_name: tempName.last_name})} />
</InputGroup>
<InputGroup>
Last Name:<Input id="last-name" value={tempName.last_name} onChange={(value) => setTempName({first_name: tempName.first_name, last_name: value.target.value})} />
</InputGroup>
</ModalBody>
<ModalFooter>
<Button className="btn-red" onClick={() => {saveNameChange()}}>Save</Button>
<Button color="secondary" onClick={() => {cancelNameChange()}}>Cancel</Button>
</ModalFooter>
</Modal>
</ListGroupItem>
</ListGroup>
</Row>
</Container>
);
}
Example #21
Source File: MeetingCard.tsx From TutorBase with MIT License | 4 votes |
export function MeetingCard(props: IProps) {
let { appt } = props;
let cardType = appt.confirmed ? "upcoming-card" : "pending-card";
let cardStatus = appt.confirmed ? "Upcoming" : "Pending";
let [modalOpen, setModalOpen] = useState(false);
let [cardExpanded, toggleCardExpansion] = useState<boolean>(false);
let [meetingLink, setMeetingLink] = useState(appt.link !== null ? appt.link! : "");
let [loading, setLoading] = useState(false);
let [check, setCheck] = useState(false);
let [err, setErr] = useState(false);
let [clientData, setClientData] = useState<User>({
_id: "",
profile_img: "",
phone: "",
email: "",
first_name: "",
last_name: "",
});
function setMeetingLinkChange(link: React.FormEvent<HTMLInputElement>) {
setMeetingLink(link.currentTarget.value);
}
async function updateMeetingLink() {
setLoading(true);
try {
let res = await api.SetMeetingLink(appt.appt_id, meetingLink);
setLoading(false);
if (res.status === 200) {
setCheck(true);
}
}
catch {
setLoading(false);
setErr(true);
}
//setModalOpen(!modalOpen);
}
let [tutorFirstName, setTutorFirstName] = useState("");
let [tutorLastName, setTutorLastName] = useState("");
const confirmAppt = async () => {
await api.ConfirmAppointment(appt.appt_id);
}
useEffect(() => {
const getTutor = async () => {
let tutor = (await api.GetTutorById(appt.tutor_id));
setTutorFirstName(tutor.data[0]?.first_name ?? "Unknown");
setTutorLastName(tutor.data[0]?.last_name ?? "Unknown");
}
getTutor();
}, []);
// Time checks for different card types
if (!IsFutureDate(appt.start_time) && appt.confirmed){
cardType = "completed-card";
cardStatus = "Completed";
}
if (!IsFutureDate(appt.start_time) && !appt.confirmed){
cardType = "denied-card";
cardStatus = "Denied";
}
if (IsFutureDate(appt.start_time) && props.includePrevious) {
return <></>
}
if (!IsFutureDate(appt.start_time) && !props.includePrevious) {
return <></>
}
// Card creation
let name = CapitalizeFirstLetter(tutorFirstName + " " + tutorLastName);
let location = CapitalizeFirstLetter(appt.location);
let date_time = BreakDownTime(appt.start_time);
// Card tag setup
let cardTag = <div className={"card-status"}>{cardStatus}</div>;
if (cardStatus === "Pending" && props.isTutor) {
cardTag = (
<>
<div className={"card-icon"}>
<Button color="success" onClick={() => confirmAppt()}>
<FontAwesomeIcon icon={faCheck} />
</Button>
</div>
<div className={"card-status"}>
{cardStatus}
</div>
</>
);
}
// Card details
let upperCardContent = (
<>
<div className={"card-name"}>{name}</div>
<div className={"card-location"}>{location}</div>
<div className={"card-time"}>{date_time[0] + " at " + date_time[1]}</div>
</>
);
// Card structure
let card = (
<CompressedCard
onClick={() => { toggleCardExpansion(!cardExpanded) }}
className={cardType}
>
<div className={"card-container-start"}>
{upperCardContent}
</div>
<div className={"card-container-end"}>
{cardStatus === "Completed"
? <FeedbackForm apptTutorId={appt.tutor_id} />
: <></>}
{cardTag}
<Button
color="none"
onClick={(e) => {
toggleCardExpansion(!cardExpanded)
}} >
<FontAwesomeIcon
icon={faArrowDown}
/>
</Button>
</div>
</CompressedCard>
);
if(cardExpanded) {
card = (
<ExpandedCard
onClick={() => { toggleCardExpansion(!cardExpanded) }}
className={cardType}
>
<div className={"card-container-start-expanded"}>{upperCardContent}</div>
<div className={"card-container-end-expanded"}>
{cardStatus === "Completed" ? <FeedbackForm apptTutorId={appt.tutor_id} /> : <></>}
{cardTag}
<Button
color="none"
onClick={(e) => {
toggleCardExpansion(!cardExpanded)
}} >
<FontAwesomeIcon
icon={faArrowUp}
/>
</Button>
</div>
<hr style={{width: '100%', backgroundColor: 'black', margin: '0 0.5em'}}/>
<div className={"card-container-item"}>
{!appt.notes || appt.notes === "" ? ""
: "Client Notes: "
}
</div>
<div className={"break"}></div>
<div className={"client-notes"}>{appt.notes}</div>
<div className={"break"}></div>
{ props.isTutor ?
<div>
<div className={"client-notes"}>
<Button
color="danger"
onClick={(e) => {
setModalOpen(!modalOpen);
e.stopPropagation();
}}
>
Add Zoom/Webex meeting link
</Button>
<Modal isOpen={modalOpen}>
<ModalHeader toggle={function noRefCheck(){}}>
Add Tutoring Meeting Link
</ModalHeader>
<ModalBody>
Link:
<Input onChange={setMeetingLinkChange} value={meetingLink}>
</Input>
</ModalBody>
<ModalFooter>
<Button
color={check ? "success": err ? "danger" : "primary"}
onClick={updateMeetingLink}
>
{loading ? (<Spinner />)
: check ? <FontAwesomeIcon icon={faCheck}/>
: err ? <div>Error<FontAwesomeIcon icon={faTimes}/></div>
: "Save"}
</Button>
{' '}
<Button onClick={() => setModalOpen(!modalOpen)}>
Cancel
</Button>
</ModalFooter>
</Modal>
</div>
</div>
: <div>{meetingLink === "" ? "" :
(<div>
<div className={"card-container-item "}>
Meeting Link:
</div>
<div className={"client-notes"}><a href={meetingLink} target="new">{meetingLink}</a></div>
</div>)
}
</div>
}
</ExpandedCard>
);
}
return <>{card}</>;
}