Merge branch 'dev'

This commit is contained in:
Robbie Antenesse 2018-03-10 11:23:52 -07:00
commit ee710988d5
17 changed files with 398 additions and 97 deletions

View File

@ -45,6 +45,7 @@
"dependencies": {
"babel-polyfill": "^6.26.0",
"bulma": "^0.6.2",
"bulma-checkradio": "^1.0.1",
"dexie": "^2.0.1",
"font-awesome": "^4.7.0",
"inferno": "^4.0.4",

View File

@ -24,7 +24,10 @@ class User {
}
} else if (password_verify($password, $user['password'])) {
$this->db->execute('UPDATE users SET last_login=' . time() . ' WHERE id=' . $user['id']);
return $this->generateUserToken($user['id'], $user['current_dictionary']);
return array(
'token' => $this->generateUserToken($user['id'], $user['current_dictionary']),
'user' => $this->getUserData($user['id']),
);
}
}
return false;
@ -60,13 +63,57 @@ VALUES (?, ?, ?, ?, ?, '. time() .')';
$new_dictionary = $this->dictionary->create($new_user_id);
if ($new_dictionary !== false) {
return $this->generateUserToken($new_user_id, $new_dictionary);
return array(
'token' => $this->generateUserToken($new_user_id, $new_dictionary),
'user' => $this->getUserData($new_user_id),
);
}
}
return false;
}
public function setUserData ($token, $user_data) {
$token_data = $this->token->decode($token);
if ($token_data !== false) {
$query = 'UPDATE users SET email=?, public_name=?, username=?, allow_email=?, use_ipa=? WHERE id=?';
$properties = array(
$user_data['email'],
$user_data['publicName'],
$user_data['username'],
$user_data['allowEmail'],
$user_data['useIPAPronunciation'],
$user_id,
);
$update_success = $this->db->execute($query, $properties);
if ($update_success) {
return array(
'token' => $token,
'userData' => $user_data,
);
}
}
return false;
}
public function getUserData ($user_id) {
$query = 'SELECT * FROM users WHERE id=?';
$stmt = $this->db->query($query, array($user_id));
$user = $stmt->fetch();
if ($stmt && $user) {
return array(
'email' => $user['email'],
'username' => $user['username'],
'publicName' => $user['public_name'],
'allowEmails' => $user['allow_email'] == 1 ? true : false,
'useIPAPronunciation' => $user['use_ipa'] == 1 ? true : false,
);
}
return false;
}
public function createNewDictionary ($token) {
$user_data = $this->token->decode($token);
if ($user_data !== false) {

View File

@ -12,10 +12,10 @@ switch ($action) {
case 'login': {
if (isset($request['email']) && isset($request['password'])) {
$user = new User();
$token = $user->logIn($request['email'], $request['password']);
if ($token !== false) {
$user_data = $user->logIn($request['email'], $request['password']);
if ($user_data !== false) {
return Response::json(array(
'data' => $token,
'data' => $user_data,
'error' => false,
), 200);
}
@ -33,10 +33,10 @@ switch ($action) {
if (isset($request['email']) && isset($request['password'])) {
$user = new User();
if (!$user->emailExists($request['email'])) {
$token = $user->create($request['email'], $request['password'], $request['userData']);
if ($token !== false) {
$user_data = $user->create($request['email'], $request['password'], $request['userData']);
if ($user_data !== false) {
return Response::json(array(
'data' => $token,
'data' => $user_data,
'error' => false,
), 201);
}
@ -103,6 +103,26 @@ switch ($action) {
'error' => true,
), 403);
}
case 'set-user-data': {
if ($token !== false && isset($request['userData'])) {
$user = new User();
$updated_user = $user->setUserData($token, $request['userData']);
if ($updated_user !== false) {
return Response::json(array(
'data' => $updated_user,
'error' => false,
), 200);
}
return Response::json(array(
'data' => 'Could not set user data: missing data',
'error' => true,
), 400);
}
return Response::json(array(
'data' => 'Could not get dictionaries: no token provided',
'error' => true,
), 403);
}
case 'create-new-dictionary': {
if ($token !== false) {
$user = new User();

View File

@ -16,8 +16,10 @@ export class MainDisplay extends Component {
PropTypes.checkPropTypes({
dictionaryInfo: PropTypes.object.isRequired,
isLoadingWords: PropTypes.bool,
wordsToDisplay: PropTypes.array.isRequired,
wordsAreFiltered: PropTypes.bool,
wordsInCurrentList: PropTypes.number,
currentPage: PropTypes.number,
itemsPerPage: PropTypes.number,
stats: PropTypes.object.isRequired,
@ -55,8 +57,10 @@ export class MainDisplay extends Component {
render () {
const {
dictionaryInfo,
isLoadingWords,
wordsToDisplay,
wordsAreFiltered,
wordsInCurrentList,
currentPage,
itemsPerPage,
stats,
@ -105,7 +109,16 @@ export class MainDisplay extends Component {
</div>
)}
<Pagination
currentPage={ currentPage }
itemsPerPage={ itemsPerPage }
stats={ stats }
setPage={ setPage }
wordsInCurrentList={ wordsInCurrentList }
isTop />
<WordsList
isLoadingWords={ isLoadingWords }
words={ wordsToDisplay }
adsEveryXWords={ 10 }
updateDisplay={ updateDisplay } />
@ -114,7 +127,8 @@ export class MainDisplay extends Component {
currentPage={currentPage}
itemsPerPage={itemsPerPage}
stats={stats}
setPage={ setPage } />
setPage={setPage}
wordsInCurrentList={wordsInCurrentList} />
</RightColumn>
</div>

View File

@ -13,6 +13,7 @@ export class WordsList extends Component {
super(props);
PropTypes.checkPropTypes({
isLoadingWords: PropTypes.bool,
adsEveryXWords: PropTypes.number,
words: PropTypes.array,
updateDisplay: PropTypes.func.isRequired,
@ -22,6 +23,12 @@ export class WordsList extends Component {
render () {
const adsEveryXWords = this.props.adsEveryXWords || 10;
if (this.props.isLoadingWords) {
return <div className="content has-text-centered">
<div className="loader" style="display: inline-block; width: 60px; height: 60px;" />
</div>;
}
return (
<div className='box'>

View File

@ -0,0 +1,94 @@
import Inferno from 'inferno';
import { Component } from 'inferno';
import PropTypes from 'prop-types';
export class MyAccount extends Component {
constructor(props) {
super(props);
PropTypes.checkPropTypes({
email: PropTypes.string.isRequired,
username: PropTypes.string.isRequired,
publicName: PropTypes.string.isRequired,
allowEmails: PropTypes.bool.isRequired,
useIPAPronunciation: PropTypes.bool.isRequired,
userDictionaries: PropTypes.array.isRequired,
updateUserData: PropTypes.func,
changeDictionary: PropTypes.func,
}, props, 'prop', 'LoginForm');
this.state = {
email: this.props.email,
username: this.props.username,
publicName: this.props.publicName,
allowEmails: this.props.allowEmails,
useIPAPronunciation: this.props.useIPAPronunciation,
userDictionaries: this.props.userDictionaries,
};
}
render() {
return (
<div>
<div className='columns'>
<div className='column'>
<h2 className='title'>Account Details</h2>
<div className='control'>
<strong>Email:</strong> <span>{this.state.email}</span>
</div>
<div className='control'>
<strong>Username:</strong> <span>{this.state.username}</span>
</div>
<div className='field'>
<label className='label'>
<span>Public Name:</span>
</label>
<div className='control'>
<input className='input' type='text' value={this.state.publicName}
onInput={(event) => {this.setState({publicName: event.target.value})}} />
</div>
</div>
<div className='field'>
<div className='control'>
<input className='is-checkradio is-rtl' type='checkbox' id='allowEmails'
checked={this.state.allowEmails ? 'checked' : false}
onChange={(event) => { this.setState({ allowEmails: !this.state.allowEmails }) }} />
<label className='label is-unselectable' htmlFor='allowEmails'>
Allow Emails
</label>
</div>
</div>
<div className='field'>
<input className='is-checkradio is-rtl' type='checkbox' id='useIPAPronunciation'
checked={this.state.useIPAPronunciation ? 'checked' : false}
onChange={(event) => { this.setState({ useIPAPronunciation: !this.state.useIPAPronunciation }) }} />
<label className='label is-unselectable' htmlFor='useIPAPronunciation'>
Use IPA in Pronunciation Fields
</label>
</div>
</div>
<div className='column'>
<h2 className='title'>Account Actions</h2>
<div className='field'>
<label className='label is-unselectable'>
<span>Change Dictionary</span>
</label>
<div className='control'>
<div className='select'>
<select>
{this.props.userDictionaries.map(item => {
return <option value={item.id}>{item.name}</option>;
})}
</select>
</div>
</div>
</div>
</div>
</div>
</div>
);
}
}

View File

@ -6,9 +6,18 @@ import store from 'store';
import { Modal } from '../../structure/Modal';
import { LoginForm } from './LoginForm';
import { MyAccount } from './MyAccount';
import { request } from '../../../Helpers';
const defaultUserData = {
email: '',
username: '',
publicName: '',
allowEmails: true,
useIPAPronunciation: true,
};
export class AccountManager extends Component {
constructor (props) {
super(props);
@ -17,9 +26,21 @@ export class AccountManager extends Component {
updater: PropTypes.object.isRequired,
}, props, 'prop', 'AccountManager');
const userData = store.get('LexicongaUserData');
this.state = {
isLoggedIn: false,
userData: {
email: userData ? userData.email : defaultUserData.email,
username: userData ? userData.username : defaultUserData.username,
publicName: userData ? userData.publicName : defaultUserData.publicName,
allowEmails: userData ? userData.allowEmails : defaultUserData.allowEmails,
useIPAPronunciation: userData ? userData.useIPAPronunciation : defaultUserData.useIPAPronunciation,
},
userDictionaries: [],
};
this.getDictionaryNames();
}
logIn (email, password) {
@ -28,7 +49,12 @@ export class AccountManager extends Component {
logOut () {
store.remove('LexicongaToken');
this.setState({ isLoggedIn: false });
store.remove('LexicongaUserData');
this.setState({
isLoggedIn: false,
userData: Object.assign({}, defaultUserData),
userDictionaries: [],
});
}
signUp (email, password, userData) {
@ -44,23 +70,69 @@ export class AccountManager extends Component {
if (error) {
console.error(data);
} else {
store.set('LexicongaToken', data);
this.setState({ isLoggedIn: true }, () => {
store.set('LexicongaToken', data.token);
store.set('LexicongaUserData', data.user);
this.setState({
isLoggedIn: true,
userData: data.user,
}, () => {
this.getDictionaryNames();
this.props.updater.sync();
});
}
}
updateUserData (newUserData) {
const token = store.get('LexicongaToken');
if (token) {
store.set('LexicongaUserData', newUserData);
this.setState({ userData: newUserData }, () => {
request('set-user-data', { token, userData: newUserData }, (response) => {
const {data, error} = response;
if (error) {
console.error(data);
} else {
console.log(data);
}
})
});
}
}
getDictionaryNames () {
const token = store.get('LexicongaToken');
if (token) {
return request('get-all-dictionary-names', { token }, (response) => {
const {data, error} = response;
if (error) {
console.error(data);
} else {
this.setState({ userDictionaries: data });
}
});
}
}
render () {
const token = store.get('LexicongaToken');
if (token) {
const { userData } = this.state;
return (
<div>
<Modal buttonText='Account' title='My Account'>
<div className='content has-text-left'>
<p>Hello My Account!</p>
</div>
<Modal buttonText='Account' title='My Account' onShow={ this.getDictionaryNames.bind(this) }>
<MyAccount
email={ userData.email }
username={ userData.username }
publicName={ userData.publicName }
allowEmails={ userData.allowEmails }
useIPAPronunciation={ userData.useIPAPronunciation }
userDictionaries={ this.state.userDictionaries }
updateUserData={ this.updateUserData.bind(this) }
changeDictionary={ () => {} } />
</Modal>
<a className='button' onClick={this.logOut.bind(this)}>
Log Out

View File

@ -30,17 +30,16 @@ export const EditSettingsForm = (props) => {
<div className='field'>
<div className='control'>
<label className='checkbox'>
<input className='is-checkradio is-rtl' type='checkbox' id='allowDuplicates'
defaultChecked={ allowDuplicates }
onChange={ (event) => {
editDictionaryModal.setState({
allowDuplicates: event.target.checked,
hasChanged: event.target.checked != editDictionaryModal.props.allowDuplicates,
});
}} />
<label class='label is-unselectable' htmlFor='allowDuplicates'>
Allow Duplicate Words
{ ' ' }
<input type='checkbox'
defaultChecked={ allowDuplicates }
onChange={ (event) => {
editDictionaryModal.setState({
allowDuplicates: event.target.checked,
hasChanged: event.target.checked != editDictionaryModal.props.allowDuplicates,
});
}} />
</label>
<div className='help'>
Checking this box will allow any number of the exact same spelling of a word to be added
@ -50,20 +49,19 @@ export const EditSettingsForm = (props) => {
<div className='field'>
<div className='control'>
<label className='checkbox'
Disabled={ allowDuplicates ? 'disabled' : null }
title={ allowDuplicates ? 'Disabled because allowing duplicates makes this unnecessary' : null}>
<input className='is-checkradio is-rtl' type='checkbox' id='caseSensitive'
defaultChecked={ caseSensitive }
disabled={ allowDuplicates }
onChange={ (event) => {
editDictionaryModal.setState({
caseSensitive: event.target.checked,
hasChanged: event.target.checked != editDictionaryModal.props.caseSensitive,
});
}} />
<label className='label is-unselectable' htmlFor='caseSensitive'
Disabled={allowDuplicates ? 'disabled' : null}
title={allowDuplicates ? 'Disabled because allowing duplicates makes this unnecessary' : null}>
Words are Case-Sensitive
{ ' ' }
<input type='checkbox'
defaultChecked={ caseSensitive }
disabled={ allowDuplicates }
onChange={ (event) => {
editDictionaryModal.setState({
caseSensitive: event.target.checked,
hasChanged: event.target.checked != editDictionaryModal.props.caseSensitive,
});
}} />
</label>
<div className='help'>
Checking this box will allow any words spelled the same but with different capitalization to be added.
@ -73,17 +71,16 @@ export const EditSettingsForm = (props) => {
<div className='field'>
<div className='control'>
<label className='checkbox'>
<input className='is-checkradio is-rtl' type='checkbox' id='sortByDefinition'
defaultChecked={ sortByDefinition }
onChange={ (event) => {
editDictionaryModal.setState({
sortByDefinition: event.target.checked,
hasChanged: event.target.checked != editDictionaryModal.props.sortByDefinition,
});
}} />
<label className='label is-unselectable' htmlFor='sortByDefinition'>
Sort by Definition
{ ' ' }
<input type='checkbox'
defaultChecked={ sortByDefinition }
onChange={ (event) => {
editDictionaryModal.setState({
sortByDefinition: event.target.checked,
hasChanged: event.target.checked != editDictionaryModal.props.sortByDefinition,
});
}} />
</label>
<div className='help'>
Checking this box will sort the words in alphabetical order based on the Definition instead of the Word.
@ -93,20 +90,19 @@ export const EditSettingsForm = (props) => {
<div className='field'>
<div className='control'>
<label className='checkbox'>
<input className='is-checkradio is-rtl' type='checkbox' id='isComplete'
defaultChecked={ isComplete }
onChange={ (event) => {
editDictionaryModal.setState({
isComplete: event.target.checked,
hasChanged: event.target.checked != editDictionaryModal.props.isComplete,
});
}} />
<label className='label is-unselectable' htmlFor='isComplete'>
Mark Complete
{ ' ' }
<input type='checkbox'
defaultChecked={ isComplete }
onChange={ (event) => {
editDictionaryModal.setState({
isComplete: event.target.checked,
hasChanged: event.target.checked != editDictionaryModal.props.isComplete,
});
}} />
</label>
<div className='help'>
Checking this box will mark your { specification } as "complete" and prevent any changes from being made.
Checking this box will mark your { specification } as 'complete' and prevent any changes from being made.
</div>
</div>
</div>
@ -115,17 +111,16 @@ export const EditSettingsForm = (props) => {
&& (
<div className='field'>
<div className='control'>
<label className='checkbox'>
<input className='is-checkradio is-rtl' type='checkbox' id='isPublic'
defaultChecked={ isPublic }
onChange={ (event) => {
editDictionaryModal.setState({
isPublic: event.target.checked,
hasChanged: event.target.checked != editDictionaryModal.props.isPublic,
});
}} />
<label className='label is-unselectable' htmlFor='isPublic'>
Make Public
{ ' ' }
<input type='checkbox'
defaultChecked={ isPublic }
onChange={ (event) => {
editDictionaryModal.setState({
isPublic: event.target.checked,
hasChanged: event.target.checked != editDictionaryModal.props.isPublic,
});
}} />
</label>
<div className='help'>
Checking this box will make your { specification } as public.

View File

@ -13,6 +13,7 @@ const DISPLAY = {
DETAILS: 1,
LINGUISTICS: 2,
SETTINGS: 3,
ACTIONS: 4,
}
export class EditDictionaryModal extends Component {
@ -58,6 +59,8 @@ export class EditDictionaryModal extends Component {
hasChanged: false,
}
this.modal = null;
}
hasChanged () {
@ -126,6 +129,14 @@ export class EditDictionaryModal extends Component {
);
break;
}
case DISPLAY.ACTIONS : {
displayJSX = (
<div class="content">
<p>Actions like import, export, delete, etc.</p>
</div>
);
}
}
return (
@ -135,7 +146,7 @@ export class EditDictionaryModal extends Component {
);
}
save () {
save (callback = () => {}) {
const updatedDetails = {};
if (this.state.name !== this.props.name) {
@ -243,7 +254,7 @@ export class EditDictionaryModal extends Component {
this.setState({ hasChanged: false }, () => {
// If setting that alters word display is changed, update the display.
if (updatedDetails.hasOwnProperty('sortByDefinition')) {
this.props.updateDisplay();
this.props.updateDisplay(callback);
}
});
})
@ -269,9 +280,16 @@ export class EditDictionaryModal extends Component {
>
Save
</button>
<button className='button'
disabled={ !hasChanged }
onClick={ () => this.save(this.modal.hide())}
>
Save & Close
</button>
</div>
)
}
ref={(modal) => this.modal = modal}
>
{!settings.isComplete
@ -293,6 +311,11 @@ export class EditDictionaryModal extends Component {
Settings
</a>
</li>
<li className={ (currentDisplay === DISPLAY.ACTIONS) ? 'is-active' : null }>
<a onClick={ this.toggleDisplay.bind(this, DISPLAY.ACTIONS) }>
Actions
</a>
</li>
</ul>
</div>
),

View File

@ -23,7 +23,7 @@ export class SearchBox extends Component {
searchTerm: '',
caseSensitive: false,
ignoreDiacritics: false,
filteredPartsOfSpeech: [...props.partsOfSpeech, 'Uncategorized'],
filteredPartsOfSpeech: this.partsOfSpeechForFilter,
showHeader: false,
showAdvanced: false,
};
@ -62,7 +62,7 @@ export class SearchBox extends Component {
}
togglePartOfSpeech (event) {
const uniquePartsOfSpeech = new Set(this.partsOfSpeechForFilter);
const uniquePartsOfSpeech = new Set(this.state.filteredPartsOfSpeech);
if (event.target.checked) {
uniquePartsOfSpeech.add(event.target.value);
} else {
@ -111,7 +111,7 @@ export class SearchBox extends Component {
this.searchBox = input;
}}
value={ this.state.searchTerm }
onChange={(event) => {
onInput={(event) => {
this.setState({ searchTerm: event.target.value.trim() }, () => this.search());
}} />
<span className='icon is-small is-left'>
@ -314,6 +314,8 @@ export class SearchBox extends Component {
showHeader () {
this.setState({
showHeader: true,
}, () => {
this.searchBox.focus();
});
}

View File

@ -109,7 +109,7 @@ export class WordForm extends Component {
<input className={ `input${(!this.state.nameIsValid) ? ' is-danger' : ''}` }
type='text' placeholder='Required'
value={ this.state.wordName }
onChange={(event) => {
onInput={(event) => {
this.setState({ wordName: event.target.value });
}} />
{(!this.state.nameIsValid)
@ -149,7 +149,7 @@ export class WordForm extends Component {
<input className={ `input${(!this.state.definitionIsValid) ? ' is-danger' : ''}` }
type='text' placeholder='Equivalent word(s)'
value={ this.state.wordDefinition }
onChange={(event) => {
onInput={(event) => {
this.setState({ wordDefinition: event.target.value })
}} />
{(!this.state.definitionIsValid)

View File

@ -13,6 +13,8 @@ export class Modal extends Component {
children: PropTypes.oneOfType([PropTypes.object, PropTypes.array]),
footerAlign: PropTypes.string,
footerContent: PropTypes.oneOfType([PropTypes.object, PropTypes.array]),
onShow: PropTypes.func,
onHide: PropTypes.func,
}, props, 'prop', 'Modal');
this.state = {
@ -23,12 +25,20 @@ export class Modal extends Component {
show () {
this.setState({
isVisible: true,
}, () => {
if (this.props.onShow) {
this.props.onShow();
}
});
}
hide () {
this.setState({
isVisible: false,
}, () => {
if (this.props.onHide) {
this.props.onHide();
}
});
}

View File

@ -1,24 +1,25 @@
import Inferno from 'inferno';
import PropTypes from 'prop-types';
import './styles.scss';
export const Pagination = (props) => {
PropTypes.checkPropTypes({
currentPage: PropTypes.number.isRequired,
itemsPerPage: PropTypes.number.isRequired,
stats: PropTypes.object.isRequired,
setPage: PropTypes.func.isRequired,
wordsInCurrentList: PropTypes.number,
isTop: PropTypes.bool,
}, props, 'prop', 'Pagination');
const { currentPage, itemsPerPage, stats, setPage } = props;
const totalWords = stats.hasOwnProperty('numberOfWords')
? stats.numberOfWords.find(group => group.name === 'Total').value : null;
const { currentPage, itemsPerPage, stats, setPage, wordsInCurrentList, isTop } = props;
if (totalWords === null) {
return <div className="loader"></div>;
if (wordsInCurrentList === null) {
return null;
}
const lastPage = Math.floor(totalWords / itemsPerPage);
const lastPage = Math.floor(wordsInCurrentList / itemsPerPage);
const nextPage = currentPage + 1 > lastPage ? lastPage : currentPage + 1;
const prevPage = currentPage - 1 < 0 ? 0 : currentPage - 1;
@ -29,7 +30,7 @@ export const Pagination = (props) => {
}
return (
<nav className="pagination is-centered" role="navigation" aria-label="pagination">
<nav className={`pagination is-centered ${isTop ? 'is-top' : ''}`} role="navigation" aria-label="pagination">
<a className="pagination-previous" aria-label="Goto page 1"
Disabled={currentPage === 0 ? 'disabled' : null}
onClick={() => changePage(prevPage)}>

View File

@ -0,0 +1,3 @@
.pagination.is-top {
margin-bottom: 15px;
}

View File

@ -46,6 +46,8 @@ class App extends Component {
ignoreDiacritics: false,
filteredPartsOfSpeech: [...dictionary.partsOfSpeech, 'Uncategorized'],
},
isLoadingWords: true,
wordsInCurrentList: null,
}
this.updater = new Updater(this, dictionary);
@ -98,7 +100,7 @@ class App extends Component {
const partsOfSpeechForFilter = [...partsOfSpeech, 'Uncategorized'];
const pageStart = currentPage * itemsPerPage;
const pageEnd = pageStart + itemsPerPage;
let displayedWords;
let displayedWords = words;
if (this.isUsingFilter) {
const {
searchingIn,
@ -109,10 +111,7 @@ class App extends Component {
filteredPartsOfSpeech
} = searchConfig;
displayedWords = words.filter((word, index) => {
if (index < pageStart || index >= pageEnd) {
return false;
}
displayedWords = displayedWords.filter((word, index) => {
const wordPartOfSpeech = word.partOfSpeech === '' ? 'Uncategorized' : word.partOfSpeech;
if (!filteredPartsOfSpeech.includes(wordPartOfSpeech)) {
return false;
@ -168,30 +167,36 @@ class App extends Component {
}
}
});
} else {
displayedWords = words.filter((word, index) => {
if (index < pageStart || index >= pageEnd) {
return false;
}
return true;
});
}
const wordsInCurrentList = displayedWords.length;
displayedWords = displayedWords.filter((word, index) => {
if (index < pageStart || index >= pageEnd) {
return false;
}
return true;
});
this.setState({
displayedWords,
stats: getWordsStats(words, partsOfSpeech, this.state.settings.caseSensitive),
wordsInCurrentList,
isLoadingWords: false,
}, () => callback());
});
}
search (searchConfig) {
this.setState({
isLoadingWords: true,
searchConfig: searchConfig,
}, () => this.updateDisplayedWords());
}
setPage (newPage) {
this.setState({
isLoadingWords: true,
currentPage: newPage,
}, () => this.updateDisplayedWords());
}
@ -207,8 +212,10 @@ class App extends Component {
<MainDisplay
dictionaryInfo={ this.dictionaryInfo }
isLoadingWords={ this.state.isLoadingWords }
wordsToDisplay={ this.state.displayedWords }
wordsAreFiltered={ this.isUsingFilter }
wordsInCurrentList={ this.state.wordsInCurrentList }
currentPage={ this.state.currentPage }
itemsPerPage={ this.state.itemsPerPage }
stats={ this.state.stats }

View File

@ -5,6 +5,7 @@ $fa-font-path: "~font-awesome/fonts/";
@import 'bulma-overrides';
@import '../../node_modules/bulma/bulma';
@import '../../node_modules/bulma-checkradio/dist/bulma-checkradio';
@import '../../node_modules/sweetalert2/dist/sweetalert2';

View File

@ -1002,6 +1002,10 @@ builtin-status-codes@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz#85982878e21b98e1c66425e03d0174788f569ee8"
bulma-checkradio@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/bulma-checkradio/-/bulma-checkradio-1.0.1.tgz#21674a9aee65c48e4028594bd0f22e5b52ea5346"
bulma@^0.6.2:
version "0.6.2"
resolved "https://registry.yarnpkg.com/bulma/-/bulma-0.6.2.tgz#f4b1d11d5acc51a79644eb0a2b0b10649d3d71f5"