Compare commits

..

11 Commits

Author SHA1 Message Date
6949aa7780 1.1.0 2018-09-17 18:52:58 -07:00
2b9e76e475 chore: remove mitt dependency 2018-09-17 18:50:05 -07:00
b4ce9557f3 chore: replace favorite emitter
simply moving code into the right component was all it took
2018-09-17 18:49:55 -07:00
3ab623fea1 chore: replace error emitter with vue.emit 2018-09-17 18:43:06 -07:00
1f2e307f05 chore: fix lint-staged hook
include vue files
2018-09-17 18:43:06 -07:00
b73957e7df chore: replace search emitter with vue.emit 2018-09-17 18:43:02 -07:00
2c60ec4afc fix: weird bug in StrainList
apparently using v-if doesn't work with the static compilation, and the page can't load correctly
2018-09-17 18:28:18 -07:00
8c45a533b0 chore: add data and lunr via plugins
this way lunr is only set up once, which speeds up the loading of the search page. also makes sure the data doesn't increadse the bundle sizes of the various pages that use it.
2018-09-17 18:09:42 -07:00
3154b1e014 chore: move filters into their own file 2018-09-17 18:08:23 -07:00
85efbd84d1 feat: show favorite strain cards 2018-09-13 20:41:29 -07:00
3b39345fa6 feat: basic routing and fav nav 2018-09-13 19:56:44 -07:00
18 changed files with 381 additions and 252 deletions

View File

@@ -1,5 +1,10 @@
### Changelog
#### [v1.1.0](https://git.w33ble.com/w33ble/strain-tools/compare/v1.0.2...v1.1.0) (18 September 2018)
- feat: basic routing and fav nav [`3b39345`](https://git.w33ble.com/w33ble/strain-tools/commit/3b39345fa66ac4748a400e9e80e032ec9ae9697b)
- feat: show favorite strain cards [`85efbd8`](https://git.w33ble.com/w33ble/strain-tools/commit/85efbd84d1704c55a3eefae7fa2d36e7fd7f3cc2)
- fix: weird bug in StrainList [`2c60ec4`](https://git.w33ble.com/w33ble/strain-tools/commit/2c60ec4afc16f81fd6b610443091d26d278d3f81)
#### [v1.0.2](https://git.w33ble.com/w33ble/strain-tools/compare/v1.0.1...v1.0.2) (13 September 2018)
#### [v1.0.1](https://git.w33ble.com/w33ble/strain-tools/compare/v1.0.0...v1.0.1) (13 September 2018)

View File

@@ -1,6 +1,6 @@
{
"name": "strain-tools",
"version": "1.0.2",
"version": "1.1.0",
"description": "strain tools",
"main": "index",
"module": "index.mjs",
@@ -33,10 +33,10 @@
"commitLimit": false
},
"lint-staged": {
"*.{js,mjs}": [
"*.{js,mjs,vue}": [
"eslint --fix"
],
"*.{js,mjs,json,css}": [
"*.{js,mjs,vue,json,css}": [
"prettier --write"
]
},

View File

@@ -1,6 +1,6 @@
{
"name": "scraper",
"version": "1.0.2",
"version": "1.1.0",
"private": true,
"description": "scrapes strain info, stores for later reference",
"main": "index",

View File

@@ -1,6 +1,6 @@
{
"name": "search-site",
"version": "1.0.2",
"version": "1.1.0",
"private": true,
"main": "index",
"module": "index.mjs",
@@ -18,7 +18,6 @@
"dependencies": {
"esm": "^3.0.82",
"lunr": "^2.3.3",
"mitt": "^1.1.3",
"vue": "^2.5.17",
"vue-router": "^3.0.1",
"vue-template-compiler": "^2.5.17"

View File

@@ -4,5 +4,9 @@ const path = require('path');
module.exports = {
entry: path.resolve(__dirname, 'src/index.mjs'),
outDir: 'dist',
plugins: [require('@poi/plugin-vue-static')()],
plugins: [
require('@poi/plugin-vue-static')({
routes: ['/', '/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,10 +1,5 @@
<template>
<form @submit.prevent="handleSubmit">
<div class="container">
<h1 class="title">
Strain Search
</h1>
<!-- Name Search Input -->
<div class="field">
<label class="label">Search By Name</label>
@@ -77,13 +72,10 @@
<button type="button" class="button is-text" @click.prevent="resetForm">Clear</button>
</div>
</div>
</div>
</form>
</template>
<script>
import emitter from '../lib/emitter.mjs';
const getMultiValues = node => Array.from(node.selectedOptions).map(o => o.value);
export default {
@@ -92,17 +84,10 @@ export default {
uses: Array,
conditions: Array,
flavors: Array,
error: {
type: String,
default: '',
},
data() {
return {
error: '',
};
},
created() {
emitter.on('error', this.setError);
},
beforeDestroy() {
emitter.off('error', this.setError);
},
methods: {
handleSubmit() {
@@ -114,15 +99,11 @@ export default {
flavors: getMultiValues(this.$refs.flavors),
};
this.error = '';
emitter.emit('search', requirements);
this.$emit('search', requirements);
},
resetForm() {
this.$el.reset();
},
setError(msg) {
this.error = msg;
},
},
};
</script>

View File

@@ -31,7 +31,6 @@
<script>
import store from '../lib/store.mjs';
import emitter from '../lib/emitter.mjs';
import TagList from './TagList.vue';
export default {
@@ -50,8 +49,22 @@ export default {
},
methods: {
toggleFavorite() {
this.favorite = !this.favorite;
emitter.emit('favorite', { id: this.strain.id, isFav: this.favorite });
const isFav = !this.favorite;
const { id } = this.strain;
const favs = store.get('favorites') || [];
const idx = favs.indexOf(id);
// update component state
this.favorite = isFav;
// update the store
if (idx >= 0 && !isFav) {
// remove previously favorited strain
store.set('favorites', favs.filter(f => f !== id));
} else if (idx === -1 && isFav) {
// add new favorited strain
store.set('favorites', favs.concat(id));
}
},
},
};

View File

@@ -1,8 +1,8 @@
<template>
<div class="container">
<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">Found {{strains.length}} Strains</h3>
<div>
<h3 v-show="strains.length === 0" class="title is-3">No Matching Strains :(</h3>
<h3 v-show="strains.length > 0" class="title is-3">Found {{strains.length}} Strains</h3>
</div>
<div class="cards">

View File

@@ -0,0 +1,12 @@
import Vue from 'vue';
Vue.filter('capitalize', value => {
if (!value) return '';
const v = value.toString();
return v.charAt(0).toUpperCase() + v.slice(1);
});
Vue.filter('round', (value, digits = 2) => {
const v = parseFloat(value, 10);
return Math.round(v * (10 * digits)) / (10 * digits);
});

View File

@@ -1,26 +1,44 @@
import Vue from 'vue';
import Router from 'vue-router';
import './filters.mjs';
import strainData from './plugins/strainData.mjs';
import lunr from './plugins/lunr.mjs';
Vue.use(strainData);
Vue.use(
lunr(function lunrSetup() {
// lunr search index setup
this.ref('id');
this.field('name');
this.field('effects');
this.field('uses');
this.field('conditions');
this.field('flavors');
Vue.prototype.$strainData.strains.forEach(doc => this.add(doc));
})
);
Vue.use(Router);
Vue.filter('capitalize', value => {
if (!value) return '';
const v = value.toString();
return v.charAt(0).toUpperCase() + v.slice(1);
});
Vue.filter('round', (value, digits = 2) => {
const v = parseFloat(value, 10);
return Math.round(v * (10 * digits)) / (10 * digits);
});
const router = new Router({
mode: 'history',
routes: [
{
path: '/',
name: 'home',
component: () => import(/* webpackChunkName: "homeapp" */ './pages/Home.vue'),
component: () => import(/* webpackChunkName: "Home" */ './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

@@ -1,5 +0,0 @@
import mitt from 'mitt';
const emitter = mitt();
export default emitter;

View File

@@ -0,0 +1,43 @@
<template>
<div id="favorites-page">
<div class="container">
<h1 class="title">
Favorites
</h1>
<h3 class="title is-5">
Strains you have saved
</h3>
</div>
<section class="section">
<div class="container">
<StrainList :strains="favoriteStrains" no-save-control />
</div>
</section>
</div>
</template>
<script>
import store from '../lib/store.mjs';
import StrainList from '../components/StrainList.vue';
export default {
name: 'FavoritesPage',
components: {
StrainList,
},
data() {
const favorites = store.get('favorites') || [];
return {
favorites,
};
},
computed: {
favoriteStrains() {
return this.$strainData.strains.filter(strain => this.favorites.indexOf(strain.id) !== -1);
},
},
};
</script>

View File

@@ -1,27 +1,31 @@
<template>
<div>
<section class="section">
<SearchForm :effects="effects" :uses="uses" :conditions="conditions" :flavors="flavors" />
</section>
<section class="section">
<StrainList :strains="matches" />
</section>
<div id="home-page">
<div id="app-nav" class="tabs is-centered">
<ul>
<li :class="{'is-active': activeTab === 'search'}" @click="setActive('search')">
<router-link :to="{name: 'search'}">Search</router-link>
</li>
<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>
</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: 'Home',
components: {
SearchForm,
StrainList,
name: 'HomePage',
data() {
return {
activeTab: this.$route.name,
};
},
methods: {
setActive(name) {
this.activeTab = name;
},
},
head: {
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>

View File

@@ -0,0 +1,140 @@
<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"
:error="error"
@search="onSearch"
/>
</div>
</section>
<section class="section">
<div class="container">
<StrainList :strains="matches" />
</div>
</section>
</div>
</template>
<script>
import SearchForm from '../components/SearchForm.vue';
import StrainList from '../components/StrainList.vue';
export default {
name: 'SearchPage',
components: {
SearchForm,
StrainList,
},
data() {
return {
error: '',
matches: [],
requirements: {
name: '',
effects: [],
uses: [],
conditions: [],
flavors: [],
},
};
},
computed: {
strains() {
return this.$strainData.strains;
},
effects() {
return this.$strainData.effects;
},
uses() {
return this.$strainData.uses;
},
conditions() {
return this.$strainData.conditions;
},
flavors() {
return this.$strainData.flavors;
},
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: {
onSearch(reqs) {
this.requirements = reqs;
this.updateMatches();
},
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) {
this.error = '';
if (!this.hasName && !this.hasFilters) {
this.showDefaults(limit);
return;
}
try {
const hits = this.$lunr.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 = [];
this.error = err.message;
}
},
},
created() {
this.updateMatches(); // set initial match list
},
};
</script>

View File

@@ -0,0 +1,10 @@
/* eslint no-param-reassign: 0 */
import lunr from 'lunr';
export default function(lunrSetup) {
return {
install(Vue) {
Vue.prototype.$lunr = lunr(lunrSetup);
},
};
}

View File

@@ -0,0 +1,8 @@
/* eslint no-param-reassign: 0 */
import data from '../../../scraper/db.json';
export default {
install(Vue) {
Vue.prototype.$strainData = data;
},
};

View File

@@ -5692,10 +5692,6 @@ mississippi@^2.0.0:
stream-each "^1.1.0"
through2 "^2.0.0"
mitt@^1.1.3:
version "1.1.3"
resolved "https://registry.yarnpkg.com/mitt/-/mitt-1.1.3.tgz#528c506238a05dce11cd914a741ea2cc332da9b8"
mixin-deep@^1.2.0:
version "1.3.1"
resolved "https://registry.yarnpkg.com/mixin-deep/-/mixin-deep-1.3.1.tgz#a49e7268dce1a0d9698e45326c5626df3543d0fe"