Add: Data managers; New SearchBox design
Also fixed logo and restructured components that don't need state.
This commit is contained in:
parent
8b6dbc0e8e
commit
dc0d7eff07
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 22 KiB |
|
@ -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;
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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>
|
||||
);
|
||||
}
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
Search term is anywhere within the {this.state.searchingIn.capitalize()}
|
||||
</span>
|
||||
<span className='help'>
|
||||
<strong>Starts With:</strong>
|
||||
The {this.state.searchingIn.capitalize()} begins with the search term
|
||||
</span>
|
||||
<span className='help'>
|
||||
<strong>Exact:</strong>
|
||||
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>
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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;
|
|
@ -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;
|
|
@ -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');
|
||||
|
|
|
@ -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"
|
||||
|
|
Loading…
Reference in New Issue