Add: Data managers; New SearchBox design

Also fixed logo and restructured components that don't need state.
This commit is contained in:
Robbie Antenesse 2017-04-19 15:11:37 -06:00
parent 8b6dbc0e8e
commit dc0d7eff07
14 changed files with 334 additions and 107 deletions

View File

@ -42,6 +42,7 @@
"inferno-component": "^3.0.5",
"inferno-devtools": "^3.0.5",
"marked": "^0.3.6",
"papaparse": "^4.2.0"
"papaparse": "^4.2.0",
"store": "^2.0.4"
}
}

3
public/logo.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 22 KiB

13
src/Helper.js Normal file
View File

@ -0,0 +1,13 @@
class Helper {
constructor () {
this.addHelpfulPrototypes();
}
addHelpfulPrototypes () {
String.prototype.capitalize = function() {
return this.charAt(0).toUpperCase() + this.slice(1);
}
}
}
export default new Helper;

View File

@ -1,45 +0,0 @@
import Inferno from 'inferno';
import Component from 'inferno-component';
import {LeftColumn} from './structure/LeftColumn';
import {RightColumn} from './structure/RightColumn';
import {WordForm} from './management/WordForm';
import {DictionaryDetails} from './display/DictionaryDetails';
export class Lexiconga extends Component {
constructor (props) {
super(props);
}
render () {
return (
<section className='section'>
<div className='container'>
<div className='columns'>
<LeftColumn>
<WordForm
partsOfSpeech={['Noun','Adjective','Verb']}
/>
</LeftColumn>
<RightColumn>
<DictionaryDetails
description='Test Description'
details={{
custom: [
{
name: 'Test Tab'
}
]
}}
/>
</RightColumn>
</div>
</div>
</section>
);
}
}

View File

@ -0,0 +1,40 @@
import Inferno from 'inferno';
import {LeftColumn} from './structure/LeftColumn';
import {RightColumn} from './structure/RightColumn';
import {WordForm} from './management/WordForm';
import {DictionaryDetails} from './display/DictionaryDetails';
export const MainDisplay = ({dictionaryInfo}) => {
return (
<section className='section'>
<div className='container'>
<div className='columns'>
<LeftColumn>
<WordForm
partsOfSpeech={dictionaryInfo.partsOfSpeech}
/>
</LeftColumn>
<RightColumn>
<DictionaryDetails
name={dictionaryInfo.name}
specification={dictionaryInfo.specification}
description={dictionaryInfo.description}
details={{
custom: [
{
name: 'Test Tab'
}
]
}}
/>
</RightColumn>
</div>
</div>
</section>
);
}

View File

@ -139,7 +139,7 @@ export class DictionaryDetails extends Component {
<div className='level-left'>
<div className='level-item'>
<h2 className='title is-2'>
Dictionary Name
{this.props.name} {this.props.specification}
</h2>
</div>
</div>

View File

@ -1,6 +1,10 @@
import Inferno from 'inferno';
import Component from 'inferno-component';
import Helper from '../../Helper';
import dictionary from '../../managers/DictionaryData';
export class SearchBox extends Component {
constructor (props) {
super(props);
@ -8,11 +12,23 @@ export class SearchBox extends Component {
this.state = {
searchingIn: 'name'
, searchTerm: ''
, filteredPartsOfSpeech: []
, showHeader: false
, showAdvanced: false
};
}
search () {
const {searchingIn, searchTerm, filteredPartsOfSpeech} = this.state;
const searchConfig = {
searchingIn
, searchTerm
, filteredPartsOfSpeech
};
this.props.search(searchConfig);
}
displaySearchHeader () {
if (this.state.showHeader) {
return (
@ -31,7 +47,6 @@ export class SearchBox extends Component {
<span className='select'>
<select value={this.state.searchingIn}
onChange={event => {
console.log(event);
this.setState({searchingIn: event.target.value});
}}>
<option value='name'>Word</option>
@ -70,16 +85,59 @@ export class SearchBox extends Component {
}
showFilterOptions () {
if (this.props.hasOwnProperty('partsOfSpeech')
&& this.props.partsOfSpeech.length > 0) {
let filterSectionJSX = (
if (dictionary.partsOfSpeech.length > 0) {
const searchMethodSectionJSX = this.state.searchingIn !== 'details'
? (
<div className='field is-horizontal'>
<div className='field-label is-normal'>
<label className='label'>Search Method</label>
</div>
<div className='field-body'>
<div className='field'>
<p className='control'>
<label className='radio'>
<input type='radio' name='searchmethod' checked={true} />
Contains
</label>
<label className='radio'>
<input type='radio' name='searchmethod' />
Starts With
</label>
<label className='radio'>
<input type='radio' name='searchmethod' />
Exact
</label>
</p>
</div>
<div className='field'>
<p className='control'>
<span className='help'>
<strong>Contains:</strong>&nbsp;
Search term is anywhere within the {this.state.searchingIn.capitalize()}
</span>
<span className='help'>
<strong>Starts With:</strong>&nbsp;
The {this.state.searchingIn.capitalize()} begins with the search term
</span>
<span className='help'>
<strong>Exact:</strong>&nbsp;
Search term matches the {this.state.searchingIn.capitalize()} exactly
</span>
</p>
</div>
</div>
</div>
)
: null;
const filterSectionJSX = (
<div className='field is-horizontal'>
<div className='field-label is-normal'>
<label className='label'>Filter</label>
</div>
<div className='field-body'>
<div className='field is-grouped'>
{this.props.partsOfSpeech.map((partOfSpeech) => {
{dictionary.partsOfSpeech.map((partOfSpeech) => {
return (
<p className='control'>
<label key={'filterPartOfSpeech' + Date.now()}
@ -95,9 +153,10 @@ export class SearchBox extends Component {
</div>
);
let advancedSectionJSX = (
const advancedSectionJSX = (
<div className='column'>
<div className='box'>
{searchMethodSectionJSX}
{filterSectionJSX}
</div>
</div>
@ -108,7 +167,7 @@ export class SearchBox extends Component {
<div class='column is-narrow'>
<div className='field'>
<p className='control'>
<a className='button is-link is-small'
<a className={`button is-link is-small${this.state.showAdvanced ? ' is-active' : ''}`}
onClick={() => this.setState({showAdvanced: !this.state.showAdvanced})}>
Advanced
</a>

View File

@ -1,50 +1,43 @@
import Inferno from 'inferno';
import Component from 'inferno-component';
export class Footer extends Component {
constructor (props) {
super(props);
}
render () {
return (
<footer className='footer'>
<div className='container'>
<div className='level'>
<div className='level-left'>
<div className='content'>
<p>
Lexiconga is only guaranteed to work with the most
up-to-date <a href='https://whatbrowser.org/' target='_blank'>HTML5 browsers</a>.
</p>
</div>
</div>
<div className='level-right'>
<span className='level-item'>
<a className='button'>
Issues
</a>
</span>
<span className='level-item'>
<a className='button'>
Updates
</a>
</span>
<span className='level-item'>
<a className='button'>
Terms
</a>
</span>
<span className='level-item'>
<a className='button'>
Privacy
</a>
</span>
export const Footer = () => {
return (
<footer className='footer'>
<div className='container'>
<div className='level'>
<div className='level-left'>
<div className='content'>
<p>
Lexiconga is only guaranteed to work with the most
up-to-date <a href='https://whatbrowser.org/' target='_blank'>HTML5 browsers</a>.
</p>
</div>
</div>
<div className='level-right'>
<span className='level-item'>
<a className='button'>
Issues
</a>
</span>
<span className='level-item'>
<a className='button'>
Updates
</a>
</span>
<span className='level-item'>
<a className='button'>
Terms
</a>
</span>
<span className='level-item'>
<a className='button'>
Privacy
</a>
</span>
</div>
</div>
</footer>
);
}
</div>
</footer>
);
}

View File

@ -6,6 +6,10 @@ import {SearchBox} from '../management/SearchBox';
export class Header extends Component {
constructor (props) {
super(props);
this.state = {
displayNavMenu: false
}
}
render () {
@ -13,24 +17,25 @@ export class Header extends Component {
<nav className='nav'>
<div className='nav-left'>
<a href='/' className='nav-item'>
<img src='images/logo.svg' alt='Lexiconga' />
<img src='./logo.svg' alt='Lexiconga' />
</a>
</div>
<div className='nav-center'>
<div className='nav-item'>
<SearchBox
partsOfSpeech={['Noun','Adjective','Verb']} />
search={searchConfig => this.props.search(searchConfig)} />
</div>
</div>
<span class='nav-toggle'>
<span className={`nav-toggle${this.state.displayNavMenu ? ' is-active' : ''}`}
onClick={() => this.setState({ displayNavMenu: !this.state.displayNavMenu })}>
<span></span>
<span></span>
<span></span>
</span>
<div className='nav-right nav-menu'>
<div className={`nav-right nav-menu${this.state.displayNavMenu ? ' is-active' : ''}`}>
<span className='nav-item'>
<a className='button'>
Login

View File

@ -3,25 +3,63 @@ import './sass/main.scss';
import Inferno from 'inferno';
import Component from 'inferno-component';
import dictionary from './managers/DictionaryData';
if (process.env.NODE_ENV !== 'production') {
require('inferno-devtools');
}
import {Header} from './components/structure/Header';
import {Lexiconga} from './components/Lexiconga';
import {MainDisplay} from './components/MainDisplay';
import {Footer} from './components/structure/Footer';
class App extends Component {
constructor (props) {
super(props);
this.state = {
name: dictionary.name
, specification: dictionary.specification
, description: dictionary.description
, partsOfSpeech: dictionary.partsOfSpeech
, searchConfig: null
}
}
get dictionaryInfo () {
const {name, specification, description, partsOfSpeech} = this.state;
const info = {
name
, specification
, description
, partsOfSpeech
};
return info;
}
get wordsToDisplay () {
// const {searchIn, searchTerm, filteredPartsOfSpeech} = this.state.searchConfig;
// TODO: Sort out searching to remove this temporary solution.
return dictionary.words;
}
search (searchConfig) {
this.setState({
searchConfig: searchConfig
});
}
render () {
return (
<div>
<Header />
<Header
search={searchConfig => this.search(searchConfig)} />
<Lexiconga />
<MainDisplay
dictionaryInfo={this.dictionaryInfo}
wordsToDisplay={this.wordsToDisplay} />
<Footer />
</div>

View File

@ -0,0 +1,99 @@
import assert from 'assert';
import store from 'store';
import wordDb from './WordDatabase';
const defaultDictionary = {
name: 'New'
, specification: 'Dictionary'
, description: 'A new dictionary.'
, partsOfSpeech: ['Noun', 'Adjective', 'Verb']
}
class DictionaryData {
constructor () {
if (['emptydb', 'donotsave'].includes(process.env.NODE_ENV)) {
store.remove('Lexiconga');
}
if (!store.get('Lexiconga')) {
store.set('Lexiconga', defaultDictionary);
}
}
get name () {
return store.get('Lexiconga').name
|| defaultDictionary.name;
}
set name (value) {
assert(typeof value === 'string', 'Name must be passed as a string.');
return store.set('Lexiconga', { name: value });
}
get specification () {
return store.get('Lexiconga').specification
|| defaultDictionary.specification;
}
set specification (value) {
assert(typeof value === 'string', 'Specification must be passed as a string.');
return store.set('Lexiconga', { specification: value });
}
get description () {
return store.get('Lexiconga').description
|| defaultDictionary.description;
}
set description (value) {
assert(typeof value === 'string', 'Description must be passed as a string.');
return store.set('Lexiconga', { description: value });
}
get partsOfSpeech () {
return store.get('Lexiconga').partsOfSpeech
|| defaultDictionary.partsOfSpeech;
}
set partsOfSpeech (array) {
assert(Array.isArray(array), 'Parts of Speech must be passed as an array');
return store.set('Lexiconga', { partsOfSpeech: array });
}
get words () {
return wordDb.words.toArray();
}
wordsWithPartOfSpeech (partOfSpeech) {
let words = wordDb.words.where('partOfSpeech');
if (Array.isArray(partOfSpeech)) {
words = words.anyOf(partOfSpeech);
} else {
assert(typeof partOfSpeech === 'string'
, 'You must use either a string or an array when searching for words with a particular part of speech');
words = words.equals(partOfSpeech);
}
return words.toArray();
}
wordsFromSearchConfig (searchConfig) {
/* TODO:
Configure search based on these qualifications:
- Starts With
- Equals
- Contains
With these specifications:
- Match Case
- Ignore Diacritics
Contains will need to be searched through Array.filter() because Dexie can only search indexes.
As such, searches on `details` will only allow "contains".
*/
}
}
export default new DictionaryData;

View File

@ -0,0 +1,12 @@
import Dexie from 'dexie';
const db = new Dexie('Lexiconga');
db.version(1).stores({
words: '++id, name, partOfSpeech'
});
if (['emptydb', 'donotsave'].includes(process.env.NODE_ENV)) {
db.words.clear();
}
export default db;

View File

@ -1,5 +1,10 @@
// Set BUILDMODE to 'production' to reduce overhead.
const BUILDMODE = 'development';
// Set BUILDMODE to:
// 'production' to reduce overhead.
// 'donotsave' to clear the dictionary details and database on each load.
// 'emptydetails' to clear the dictionary details on each load.
// 'emptydb' to clear the database on each load.
// 'development' to not do anything special.
const BUILDMODE = 'donotsave';
const webpack = require('webpack');
const path = require('path');

View File

@ -2847,6 +2847,10 @@ stdout-stream@^1.4.0:
dependencies:
readable-stream "^2.0.1"
store@^2.0.4:
version "2.0.4"
resolved "https://registry.yarnpkg.com/store/-/store-2.0.4.tgz#6c6819602a5497166ade85db2442cc64ebcc5761"
stream-browserify@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/stream-browserify/-/stream-browserify-2.0.1.tgz#66266ee5f9bdb9940a4e4514cafb43bb71e5c9db"