feature (search): basic search

This commit is contained in:
Mickael Kerjean 2018-08-03 00:39:07 +10:00
parent bbb9572012
commit 8561193f8b
16 changed files with 513 additions and 129 deletions

View file

@ -0,0 +1,100 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
version="1.1"
id="Capa_1"
x="0px"
y="0px"
viewBox="0 0 51.976 51.976"
style="enable-background:new 0 0 51.976 51.976;"
xml:space="preserve"
width="512px"
height="512px"
sodipodi:docname="close_dark.svg"
inkscape:version="0.92.2 2405546, 2018-03-11"><metadata
id="metadata1544"><rdf:RDF><cc:Work
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
id="defs1542" /><sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1894"
inkscape:window-height="1027"
id="namedview1540"
showgrid="false"
inkscape:zoom="0.921875"
inkscape:cx="153.6191"
inkscape:cy="213.26215"
inkscape:window-x="10"
inkscape:window-y="37"
inkscape:window-maximized="0"
inkscape:current-layer="g1507" />
<g
id="g1507">
<path
d="m 45.930983,45.603013 c -1.506621,1.506622 -3.948855,1.506622 -5.455477,0 L 26.151986,31.279492 11.147494,46.283983 c -1.5066223,1.506623 -3.948855,1.506623 -5.4554772,0 -1.5066222,-1.506621 -1.5066222,-3.948855 0,-5.455477 L 20.696508,25.824014 6.3729868,11.500494 c -1.5066221,-1.5066223 -1.5066221,-3.9507841 0,-5.4554772 1.5066223,-1.5066222 3.9488552,-1.5066222 5.4554762,0 L 26.151986,20.368538 39.792606,6.727916 c 1.506623,-1.5066223 3.948856,-1.5066223 5.455478,0 1.506623,1.5066222 1.506623,3.948855 0,5.455478 l -13.640622,13.64062 14.323521,14.323523 c 1.506623,1.506621 1.506623,3.948853 0,5.455476 z"
id="path1505"
inkscape:connector-curvature="0"
style="fill:#626469;fill-opacity:1;stroke-width:1.92909384"
sodipodi:nodetypes="sscssscscscssscss" />
</g>
<g
id="g1509">
</g>
<g
id="g1511">
</g>
<g
id="g1513">
</g>
<g
id="g1515">
</g>
<g
id="g1517">
</g>
<g
id="g1519">
</g>
<g
id="g1521">
</g>
<g
id="g1523">
</g>
<g
id="g1525">
</g>
<g
id="g1527">
</g>
<g
id="g1529">
</g>
<g
id="g1531">
</g>
<g
id="g1533">
</g>
<g
id="g1535">
</g>
<g
id="g1537">
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.7 KiB

View file

@ -25,6 +25,7 @@ import img_arrow_right_white from '../assets/img/arrow_right_white.svg';
import img_arrow_left_white from '../assets/img/arrow_left_white.svg';
import img_more from '../assets/img/more.svg';
import img_close from '../assets/img/close.svg';
import img_close_dark from '../assets/img/close_dark.svg';
import img_schedule from '../assets/img/schedule.svg';
import img_deadline from '../assets/img/deadline.svg';
import img_arrow_down from '../assets/img/arrow-down.svg';
@ -92,6 +93,8 @@ export const Icon = (props) => {
img = img_more;
}else if(props.name === 'close'){
img = img_close;
}else if(props.name === 'close_dark'){
img = img_close_dark;
}else if(props.name === 'arrow_up_double'){
img = img_arrow_up_double;
}else if(props.name === 'arrow_down_double'){

View file

@ -8,7 +8,7 @@ function Data(){
}
Data.prototype._init = function(){
const request = window.indexedDB.open('nuage', 1);
const request = indexedDB.open('nuage', 2);
request.onupgradeneeded = (e) => this._setup(e.target.result);
this.db = new Promise((done, err) => {
@ -23,13 +23,14 @@ Data.prototype._init = function(){
Data.prototype._setup = function(db){
let store;
if(!db.objectStoreNames.contains(this.FILE_PATH)){
store = db.createObjectStore(this.FILE_PATH, {keyPath: "path"});
}
db.deleteObjectStore(this.FILE_PATH);
db.deleteObjectStore(this.FILE_CONTENT);
if(!db.objectStoreNames.contains(this.FILE_CONTENT)){
store = db.createObjectStore(this.FILE_CONTENT, {keyPath: "path"});
}
store = db.createObjectStore(this.FILE_PATH, {keyPath: "path"});
store.createIndex("idx_path", "path", { unique: true });
store = db.createObjectStore(this.FILE_CONTENT, {keyPath: "path"});
store.createIndex("idx_path", "path", { unique: true });
}
/*
@ -142,30 +143,47 @@ Data.prototype.remove = function(type, path, exact = true){
}).catch(() => Promise.resolve(null))
}
Data.prototype.fetchAll = function(fn, type = this.FILE_PATH){
Data.prototype.fetchAll = function(fn, type = this.FILE_PATH, key = "/"){
return this.db.then((db) => {
const tx = db.transaction(type, "readwrite");
const tx = db.transaction([type], "readonly");
const store = tx.objectStore(type);
const request = store.openCursor();
const index = store.index("idx_path");
const request = index.openCursor(IDBKeyRange.lowerBound(key));
return new Promise((done, error) => {
request.onsuccess = function(event) {
const cursor = event.target.result;
if(!cursor) return done();
const new_value = fn(cursor.value);
cursor.continue();
const ret = fn(cursor.value);
if(ret !== false){
cursor.continue();
return
}
db.close();
};
request.onerror = () => {
db.close();
done();
}
});
}).catch(() => Promise.resolve(null))
}
Data.prototype.destroy = function(){
this.db
.then((db) => db.close())
.catch(() => {})
clearTimeout(this.intervalId);
window.indexedDB.deleteDatabase('nuage');
this._init();
return new Promise((done, err) => {
this.db.then((db) => {
purgeAll(db, this.FILE_PATH);
purgeAll(db, this.FILE_CONTENT);
});
done();
function purgeAll(db, type){
const tx = db.transaction(type, "readwrite");
const store = tx.objectStore(type);
store.clear();
}
});
}

View file

@ -1,6 +1,8 @@
import { Files } from '../model/';
import { notify } from '../helpers/';
import Path from 'path';
import Worker from "../worker/search.worker.js";
import { Observable } from "rxjs/Observable";
export const sort = function(files, type){
if(type === 'name'){
@ -270,3 +272,20 @@ export const onUpload = function(path, e){
);
}
};
const worker = new Worker();9
export const onSearch = (keyword, path = "/") => {
worker.postMessage({
action: "search::find",
path: path,
keyword: keyword
});
return new Observable((obs) => {
worker.onmessage = (m) => {
obs.next(m.data);
}
});
};

View file

@ -5,10 +5,10 @@ import HTML5Backend from 'react-dnd-html5-backend-filedrop';
import './filespage.scss';
import './error.scss';
import { Files } from '../model/';
import { sort, onCreate, onRename, onDelete, onUpload } from './filespage.helper';
import { sort, onCreate, onRename, onDelete, onUpload, onSearch } from './filespage.helper';
import { NgIf, Loader, Uploader, EventReceiver } from '../components/';
import { notify, debounce, goToFiles, goToViewer, event, settings_get, settings_put } from '../helpers/';
import { BreadCrumb, FileSystem, FrequentlyAccess } from './filespage/';
import { BreadCrumb, FileSystem, FrequentlyAccess, Submenu } from './filespage/';
import InfiniteScroll from 'react-infinite-scroller';
const PAGE_NUMBER_INIT = 3;
@ -26,6 +26,7 @@ export class FilesPage extends React.Component {
show_hidden: settings_get('filespage_show_hidden') || CONFIG["display_hidden"],
view: settings_get('filespage_view') || 'grid',
files: [],
search_loading: false,
metadata: null,
frequents: [],
page_number: PAGE_NUMBER_INIT,
@ -122,7 +123,9 @@ export class FilesPage extends React.Component {
});
this.observers.push(observer);
this.setState({error: null});
Files.frequents().then((s) => this.setState({frequents: s}));
if(path === "/"){
Files.frequents().then((s) => console.log(s) && this.setState({frequents: s}));
}
}
_cleanupListeners(){
@ -166,6 +169,32 @@ export class FilesPage extends React.Component {
});
}
onSearch(search){
if(search == null || search.length === 0){
this.onRefresh();
return;
}
if(search.length < 2){
return;
}
if(this._search){
this._search.unsubscribe();
}
this._search = onSearch(search, this.state.path).subscribe((message) => {
if(message.type === "search::found"){
this.setState({
files: message.files || [],
metadata: {
can_rename: false,
can_delete: false
}
});
}
});
}
loadMore(){
requestAnimationFrame(() => {
let page_number = this.state.page_number + 1;
@ -189,9 +218,12 @@ export class FilesPage extends React.Component {
<NgIf cond={this.state.path === '/'}>
<FrequentlyAccess files={this.state.frequents}/>
</NgIf>
<FileSystem path={this.state.path} sort={this.state.sort} view={this.state.view}
files={this.state.files.slice(0, this.state.page_number * LOAD_PER_SCROLL)}
metadata={this.state.metadata} onSort={this.onSort.bind(this)} onView={this.onView.bind(this)} />
<Submenu path={this.state.path} sort={this.state.sort} view={this.state.view} onSearch={this.onSearch.bind(this)} onViewUpdate={(value) => this.onView(value)} onSortUpdate={(value) => {this.onSort(value);}} accessRight={this.state.metadata || {}}></Submenu>
<NgIf cond={true}>
<FileSystem path={this.state.path} sort={this.state.sort} view={this.state.view}
files={this.state.files.slice(0, this.state.page_number * LOAD_PER_SCROLL)}
metadata={this.state.metadata} onSort={this.onSort.bind(this)} onView={this.onView.bind(this)} />
</NgIf>
<Uploader path={this.state.path} />
</NgIf>
</InfiniteScroll>

View file

@ -35,7 +35,7 @@ export class FileSystem extends React.Component {
{
this.props.files.map((file, index) => {
if(file.type === 'directory' || file.type === 'file' || file.type === 'link' || file.type === 'bucket'){
return ( <ExistingThing view={this.props.view} key={file.name+(file.icon || '')} file={file} path={this.props.path} metadata={metadata} /> );
return ( <ExistingThing view={this.props.view} key={file.name+file.path+(file.icon || '')} file={file} path={this.props.path} metadata={metadata} /> );
}
})
}

View file

@ -1,3 +1,4 @@
export { FileSystem } from './filesystem.js';
export { FileSystem } from './filesystem';
export { Submenu } from './submenu';
export { BreadCrumbTargettable as BreadCrumb } from './breadcrumb';
export { FrequentlyAccess } from './frequently_access';

View file

@ -0,0 +1,147 @@
import React from 'react';
import PropTypes from 'prop-types';
import Ripples from 'react-ripples';
import { Card, NgIf, Icon, EventEmitter, Dropdown, DropdownButton, DropdownList, DropdownItem, Container } from '../../components/';
import { pathBuilder, debounce } from '../../helpers/';
import "./submenu.scss";
@EventEmitter
export class Submenu extends React.Component {
constructor(props){
super(props);
this.state = {
search_enabled: "ServiceWorker" in window ? true : false,
search_input_visible: false,
search_keyword: ""
};
this.onSearchChange_Backpressure = debounce(this.onSearchChange, 400);
this._onEscapeKeyPress = (e) => {
if(e.keyCode === 27){ // escape key
this.setState({
search_keyword: "",
search_input_visible: false
});
this.refs.$input.blur();
this.props.onSearch(null);
}else if(e.ctrlKey && e.keyCode === 70){ // 'Ctrl F' shortcut to search
e.preventDefault();
this.setState({
search_input_visible: true
});
this.refs.$input.focus();
}else if(e.altKey && (e.keyCode === 49 || e.keyCode === 50)){ // 'alt 1' 'alt 2' shortcut
e.preventDefault();
this.onViewChange();
}
};
}
componentDidMount(){
window.addEventListener('keydown', this._onEscapeKeyPress);
}
componentWillUnmount(){
window.removeEventListener('keydown', this._onEscapeKeyPress);
}
onNew(type){
this.props.emit("new::"+type);
}
onViewChange(){
requestAnimationFrame(() => this.props.onViewUpdate());
}
onSortChange(e){
this.props.onSortUpdate(e);
}
onSearchChange(search, e){
this.props.onSearch(search.trim());
}
onSearchToggle(){
if(new Date () - this.search_last_toggle < 200){
// avoid bluring event cancelling out the toogle
return;
}
this.refs.$input.focus();
this.setState({search_input_visible: !this.state.search_input_visible}, () => {
if(this.state.search_input_visible == false){
this.props.onSearch(null);
this.setState({search_keyword: ""});
}
});
}
closeIfEmpty(){
if(this.state.search_keyword.trim().length > 0) return;
this.search_last_toggle = new Date();
this.setState({
search_input_visible: false,
search_keyword: ""
});
this.props.onSearch(null);
}
onSearchKeypress(s, backpressure = true, e){
if(backpressure){
this.onSearchChange_Backpressure(s);
}else{
this.onSearchChange(s);
}
this.setState({search_keyword: s});
if(e && e.preventDefault){
e.preventDefault();
}
}
render(){
return (
<div className="component_submenu">
<Container>
<div className={"menubar no-select "+(this.state.search_input_visible ? "search_focus" : "")}>
<NgIf cond={this.props.accessRight.can_create_file !== false} onClick={this.onNew.bind(this, 'file')} type="inline">
New File
</NgIf>
<NgIf cond={this.props.accessRight.can_create_directory !== false} onClick={this.onNew.bind(this, 'directory')} type="inline">
New Directory
</NgIf>
<Dropdown className="view sort" onChange={this.onSortChange.bind(this)}>
<DropdownButton>
<Icon name="sort"/>
</DropdownButton>
<DropdownList>
<DropdownItem name="type" icon={this.props.sort === "type" ? "check" : null}> Sort By Type </DropdownItem>
<DropdownItem name="date" icon={this.props.sort === "date" ? "check" : null}> Sort By Date </DropdownItem>
<DropdownItem name="name" icon={this.props.sort === "name" ? "check" : null}> Sort By Name </DropdownItem>
</DropdownList>
</Dropdown>
<div className="view list-grid" onClick={this.onViewChange.bind(this)}><Icon name={this.props.view === "grid" ? "list" : "grid"}/></div>
<form onSubmit={(e) => this.onSearchKeypress(this.state.search_keyword, false, e)} className="view" style={{display: this.state.search_enabled === true ? "block" : "none"}}>
<label className="view search" onClick={this.onSearchToggle.bind(this, null)}>
<NgIf cond={this.state.search_input_visible !== true}>
<Icon name="search_dark"/>
</NgIf>
<NgIf cond={this.state.search_input_visible === true}>
<Icon name="close_dark"/>
</NgIf>
</label>
<NgIf cond={this.state.search_input_visible !== null} type="inline">
<input ref="$input" onBlur={this.closeIfEmpty.bind(this, false)} style={{"width": this.state.search_input_visible ? "180px" : "0px"}} value={this.state.search_keyword} onChange={(e) => this.onSearchKeypress(e.target.value, true)} type="text" id="search" placeholder="search" name="search" autoComplete="off" />
</NgIf>
</form>
</div>
</Container>
</div>
);
};
}
Submenu.PropTypes = {
accessRight: PropTypes.obj,
onCreate: PropTypes.func.isRequired,
onSortUpdate: PropTypes.func.isRequired,
sort: PropTypes.string.isRequired
};

View file

@ -0,0 +1,77 @@
@import "../../assets/css/mixin.scss";
.component_submenu{
.component_container{
padding-left: 0;
padding-right: 0;
padding-bottom: 0;
margin-bottom: -10px;
.menubar{
font-size: 15px;
line-height: 15px;
height: 15px;
margin-top: 5px;
color: var(--light);
margin: 5px 0 10px 0;
/* Create Buttons */
> span, form label {
display: inline-block;
margin-right: 5px;
margin-top: -2px;
border-radius: 2px;
cursor: pointer;
&:active, &:hover{
color: var(--color);
}
@include ripple(var(--bg-color));
}
> span{padding: 3px 5px;}
> div {cursor: pointer; margin-right: 15px;}
.view{
float: right;
padding: 2px;
transition: 0.15s ease-out background;
margin-right: 0px;
margin-left: 0px;
& .search, &.list-grid, > .dropdown_button, form > label {
min-width: inherit;
border-radius: 2px;
padding: 5px;
margin: 2px 0 2px 0;
}
&.list-grid, & .dropdown_button, & .search{ @include ripple(var(--bg-color)); }
.dropdown_container .component_icon{
box-sizing: border-box;
border: 2px solid rgba(0,0,0,0);
}
&.list-grid, & .search{margin-top: -5px!important;}
&.component_dropdown { margin-top: -10px; }
}
&.search_focus form input{transition: 0.2s width ease-in;}
form{
float: right;
margin-top: -3px;
input{
font-size: 1em;
margin-top: -3px;
border: none;
background: inherit;
border-bottom: 2px solid var(--light);
color: var(--color);
}
}
&.search_focus > span {
display: none;
}
.component_icon{
height: 15px;
width: 15px;
}
}
}
}

View file

@ -119,10 +119,9 @@ export class ExistingThing extends React.Component {
updateThumbnail(props){
if(props.view === "grid" && props.icon !== "loading"){
const _path = path.join(props.path, props.file.name);
const type = getMimeType(_path).split("/")[0];
const type = getMimeType(props.file.path).split("/")[0];
if(type === "image"){
Files.url(_path).then((url) => {
Files.url(props.file.path).then((url) => {
this.setState({preview: url+"&thumbnail=true"});
});
}

View file

@ -1,11 +1,13 @@
import React from 'react';
import PropTypes from 'prop-types';
import Ripples from 'react-ripples';
import { Card, NgIf, Icon, EventEmitter, Dropdown, DropdownButton, DropdownList, DropdownItem } from '../../components/';
import { pathBuilder } from '../../helpers/';
import { Card, NgIf, Icon, EventEmitter, EventReceiver, Dropdown, DropdownButton, DropdownList, DropdownItem } from '../../components/';
import { pathBuilder, debounce } from '../../helpers/';
import "./thing.scss";
@EventEmitter
@EventReceiver
export class NewThing extends React.Component {
constructor(props){
super(props);
@ -14,19 +16,41 @@ export class NewThing extends React.Component {
type: null,
message: null,
icon: null,
search: "ServiceWorker" in window ? "" : null
search_enabled: "ServiceWorker" in window ? true : false,
search_input_visible: false,
search_keyword: ""
};
this._onEscapeKeyPress = (e) => {
if(e.keyCode === 27) this.onDelete();
};
this._onSearchEvent = debounce((state) => {
if(typeof state === "boolean"){
if(this.state.search_keyword.length != 0) return;
this.setState({search_input_visible: state});
return;
}
this.setState({search_input_visible: !this.state.search_input_visible});
}, 200);
this.onPropageSearch = debounce(() => {
this.props.onSearch(this.state.search_keyword);
}, 1000);
}
componentDidMount(){
window.addEventListener('keydown', this._onEscapeKeyPress);
this.props.subscribe('new::file', () => {
this.onNew("file");
});
this.props.subscribe('new::directory', () => {
this.onNew("directory");
});
}
componentWillUnmount(){
window.removeEventListener('keydown', this._onEscapeKeyPress);
this.props.unsubscribe('new::file');
this.props.unsubscribe('new::directory');
}
onNew(type){
@ -57,28 +81,14 @@ export class NewThing extends React.Component {
this.props.onSortUpdate(e);
}
onSearchChange(search){
this.setState({search_keyword: search});
console.log(search);
}
render(){
return (
<div>
<div className="menubar no-select">
<NgIf cond={this.props.accessRight.can_create_file !== false} onClick={this.onNew.bind(this, 'file')} type="inline">
New File
</NgIf>
<NgIf cond={this.props.accessRight.can_create_directory !== false} onClick={this.onNew.bind(this, 'directory')} type="inline">
New Directory
</NgIf>
<Dropdown className="view sort" onChange={this.onSortChange.bind(this)}>
<DropdownButton>
<Icon name="sort"/>
</DropdownButton>
<DropdownList>
<DropdownItem name="type" icon={this.props.sort === "type" ? "check" : null}> Sort By Type </DropdownItem>
<DropdownItem name="date" icon={this.props.sort === "date" ? "check" : null}> Sort By Date </DropdownItem>
<DropdownItem name="name" icon={this.props.sort === "name" ? "check" : null}> Sort By Name </DropdownItem>
</DropdownList>
</Dropdown>
<div className="view list-grid" onClick={this.onViewChange.bind(this)}><Icon name={this.props.view === "grid" ? "list" : "grid"}/></div>
</div>
<NgIf cond={this.state.type !== null} className="component_thing">
<Card className="mouse-is-hover highlight">
<Icon className="component_updater--icon" name={this.state.icon} />

View file

@ -1,79 +1,3 @@
@import "../../assets/css/mixin.scss";
// Menu for new file and directory
.menubar{
font-size: 15px;
line-height: 15px;
height: 15px;
margin-top: 5px;
color: var(--light);
margin: 5px 0 10px 0;
/* Create Buttons */
> span {
display: inline-block;
margin-right: 5px;
margin-top: -2px;
border-radius: 2px;
cursor: pointer;
padding: 3px 5px;
&:active, &:hover{
color: var(--color);
}
@include ripple(var(--bg-color));
}
> div {cursor: pointer; margin-right: 15px;}
.view{
float: right;
padding: 2px;
transition: 0.15s ease-out background;
margin-right: 0px;
margin-left: 0px;
&.list-grid, > .dropdown_button, > label > img{
min-width: inherit;
border-radius: 2px;
padding: 5px;
margin: 2px 0 2px 0;
}
&.list-grid, & .dropdown_button{ @include ripple(var(--bg-color)); }
.dropdown_container .component_icon{
box-sizing: border-box;
border: 2px solid rgba(0,0,0,0);
}
&.list-grid{margin-top: -5px!important;}
&.component_dropdown { margin-top: -10px; }
}
.search{
float: right;
margin-right: 0;
label{
margin-top: -5px;
display: block;
img{
padding: 5px;
vertical-align: bottom;
&:hover, &:focus{
background: white;
border-color: var(--bg-color);
box-shadow: 1px 1px 2px rgba(0, 0, 0, 0.1);
}
}
input{
border: none;
float: left;
font-size: 1em;
}
}
}
.component_icon{
height: 15px;
width: 15px;
}
}
.component_thing{
clear: both;
&:hover .box, .highlight.box{

View file

@ -4,7 +4,7 @@ import { Link, withRouter } from 'react-router-dom';
import { Files } from '../../model/';
import { sort } from '../../pages/filespage.helper.js';
import { Icon, NgIf, EventReceiver, EventEmitter } from '../../components/';
import { dirname, basename, settings_get, getMimeType, debounce } from '../../helpers/';
import { dirname, basename, settings_get, getMimeType, debounce, gid } from '../../helpers/';
import './pager.scss';
@ -57,7 +57,7 @@ export class Pager extends React.Component {
navigatePage(n){
if(this.state.files[n]){
this.props.history.push(this.state.files[n].link);
this.props.history.push(this.state.files[n].link+"?once="+gid());
if(this.refs.$page) this.refs.$page.blur();
let preload_index = (n >= this.state.n || (this.state.n === this.state.files.length - 1 && n === 0)) ? this.calculateNextPageNumber(n) : this.calculatePrevPageNumber(n);
if(!this.state.files[preload_index].path){

View file

@ -0,0 +1,48 @@
import { Observable } from 'rxjs/Observable';
import { cache } from '../helpers/cache';
let current_search = null;
self.onmessage = function(message){
if(message.data.action === "search::find"){
if(current_search != null){
current_search.unsubscribe();
}
current_search = Search(message.data.path, message.data.keyword).subscribe((a) => {
self.postMessage({type: "search::found", files: a});
}, null, () => {
self.postMessage({type: "search::completed"})
});
}else if(message.data.action === "search::index"){
Indexing(message.data.config);
}
}
function Search(path, keyword){
let results = [];
return new Observable((obs) => {
obs.next(results);
const keys = keyword.split(" ").map((e) => e.toLowerCase());
cache.fetchAll((record) => {
const found = record.results.filter((file) => {
for(let i=0, l=keys.length; i<l; i++){
if(file.name.toLowerCase().indexOf(keys[i]) === -1) return false;
}
return true;
});
if(found.length > 0){
results = results.concat(found);
obs.next(results);
}
}, cache.FILE_PATH, path).then(() => {
obs.complete(results);
});
});
}
function Indexing(config){
return;
}

View file

@ -78,6 +78,7 @@
"wavesurfer.js": "^1.4.0",
"webpack": "^2.7.0",
"webpack-bundle-analyzer": "^2.8.2",
"webpack-dev-server": "^3.1.0"
"webpack-dev-server": "^3.1.0",
"worker-loader": "^2.0.0"
}
}

View file

@ -40,6 +40,11 @@ let config = {
{
test: /\.(pdf|jpg|png|gif|svg|ico|woff|woff2|eot|ttf)$/,
loader: "url-loader"
},
{
test: /[a-z]+\.worker\.js$/,
loader: "worker-loader",
options: { name: 'assets/js/[name]_[hash].js' }
}
]
},