feat: basic routing and fav nav

This commit is contained in:
2018-09-13 19:56:44 -07:00
parent 1c2df7733d
commit 3b39345fa6
8 changed files with 283 additions and 203 deletions

View File

@@ -4,5 +4,9 @@ const path = require('path');
module.exports = { module.exports = {
entry: path.resolve(__dirname, 'src/index.mjs'), entry: path.resolve(__dirname, 'src/index.mjs'),
outDir: 'dist', outDir: 'dist',
plugins: [require('@poi/plugin-vue-static')()], plugins: [
require('@poi/plugin-vue-static')({
routes: ['/', '/home', '/favorites'],
}),
],
}; };

View File

@@ -0,0 +1,14 @@
<template>
<div id="app-nav" class="tabs">
<ul>
<li class="is-active"><a>Search</a></li>
<li><a>Favorites</a></li>
</ul>
</div>
</template>
<script>
export default {
name: 'AppNav',
}
</script>

View File

@@ -1,84 +1,78 @@
<template> <template>
<form @submit.prevent="handleSubmit"> <form @submit.prevent="handleSubmit">
<div class="container"> <!-- Name Search Input -->
<h1 class="title"> <div class="field">
Strain Search <label class="label">Search By Name</label>
</h1> <div class="control">
<input ref="name" class="input" type="text" placeholder="Text input">
<p v-if="error.length" class="help is-danger">{{error}}</p>
</div>
</div>
<!-- Name Search Input --> <!-- Multi-Selects -->
<div class="field"> <div class="columns filters">
<label class="label">Search By Name</label> <div class="column is-half">
<div class="control"> <div class="columns is-mobile">
<input ref="name" class="input" type="text" placeholder="Text input">
<p v-if="error.length" class="help is-danger">{{error}}</p>
</div>
</div>
<!-- Multi-Selects -->
<div class="columns filters">
<div class="column is-half"> <div class="column is-half">
<div class="columns is-mobile"> <div class="field">
<div class="column is-half"> <label class="label">Desired Effects</label>
<div class="field"> <div class="select is-multiple">
<label class="label">Desired Effects</label> <select ref="effects" multiple size="6">
<div class="select is-multiple"> <option v-for="effect in effects" :key="effect" :value="effect">{{effect | capitalize}}</option>
<select ref="effects" multiple size="6"> </select>
<option v-for="effect in effects" :key="effect" :value="effect">{{effect | capitalize}}</option>
</select>
</div>
</div>
</div>
<div class="column is-half">
<div class="field">
<label class="label">Medical Use</label>
<div class="select is-multiple">
<select ref="uses" multiple size="6">
<option v-for="use in uses" :key="use" :value="use">{{use | capitalize}}</option>
</select>
</div>
</div>
</div> </div>
</div> </div>
</div> </div>
<div class="column is-half"> <div class="column is-half">
<div class="columns is-mobile"> <div class="field">
<div class="column is-half"> <label class="label">Medical Use</label>
<div class="field"> <div class="select is-multiple">
<label class="label">Condition</label> <select ref="uses" multiple size="6">
<div class="select is-multiple"> <option v-for="use in uses" :key="use" :value="use">{{use | capitalize}}</option>
<select ref="conditions" multiple size="6"> </select>
<option v-for="condition in conditions" :key="condition" :value="condition">{{condition | capitalize}}</option>
</select>
</div>
</div>
</div>
<div class="column is-half">
<div class="field">
<label class="label">Flavor</label>
<div class="select is-multiple">
<select ref="flavors" multiple size="6">
<option v-for="flavor in flavors" :key="flavor" :value="flavor">{{flavor | capitalize}}</option>
</select>
</div>
</div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<!-- Form Submits -->
<div class="field is-grouped">
<div class="control">
<button type="submit" class="button is-link">Submit</button>
</div>
<div class="control">
<button type="button" class="button is-text" @click.prevent="resetForm">Clear</button>
</div>
</div>
</div> </div>
</form>
<div class="column is-half">
<div class="columns is-mobile">
<div class="column is-half">
<div class="field">
<label class="label">Condition</label>
<div class="select is-multiple">
<select ref="conditions" multiple size="6">
<option v-for="condition in conditions" :key="condition" :value="condition">{{condition | capitalize}}</option>
</select>
</div>
</div>
</div>
<div class="column is-half">
<div class="field">
<label class="label">Flavor</label>
<div class="select is-multiple">
<select ref="flavors" multiple size="6">
<option v-for="flavor in flavors" :key="flavor" :value="flavor">{{flavor | capitalize}}</option>
</select>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Form Submits -->
<div class="field is-grouped">
<div class="control">
<button type="submit" class="button is-link">Submit</button>
</div>
<div class="control">
<button type="button" class="button is-text" @click.prevent="resetForm">Clear</button>
</div>
</div>
</form>
</template> </template>
<script> <script>

View File

@@ -1,5 +1,5 @@
<template> <template>
<div class="container"> <div>
<div> <div>
<h3 v-if="strains.length === 0" class="title is-3">No Matching Strains :(</h3> <h3 v-if="strains.length === 0" class="title is-3">No Matching Strains :(</h3>
<h3 v-if="strains.length > 0" class="title is-3">Found {{strains.length}} Strains</h3> <h3 v-if="strains.length > 0" class="title is-3">Found {{strains.length}} Strains</h3>

View File

@@ -19,8 +19,19 @@ const router = new Router({
routes: [ routes: [
{ {
path: '/', path: '/',
name: 'home', component: () => import(/* webpackChunkName: "Home" */ './pages/Home.vue'),
component: () => import(/* webpackChunkName: "homeapp" */ './pages/Home.vue'), children: [
{
path: '',
name: 'search',
component: () => import(/* webpackChunkName: "Search" */ './pages/Search.vue'),
},
{
path: 'favorites',
name: 'favorites',
component: () => import(/* webpackChunkName: "Favorites" */ './pages/Favorites.vue'),
},
],
}, },
], ],
}); });

View File

@@ -0,0 +1,15 @@
<template>
<div id="favorites-page">
<div class="container">
<h1 class="title">
Favorites
</h1>
</div>
</div>
</template>
<script>
export default {
name: 'FavoritesPage',
}
</script>

View File

@@ -1,27 +1,31 @@
<template> <template>
<div> <div id="home-page">
<section class="section"> <div id="app-nav" class="tabs is-centered">
<SearchForm :effects="effects" :uses="uses" :conditions="conditions" :flavors="flavors" /> <ul>
</section> <li :class="{'is-active': activeTab === 'search'}" @click="setActive('search')">
<section class="section"> <router-link :to="{name: 'search'}">Search</router-link>
<StrainList :strains="matches" /> </li>
</section> <li :class="{'is-active': activeTab === 'favorites'}" @click="setActive('favorites')">
<router-link :to="{name: 'favorites'}">Favorites</router-link>
</li>
</ul>
</div>
<router-view></router-view>
</div> </div>
</template> </template>
<script> <script>
import lunr from 'lunr';
import SearchForm from '../components/SearchForm.vue';
import StrainList from '../components/StrainList.vue';
import data from '../../../scraper/db.json';
import emitter from '../lib/emitter.mjs';
import store from '../lib/store.mjs';
export default { export default {
name: 'Home', name: 'HomePage',
components: { data() {
SearchForm, return {
StrainList, activeTab: 'search',
};
},
methods: {
setActive(name) {
this.activeTab = name;
},
}, },
head: { head: {
title: 'Strain Search', title: 'Strain Search',
@@ -32,118 +36,5 @@ export default {
}, },
], ],
}, },
data() {
return {
...data,
matches: [],
requirements: {
name: '',
effects: [],
uses: [],
conditions: [],
flavors: [],
},
};
},
computed: {
hasName() {
return this.requirements.name.length > 0;
},
hasFilters() {
return (
this.requirements.effects.length > 0 ||
this.requirements.uses.length > 0 ||
this.requirements.conditions.length > 0 ||
this.requirements.flavors.length > 0
);
},
searchParams() {
const searchParts = [this.requirements.name];
this.requirements.effects.forEach(t => searchParts.push(`+effects:${t}`));
this.requirements.uses.forEach(t => searchParts.push(`+uses:${t}`));
this.requirements.conditions.forEach(t => searchParts.push(`+conditions:${t}`));
this.requirements.flavors.forEach(t => searchParts.push(`+flavors:${t}`));
return searchParts.join(' ');
},
},
methods: {
showDefaults(limit = 40) {
// const favs = store.get('favorites') || [];
// const favStrains = this.strains.filter(({ id }) => favs.indexOf(id) !== -1);
// this.matches = favStrains.concat(this.strains.slice(0, limit - favStrains.length));
this.matches = this.strains.slice(0, limit);
},
updateMatches(limit = 40) {
if (!this.hasName && !this.hasFilters) {
this.showDefaults(limit);
return;
}
try {
const hits = this.idx.search(this.searchParams);
// .slice(0, limit);
const refs = hits.map(({ ref }) => parseInt(ref, 10));
this.matches = this.strains
.map(strain => {
const idx = refs.indexOf(strain.id);
if (idx < 0) return null;
return Object.assign({ score: hits[idx].score }, strain);
})
.filter(Boolean)
.sort((a, b) => {
if (a.score === b.score) {
// sort matching search score by adjusted rating
if (a.rating_adjusted === b.rating_adjusted) return 0;
return a.rating_adjusted > b.rating_adjusted ? -1 : 1;
}
return a.score > b.score ? -1 : 1;
});
} catch (err) {
this.matches = [];
emitter.emit('error', err.message);
}
},
},
created() {
// lunr setup
this.idx = lunr(function lunrSetup() {
this.ref('id');
this.field('name');
this.field('effects');
this.field('uses');
this.field('conditions');
this.field('flavors');
data.strains.forEach(doc => this.add(doc));
});
// function to handle search form submissions
this.searchListener = reqs => {
this.requirements = reqs;
this.updateMatches();
};
// listen for search form submissions
emitter.on('search', r => this.searchListener(r));
// listen for favorite changes
emitter.on('favorite', ({ id, isFav }) => {
const favs = store.get('favorites') || [];
const idx = favs.indexOf(id);
// remove previously favorited strain
if (idx >= 0 && !isFav) {
store.set('favorites', favs.filter(f => f !== id));
} else if (idx === -1 && isFav) {
store.set('favorites', favs.concat(id));
}
});
this.updateMatches(); // set initial match list
},
beforeDestroy() {
emitter.off('search', r => this.searchListener(r));
},
}; };
</script> </script>

View File

@@ -0,0 +1,151 @@
<template>
<div id="search-page">
<div class="container">
<h1 class="title">
Strain Search
</h1>
</div>
<section class="section">
<div class="container">
<SearchForm :effects="effects" :uses="uses" :conditions="conditions" :flavors="flavors" />
</div>
</section>
<section class="section">
<div class="container">
<StrainList :strains="matches" />
</div>
</section>
</div>
</template>
<script>
import lunr from 'lunr';
import SearchForm from '../components/SearchForm.vue';
import StrainList from '../components/StrainList.vue';
import data from '../../../scraper/db.json';
import emitter from '../lib/emitter.mjs';
import store from '../lib/store.mjs';
export default {
name: 'SearchPage',
components: {
SearchForm,
StrainList,
},
data() {
return {
...data,
matches: [],
requirements: {
name: '',
effects: [],
uses: [],
conditions: [],
flavors: [],
},
};
},
computed: {
hasName() {
return this.requirements.name.length > 0;
},
hasFilters() {
return (
this.requirements.effects.length > 0 ||
this.requirements.uses.length > 0 ||
this.requirements.conditions.length > 0 ||
this.requirements.flavors.length > 0
);
},
searchParams() {
const searchParts = [this.requirements.name];
this.requirements.effects.forEach(t => searchParts.push(`+effects:${t}`));
this.requirements.uses.forEach(t => searchParts.push(`+uses:${t}`));
this.requirements.conditions.forEach(t => searchParts.push(`+conditions:${t}`));
this.requirements.flavors.forEach(t => searchParts.push(`+flavors:${t}`));
return searchParts.join(' ');
},
},
methods: {
showDefaults(limit = 40) {
// const favs = store.get('favorites') || [];
// const favStrains = this.strains.filter(({ id }) => favs.indexOf(id) !== -1);
// this.matches = favStrains.concat(this.strains.slice(0, limit - favStrains.length));
this.matches = this.strains.slice(0, limit);
},
updateMatches(limit = 40) {
if (!this.hasName && !this.hasFilters) {
this.showDefaults(limit);
return;
}
try {
const hits = this.idx.search(this.searchParams);
// .slice(0, limit);
const refs = hits.map(({ ref }) => parseInt(ref, 10));
this.matches = this.strains
.map(strain => {
const idx = refs.indexOf(strain.id);
if (idx < 0) return null;
return Object.assign({ score: hits[idx].score }, strain);
})
.filter(Boolean)
.sort((a, b) => {
if (a.score === b.score) {
// sort matching search score by adjusted rating
if (a.rating_adjusted === b.rating_adjusted) return 0;
return a.rating_adjusted > b.rating_adjusted ? -1 : 1;
}
return a.score > b.score ? -1 : 1;
});
} catch (err) {
this.matches = [];
emitter.emit('error', err.message);
}
},
},
created() {
// lunr setup
this.idx = lunr(function lunrSetup() {
this.ref('id');
this.field('name');
this.field('effects');
this.field('uses');
this.field('conditions');
this.field('flavors');
data.strains.forEach(doc => this.add(doc));
});
// function to handle search form submissions
this.searchListener = reqs => {
this.requirements = reqs;
this.updateMatches();
};
// listen for search form submissions
emitter.on('search', r => this.searchListener(r));
// listen for favorite changes
emitter.on('favorite', ({ id, isFav }) => {
const favs = store.get('favorites') || [];
const idx = favs.indexOf(id);
// remove previously favorited strain
if (idx >= 0 && !isFav) {
store.set('favorites', favs.filter(f => f !== id));
} else if (idx === -1 && isFav) {
store.set('favorites', favs.concat(id));
}
});
this.updateMatches(); // set initial match list
},
beforeDestroy() {
emitter.off('search', r => this.searchListener(r));
},
};
</script>