Searchbar (#1132)
* search bar function * Fixes #231, adds searchbar to sketchlist * Fixes #231, update requested changes to searchbar * Fixes #231, reset search term after modal closed
This commit is contained in:
parent
973bf7e98d
commit
3d779734c3
15 changed files with 232 additions and 6 deletions
|
@ -121,6 +121,9 @@ export const SET_ASSETS = 'SET_ASSETS';
|
||||||
|
|
||||||
export const TOGGLE_DIRECTION = 'TOGGLE_DIRECTION';
|
export const TOGGLE_DIRECTION = 'TOGGLE_DIRECTION';
|
||||||
export const SET_SORTING = 'SET_SORTING';
|
export const SET_SORTING = 'SET_SORTING';
|
||||||
|
export const SET_SORT_PARAMS = 'SET_SORT_PARAMS';
|
||||||
|
export const SET_SEARCH_TERM = 'SET_SEARCH_TERM';
|
||||||
|
export const CLOSE_SKETCHLIST_MODAL = 'CLOSE_SKETCHLIST_MODAL';
|
||||||
|
|
||||||
export const START_LOADING = 'START_LOADING';
|
export const START_LOADING = 'START_LOADING';
|
||||||
export const STOP_LOADING = 'STOP_LOADING';
|
export const STOP_LOADING = 'STOP_LOADING';
|
||||||
|
|
13
client/images/magnifyingglass.svg
Normal file
13
client/images/magnifyingglass.svg
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Generator: Adobe Illustrator 15.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
|
||||||
|
<svg version="1.0" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||||
|
width="100px" height="100px" viewBox="0 0 100 100" enable-background="new 0 0 100 100" xml:space="preserve">
|
||||||
|
<g>
|
||||||
|
<path d="M57.281,62.129c-4.16,0.4-8.32-0.721-11.92-3.2l-2.56,8.24l-8.24,9.841c-0.72,0.879-1.76,1.119-2.8,1.199
|
||||||
|
c-2.56,0.24-4.96-1.92-5.2-4.4c-0.08-1.039,0.08-2.16,0.72-2.959l8.16-9.841l7.76-4c-3.12-3.04-4.88-7.121-5.28-11.201
|
||||||
|
c-0.8-9.68,6.64-18.32,16.24-19.2c9.601-0.8,18.4,6.56,19.28,16.16c0.4,4.64-0.88,9.281-4,13.041
|
||||||
|
C66.242,59.568,61.842,61.729,57.281,62.129z M56.722,55.328c5.84-0.56,10.4-5.92,9.84-12c-0.479-5.84-6-10.4-11.76-9.84
|
||||||
|
c-6,0.48-10.48,5.92-10,11.76c0.24,2.88,1.52,5.6,3.84,7.52C51.041,54.688,53.922,55.568,56.722,55.328z"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1 KiB |
|
@ -25,3 +25,14 @@ export function toggleDirectionForField(field) {
|
||||||
field
|
field
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function setSearchTerm(searchTerm) {
|
||||||
|
return {
|
||||||
|
type: ActionTypes.SET_SEARCH_TERM,
|
||||||
|
query: searchTerm
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function resetSearchTerm() {
|
||||||
|
return setSearchTerm('');
|
||||||
|
}
|
||||||
|
|
91
client/modules/IDE/components/Searchbar.jsx
Normal file
91
client/modules/IDE/components/Searchbar.jsx
Normal file
|
@ -0,0 +1,91 @@
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import React from 'react';
|
||||||
|
import InlineSVG from 'react-inlinesvg';
|
||||||
|
import { bindActionCreators } from 'redux';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { throttle } from 'lodash';
|
||||||
|
import * as SortingActions from '../actions/sorting';
|
||||||
|
|
||||||
|
const searchIcon = require('../../../images/magnifyingglass.svg');
|
||||||
|
|
||||||
|
class Searchbar extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
searchValue: this.props.searchTerm
|
||||||
|
};
|
||||||
|
this.throttledSearchChange = throttle(this.searchChange, 500);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
this.props.resetSearchTerm();
|
||||||
|
}
|
||||||
|
|
||||||
|
handleResetSearch = () => {
|
||||||
|
this.setState({ searchValue: '' }, () => {
|
||||||
|
this.props.resetSearchTerm();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
handleSearchEnter = (e) => {
|
||||||
|
if (e.key === 'Enter') {
|
||||||
|
this.props.setSearchTerm(this.state.searchValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
searchChange = (value) => {
|
||||||
|
this.props.setSearchTerm(this.state.searchValue);
|
||||||
|
};
|
||||||
|
|
||||||
|
handleSearchChange = (e) => {
|
||||||
|
this.setState({ searchValue: e.target.value }, () => {
|
||||||
|
this.throttledSearchChange(this.state.searchValue);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { searchValue } = this.state;
|
||||||
|
return (
|
||||||
|
<div className="searchbar">
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
className="searchbar__button"
|
||||||
|
onClick={this.handleSearchEnter}
|
||||||
|
>
|
||||||
|
<InlineSVG className="searchbar__icon" src={searchIcon} />
|
||||||
|
</button>
|
||||||
|
<input
|
||||||
|
className="searchbar__input"
|
||||||
|
type="text"
|
||||||
|
value={searchValue}
|
||||||
|
placeholder="Search files..."
|
||||||
|
onChange={this.handleSearchChange}
|
||||||
|
onKeyUp={this.handleSearchEnter}
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
className="searchbar__clear-button"
|
||||||
|
onClick={this.handleResetSearch}
|
||||||
|
>clear
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Searchbar.propTypes = {
|
||||||
|
searchTerm: PropTypes.string.isRequired,
|
||||||
|
setSearchTerm: PropTypes.func.isRequired,
|
||||||
|
resetSearchTerm: PropTypes.func.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
function mapStateToProps(state) {
|
||||||
|
return {
|
||||||
|
searchTerm: state.search.searchTerm
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapDispatchToProps(dispatch) {
|
||||||
|
return bindActionCreators(Object.assign({}, SortingActions), dispatch);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect(mapStateToProps, mapDispatchToProps)(Searchbar);
|
|
@ -30,7 +30,6 @@ class SketchListRowBase extends React.Component {
|
||||||
isFocused: false
|
isFocused: false
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
onFocusComponent = () => {
|
onFocusComponent = () => {
|
||||||
this.setState({ isFocused: true });
|
this.setState({ isFocused: true });
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,6 +30,7 @@ import * as ConsoleActions from '../actions/console';
|
||||||
import { getHTMLFile } from '../reducers/files';
|
import { getHTMLFile } from '../reducers/files';
|
||||||
import Overlay from '../../App/components/Overlay';
|
import Overlay from '../../App/components/Overlay';
|
||||||
import SketchList from '../components/SketchList';
|
import SketchList from '../components/SketchList';
|
||||||
|
import Searchbar from '../components/Searchbar';
|
||||||
import AssetList from '../components/AssetList';
|
import AssetList from '../components/AssetList';
|
||||||
import About from '../components/About';
|
import About from '../components/About';
|
||||||
import Feedback from '../components/Feedback';
|
import Feedback from '../components/Feedback';
|
||||||
|
@ -371,6 +372,7 @@ class IDEView extends React.Component {
|
||||||
title="Open a Sketch"
|
title="Open a Sketch"
|
||||||
previousPath={this.props.ide.previousPath}
|
previousPath={this.props.ide.previousPath}
|
||||||
>
|
>
|
||||||
|
<Searchbar />
|
||||||
<SketchList
|
<SketchList
|
||||||
username={this.props.params.username}
|
username={this.props.params.username}
|
||||||
user={this.props.user}
|
user={this.props.user}
|
||||||
|
|
|
@ -13,6 +13,7 @@ const initialState = {
|
||||||
shareModalProjectId: null,
|
shareModalProjectId: null,
|
||||||
shareModalProjectName: null,
|
shareModalProjectName: null,
|
||||||
shareModalProjectUsername: null,
|
shareModalProjectUsername: null,
|
||||||
|
sketchlistModalVisible: false,
|
||||||
editorOptionsVisible: false,
|
editorOptionsVisible: false,
|
||||||
keyboardShortcutVisible: false,
|
keyboardShortcutVisible: false,
|
||||||
unsavedChanges: false,
|
unsavedChanges: false,
|
||||||
|
|
14
client/modules/IDE/reducers/search.js
Normal file
14
client/modules/IDE/reducers/search.js
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
import * as ActionTypes from '../../../constants';
|
||||||
|
|
||||||
|
const initialState = {
|
||||||
|
searchTerm: ''
|
||||||
|
};
|
||||||
|
|
||||||
|
export default (state = initialState, action) => {
|
||||||
|
switch (action.type) {
|
||||||
|
case ActionTypes.SET_SEARCH_TERM:
|
||||||
|
return { ...state, searchTerm: action.query };
|
||||||
|
default:
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
};
|
|
@ -6,9 +6,27 @@ import { DIRECTION } from '../actions/sorting';
|
||||||
const getSketches = state => state.sketches;
|
const getSketches = state => state.sketches;
|
||||||
const getField = state => state.sorting.field;
|
const getField = state => state.sorting.field;
|
||||||
const getDirection = state => state.sorting.direction;
|
const getDirection = state => state.sorting.direction;
|
||||||
|
const getSearchTerm = state => state.search.searchTerm;
|
||||||
|
|
||||||
|
const getFilteredSketches = createSelector(
|
||||||
|
getSketches,
|
||||||
|
getSearchTerm,
|
||||||
|
(sketches, search) => {
|
||||||
|
if (search) {
|
||||||
|
const searchStrings = sketches.map((sketch) => {
|
||||||
|
const smallSketch = {
|
||||||
|
name: sketch.name
|
||||||
|
};
|
||||||
|
return { ...sketch, searchString: Object.values(smallSketch).join(' ').toLowerCase() };
|
||||||
|
});
|
||||||
|
return searchStrings.filter(sketch => sketch.searchString.includes(search.toLowerCase()));
|
||||||
|
}
|
||||||
|
return sketches;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
const getSortedSketches = createSelector(
|
const getSortedSketches = createSelector(
|
||||||
getSketches,
|
getFilteredSketches,
|
||||||
getField,
|
getField,
|
||||||
getDirection,
|
getDirection,
|
||||||
(sketches, field, direction) => {
|
(sketches, field, direction) => {
|
||||||
|
|
|
@ -10,6 +10,7 @@ import sketches from './modules/IDE/reducers/projects';
|
||||||
import toast from './modules/IDE/reducers/toast';
|
import toast from './modules/IDE/reducers/toast';
|
||||||
import console from './modules/IDE/reducers/console';
|
import console from './modules/IDE/reducers/console';
|
||||||
import assets from './modules/IDE/reducers/assets';
|
import assets from './modules/IDE/reducers/assets';
|
||||||
|
import search from './modules/IDE/reducers/search';
|
||||||
import sorting from './modules/IDE/reducers/sorting';
|
import sorting from './modules/IDE/reducers/sorting';
|
||||||
import loading from './modules/IDE/reducers/loading';
|
import loading from './modules/IDE/reducers/loading';
|
||||||
|
|
||||||
|
@ -21,6 +22,7 @@ const rootReducer = combineReducers({
|
||||||
user,
|
user,
|
||||||
project,
|
project,
|
||||||
sketches,
|
sketches,
|
||||||
|
search,
|
||||||
sorting,
|
sorting,
|
||||||
editorAccessibility,
|
editorAccessibility,
|
||||||
toast,
|
toast,
|
||||||
|
|
|
@ -57,7 +57,10 @@ $themes: (
|
||||||
input-text-color: #333,
|
input-text-color: #333,
|
||||||
input-border-color: #b5b5b5,
|
input-border-color: #b5b5b5,
|
||||||
about-list-text-color: #4a4a4a,
|
about-list-text-color: #4a4a4a,
|
||||||
search-background-color: #ebebeb,
|
search-background-color: #ffffff,
|
||||||
|
search-clear-background-color: #e9e9e9,
|
||||||
|
search-hover-text-color: #ffffff,
|
||||||
|
search-hover-background-color: #4d4d4d,
|
||||||
dropdown-color: #414141,
|
dropdown-color: #414141,
|
||||||
keyboard-shortcut-color: #757575,
|
keyboard-shortcut-color: #757575,
|
||||||
nav-hover-color: $p5js-pink,
|
nav-hover-color: $p5js-pink,
|
||||||
|
@ -106,7 +109,10 @@ $themes: (
|
||||||
input-text-color: #333,
|
input-text-color: #333,
|
||||||
input-border-color: #b5b5b5,
|
input-border-color: #b5b5b5,
|
||||||
about-list-text-color: #f4f4f4,
|
about-list-text-color: #f4f4f4,
|
||||||
search-background-color: #ebebeb,
|
search-background-color: #ffffff,
|
||||||
|
search-clear-background-color: #4f4f4f,
|
||||||
|
search-hover-text-color: #ffffff,
|
||||||
|
search-hover-background-color: $p5js-pink,
|
||||||
dropdown-color: #dadada,
|
dropdown-color: #dadada,
|
||||||
keyboard-shortcut-color: #B5B5B5,
|
keyboard-shortcut-color: #B5B5B5,
|
||||||
nav-hover-color: $p5js-pink,
|
nav-hover-color: $p5js-pink,
|
||||||
|
@ -155,6 +161,9 @@ $themes: (
|
||||||
input-border-color: #b5b5b5,
|
input-border-color: #b5b5b5,
|
||||||
about-list-text-color: #f4f4f4,
|
about-list-text-color: #f4f4f4,
|
||||||
search-background-color: $white,
|
search-background-color: $white,
|
||||||
|
search-clear-background-color: #444,
|
||||||
|
search-hover-text-color: $black,
|
||||||
|
search-hover-background-color: $yellow,
|
||||||
dropdown-color: #e1e1e1,
|
dropdown-color: #e1e1e1,
|
||||||
keyboard-shortcut-color: #e1e1e1,
|
keyboard-shortcut-color: #e1e1e1,
|
||||||
nav-hover-color: $yellow,
|
nav-hover-color: $yellow,
|
||||||
|
|
|
@ -24,6 +24,7 @@
|
||||||
flex-flow: column;
|
flex-flow: column;
|
||||||
max-height: 80%;
|
max-height: 80%;
|
||||||
max-width: 65%;
|
max-width: 65%;
|
||||||
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.overlay__header {
|
.overlay__header {
|
||||||
|
|
61
client/styles/components/_searchbar.scss
Normal file
61
client/styles/components/_searchbar.scss
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
.searchbar {
|
||||||
|
position: absolute;
|
||||||
|
display: flex;
|
||||||
|
padding-left: #{17 / $base-font-size}rem;
|
||||||
|
right: #{50 / $base-font-size}rem;
|
||||||
|
top: #{14 / $base-font-size}rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.searchbar__input {
|
||||||
|
width: #{240 / $base-font-size}rem;
|
||||||
|
height: #{36 / $base-font-size}rem;
|
||||||
|
border: solid 0.5px;
|
||||||
|
padding-left: #{36 / $base-font-size}rem;
|
||||||
|
padding-right: #{38 / $base-font-size}rem;
|
||||||
|
@include themify() {
|
||||||
|
border-color: getThemifyVariable('input-border-color');
|
||||||
|
background-color: getThemifyVariable('search-background-color');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.searchbar__button {
|
||||||
|
width: #{31 / $base-font-size}rem;
|
||||||
|
height: #{36 / $base-font-size}rem;
|
||||||
|
position: absolute;
|
||||||
|
padding: 0;
|
||||||
|
border-right: solid 1px;
|
||||||
|
@include themify() {
|
||||||
|
border-right-color: getThemifyVariable('input-border-color');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.searchbar__icon {
|
||||||
|
display: inline-block;
|
||||||
|
& svg {
|
||||||
|
width: #{22 / $base-font-size}rem;
|
||||||
|
height: #{27 / $base-font-size}rem;
|
||||||
|
transform: scaleX(-1);
|
||||||
|
@include themify() {
|
||||||
|
fill: getThemifyVariable('input-text-color');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.searchbar__clear-button {
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: #{10 / $base-font-size}rem;
|
||||||
|
text-align: center;
|
||||||
|
border-radius: 2px;
|
||||||
|
align-self: center;
|
||||||
|
position: absolute;
|
||||||
|
padding: #{3 / $base-font-size}rem #{4 / $base-font-size}rem;
|
||||||
|
left: #{216 / $base-font-size}rem;;
|
||||||
|
@include themify() {
|
||||||
|
color: getThemifyVariable('primary-text-color');
|
||||||
|
background-color: getThemifyVariable('search-clear-background-color');
|
||||||
|
&:hover, &:focus {
|
||||||
|
color: getThemifyVariable('search-hover-text-color');
|
||||||
|
background-color: getThemifyVariable('search-hover-background-color');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,7 +2,8 @@
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
width: #{1000 / $base-font-size}rem;
|
width: #{1000 / $base-font-size}rem;
|
||||||
min-height: #{400 / $base-font-size}rem;
|
height: #{628 / $base-font-size}rem;
|
||||||
|
// min-height: #{400 / $base-font-size}rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sketches-table {
|
.sketches-table {
|
||||||
|
@ -71,7 +72,6 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.sketches-table thead {
|
.sketches-table thead {
|
||||||
font-size: #{12 / $base-font-size}rem;
|
font-size: #{12 / $base-font-size}rem;
|
||||||
@include themify() {
|
@include themify() {
|
||||||
|
|
|
@ -21,6 +21,7 @@
|
||||||
@import 'components/preferences';
|
@import 'components/preferences';
|
||||||
@import 'components/reset-password';
|
@import 'components/reset-password';
|
||||||
@import 'components/new-password';
|
@import 'components/new-password';
|
||||||
|
@import 'components/searchbar';
|
||||||
@import 'components/sketch-list';
|
@import 'components/sketch-list';
|
||||||
@import 'components/sidebar';
|
@import 'components/sidebar';
|
||||||
@import 'components/modal';
|
@import 'components/modal';
|
||||||
|
|
Loading…
Reference in a new issue