Merge branch 'dev'
This commit is contained in:
commit
ee710988d5
|
@ -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",
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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'>
|
||||
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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>
|
||||
),
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -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)}>
|
|
@ -0,0 +1,3 @@
|
|||
.pagination.is-top {
|
||||
margin-bottom: 15px;
|
||||
}
|
|
@ -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 }
|
||||
|
|
|
@ -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';
|
||||
|
||||
|
|
|
@ -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"
|
||||
|
|
Loading…
Reference in New Issue