Make coding contract title click-to-copy

This commit is contained in:
Olivier Gagnon
2021-05-03 19:46:04 -04:00
parent dae0448744
commit 7777c400a5
5 changed files with 188 additions and 70 deletions
+61
View File
@@ -0,0 +1,61 @@
import * as React from "react";
import { KEY } from "../../../utils/helpers/keyCodes";
import { CodingContract, CodingContractType, CodingContractTypes } from "../../CodingContracts";
import { ClickableTag, CopyableText } from "./CopyableText";
import { PopupCloseButton } from "./PopupCloseButton";
type IProps = {
c: CodingContract;
popupId: string;
onClose: () => void;
onAttempt: (answer: string) => void;
}
type IState = {
answer: string;
}
export class CodingContractPopup extends React.Component<IProps, IState>{
constructor(props: IProps) {
super(props);
this.state = { answer: ''};
this.setAnswer = this.setAnswer.bind(this);
this.onInputKeydown = this.onInputKeydown.bind(this);
}
setAnswer(event: any) {
this.setState({ answer: event.target.value });
}
onInputKeydown(e:any){
if (e.keyCode === KEY.ENTER && e.target.value !== "") {
e.preventDefault();
this.props.onAttempt(this.state.answer);
} else if (e.keyCode === KEY.ESC) {
e.preventDefault();
this.props.onClose();
}
}
render(): React.ReactNode {
const contractType: CodingContractType = CodingContractTypes[this.props.c.type];
let description = [];
for (const [i, value] of contractType.desc(this.props.c.data).split('\n').entries())
description.push(<span key={i}>{value}<br/></span>);
return (
<div>
<CopyableText value={this.props.c.type} tag={ClickableTag.Tag_h1} />
<br/><br/>
<p>You are attempting to solve a Coding Contract. You have {this.props.c.getMaxNumTries() - this.props.c.tries} tries remaining, after which the contract will self-destruct.</p>
<br/>
<p>{description}</p>
<br/>
<input className="text-input" style={{ width:"50%",marginTop:"8px" }} autoFocus={true} placeholder="Enter Solution here" value={this.state.answer}
onChange={this.setAnswer} onKeyDown={this.onInputKeydown} />
<PopupCloseButton popup={this.props.popupId} onClose={() => this.props.onAttempt(this.state.answer)} text={"Solve"} />
<PopupCloseButton popup={this.props.popupId} onClose={this.props.onClose} text={"Close"} />
</div>
)
}
}
+25 -4
View File
@@ -1,7 +1,13 @@
import * as React from "react";
export enum ClickableTag{
Tag_span,
Tag_h1
}
type IProps = {
value: string;
tag: ClickableTag;
}
type IState = {
@@ -9,6 +15,11 @@ type IState = {
}
export class CopyableText extends React.Component<IProps, IState> {
public static defaultProps = {
//Default span to prevent destroying current clickables
tag: ClickableTag.Tag_span
};
constructor(props: IProps) {
super(props);
@@ -53,9 +64,19 @@ export class CopyableText extends React.Component<IProps, IState> {
render(): React.ReactNode {
return (<span className={this.textClasses()} onClick={this.copy}>
<b>{this.props.value}</b>
<span className={this.tooltipClasses()}>Copied!</span>
</span>);
switch (this.props.tag) {
case ClickableTag.Tag_h1:
return (
<h1 className={this.textClasses()} onClick={this.copy}>
{this.props.value}
<span className={this.tooltipClasses()}>Copied!</span>
</h1>)
case ClickableTag.Tag_span:
return (
<span className={this.textClasses()} onClick={this.copy}>
<b>{this.props.value}</b>
<span className={this.tooltipClasses()}>Copied!</span>
</span>)
}
}
}
+72
View File
@@ -0,0 +1,72 @@
/**
* Basic button for popup dialog boxes
* It creates an event handler such that pressing Esc will perform the click handler.
*
* Should only be used in other React components, otherwise it may not be properly
* unmounted
*/
import * as React from "react";
import * as ReactDOM from "react-dom";
import { KEY } from "../../../utils/helpers/keyCodes";
import { removeElement } from "../../../utils/uiHelpers/removeElement";
export interface IPopupButtonProps {
class?: string;
popup: HTMLElement | string;
style?: object;
text: string;
onClose?: () => void;
}
export class PopupButton extends React.Component<IPopupButtonProps, any> {
constructor(props: IPopupButtonProps) {
super(props);
this.handleClick = this.handleClick.bind(this);
this.keyListener = this.keyListener.bind(this);
}
componentDidMount() {
document.addEventListener("keydown", this.keyListener);
}
componentWillUnmount() {
document.removeEventListener("keydown", this.keyListener);
}
handleClick() {
if(this.props.onClose)
this.props.onClose();
//We might be able to remove this?
//Clickhandler from the props will override this anyhow.
let popup: HTMLElement | null;
if (typeof this.props.popup === "string") {
popup = document.getElementById(this.props.popup);
} else {
popup = this.props.popup;
}
// TODO Check if this is okay? This is essentially calling to unmount a parent component
if (popup instanceof HTMLElement) {
ReactDOM.unmountComponentAtNode(popup); // Removes everything inside the wrapper container
removeElement(popup); // Removes the wrapper container
}
}
keyListener(e: KeyboardEvent) {
//This doesn't really make sense, a button doesnt have to listen to escape IMO
//Too affraid to remove it since im not sure what it will break.. But yuck..
if (e.keyCode === KEY.ESC) {
this.handleClick();
}
}
render(): React.ReactNode {
const className = this.props.class ? this.props.class : "std-button";
return (
<button className={className} onClick={this.handleClick} style={this.props.style}>
{this.props.text}
</button>
)
}
}
+14 -21
View File
@@ -8,33 +8,27 @@
import * as React from "react";
import * as ReactDOM from "react-dom";
import { KEY } from "../../../utils/helpers/keyCodes";
import { removeElement } from "../../../utils/uiHelpers/removeElement";
import { IPopupButtonProps, PopupButton } from "./PopupButton";
export interface IPopupCloseButtonProps {
export interface IPopupCloseButtonProps extends IPopupButtonProps {
class?: string;
popup: HTMLElement | string;
style?: any;
text: string;
onClose: () => void;
}
export class PopupCloseButton extends React.Component<IPopupCloseButtonProps, any> {
export class PopupCloseButton extends PopupButton {
constructor(props: IPopupCloseButtonProps) {
super(props);
this.closePopup = this.closePopup.bind(this);
this.keyListener = this.keyListener.bind(this);
}
componentDidMount(): void {
document.addEventListener("keydown", this.keyListener);
}
componentWillUnmount(): void {
document.removeEventListener("keydown", this.keyListener);
}
closePopup(): void {
if(this.props.onClose)
this.props.onClose();
let popup: HTMLElement | null;
if (typeof this.props.popup === "string") {
popup = document.getElementById(this.props.popup);
@@ -42,24 +36,23 @@ export class PopupCloseButton extends React.Component<IPopupCloseButtonProps, an
popup = this.props.popup;
}
// TODO Check if this is okay? This is essentially calling to unmount a parent component
// TODO Check if this is okay? This is essentially calling to unmount a
// parent component
if (popup instanceof HTMLElement) {
ReactDOM.unmountComponentAtNode(popup); // Removes everything inside the wrapper container
// Removes everything inside the wrapper container
ReactDOM.unmountComponentAtNode(popup);
removeElement(popup); // Removes the wrapper container
}
}
keyListener(e: KeyboardEvent): void {
if (e.keyCode === KEY.ESC) {
this.closePopup();
}
}
render(): React.ReactNode {
const className = this.props.class ? this.props.class : "std-button";
return (
<button className={className} onClick={this.closePopup} style={this.props.style}>
<button
className={className}
onClick={this.closePopup}
style={this.props.style}>
{this.props.text}
</button>
)