Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 35 additions & 1 deletion src/components/Chat/Chat.js
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,35 @@ export default class Chat extends Component {
);
}

renderMobileChat() {
return (
<div className="chat-overlay">
<div className="chat-header">
<div>Chat</div>
<div className="chat-close-button" onClick={this.handleToggleChat}>
<MdClose />
</div>
</div>
<div className="chat-messages">
{this.renderChatHeader()}
{this.renderChatSubheader()}
<div
ref={(el) => {
if (el) {
el.scrollTop = el.scrollHeight;
}
}}
className="chat--messages"
>
{this.props.data.messages &&
this.props.data.messages.map((message, i) => <div key={i}>{this.renderMessage(message)}</div>)}
</div>
</div>
<div className="chat-input-area">{this.renderChatBar()}</div>
</div>
);
}

renderFencingOptions() {
const fencingUrl = `/fencing/${this.props.gid}`;
const normalUrl = `/beta/game/${this.props.gid}`;
Expand Down Expand Up @@ -411,10 +440,15 @@ export default class Chat extends Component {

render() {
const messages = this.mergeMessages(this.props.data, this.props.opponentData);

if (this.props.mobile && this.props.chatOpen) {
return this.renderMobileChat();
}

return (
<Flex column grow={1}>
{this.renderToolbar()}
<div className="chat">
<div className={`chat ${this.props.mobile ? 'mobile' : ''}`}>
{this.renderChatHeader()}
{this.renderChatSubheader()}
<div
Expand Down
51 changes: 51 additions & 0 deletions src/components/Chat/css/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -186,3 +186,54 @@
color: var(--main-gray-1);
}
}

/* Mobile chat overlay */
.chat-overlay {
position: fixed;
top: 48px; /* below toolbar */
left: 0;
width: 100%;
height: calc(100% - 48px);
background: #fff;
z-index: 100;
display: flex;
flex-direction: column;
}

.chat-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px 15px;
border-bottom: 1px solid #e2e2e2;
background-color: var(--main-blue-3);
color: #333;
}

.chat-close-button {
font-size: 24px;
cursor: pointer;
}

.chat-messages {
flex: 1 1 auto;
overflow-y: auto;
padding: 8px;
}

.chat-input-area {
flex: 0 0 auto;
padding: 8px;
border-top: 1px solid #e2e2e2;
}

body.dark-mode .chat-overlay {
background: #222;
color: #fff;
}

body.dark-mode .chat-header {
background-color: #333;
color: #fff;
border-bottom: 1px solid #444;
}
63 changes: 63 additions & 0 deletions src/components/Player/ClueList.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import React from 'react';
import {MdClose} from 'react-icons/md';
import Clue from './ClueText';
import './css/clueList.css';

export default class ClueList extends React.Component {
handleClueClick = (direction, number) => {
this.props.onSelectClue(direction, number);
if (this.props.onClose) {
this.props.onClose();
}
};

renderClueSection(direction, clues = {}, clueLengths) {
const title = direction === 'across' ? 'Across' : 'Down';
const {selected} = this.props;
const isSelectedDirection = selected.direction === direction;

return (
<div className="mobile-grid-controls--clue-section">
<div className="mobile-grid-controls--clue-section-header">{title}</div>
{Object.keys(clues).map((number) => {
const isSelected = isSelectedDirection && selected.number === number;
const clueText = clues[number];
const clueLength = clueLengths && clueLengths[direction] && clueLengths[direction][number];
const lengthText = clueLength ? ` (${clueLength})` : '';

return (
<div
key={`${direction}-${number}`}
className={`mobile-grid-controls--clue-item ${isSelected ? 'active' : ''}`}
onClick={() => this.handleClueClick(direction, number)}
>
<div className="mobile-grid-controls--clue-item-number">{number}</div>
<div className="mobile-grid-controls--clue-item-text">
<Clue text={`${clueText}${lengthText}`} />
</div>
</div>
);
})}
</div>
);
}

render() {
const {clues, clueLengths, onClose} = this.props;

return (
<div className="mobile-grid-controls--clue-list-overlay">
<div className="mobile-grid-controls--clue-list-header">
<div className="mobile-grid-controls--clue-list-title">Clues</div>
<div className="mobile-grid-controls--clue-list-close" onClick={onClose}>
<MdClose />
</div>
</div>
<div className="mobile-grid-controls--clue-list-content">
{this.renderClueSection('across', clues.across, clueLengths)}
{this.renderClueSection('down', clues.down, clueLengths)}
</div>
</div>
);
}
}
76 changes: 74 additions & 2 deletions src/components/Player/MobileGridControls.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {MdKeyboardArrowLeft, MdKeyboardArrowRight} from 'react-icons/md';
import classnames from 'classnames';
import _ from 'lodash';
import Clue from './ClueText';
import ClueList from './ClueList';
import GridControls from './GridControls';
import GridObject from '../../lib/wrappers/GridWrapper';
import * as gameUtils from '../../lib/gameUtils';
Expand All @@ -21,6 +22,8 @@ export default class MobileGridControls extends GridControls {
anchors: [],
transform: {scale: 1, translateX: 0, translateY: 0},
dbgstr: undefined,
showClueList: false,
showKeyboard: true,
};
this.prvInput = '';
this.inputRef = React.createRef();
Expand Down Expand Up @@ -407,6 +410,7 @@ export default class MobileGridControls extends GridControls {
focusKeyboard() {
this.inputRef.current.selectionStart = this.inputRef.current.selectionEnd = this.inputRef.current.value.length;
this.inputRef.current.focus();
this.setState({showKeyboard: true});
}

keepFocus = () => {
Expand Down Expand Up @@ -525,14 +529,82 @@ export default class MobileGridControls extends GridControls {
);
}

toggleClueList = () => {
this.setState((state) => ({showClueList: !state.showClueList}));
};

handleSelectClue = (direction, number) => {
this.props.onSetDirection(direction);
const grid = this.grid;
const clueRoot = grid.getCellByNumber(number);
if (clueRoot) {
this.props.onSetSelected(clueRoot);
}
};

renderOnscreenKeyboard() {
if (!this.state.showKeyboard) return null;

return (
<div className="onscreen-keyboard">
{'QWERTYUIOP'.split('').map((letter) => (
<button key={letter} className="key-btn" onClick={() => this.handleKeyClick(letter)}>
{letter}
</button>
))}
{'ASDFGHJKL'.split('').map((letter) => (
<button key={letter} className="key-btn" onClick={() => this.handleKeyClick(letter)}>
{letter}
</button>
))}
{'ZXCVBNM'.split('').map((letter) => (
<button key={letter} className="key-btn" onClick={() => this.handleKeyClick(letter)}>
{letter}
</button>
))}
<button className="key-btn wide" onClick={this.handleBackspaceClick}>
</button>
<button className="key-btn wide" onClick={this.handleToggleDirection}>
↕︎
</button>
</div>
);
}

handleKeyClick = (letter) => {
this.typeLetter(letter, false, {nextClueIfFilled: true});
};

handleBackspaceClick = () => {
this.backspace();
};

handleToggleDirection = () => {
this.flipDirection();
};

render() {
const {showClueList} = this.state;

return (
// eslint-disable-next-line react/no-string-refs
<div ref="gridControls" className="mobile-grid-controls">
{this.renderClueBar()}
{this.renderGridContent()}
<div className="grid-wrapper">{this.renderGridContent()}</div>
{this.renderMobileInputs()}
{/* {this.renderMobileKeyboard()} */}
{this.renderOnscreenKeyboard()}
{showClueList && (
<ClueList
clues={this.props.clues}
selected={{
direction: this.props.direction,
number: this.getSelectedClueNumber(),
}}
onSelectClue={this.handleSelectClue}
onClose={this.toggleClueList}
/>
)}
{this.props.enableDebug && (this.state.dbgstr || 'No message')}
</div>
);
Expand Down
13 changes: 13 additions & 0 deletions src/components/Player/css/clueList.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/* Styles for clue list component */
.mobile-grid-controls--clue-list-overlay .mobile-grid-controls--clue-list-title {
font-size: 18px;
font-weight: bold;
}

.mobile-grid-controls--clue-list-content {
padding-bottom: 80px; /* Ensure bottom clues are visible when scrolling */
}

.mobile-grid-controls--clue-item-text {
flex: 1;
}
11 changes: 10 additions & 1 deletion src/components/Player/css/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -91,18 +91,27 @@
.player--mobile--wrapper {
display: flex;
flex: 1;
flex-direction: column;
margin-top: 48px; /* Account for fixed toolbar */
height: calc(100vh - 48px);
overflow: hidden;
}

.player--mobile {
flex: 1;
display: flex;
flex-direction: column;
justify-content: center;
justify-content: flex-start;
align-items: center;
overflow-y: auto;
padding-bottom: 200px; /* Space for keyboard */
}

.player--mobile--grid {
display: flex;
width: 100%;
max-width: 600px;
margin: 0 auto;
}

.player--mobile--list-view {
Expand Down
64 changes: 64 additions & 0 deletions src/components/Player/css/mobileGridControls.css
Original file line number Diff line number Diff line change
Expand Up @@ -84,3 +84,67 @@
display: flex;
align-items: center;
}

/* Clue list overlay */
.mobile-grid-controls--clue-list-overlay {
position: fixed;
top: 48px; /* below toolbar */
left: 0;
width: 100%;
height: calc(100% - 48px);
background: white;
z-index: 10;
display: flex;
flex-direction: column;
overflow: hidden;
}

.mobile-grid-controls--clue-list-header {
display: flex;
justify-content: space-between;
padding: 10px;
border-bottom: 1px solid #eee;
background: var(--main-blue-3);
}

.mobile-grid-controls--clue-list-close {
font-size: 24px;
cursor: pointer;
}

.mobile-grid-controls--clue-list-content {
flex: 1;
overflow-y: auto;
padding: 0 10px;
}

.mobile-grid-controls--clue-section {
margin: 10px 0;
}

.mobile-grid-controls--clue-section-header {
font-weight: bold;
margin-bottom: 5px;
}

.mobile-grid-controls--clue-item {
padding: 8px 5px;
border-bottom: 1px solid #eee;
display: flex;
}

.mobile-grid-controls--clue-item.active {
background-color: var(--main-blue-3);
}

.mobile-grid-controls--clue-item-number {
font-weight: bold;
margin-right: 10px;
min-width: 30px;
}

/* Grid wrapper for scrolling above keyboard */
.grid-wrapper {
padding-bottom: 200px; /* space for keyboard */
overflow-y: auto;
}
Loading