diff --git a/README.md b/README.md index c938cc92c..f9370422b 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,6 @@ $ yarn lint --fix && yarn test --coverage Some code taken from the following (all MIT licensed): -* [`redux-react-material-boilerplate`](https://github.com/WapGeaR/redux-react-material-boilerplate) * [`react-hexgrid`](https://github.com/hellenic/react-hexgrid) * notsurt's [`spritegen`](https://github.com/not-surt/spritegen) * gimenete's [`identicons-react`](https://github.com/gimenete/identicons-react) diff --git a/fonts/space age.ttf b/fonts/space age.ttf new file mode 100755 index 000000000..e89b0fe64 Binary files /dev/null and b/fonts/space age.ttf differ diff --git a/package.json b/package.json index b721e7244..30fa8f2c3 100644 --- a/package.json +++ b/package.json @@ -44,6 +44,7 @@ }, "dependencies": { "@material-ui/core": "3.9.1", + "@material-ui/icons": "^3.0.2", "@material-ui/lab": "^3.0.0-alpha.30", "@wordbots/trianglify-no-canvas": "1.3.0", "bad-words": "^3.0.4", diff --git a/src/common/actions/global.ts b/src/common/actions/global.ts index f25a137b1..bf01709a9 100644 --- a/src/common/actions/global.ts +++ b/src/common/actions/global.ts @@ -5,7 +5,6 @@ import * as w from '../types'; export const FIREBASE_DATA = 'FIREBASE_DATA'; export const LOGGED_IN = 'LOGGED_IN'; export const LOGGED_OUT = 'LOGGED_OUT'; -export const RE_RENDER = 'RE_RENDER'; export function firebaseData(data: any): w.Action { return { @@ -26,9 +25,3 @@ export function loggedOut(): w.Action { type: LOGGED_OUT }; } - -export function rerender(): w.Action { - return { - type: RE_RENDER - }; -} diff --git a/src/common/components/Background.tsx b/src/common/components/Background.tsx new file mode 100644 index 000000000..abc13db9f --- /dev/null +++ b/src/common/components/Background.tsx @@ -0,0 +1,28 @@ +import * as React from 'react'; + +interface BackgroundProps { + asset: string + opacity: number + style?: React.CSSProperties +} + +export default class Background extends React.PureComponent { + public render(): JSX.Element { + const { asset, opacity, style } = this.props; + return ( +
+ ); + } +} diff --git a/src/common/components/MarkdownBlock.tsx b/src/common/components/MarkdownBlock.tsx index 343b2a64c..72ceb2d7b 100644 --- a/src/common/components/MarkdownBlock.tsx +++ b/src/common/components/MarkdownBlock.tsx @@ -12,7 +12,7 @@ const MarkdownBlock = (props: MarkdownBlockProps): JSX.Element => ( ); diff --git a/src/common/components/NavMenu.tsx b/src/common/components/NavMenu.tsx index dbf953b18..b8e972d1e 100644 --- a/src/common/components/NavMenu.tsx +++ b/src/common/components/NavMenu.tsx @@ -1,140 +1,56 @@ -import { withStyles, WithStyles } from '@material-ui/core'; -import Drawer from '@material-ui/core/Drawer'; import Icon from '@material-ui/core/Icon'; -import MenuItem from '@material-ui/core/MenuItem'; -import { CSSProperties } from '@material-ui/core/styles/withStyles'; +import BuildIcon from '@material-ui/icons/Build'; +import HelpOutlineIcon from '@material-ui/icons/HelpOutline'; +import HomeIcon from '@material-ui/icons/Home'; +import InfoOutlineIcon from '@material-ui/icons/InfoOutlined'; +import LayersIcon from '@material-ui/icons/Layers'; +import PeopleIcon from '@material-ui/icons/People'; +import ViewListIcon from '@material-ui/icons/ViewList'; +import ViewModuleIcon from '@material-ui/icons/ViewModule'; import * as React from 'react'; -import { NavLink } from 'react-router-dom'; +import { RouteComponentProps, withRouter } from 'react-router'; -import { HEADER_HEIGHT, MAX_Z_INDEX, SIDEBAR_COLLAPSED_WIDTH, SIDEBAR_WIDTH, SIDEBAR_Z_INDEX, UNSUPPORTED_BROWSER_MESSAGE_HEIGHT } from '../constants'; -import { isFlagSet, isSupportedBrowser, toggleFlag } from '../util/browser'; +import { HEADER_HEIGHT, SIDEBAR_COLLAPSED_WIDTH, SIDEBAR_Y_OFFSET, SIDEBAR_Z_INDEX, UNSUPPORTED_BROWSER_MESSAGE_HEIGHT } from '../constants'; -import Tooltip from './Tooltip'; +import NavMenuLink from './NavMenuLink'; interface NavMenuProps { - canExpand: boolean - isExpanded: boolean cardIdBeingEdited: string | null - onRerender: () => void + isUnsupportedBrowser: boolean } -class NavMenu extends React.Component { - public static styles: Record = { - drawerPaper: { - top: HEADER_HEIGHT, - transition: 'width 200ms ease-in-out', - height: 'calc(100% - 54px)', - overflow: 'visible', - zIndex: SIDEBAR_Z_INDEX, - '& .material-icons': { - color: '#666' - }, - '& li .material-icons': { - marginRight: 20 - } - }, - expanded: { - width: SIDEBAR_WIDTH - }, - collapsed: { - width: SIDEBAR_COLLAPSED_WIDTH - }, - unsupportedBrowser: { - top: HEADER_HEIGHT + UNSUPPORTED_BROWSER_MESSAGE_HEIGHT - } - }; - +class NavMenu extends React.PureComponent { public render(): JSX.Element { - const { canExpand, cardIdBeingEdited, isExpanded, classes } = this.props; + const { cardIdBeingEdited, isUnsupportedBrowser, history: { location } } = this.props; + + const iconStyle = { + transform: 'skewY(-20deg)', + marginRight: 15 + }; + return ( - - {this.renderLink('/', 'Home', 'home')} - {this.renderLink('/play', 'Arena', 'crossed-swords', 'ra')} - {this.renderLink(`/card/${cardIdBeingEdited || 'new'}`, 'Workshop', 'build')} - {this.renderLink('/collection', 'Collection', 'view_module')} - {this.renderLink('/decks', 'Decks', 'view_list')} - {this.renderLink('/sets', 'Sets', 'layers')} - {this.renderLink('/community', 'Community', 'people')} - {this.renderLink('/help', 'Help', 'help_outline')} - {this.renderLink('/about', 'About', 'info_outline')} - {canExpand && this.renderExpandCollapseButton()} - + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> +
); } - - private toggleExpanded = () => { - toggleFlag('sidebarCollapsed'); - this.props.onRerender(); - } - - private renderIcon = (icon: string, iconFont: 'material' | 'ra'): React.ReactNode => { - if (iconFont === 'material') { - return ( - - {icon} - - ); - } else if (iconFont === 'ra') { - return ( - - ); - } - } - - private renderLink = (path: string, text: string, icon: string, iconFont: 'material' | 'ra' = 'material') => ( - - - - {this.renderIcon(icon, iconFont)} - {this.props.isExpanded ? text : ''} - - - - ) - - private renderExpandCollapseButton = () => ( -
- - {this.props.isExpanded - ? - arrow_forward - arrow_back - - : - arrow_back - arrow_forward - - } - -
- ) } -export default withStyles(NavMenu.styles)(NavMenu); +export default withRouter(NavMenu); diff --git a/src/common/components/NavMenuLink.tsx b/src/common/components/NavMenuLink.tsx new file mode 100644 index 000000000..1cf224e38 --- /dev/null +++ b/src/common/components/NavMenuLink.tsx @@ -0,0 +1,70 @@ +import MenuItem from '@material-ui/core/MenuItem'; +import { red } from '@material-ui/core/colors'; +import { Location } from 'history'; +import * as React from 'react'; +import { NavLink } from 'react-router-dom'; + +import { MAX_Z_INDEX } from '../constants'; + +interface NavMenuLinkProps { + path: string + text: string + icon: JSX.Element + location: Location +} + +interface NavMenuLinkState { + isHovered: boolean +} + +class NavMenuLink extends React.Component { + public state = { + isHovered: false + }; + + get isActive(): boolean { + const { path, location } = this.props; + + if (path === '/') { + return location.pathname === '/' || location.pathname.startsWith('/home'); + } else { + return location.pathname.startsWith(path); + } + } + + public render(): JSX.Element { + const { path, text, icon } = this.props; + const { isHovered } = this.state; + + return ( + + + {icon} + {isHovered && + {text} + } + + + ); + } + + private onHover = () => { this.setState({ isHovered: true }); }; + private onUnhover = () => { this.setState({ isHovered: false }); }; +} + +export default NavMenuLink; diff --git a/src/common/components/PageHelp.tsx b/src/common/components/PageHelp.tsx new file mode 100644 index 000000000..f6e2e12be --- /dev/null +++ b/src/common/components/PageHelp.tsx @@ -0,0 +1,96 @@ +import IconButton from '@material-ui/core/IconButton'; +import Paper from '@material-ui/core/Paper'; +import { withStyles, WithStyles } from '@material-ui/core/styles'; +import { CSSProperties } from '@material-ui/core/styles/withStyles'; +import CloseIcon from '@material-ui/icons/Close'; +import HelpOutlineIcon from '@material-ui/icons/HelpOutline'; +import * as React from 'react'; + +import Tooltip from '../components/Tooltip'; +import { isFlagSet, toggleFlag } from '../util/browser'; + +interface PageHelpProps { + children: JSX.Element + flagSuffix: string + openByDefault?: true +} + +interface PageHelpState { + isOpen: boolean +} + +class PageHelp extends React.Component { + public static styles: Record = { + openButton: { + float: 'right', + top: 10, + right: 10 + }, + closeButton: { + float: 'right', + top: -8, + right: -8 + }, + container: { + display: 'inline' + }, + helpPaper: { + float: 'right', + marginTop: 10, + marginRight: 10, + maxWidth: 700, + padding: 10, + '& p': { + marginTop: 0 + }, + '& p:last-child': { + marginBottom: 0 + } + } + }; + + public showHelpTextFlag = `wb$showHelpText$${this.props.flagSuffix}`; + + public state = { + isOpen: isFlagSet(this.showHelpTextFlag, this.props.openByDefault || false) + } + + public render(): JSX.Element { + const { children, classes } = this.props; + const { isOpen } = this.state; + + return ( +
+ { + isOpen + ?
+ + + + + {children} + +
+
+ : + + + + + } +
+ ); + } + + private handleShowHelpText = () => { + toggleFlag(this.showHelpTextFlag, true); + this.setState({ isOpen: true }); + } + + private handleHideHelpText = () => { + toggleFlag(this.showHelpTextFlag, false); + this.setState({ isOpen: false }); + } +} + +export default withStyles(PageHelp.styles)(PageHelp); diff --git a/src/common/components/PaperButton.tsx b/src/common/components/PaperButton.tsx index c21a895a6..8b4d0e11f 100644 --- a/src/common/components/PaperButton.tsx +++ b/src/common/components/PaperButton.tsx @@ -14,7 +14,7 @@ interface PaperButtonState { export default class PaperButton extends React.Component { public state = { - shadow: 1 + shadow: 3 }; public render(): JSX.Element { @@ -39,11 +39,11 @@ export default class PaperButton extends React.Component { if (!this.props.disabled) { - this.setState({shadow: 5}); + this.setState({ shadow: 6 }); } } private onMouseOut = () => { - this.setState({shadow: 1}); + this.setState({ shadow: 3 }); } } diff --git a/src/common/components/SmartLink.tsx b/src/common/components/SmartLink.tsx index 793802be6..6db0ed5fb 100644 --- a/src/common/components/SmartLink.tsx +++ b/src/common/components/SmartLink.tsx @@ -7,19 +7,19 @@ interface SmartLinkProps { } // Renders s for internal links and s for exteral links. -export default class SmartLink extends React.Component { +export default class SmartLink extends React.PureComponent { public render(): JSX.Element { const { href, children } = this.props; - const style: React.CSSProperties = {color: 'red', fontWeight: 'bold'}; + const style: React.CSSProperties = {color: 'rgb(0, 120, 135)', fontWeight: 'bold'}; if (href.match(/^(https?:)?\/\//)) { return ( - + {children} ); } else { - return {children}; + return {children}; } } } diff --git a/src/common/components/SplashSection.tsx b/src/common/components/SplashSection.tsx index 76cdd40ff..c5599ec35 100644 --- a/src/common/components/SplashSection.tsx +++ b/src/common/components/SplashSection.tsx @@ -18,6 +18,7 @@ export default class SplashSection extends React.PureComponent { { backgroundColor: '#f44336', opacity: 0.8, borderTopRightRadius: 0, - borderBottomLeftRadius: 0, + borderTopLeftRadius: 0, ...style }} > diff --git a/src/common/components/ToolbarButton.tsx b/src/common/components/ToolbarButton.tsx new file mode 100644 index 000000000..6c334575a --- /dev/null +++ b/src/common/components/ToolbarButton.tsx @@ -0,0 +1,48 @@ +import { Button, Icon } from '@material-ui/core'; +import * as React from 'react'; + +import Tooltip from './Tooltip'; + +interface ToolbarButtonProps { + icon: string | null + children: string + onClick: () => void + vertical?: boolean + disabled?: boolean + tooltip?: string + color?: 'primary' | 'secondary' // defaults to 'primary' +} + +const ToolbarButton: React.SFC = (props: ToolbarButtonProps) => { + const { icon, tooltip, children, onClick, vertical, disabled, color } = props; + return ( + + + + ); +}; + +export default ToolbarButton; diff --git a/src/common/components/Tooltip.tsx b/src/common/components/Tooltip.tsx index 4c1b27bde..052fd4c3e 100644 --- a/src/common/components/Tooltip.tsx +++ b/src/common/components/Tooltip.tsx @@ -20,12 +20,14 @@ interface TooltipState { } export default class Tooltip extends React.Component { + public static displayName = 'Tooltip'; + public state = { tooltipId: id() }; public render(): JSX.Element { - const { inline, style, text, children, disable, place, html, className, additionalStyles} = this.props; + const { inline, style, text, children, disable, place, html, className, additionalStyles } = this.props; const { tooltipId } = this.state; const SpanOrDiv = inline ? 'span' : 'div'; diff --git a/src/common/components/UserMenuItem.tsx b/src/common/components/UserMenuItem.tsx new file mode 100644 index 000000000..8e42fef36 --- /dev/null +++ b/src/common/components/UserMenuItem.tsx @@ -0,0 +1,41 @@ +import ListItemIcon from '@material-ui/core/ListItemIcon'; +import ListItemText from '@material-ui/core/ListItemText'; +import MenuItem from '@material-ui/core/MenuItem'; +import { red } from '@material-ui/core/colors'; +import * as React from 'react'; + +interface UserMenuItemProps { + icon: JSX.Element + text: string + onClick: () => void +} + +export default class UserMenuItem extends React.PureComponent { + public render(): JSX.Element { + const { icon, text, onClick } = this.props; + + return ( + + + {icon} + + + {text} + + + ); + } +} diff --git a/src/common/components/card/Card.tsx b/src/common/components/card/Card.tsx index 47f1c1c55..d022701cb 100644 --- a/src/common/components/card/Card.tsx +++ b/src/common/components/card/Card.tsx @@ -57,6 +57,7 @@ export interface CardProps { rotation?: number yTranslation?: number zIndex?: number + overrideContainerStyles?: CSSProperties onCardClick?: (id: string) => void onCardHover?: (enterOrLeave: boolean) => void @@ -170,7 +171,7 @@ export class Card extends React.Component { const { name, spriteID, spriteV, type, img, cost, baseCost, source, collection, flavorText, showSpinner, status, visible, selected, targetable, - scale, margin, rotation, yTranslation, + scale, margin, rotation, yTranslation, overrideContainerStyles, onSpriteClick, classes } = this.props; const blueShadow = 'rgba(0, 120, 135, 0.45)'; @@ -199,7 +200,8 @@ export class Card extends React.Component { style={{ padding: '24px 0 12px 0', marginRight: margin, - transform + transform, + ...(overrideContainerStyles || {}) }} > { private static styles: Record = { - paper: {padding: 30, maxWidth: 800, margin: '0 auto'}, + paper: { + padding: 30, + maxWidth: 800, + margin: '0 auto', + // below adapted from https://codetea.com/pure-css-blueprint-pattern-using-css3-linear-gradients/ + backgroundColor: 'rgba(255, 255, 255, 0.8)', + backgroundImage: 'linear-gradient(rgba(34, 102, 153, .07) 2px, transparent 2px), linear-gradient(90deg, rgba(34, 102, 153, .07) 2px, transparent 2px), linear-gradient(rgba(34, 102, 153, .05) 1px, transparent 1px), linear-gradient(90deg, rgba(34, 102, 153, .05) 1px, transparent 1px)', + backgroundSize: '100px 100px, 100px 100px, 20px 20px, 20px 20px', + backgroundPosition: '-2px -2px, -2px -2px, -1px -1px, -1px -1px' + }, section: { display: 'flex', justifyContent: 'space-between', marginBottom: 5 }, @@ -67,7 +76,7 @@ export default class CardCreationForm extends React.Component
-
+
- +
) } diff --git a/src/common/components/cards/CardPreview.tsx b/src/common/components/cards/CardPreview.tsx index d480572bb..4ce98832f 100644 --- a/src/common/components/cards/CardPreview.tsx +++ b/src/common/components/cards/CardPreview.tsx @@ -42,24 +42,58 @@ export default class CardPreview extends React.Component { paddingRight: 32 }} > - )} - rawText={this.props.sentences.map((s) => s.sentence).join('. ')} - source={{ type: 'user' }} - parseResults={JSON.stringify(sentences.map((s) => s.result))} - showSpinner={sentences.some((s) => !s.result.js && !s.result.error)} - scale={2.5} - onSpriteClick={onSpriteClick} - /> +
+
+
+ Preview +
+
+ + )} + rawText={this.props.sentences.map((s) => s.sentence).join('. ')} + source={{ type: 'user' }} + parseResults={JSON.stringify(sentences.map((s) => s.result))} + showSpinner={sentences.some((s) => !s.result.js && !s.result.error)} + scale={2.5} + onSpriteClick={onSpriteClick} + overrideContainerStyles={{ padding: 0 }} + /> +
); } else { diff --git a/src/common/components/cards/CreatorToolbarButton.tsx b/src/common/components/cards/CreatorToolbarButton.tsx deleted file mode 100644 index 6bc0760a4..000000000 --- a/src/common/components/cards/CreatorToolbarButton.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import { Button, Icon } from '@material-ui/core'; -import * as React from 'react'; - -import Tooltip from '../Tooltip'; - -interface CreatorToolbarButtonProps { - icon: string - tooltip: string - children: string - onClick: () => void - disabled?: boolean -} - -/* eslint-disable react/no-multi-comp */ -const CreatorToolbarButton: React.SFC = (props: CreatorToolbarButtonProps) => { - const { icon, tooltip, children, onClick, disabled } = props; - return ( - - - - ); -}; - -export default CreatorToolbarButton; diff --git a/src/common/components/cards/DeckCreationSidebarControls.tsx b/src/common/components/cards/DeckCreationSidebarControls.tsx index f933ce502..6cf37d3da 100644 --- a/src/common/components/cards/DeckCreationSidebarControls.tsx +++ b/src/common/components/cards/DeckCreationSidebarControls.tsx @@ -11,8 +11,8 @@ interface DeckCreationSidebarControlsProps { layout: DeckCreationProperties['layout'] sortCriteria: DeckCreationProperties['sortCriteria'] sortOrder: DeckCreationProperties['sortOrder'] - onSetField: (key: keyof DeckCreationProperties) => (value: any) => void - onToggleFilter: (filter: FilterKey) => (event: React.SyntheticEvent, toggled: boolean) => void + onSetField: (key: keyof DeckCreationProperties) => (value: DeckCreationProperties[typeof key]) => void + onToggleFilter: (filter: FilterKey) => (event: React.SyntheticEvent, toggled: boolean) => void } /** diff --git a/src/common/components/cards/FilterControls.tsx b/src/common/components/cards/FilterControls.tsx index 0f6b9df1b..65906d28e 100644 --- a/src/common/components/cards/FilterControls.tsx +++ b/src/common/components/cards/FilterControls.tsx @@ -5,7 +5,7 @@ import * as React from 'react'; import { FilterKey } from './types'; interface FilterControlsProps { - onToggleFilter: (filter: FilterKey) => (event: React.ChangeEvent, toggled: boolean) => void + onToggleFilter: (filter: FilterKey) => (event: React.ChangeEvent, toggled: boolean) => void onSetCostRange: (values: [number, number]) => void } diff --git a/src/common/components/cards/PageSwitcher.tsx b/src/common/components/cards/PageSwitcher.tsx index 6d7a78723..b8c2e48a8 100644 --- a/src/common/components/cards/PageSwitcher.tsx +++ b/src/common/components/cards/PageSwitcher.tsx @@ -28,7 +28,9 @@ export default class PageSwitcher extends React.PureComponent > arrow_back -
{`${page} / ${maxPages}`}
+
+ {`${page} / ${maxPages}`} +
) } diff --git a/src/common/components/cards/SetSummary.tsx b/src/common/components/cards/SetSummary.tsx index 205bd3307..eee794604 100644 --- a/src/common/components/cards/SetSummary.tsx +++ b/src/common/components/cards/SetSummary.tsx @@ -1,7 +1,9 @@ -import { Button, Dialog, DialogActions, DialogContent, Paper } from '@material-ui/core'; +import { Button, Dialog, DialogActions, DialogContent, IconButton, Paper } from '@material-ui/core'; import { ButtonProps } from '@material-ui/core/Button'; import { withStyles, WithStyles } from '@material-ui/core/styles'; import { CSSProperties } from '@material-ui/core/styles/withStyles'; +import ExpandLessIcon from '@material-ui/icons/ExpandLess'; +import ExpandMoreIcon from '@material-ui/icons/ExpandMore'; import * as fb from 'firebase'; import { History } from 'history'; import { isUndefined } from 'lodash'; @@ -10,7 +12,9 @@ import * as CopyToClipboard from 'react-copy-to-clipboard'; import * as w from '../../types'; import { sortCards } from '../../util/cards'; +import { SetDraftFormat } from '../../util/formats'; import { Card } from '../card/Card'; +import Tooltip from '../Tooltip'; import MustBeLoggedIn from '../users/MustBeLoggedIn'; import ProfileLink from '../users/ProfileLink'; @@ -42,9 +46,13 @@ type SetSummaryProps = SetSummaryBaseProps & WithStyles; class SetSummary extends React.Component { public static styles: Record = { paper: { + display: 'inline-block', position: 'relative', padding: 10, - marginBottom: 5 + margin: '5px 15px', + textAlign: 'left', + maxWidth: '95%', + transition: 'width 250ms ease-in-out' }, confirmDeleteControl: { fontSize: '13px', @@ -69,7 +77,14 @@ class SetSummary extends React.Component { dialogButton: { marginLeft: 10 }, + description: { + margin: '15px 100px 0 48px', + color: '#333', + fontSize: '0.9em' + }, link: { + fontSize: '0.9em', + color: '#666', cursor: 'pointer', textDecoration: 'underline', '&:hover': { @@ -118,44 +133,56 @@ class SetSummary extends React.Component { const canEditSet = this.doesSetBelongToUser && !metadata.isPublished; return ( - + + + {isCardListExpanded ? : } +
- {name} by + {name} by {!inPublishedSetsList && metadata.isPublished && (published)} + + [{isCardListExpanded ? 'hide' : 'show'} {cards.length} cards] + + {' '} + + [{isPermalinkCopied ? 'copied' : 'copy permalink'}] +
- - {this.renderButton('Create Deck', onCreateDeckFromSet, { disabled: cards.length < 15 })} - {canEditSet ? this.renderButton('Publish', this.handleOpenPublishConfirmation, { disabled: cards.length < 15 }) : null} + + {inPublishedSetsList ? this.renderButton('Draft!', this.handleDraftFromSet) : null} + {this.renderButton('Create Deck', onCreateDeckFromSet, { disabled: cards.length < 15, reason: "You can't create a deck from this set because it has less than 15 cards." })} + {canEditSet ? this.renderButton('Publish', this.handleOpenPublishConfirmation, { disabled: cards.length < 15, reason: "You can't publish this set because it has less than 15 cards." }) : null} {canEditSet ? this.renderButton('Edit', onEditSet) : null} {this.doesSetBelongToUser ? this.renderButton('Duplicate', onDuplicateSet) : null} {this.renderDeleteControl()}
-
- {description} +
+ {description}
- - {isCardListExpanded &&
- { - cards - .sort((c1, c2) => sortCards(c1, c2, SortCriteria.Cost)) - .map((card, idx) => ( -
- {Card.fromObj(card, { scale: 0.7, onCardClick: () => { this.handleClickCard(card); } })} -
- )) - } -
-
} + {isCardListExpanded && +
+
+ { + cards + .sort((c1, c2) => sortCards(c1, c2, SortCriteria.Cost)) + .map((card, idx) => ( +
+ {Card.fromObj(card, { scale: 0.7, onCardClick: () => { this.handleClickCard(card); } })} +
+ )) + } +
+
+ }
{!isUndefined(numDecksCreated) ? numDecksCreated : '?'} decks created
@@ -212,24 +239,27 @@ class SetSummary extends React.Component { ); } else { - return this.renderButton('Delete', this.handleOpenDeleteConfirmation, { color: 'primary' }); + return this.renderButton('Delete', this.handleOpenDeleteConfirmation, undefined, { color: 'primary' }); } } else { return null; } } - private renderButton = (text: string, action: () => void, additionalProps?: ButtonProps): JSX.Element => ( - + private renderButton = (text: string, action: () => void, disabled?: { disabled: boolean, reason: string }, additionalProps?: ButtonProps): JSX.Element => ( + + + ) private handleClickCard = (card: w.CardInStore) => { @@ -256,6 +286,11 @@ class SetSummary extends React.Component { this.props.onPublishSet(); } + private handleDraftFromSet = () => { + const { set, history } = this.props; + history.push(`/play//host?format=${(new SetDraftFormat(set)).name}`); + } + private handleOpenDeleteConfirmation = () => { this.setState({ isDeleteConfirmationOpen: true }); }; private handleCloseDeleteConfirmation = () => { this.setState({ isDeleteConfirmationOpen: false }); }; diff --git a/src/common/components/game/GameAreaContents.tsx b/src/common/components/game/GameAreaContents.tsx index 7e2304187..c4e61bec3 100644 --- a/src/common/components/game/GameAreaContents.tsx +++ b/src/common/components/game/GameAreaContents.tsx @@ -2,7 +2,7 @@ import { History } from 'history'; import * as React from 'react'; import * as w from '../../types'; -import { BACKGROUND_Z_INDEX, BOARD_Z_INDEX } from '../../constants'; +import { BACKGROUND_Z_INDEX, BOARD_Z_INDEX, SIDEBAR_COLLAPSED_WIDTH } from '../../constants'; import Board from './Board'; import DraftArea from './DraftArea'; @@ -67,7 +67,8 @@ export default class GameAreaContents extends React.PureComponent { +export default class PlayerName extends React.PureComponent { public render(): JSX.Element { - const { color, playerName, opponent } = this.props; + const { color, playerName, opponent, isSandbox } = this.props; return (
{ color: 'white', fontFamily: 'Carter One', fontSize: 32, - left: 0, + left: isSandbox ? (SIDEBAR_COLLAPSED_WIDTH + 5) : 0, top: opponent ? 0 : 'auto', bottom: opponent ? 'auto' : 0, padding: '8px 10px', diff --git a/src/common/components/hexgrid/LICENSE b/src/common/components/hexgrid/LICENSE new file mode 100644 index 000000000..7014258b4 --- /dev/null +++ b/src/common/components/hexgrid/LICENSE @@ -0,0 +1,26 @@ +Note that the code in /hexgrid is heavily adapted from + https://github.com/hellenic/react-hexgrid +The original license for react-hexgrid follows: +------------------------------------------------------------------------------ + +MIT License + +Copyright (c) 2016 Hannu Kärkkäinen + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/src/common/components/play/FormatPicker.tsx b/src/common/components/play/FormatPicker.tsx index db2c22f0c..219a1975d 100644 --- a/src/common/components/play/FormatPicker.tsx +++ b/src/common/components/play/FormatPicker.tsx @@ -38,8 +38,20 @@ export default class FormatPicker extends React.Component { return `${headerMsg}

${builtinFormatRows}

Set formats: ${SetFormat.description}

Set Draft formats: ${SetDraftFormat.description}`; } + get isSetDraftFormatSelected(): boolean { + return this.setDraftFormats.map((f) => f.name).includes(this.props.selectedFormatName); + } + + get setDraftFormats(): SetDraftFormat[] { + return this.props.availableFormats.filter((f) => f.name?.startsWith('setDraft(')) as SetDraftFormat[]; + } + + get nonSetDraftFormats(): GameFormat[] { + return this.props.availableFormats.filter((f) => !f.name?.startsWith('setDraft(')); + } + public render(): JSX.Element { - const { availableFormats, selectedFormatName } = this.props; + const { selectedFormatName } = this.props; return (
@@ -47,14 +59,28 @@ export default class FormatPicker extends React.Component { + {this.isSetDraftFormatSelected && + Choose a set to draft + + } { } private handleSelectFormat = (event: React.ChangeEvent) => { - this.props.onChooseFormat(event.target.value); + const formatName = event.target.value; + console.log(formatName); + if (formatName === 'setDraft') { + console.log(this.setDraftFormats[0].name); + this.props.onChooseFormat(this.setDraftFormats[0].name); + } else { + this.props.onChooseFormat(formatName); + } } } diff --git a/src/common/components/play/GameBrowser.tsx b/src/common/components/play/GameBrowser.tsx index a2f511655..6e9051a70 100644 --- a/src/common/components/play/GameBrowser.tsx +++ b/src/common/components/play/GameBrowser.tsx @@ -40,7 +40,7 @@ export default class GameBrowser extends React.Component { - + Game Name Format Players @@ -82,11 +82,11 @@ export default class GameBrowser extends React.Component { No open games. diff --git a/src/common/components/play/Lobby.tsx b/src/common/components/play/Lobby.tsx index 793fc1acd..cc44b620a 100644 --- a/src/common/components/play/Lobby.tsx +++ b/src/common/components/play/Lobby.tsx @@ -7,6 +7,7 @@ import { CHAT_WIDTH } from '../../constants'; import * as w from '../../types'; import { unpackDeck } from '../../util/cards'; import { GameFormat, renderFormatDisplayName } from '../../util/formats'; +import Background from '../Background'; import RouterDialog from '../RouterDialog'; import Title from '../Title'; @@ -73,6 +74,8 @@ export default class Lobby extends React.Component { return (
+ +
{casualGameBeingJoined && { public state: PreGameModalState = { selectedDeckId: null, - selectedFormatName: this.props.mode === 'practice' ? 'normal' : 'sharedDeck', + selectedFormatName: getQueryString(this.props.history, 'format') || (this.props.mode === 'practice' ? 'normal' : 'sharedDeck'), enteredPassword: '', isPasswordInvalid: false }; @@ -49,7 +50,7 @@ export default class PreGameModal extends React.Component f.name === selectedFormatName)!; + return format || formats.find((f) => f.name === selectedFormatName) || formats[0]; } // The full list of formats that are available to the player (given the game mode). diff --git a/src/common/components/users/MustBeLoggedIn.tsx b/src/common/components/users/MustBeLoggedIn.tsx index 0ea9ccab4..55c15d533 100644 --- a/src/common/components/users/MustBeLoggedIn.tsx +++ b/src/common/components/users/MustBeLoggedIn.tsx @@ -1,4 +1,4 @@ -import { compact, isArray, isObject, pick } from 'lodash'; +import { compact, isArray, isObject, isString, pick } from 'lodash'; import * as React from 'react'; import Tooltip from '../Tooltip'; @@ -26,7 +26,7 @@ export default class MustBeLoggedIn extends React.Component } else { return (
- {React.Children.map(this.children, this.renderDisabledChild)} + {React.Children.map(this.children, this.renderDisabledChild.bind(this))}
); } @@ -37,6 +37,12 @@ export default class MustBeLoggedIn extends React.Component return child; } + // black magick to bypass s in the component chain and dig down further, + // so