From 39748c5013b2fe61e740ed6251e01e5f8639cb33 Mon Sep 17 00:00:00 2001 From: zkhussain Date: Tue, 21 Sep 2021 22:08:26 -0400 Subject: [PATCH 01/25] Display search --- src/components/SearchBar/SearchBar.tsx | 28 ++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 src/components/SearchBar/SearchBar.tsx diff --git a/src/components/SearchBar/SearchBar.tsx b/src/components/SearchBar/SearchBar.tsx new file mode 100644 index 00000000..170128ad --- /dev/null +++ b/src/components/SearchBar/SearchBar.tsx @@ -0,0 +1,28 @@ +import {FontAwesomeIcon} from '@fortawesome/react-fontawesome'; +import React, { useState } from 'react'; +import styles from './SearchBar.module.scss'; +import {Form} from 'react-bootstrap'; +import {Link} from 'react-router-dom'; +import ToolTip from '../util/ToolTip'; + +const SearchBar = (props : any) => { + + + + + + return ( +
+ + + } > + + + + + ); +} + +export default SearchBar; \ No newline at end of file From 4f8e5776bbd4e28ca4ece05d0245abb57a3b3cba Mon Sep 17 00:00:00 2001 From: zkhussain Date: Tue, 21 Sep 2021 22:09:05 -0400 Subject: [PATCH 02/25] Add api call --- src/components/SearchBar/SearchBar.tsx | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/components/SearchBar/SearchBar.tsx b/src/components/SearchBar/SearchBar.tsx index 170128ad..84681646 100644 --- a/src/components/SearchBar/SearchBar.tsx +++ b/src/components/SearchBar/SearchBar.tsx @@ -4,10 +4,22 @@ import styles from './SearchBar.module.scss'; import {Form} from 'react-bootstrap'; import {Link} from 'react-router-dom'; import ToolTip from '../util/ToolTip'; +import { searchModules } from 'api'; const SearchBar = (props : any) => { + const [searchTerm, setSearchTerm] = useState(''); + const getModuleRequest = async (searchValue : any) => { + try { + if (searchTerm.length !== 0) { + let response = await searchModules(searchValue); + props.showModules(response) + } + } catch (error) { + console.error(error); + } + }; From 6a1a0d10194652052482554c663e81a14f509215 Mon Sep 17 00:00:00 2001 From: zkhussain Date: Tue, 21 Sep 2021 22:10:00 -0400 Subject: [PATCH 03/25] Handle search submit --- src/components/SearchBar/SearchBar.tsx | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/components/SearchBar/SearchBar.tsx b/src/components/SearchBar/SearchBar.tsx index 84681646..b90bb3d4 100644 --- a/src/components/SearchBar/SearchBar.tsx +++ b/src/components/SearchBar/SearchBar.tsx @@ -21,7 +21,11 @@ const SearchBar = (props : any) => { } }; - + function handleChange(e : any) { + let value = e.target.value; + setSearchTerm(value); + getModuleRequest(value); + }; return (
@@ -32,7 +36,7 @@ const SearchBar = (props : any) => { + type="search" placeholder="Search..." aria-label="Search" value={searchTerm} onChange={handleChange}/> ); } From aa4dbd70442cb7b58f57ee2653648930fc36a2bc Mon Sep 17 00:00:00 2001 From: zkhussain Date: Tue, 21 Sep 2021 22:10:35 -0400 Subject: [PATCH 04/25] Add style for SearchBar --- .../SearchBar/SearchBar.module.scss | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 src/components/SearchBar/SearchBar.module.scss diff --git a/src/components/SearchBar/SearchBar.module.scss b/src/components/SearchBar/SearchBar.module.scss new file mode 100644 index 00000000..4fb1e4cb --- /dev/null +++ b/src/components/SearchBar/SearchBar.module.scss @@ -0,0 +1,28 @@ +.search-bar { + padding-left: 42px; + border: 1px solid #343a40; + border-radius: 25px; + box-shadow: inset 0 -2px 0 #343a40; + height: 38px; +} + +.search-bar:focus { + border-color: rgba(223,225,229,0); + box-shadow: 0 2px 4px rgb(32, 33, 36); +} + +.search-icon { + margin-right: -30px; + float: right; + display: flex; + justify-content: center; + align-items: center; + font-size: 10px; + position: relative; + border-radius: 5px; + color: rgba(128, 0, 0, 0.651); +} + +.search-icon:hover { + color: maroon; +} \ No newline at end of file From 62bd960fe96a3cc11bc6a7b923ff5f5390619b49 Mon Sep 17 00:00:00 2001 From: zkhussain Date: Tue, 21 Sep 2021 22:11:12 -0400 Subject: [PATCH 05/25] Create api call for search modules --- src/api/index.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/api/index.ts b/src/api/index.ts index f8fbbece..d44c0768 100644 --- a/src/api/index.ts +++ b/src/api/index.ts @@ -77,6 +77,10 @@ export async function getModule(id: number) { return ( await api.get(`/modules/${id}`)).data; } +export async function searchModules(searchTerm : string) { + return ( await api.get(`/module/search/${searchTerm}`)).data; +} + export async function getUserList() { return ( await api.get(`/user/`) ).data; } From e45d0e884853f445a14a68bda85aa3e4feb4d448 Mon Sep 17 00:00:00 2001 From: zkhussain Date: Tue, 21 Sep 2021 22:11:52 -0400 Subject: [PATCH 06/25] Update Explore to display search bar --- src/pages/Explore/Explore.tsx | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/src/pages/Explore/Explore.tsx b/src/pages/Explore/Explore.tsx index 4a2a3d11..25607cbb 100644 --- a/src/pages/Explore/Explore.tsx +++ b/src/pages/Explore/Explore.tsx @@ -1,4 +1,4 @@ -import {CardColumns} from 'react-bootstrap'; +import {CardColumns, Row} from 'react-bootstrap'; import React from 'react'; import {ModuleCard} from '../../components/ModuleCard/ModuleCard'; import {Module} from '../../types/Module'; @@ -7,6 +7,7 @@ import {Layout} from '../Layout/Layout'; import {HorizontallyCenteredSpinner} from '../../components/util/HorizonallyCenteredSpinner'; import {Message} from '../../util/Message'; import {PageTitle} from '../../components/util/PageTitle'; +import SearchBar from '../../components/SearchBar/SearchBar'; interface ExploreState { modules: Module[]; @@ -34,6 +35,13 @@ class Explore extends React.Component<{}, ExploreState> { } + loadSearchModules = (arr : Module[]) => { + console.log(this.state.modules); + if (arr.length != 0) { + this.setState({modules: arr, state: 'success'}); + } + } + render() { const cards = this.state.modules.map((m, i) => ); return ( @@ -42,12 +50,13 @@ class Explore extends React.Component<{}, ExploreState> { this.state.state === 'error' ? : <> - Explore + + Explore + + {cards.length === 0 ?

No modules published at this time, please come back later.

: - {cards} - } - + {cards}} } From 2614deb11f35f36faf32359df3973d0faffc8f66 Mon Sep 17 00:00:00 2001 From: zkhussain Date: Tue, 21 Sep 2021 22:12:08 -0400 Subject: [PATCH 07/25] Add Tooltip --- src/components/util/ToolTip.tsx | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 src/components/util/ToolTip.tsx diff --git a/src/components/util/ToolTip.tsx b/src/components/util/ToolTip.tsx new file mode 100644 index 00000000..aaa382af --- /dev/null +++ b/src/components/util/ToolTip.tsx @@ -0,0 +1,21 @@ +import React from 'react'; +import {Tooltip, OverlayTrigger} from 'react-bootstrap'; + +const ToolTip = (props: any) => { + return ( + + {props.text} + + } + > + {props.tool} + + ); +} + +export default ToolTip; \ No newline at end of file From e94ef234ac787db1c192eaca2d5b106d70a00a04 Mon Sep 17 00:00:00 2001 From: zkhussain Date: Tue, 5 Oct 2021 20:58:29 -0400 Subject: [PATCH 08/25] Remove Tooltip link --- src/components/SearchBar/SearchBar.module.scss | 1 - src/components/SearchBar/SearchBar.tsx | 6 ++---- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/src/components/SearchBar/SearchBar.module.scss b/src/components/SearchBar/SearchBar.module.scss index 4fb1e4cb..832bdbf1 100644 --- a/src/components/SearchBar/SearchBar.module.scss +++ b/src/components/SearchBar/SearchBar.module.scss @@ -17,7 +17,6 @@ display: flex; justify-content: center; align-items: center; - font-size: 10px; position: relative; border-radius: 5px; color: rgba(128, 0, 0, 0.651); diff --git a/src/components/SearchBar/SearchBar.tsx b/src/components/SearchBar/SearchBar.tsx index b90bb3d4..3f482615 100644 --- a/src/components/SearchBar/SearchBar.tsx +++ b/src/components/SearchBar/SearchBar.tsx @@ -2,7 +2,6 @@ import {FontAwesomeIcon} from '@fortawesome/react-fontawesome'; import React, { useState } from 'react'; import styles from './SearchBar.module.scss'; import {Form} from 'react-bootstrap'; -import {Link} from 'react-router-dom'; import ToolTip from '../util/ToolTip'; import { searchModules } from 'api'; @@ -30,9 +29,8 @@ const SearchBar = (props : any) => { return (
- - } > + + }> Date: Tue, 5 Oct 2021 21:11:27 -0400 Subject: [PATCH 09/25] Add search bar to a user --- src/pages/MyModules/MyModules.tsx | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/pages/MyModules/MyModules.tsx b/src/pages/MyModules/MyModules.tsx index c113be7f..e5700516 100644 --- a/src/pages/MyModules/MyModules.tsx +++ b/src/pages/MyModules/MyModules.tsx @@ -1,10 +1,11 @@ -import {CardColumns} from 'react-bootstrap'; +import {CardColumns, Row} from 'react-bootstrap'; import React from 'react'; import {ModuleCard} from '../../components/ModuleCard/ModuleCard'; import {getUserModules} from '../../api'; import {Layout} from '../Layout/Layout'; import {RoutePaths} from '../../router/RoutePaths'; import {UserModule} from '../../types/UserModule'; +import SearchBar from 'components/SearchBar/SearchBar'; interface MyModulesState { modules: UserModule[]; @@ -35,11 +36,21 @@ class MyModules extends React.Component<{}, MyModulesState> { ); } + loadSearchModules = (arr : UserModule[]) => { + console.log(this.state.modules); + if (arr.length != 0) { + this.setState({modules: arr}); + } + } + render() { const cards = this.state.modules.map((m, i) => ); return ( -

My Modules

+ +

My Modules

+ +
{cards.length === 0 ? this.renderNoModules() : {cards}}
); From 8af8d799abb4b7ef35b870089dae1790f1ec1845 Mon Sep 17 00:00:00 2001 From: zkhussain Date: Tue, 19 Oct 2021 20:20:30 -0400 Subject: [PATCH 10/25] Improve search --- src/api/index.ts | 3 +++ src/components/SearchBar/SearchBar.tsx | 26 +++++++------------------- src/pages/Explore/Explore.tsx | 12 +++++++----- src/pages/MyModules/MyModules.tsx | 12 +++++++----- 4 files changed, 24 insertions(+), 29 deletions(-) diff --git a/src/api/index.ts b/src/api/index.ts index d44c0768..3baf0569 100644 --- a/src/api/index.ts +++ b/src/api/index.ts @@ -124,6 +124,9 @@ export async function getUserModules() { export async function getUserModule(id: number) { return handleResponse( await api.get(`/user-module/${id}`)).data; } +export async function searchUserModules(searchTerm : string) { + return handleResponse( await api.get(`/user-module/search/${searchTerm}`)).data; +} export async function getEditorsModules() { return handleResponse(await api.get(`module/modules-editor`)).data; } diff --git a/src/components/SearchBar/SearchBar.tsx b/src/components/SearchBar/SearchBar.tsx index 3f482615..9014caee 100644 --- a/src/components/SearchBar/SearchBar.tsx +++ b/src/components/SearchBar/SearchBar.tsx @@ -3,38 +3,26 @@ import React, { useState } from 'react'; import styles from './SearchBar.module.scss'; import {Form} from 'react-bootstrap'; import ToolTip from '../util/ToolTip'; -import { searchModules } from 'api'; -const SearchBar = (props : any) => { +const SearchBar = (props: any) => { const [searchTerm, setSearchTerm] = useState(''); - const getModuleRequest = async (searchValue : any) => { - try { - if (searchTerm.length !== 0) { - let response = await searchModules(searchValue); - props.showModules(response) - } - } catch (error) { - console.error(error); - } - }; - - function handleChange(e : any) { - let value = e.target.value; - setSearchTerm(value); - getModuleRequest(value); + const handleChange = (e: any) => { + let searchValue = e.target.value; + setSearchTerm(searchValue); + props.showModules(searchValue) }; return ( - + {e.preventDefault()}}> }> + type="search" placeholder="Search..." aria-label="Search" value={searchTerm} onChange={handleChange}/> ); } diff --git a/src/pages/Explore/Explore.tsx b/src/pages/Explore/Explore.tsx index 25607cbb..ceba9b46 100644 --- a/src/pages/Explore/Explore.tsx +++ b/src/pages/Explore/Explore.tsx @@ -2,7 +2,7 @@ import {CardColumns, Row} from 'react-bootstrap'; import React from 'react'; import {ModuleCard} from '../../components/ModuleCard/ModuleCard'; import {Module} from '../../types/Module'; -import {getPublicModules} from '../../api'; +import {getPublicModules, searchModules} from '../../api'; import {Layout} from '../Layout/Layout'; import {HorizontallyCenteredSpinner} from '../../components/util/HorizonallyCenteredSpinner'; import {Message} from '../../util/Message'; @@ -35,10 +35,12 @@ class Explore extends React.Component<{}, ExploreState> { } - loadSearchModules = (arr : Module[]) => { - console.log(this.state.modules); - if (arr.length != 0) { - this.setState({modules: arr, state: 'success'}); + loadSearchModules = async (searchValue: string) => { + try { + let response = searchValue.length !== 0 ? await searchModules(searchValue) : await getPublicModules(); + this.setState({modules: response, state: 'success'}); + } catch (_) { + this.setState({state: 'error'}); } } diff --git a/src/pages/MyModules/MyModules.tsx b/src/pages/MyModules/MyModules.tsx index e5700516..5873500a 100644 --- a/src/pages/MyModules/MyModules.tsx +++ b/src/pages/MyModules/MyModules.tsx @@ -1,7 +1,7 @@ import {CardColumns, Row} from 'react-bootstrap'; import React from 'react'; import {ModuleCard} from '../../components/ModuleCard/ModuleCard'; -import {getUserModules} from '../../api'; +import {getUserModules, searchUserModules} from '../../api'; import {Layout} from '../Layout/Layout'; import {RoutePaths} from '../../router/RoutePaths'; import {UserModule} from '../../types/UserModule'; @@ -36,10 +36,12 @@ class MyModules extends React.Component<{}, MyModulesState> { ); } - loadSearchModules = (arr : UserModule[]) => { - console.log(this.state.modules); - if (arr.length != 0) { - this.setState({modules: arr}); + loadSearchModules = async (searchValue: string) => { + try { + let response = searchValue.length !== 0 ? await searchUserModules(searchValue) : await getUserModules(); + this.setState({modules: response}); + } catch (error_msg) { + console.error(error_msg); } } From 6e47d9a4401bd9044f7034bdcc00aca3d75b51bb Mon Sep 17 00:00:00 2001 From: zkhussain Date: Thu, 4 Nov 2021 14:46:07 -0400 Subject: [PATCH 11/25] Update Tooltip --- src/components/util/ToolTip.tsx | 34 +++++++++++++++++++++------------ 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/src/components/util/ToolTip.tsx b/src/components/util/ToolTip.tsx index aaa382af..b92a8ce2 100644 --- a/src/components/util/ToolTip.tsx +++ b/src/components/util/ToolTip.tsx @@ -1,21 +1,31 @@ import React from 'react'; -import {Tooltip, OverlayTrigger} from 'react-bootstrap'; +import { Tooltip, OverlayTrigger, Container } from 'react-bootstrap'; -const ToolTip = (props: any) => { - return ( +interface ToolTipProps { + key: string; + placement: 'auto-start' | 'auto' | 'auto-end' + | 'top-start' | 'top' | 'top-end' | 'right-start' + | 'right' | 'right-end' | 'bottom-end' | 'bottom' + | 'bottom-start' | 'left-end' | 'left' | 'left-start'; + delay?: { show: number; hide: number }; + text: string; + children: any; +} + +export default function ToolTip(props: ToolTipProps) { + return ( + {props.text} - - } + } > - {props.tool} + + {props.children} + - ); + ); } - -export default ToolTip; \ No newline at end of file From b6586f1f552489a1ce2930bb0bee31e99e69c702 Mon Sep 17 00:00:00 2001 From: Zaid Hussain Date: Tue, 14 Dec 2021 21:05:38 -0500 Subject: [PATCH 12/25] Update search to display advanced options --- src/api/index.ts | 2 +- .../SearchBar/SearchBar.module.scss | 89 ++++---- src/components/SearchBar/SearchBar.tsx | 193 ++++++++++-------- src/components/TagEditor/TagEditor.scss | 5 +- src/components/TagEditor/TagEditor.tsx | 29 +-- src/pages/ModuleEditor/ModuleEditor.tsx | 4 +- 6 files changed, 170 insertions(+), 152 deletions(-) diff --git a/src/api/index.ts b/src/api/index.ts index 4a60486b..88954345 100644 --- a/src/api/index.ts +++ b/src/api/index.ts @@ -154,7 +154,7 @@ export async function getUserLab(id: number) { return handleResponse( await api.get(`/user-lab/${id}`)).data; } -export async function getTags(name: string) { +export async function getTags() { return handleResponse( await api.get(`/tag`)).data; } diff --git a/src/components/SearchBar/SearchBar.module.scss b/src/components/SearchBar/SearchBar.module.scss index 84540d0a..3c42b682 100644 --- a/src/components/SearchBar/SearchBar.module.scss +++ b/src/components/SearchBar/SearchBar.module.scss @@ -1,3 +1,5 @@ +@import '../../theme'; + .search-bar { padding-left: 12px; padding-right: 12px; @@ -7,52 +9,49 @@ height: 42px; } -.search-bar:focus { - border-color: rgba(223, 225, 229, 0); - box-shadow: 0 2px 4px rgb(32, 33, 36); -} - -.search-icon { - top: 32%; - position: absolute; - border-radius: 5px; - color: rgba(128, 0, 0, 0.651); -} - -.search-icon:hover { +.icon:hover { + cursor: pointer; color: maroon; } .search-options { - min-width: '356px'; - position: 'absolute'; - right: '0' !important; - top: '4rem' !important; - z-index: 1 !important; - border-radius: 6; - box-shadow: '0 0 8px rgb(175, 175, 175)'; - padding: '24px' -} - -/* -.search-bar { - padding-left: 28px; - border: 1px solid #343a40; - border-radius: 8px; - box-shadow: inset 0 -2px 0 #343a40; - height: 34px; -} - -.search-bar:focus { - border-color: rgba(223,225,229,0); - box-shadow: 0 2px 4px rgb(32, 33, 36); -} - -.search-icon { - z-index: 1; - color: rgba(128, 0, 0, 0.8); -} - -.search-icon:hover { - color: maroon; -}*/ + background-color: #FFF; + width: 28rem; + position: absolute; + right: 0; + top: 58px; + padding: 24px; + z-index: 1; + border-radius: 0.25rem; + box-shadow: 0 0 8px rgb(175, 175, 175); +} + +.react-tags.is-focused { + box-shadow: 0 0 0 0.2rem rgba(69, 130, 236, 0.25); + border-color: #b1b1b1; + transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out, -webkit-box-shadow 0.15s ease-in-out; +} + +.react-tags-selected-tag { + display: inline-block; + box-sizing: border-box; + margin: 0 6px 6px 0; + border: 1px solid #d1d1d1; + border-radius: 4px; + background: #f1f1f1; + font-size: 0.763rem; + font-weight: 400; + color: map-get($colors, text-primary); + line-height: inherit; +} + +.react-tags-search { + position: relative; + display: inline-block; + margin-bottom: 6px; + max-width: 100%; +} + +.react-tags-suggestions li { + border-bottom: 1px solid #d1d1d1; +} \ No newline at end of file diff --git a/src/components/SearchBar/SearchBar.tsx b/src/components/SearchBar/SearchBar.tsx index 2e5ab4b1..d4b28f5c 100644 --- a/src/components/SearchBar/SearchBar.tsx +++ b/src/components/SearchBar/SearchBar.tsx @@ -1,137 +1,158 @@ -import {faSlidersH, faSearch} from '@fortawesome/free-solid-svg-icons'; +import { faSlidersH, faSearch } from '@fortawesome/free-solid-svg-icons'; import React, { useState } from 'react'; +import { Tag } from 'react-tag-autocomplete'; import styles from './SearchBar.module.scss'; -import {Form, Container, Row, Col, Button} from 'react-bootstrap'; +import { Form, Container, Row, Col, Button } from 'react-bootstrap'; import ToolTip from '../util/ToolTip'; +import { getTags } from '../../api'; +import { TagEditor } from '../../components/TagEditor/TagEditor'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import {FieldArray} from 'formik'; -import {TagEditor} from '../../components/TagEditor/TagEditor'; const SearchBar = (props: any) => { - const [searchTerm, setSearchTerm] = useState(''); - const [description, setDescription] = useState(''); - const [advancedSearchOn, setAdvancedSearchOn] = useState(false); + const [searchTerm, setSearchTerm] = useState(''); + const [title, setTitle] = useState(''); + const [description, setDescription] = useState(''); + const [difficulty, setDifficulty] = useState(''); + const [tags, setTags] = React.useState([]); + const [tagSuggestions, setTagSuggestions] = React.useState([]); - const handleChange = (e: any) => { - const searchValue = e.target.value; - setSearchTerm(searchValue); - // props.showModules(searchValue); - }; + const [searchOptionsOn, setSearchOptionsOn] = useState(false); - const handleDescription = (e: any) => { - const searchValue = e.target.value; - setDescription(searchValue); - // props.showModules(searchValue); - }; + const onSearchTermChange = (e: any) => { + if (!searchOptionsOn) { + setSearchTerm(e.target.value); + } + }; - const getUpdateSearchValue = () => { - // searchTerm && `title:${searchTerm}` + description && `desc:${description}` - let searchText = ''; - if (searchTerm) {searchText += `title:${searchTerm} `} - if (description) {searchText += `desc:${description}`} - return searchText; - }; + const onTitleChange = (e: any) => { + const searchTitle = e.target.value; + setTitle(searchTitle); - const handleSettings = () => { - (advancedSearchOn) ? setAdvancedSearchOn(false) : setAdvancedSearchOn(true); - }; + setSearchTerm(searchTitle && 'title:' + searchTitle); + }; - return ( - -
e.preventDefault()}> + const onDescriptionChange = (e: any) => { + const desc = e.target.value; + setDescription(desc); + // props.showModules(searchValue); + }; + + const onDifficultyChange = (e: any) => { + const diff = e.target.value; + setDifficulty(diff); + }; + + async function handleTagSuggestions() { + setTagSuggestions(await getTags()); + } + + const handleSearchOptionsOn = (e: any) => { + (searchOptionsOn) ? setSearchOptionsOn(false) : setSearchOptionsOn(true); + }; + + function onAddTags(newTag: Tag) { + handleTagSuggestions(); + let newTags = new Array(); + newTags = newTags.concat(tags, newTag); + setTags(newTags); + } + + function onDeleteTags(i: number) { + const newTags = tags.slice(0); + newTags.splice(i, 1); + setTags(newTags); + } + + return ( + + - + - + - {advancedSearchOn && - - -

Title

- + {searchOptionsOn && + + + Title - -
- -

Description

- + + + Description - -
- -

Difficulty

- + + + Difficulty - -
-
- -

Tags

- - +
+ + Tags + - - Tags - ('moduleTags')}> - {(helpers) => ( - mt.tag)} - tagSuggestions={tagSuggestions} - mes={message?.variant} - editing={editing} - onAdd={t => helpers.push(cast({moduleId: Number(moduleId), tagId: (t.id === 0) ? t.id : 0, tag: t}))} - onDelete={i => helpers.remove(i)} - onInput={onTagInput} - /> - )} - -
- - - - -
} + + + + + + } -
- ); +
+ ); }; export default SearchBar; \ No newline at end of file diff --git a/src/components/TagEditor/TagEditor.scss b/src/components/TagEditor/TagEditor.scss index 90ae6d9f..34590d04 100644 --- a/src/components/TagEditor/TagEditor.scss +++ b/src/components/TagEditor/TagEditor.scss @@ -12,11 +12,10 @@ display: inline-block; box-sizing: border-box; margin: 0 6px 6px 0; - padding: 6px 8px; border: 1px solid #d1d1d1; border-radius: 4px; background: #f1f1f1; - font-size: 1.063rem; + font-size: 0.763rem; font-weight: 400; color: map-get($colors, text-primary); line-height: inherit; @@ -30,7 +29,6 @@ .react-tags-search { position: relative; display: inline-block; - padding: 7px 2px; margin-bottom: 6px; max-width: 100%; } @@ -53,7 +51,6 @@ .react-tags-suggestions li { border-bottom: 1px solid #d1d1d1; - padding: 6px 8px; } .react-tags-suggestions li mark { diff --git a/src/components/TagEditor/TagEditor.tsx b/src/components/TagEditor/TagEditor.tsx index fde4635c..066ec0d6 100644 --- a/src/components/TagEditor/TagEditor.tsx +++ b/src/components/TagEditor/TagEditor.tsx @@ -5,14 +5,15 @@ import {} from './TagEditor.scss'; interface Props { tags: Tag[]; tagSuggestions: Tag[]; - mes: string | undefined; + mes?: string; editing: boolean; + classNames?: any; onAdd(tag: Tag): void; onDelete(i: number): void; onInput(name: string): void; } -export function TagEditor({tags, tagSuggestions, onInput, onAdd, onDelete, mes, editing}: Props) { +export function TagEditor({tags, tagSuggestions, onInput, onAdd, onDelete, mes, editing, classNames}: Props) { const [className, setClassName] = useState('disabled'); useEffect(() => { @@ -32,17 +33,17 @@ export function TagEditor({tags, tagSuggestions, onInput, onAdd, onDelete, mes, return valid; } - const classNames: ClassNames = { - root: 'react-tags', - rootFocused: 'is-focused', - selected: 'react-tags-selected', - selectedTag: 'react-tags-selected-tag', - selectedTagName: 'react-tags-selected-tag-name', - search: 'react-tags-search', - searchInput: 'react-tags-search-input', - suggestions: 'react-tags-suggestions', - suggestionActive: 'is-active', - suggestionDisabled: 'is-disabled' + const defaultClassNames: ClassNames = { + root: classNames?.root || 'react-tags', + rootFocused: classNames?.rootFocused || 'is-focused', + selected: classNames?.selected || 'react-tags-selected', + selectedTag: classNames?.selectedTag || 'react-tags-selected-tag', + selectedTagName: classNames?.selectedTagName || 'react-tags-selected-tag-name', + search: classNames?.search || 'react-tags-search', + searchInput: classNames?.searchInput || 'react-tags-search-input', + suggestions: classNames?.suggestions || 'react-tags-suggestions', + suggestionActive: classNames?.suggestionActive || 'is-active', + suggestionDisabled: classNames?.suggestionDisabled || 'is-disabled' }; return ( @@ -55,7 +56,7 @@ export function TagEditor({tags, tagSuggestions, onInput, onAdd, onDelete, mes, suggestions={tagSuggestions} allowNew={true} onValidate={onValidate} - classNames={classNames} + classNames={defaultClassNames} /> ); diff --git a/src/pages/ModuleEditor/ModuleEditor.tsx b/src/pages/ModuleEditor/ModuleEditor.tsx index 63b229e4..43648ef1 100644 --- a/src/pages/ModuleEditor/ModuleEditor.tsx +++ b/src/pages/ModuleEditor/ModuleEditor.tsx @@ -69,8 +69,8 @@ export default function ModuleEditor({match: {params: {moduleId}}}: Props) { setMessage(undefined); } - async function onTagInput(input: string) { - setTagSuggestions(await getTags(input)); + async function onTagInput() { + setTagSuggestions(await getTags()); } React.useEffect(() => { From 6e510a011b359806b61f8eb117e6a421103f2b7a Mon Sep 17 00:00:00 2001 From: Zaid Hussain Date: Tue, 14 Dec 2021 21:09:47 -0500 Subject: [PATCH 13/25] Fix code smell --- src/components/SearchBar/SearchBar.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/SearchBar/SearchBar.tsx b/src/components/SearchBar/SearchBar.tsx index d4b28f5c..ee04c7b3 100644 --- a/src/components/SearchBar/SearchBar.tsx +++ b/src/components/SearchBar/SearchBar.tsx @@ -35,7 +35,6 @@ const SearchBar = (props: any) => { const onDescriptionChange = (e: any) => { const desc = e.target.value; setDescription(desc); - // props.showModules(searchValue); }; const onDifficultyChange = (e: any) => { From 67b869307ead06e4931b7386946ce4185f2f7d1b Mon Sep 17 00:00:00 2001 From: Zaid Hussain Date: Mon, 3 Jan 2022 11:31:37 -0500 Subject: [PATCH 14/25] Add module difficulty --- .../CreatorsModuleCard.module.scss | 2 +- .../CreatorsModuleCard/CreatorsModuleCard.tsx | 3 ++- src/components/ModuleCard/ModuleCard.tsx | 7 ++++--- src/pages/ModuleEditor/ModuleEditor.tsx | 18 ++++++++++++++---- src/pages/ModuleEditor/ModuleEditorSchema.ts | 7 ++++--- src/pages/PublicModule/PublicModule.tsx | 3 ++- src/pages/UserModulePage/UserModulePage.tsx | 3 ++- src/types/Module.ts | 2 ++ 8 files changed, 31 insertions(+), 14 deletions(-) diff --git a/src/components/CreatorsModuleCard/CreatorsModuleCard.module.scss b/src/components/CreatorsModuleCard/CreatorsModuleCard.module.scss index e314b00b..3b87a3b5 100644 --- a/src/components/CreatorsModuleCard/CreatorsModuleCard.module.scss +++ b/src/components/CreatorsModuleCard/CreatorsModuleCard.module.scss @@ -1,5 +1,5 @@ .card { - margin-bottom: 35px; + margin-bottom: 25px; } .card-footer a { diff --git a/src/components/CreatorsModuleCard/CreatorsModuleCard.tsx b/src/components/CreatorsModuleCard/CreatorsModuleCard.tsx index 0991d99f..cba0ac6d 100644 --- a/src/components/CreatorsModuleCard/CreatorsModuleCard.tsx +++ b/src/components/CreatorsModuleCard/CreatorsModuleCard.tsx @@ -6,7 +6,7 @@ import {WebState} from '../../redux/types/WebState'; import {connect} from 'react-redux'; import {isAuthenticated} from '../../redux/selectors/entities'; import {UserModule} from '../../types/UserModule'; -import {getLocalDateTimeString} from '../../util'; +import {getLocalDateTimeString, getModuleDifficultyLabel} from '../../util'; import CopyToClipboard from 'react-copy-to-clipboard'; import {Link} from 'react-router-dom'; import {IconButton} from '../util/IconButton/IconButton'; @@ -35,6 +35,7 @@ class CreatorsModuleCardComponent extends Component {
Published: {`${module.published}`}
Module type: {`${module.type}`}
+ Difficulty: {`${getModuleDifficultyLabel(module.difficulty)}`}
Share Link { diff --git a/src/components/ModuleCard/ModuleCard.tsx b/src/components/ModuleCard/ModuleCard.tsx index bebe2d5d..043e4756 100644 --- a/src/components/ModuleCard/ModuleCard.tsx +++ b/src/components/ModuleCard/ModuleCard.tsx @@ -7,7 +7,7 @@ import {connect} from 'react-redux'; import {isAuthenticated} from '../../redux/selectors/entities'; import {Link} from 'react-router-dom'; import {UserModule} from '../../types/UserModule'; -import {getLocalDateTimeString} from '../../util'; +import {getLocalDateTimeString, getModuleDifficultyLabel} from '../../util'; interface ModuleCardProps extends ReturnType { module: Module|UserModule; @@ -33,6 +33,7 @@ class ModuleCardComponent extends Component { {getLocalDateTimeString(module.updatedAt)} + {getModuleDifficultyLabel(module.difficulty)} {this.getStartButton()} @@ -44,11 +45,11 @@ class ModuleCardComponent extends Component { if (this.props.buttonLink) { return ( - + ); } else if (this.props.buttonAction) { - return (); + return (); } else { return null; } diff --git a/src/pages/ModuleEditor/ModuleEditor.tsx b/src/pages/ModuleEditor/ModuleEditor.tsx index 43648ef1..dd3f1738 100644 --- a/src/pages/ModuleEditor/ModuleEditor.tsx +++ b/src/pages/ModuleEditor/ModuleEditor.tsx @@ -14,7 +14,7 @@ import {ModuleForm} from '../../types/editorTypes'; import {makeModuleForm} from '../../factories'; import {DropdownInput} from '../../components/util/DropdownInput/DropdownInput'; import {DropdownOption} from '../../components/util/SearchableDropdown/SearchableDropdown'; -import {getModuleShareLink, ModuleType} from '../../types/Module'; +import {getModuleShareLink, ModuleDifficulty, ModuleType} from '../../types/Module'; import {Redirect, RouteComponentProps} from 'react-router'; import {getModuleForEditor, getTags, saveModule} from '../../api'; import {HorizontallyCenteredSpinner} from '../../components/util/HorizonallyCenteredSpinner'; @@ -33,6 +33,12 @@ const moduleTypeOptions: DropdownOption[] = [ {value: 'MultiUser', label: 'Multi User'} ]; +const moduleDifficultyOptions: DropdownOption[] = [ + {value: 1, label: 'Easy'}, + {value: 2, label: 'Medium'}, + {value: 3, label: 'Hard'} +]; + type Props = RouteComponentProps<{ moduleId?: string }>; export default function ModuleEditor({match: {params: {moduleId}}}: Props) { @@ -69,8 +75,8 @@ export default function ModuleEditor({match: {params: {moduleId}}}: Props) { setMessage(undefined); } - async function onTagInput() { - setTagSuggestions(await getTags()); + async function onTagInput(input: string) { + setTagSuggestions(await getTags(input)); } React.useEffect(() => { @@ -125,7 +131,7 @@ export default function ModuleEditor({match: {params: {moduleId}}}: Props) { { !editing && ( Share Link - {getModuleShareLink(values.specialCode)} + {getModuleShareLink(values.specialCode)} )} @@ -139,6 +145,10 @@ export default function ModuleEditor({match: {params: {moduleId}}}: Props) { Type ('type')} dropdownData={moduleTypeOptions} disabled={!editing}/> + + Difficulty + ('difficulty')} dropdownData={moduleDifficultyOptions} disabled={!editing}/> + Module Description ('description')} placeholder='Description' type='textarea' disabled={!editing}/> diff --git a/src/pages/ModuleEditor/ModuleEditorSchema.ts b/src/pages/ModuleEditor/ModuleEditorSchema.ts index cb041245..2e042a2e 100644 --- a/src/pages/ModuleEditor/ModuleEditorSchema.ts +++ b/src/pages/ModuleEditor/ModuleEditorSchema.ts @@ -1,6 +1,6 @@ -import {array, boolean, number, object, string, StringSchema} from 'yup'; +import {array, boolean, number, NumberSchema, object, string, StringSchema} from 'yup'; import {ModuleForm} from '../../types/editorTypes'; -import {ModuleType} from '../../types/Module'; +import {ModuleDifficulty, ModuleType} from '../../types/Module'; export const ModuleEditorSchema = object({ @@ -14,5 +14,6 @@ export const ModuleEditorSchema = object({ type: string() as StringSchema, published: boolean(), labs: array(), - moduleTags: array() + moduleTags: array(), + difficulty: number() as NumberSchema }); diff --git a/src/pages/PublicModule/PublicModule.tsx b/src/pages/PublicModule/PublicModule.tsx index e580b947..d526631a 100644 --- a/src/pages/PublicModule/PublicModule.tsx +++ b/src/pages/PublicModule/PublicModule.tsx @@ -33,7 +33,8 @@ class PublicModule extends Component { userId: 0, specialCode: '', type: 'SingleUser', - updatedAt: '' + updatedAt: '', + difficulty: 0 }, startingModule: false }; diff --git a/src/pages/UserModulePage/UserModulePage.tsx b/src/pages/UserModulePage/UserModulePage.tsx index bdacf139..0d8d19ab 100644 --- a/src/pages/UserModulePage/UserModulePage.tsx +++ b/src/pages/UserModulePage/UserModulePage.tsx @@ -40,7 +40,8 @@ class UserModulePage extends Component Date: Mon, 3 Jan 2022 13:54:30 -0500 Subject: [PATCH 15/25] Update TagEditor to allow custom styles --- src/components/TagEditor/TagEditor.scss | 7 ++++- src/components/TagEditor/TagEditor.tsx | 40 ++++++++++++------------- 2 files changed, 26 insertions(+), 21 deletions(-) diff --git a/src/components/TagEditor/TagEditor.scss b/src/components/TagEditor/TagEditor.scss index 34590d04..379732e5 100644 --- a/src/components/TagEditor/TagEditor.scss +++ b/src/components/TagEditor/TagEditor.scss @@ -2,6 +2,8 @@ .react-tags.is-focused { border-color: #b1b1b1; + box-shadow: 0 0 0 0.2rem rgba(69, 130, 236, 0.25); + transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out, -webkit-box-shadow 0.15s ease-in-out; } .react-tags-selected { @@ -12,10 +14,11 @@ display: inline-block; box-sizing: border-box; margin: 0 6px 6px 0; + padding: 6px 8px; border: 1px solid #d1d1d1; border-radius: 4px; background: #f1f1f1; - font-size: 0.763rem; + font-size: 1.063rem; font-weight: 400; color: map-get($colors, text-primary); line-height: inherit; @@ -29,6 +32,7 @@ .react-tags-search { position: relative; display: inline-block; + padding: 7px 2px; margin-bottom: 6px; max-width: 100%; } @@ -51,6 +55,7 @@ .react-tags-suggestions li { border-bottom: 1px solid #d1d1d1; + padding: 6px 8px; } .react-tags-suggestions li mark { diff --git a/src/components/TagEditor/TagEditor.tsx b/src/components/TagEditor/TagEditor.tsx index 066ec0d6..14c80b42 100644 --- a/src/components/TagEditor/TagEditor.tsx +++ b/src/components/TagEditor/TagEditor.tsx @@ -2,29 +2,29 @@ import React, {useEffect, useState} from 'react'; import ReactTags, {Tag, ClassNames} from 'react-tag-autocomplete'; import {} from './TagEditor.scss'; -interface Props { +export interface Props { tags: Tag[]; - tagSuggestions: Tag[]; + tagSuggestions?: Tag[]; mes?: string; editing: boolean; classNames?: any; onAdd(tag: Tag): void; onDelete(i: number): void; - onInput(name: string): void; + onInput?(name: string): void; } export function TagEditor({tags, tagSuggestions, onInput, onAdd, onDelete, mes, editing, classNames}: Props) { - const [className, setClassName] = useState('disabled'); + const [status, setStatus] = useState('disabled'); useEffect(() => { if (mes === 'success' && editing) - setClassName('enabled success'); + setStatus('enabled success'); else if (mes === 'success' && !editing) - setClassName('disabled success'); + setStatus('disabled success'); else if (!editing) - setClassName('disabled'); + setStatus('disabled'); else - setClassName('enabled'); + setStatus('enabled'); }, [mes, editing]); function onValidate(tag: Tag) { @@ -34,20 +34,20 @@ export function TagEditor({tags, tagSuggestions, onInput, onAdd, onDelete, mes, } const defaultClassNames: ClassNames = { - root: classNames?.root || 'react-tags', - rootFocused: classNames?.rootFocused || 'is-focused', - selected: classNames?.selected || 'react-tags-selected', - selectedTag: classNames?.selectedTag || 'react-tags-selected-tag', - selectedTagName: classNames?.selectedTagName || 'react-tags-selected-tag-name', - search: classNames?.search || 'react-tags-search', - searchInput: classNames?.searchInput || 'react-tags-search-input', - suggestions: classNames?.suggestions || 'react-tags-suggestions', - suggestionActive: classNames?.suggestionActive || 'is-active', - suggestionDisabled: classNames?.suggestionDisabled || 'is-disabled' + // add any additional CSS class names defined in props to existing classes + root: `react-tags ${classNames?.root? classNames.root : ''}`, + rootFocused: `is-focused ${classNames?.rootFocused? classNames.rootFocused : ''}`, + selected: `react-tags-selected ${classNames?.selected? classNames.selected : ''}`, + selectedTag: `react-tags-selected-tag ${classNames?.selectedTag? classNames.selectedTag : ''}`, + selectedTagName: `react-tags-selected-tag-name ${classNames?.selectedTagName? classNames.selectedTagName : ''}`, + search: `react-tags-search ${classNames?.search? classNames.search : ''}`, + searchInput: `react-tags-search-input ${classNames?.searchInput? classNames.searchInput : ''}`, + suggestions: `react-tags-suggestions ${classNames?.suggestions? classNames.suggestions : ''}`, + suggestionActive: `is-active ${classNames?.suggestionActive? classNames.suggestionActive : ''}`, + suggestionDisabled: `is-disabled ${classNames?.suggestionDisabled? classNames.suggestionDisbled : ''}` }; - return ( -
+
Date: Mon, 3 Jan 2022 13:58:35 -0500 Subject: [PATCH 16/25] Make search options type --- src/types/SearchOptions.tsx | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 src/types/SearchOptions.tsx diff --git a/src/types/SearchOptions.tsx b/src/types/SearchOptions.tsx new file mode 100644 index 00000000..6413e106 --- /dev/null +++ b/src/types/SearchOptions.tsx @@ -0,0 +1,10 @@ +import {Tag} from 'react-tag-autocomplete'; +import {Entity} from './Entity'; +import {ModuleDifficulty} from './Module'; + +export interface SearchOptions extends Entity { + title: string; + description: string; + difficulty: ModuleDifficulty; + tags: Tag[]; +} From 87fabba3e5b16dd01c6eb84a9bab102d75828ebd Mon Sep 17 00:00:00 2001 From: Zaid Hussain Date: Mon, 3 Jan 2022 14:20:54 -0500 Subject: [PATCH 17/25] Update modules search to display tags --- .../ModulesSearch/ModulesSearch.module.scss | 78 +++++ .../ModulesSearch/ModulesSearch.tsx | 309 ++++++++++++++++++ .../SearchBar/SearchBar.module.scss | 57 ---- src/components/SearchBar/SearchBar.tsx | 157 --------- 4 files changed, 387 insertions(+), 214 deletions(-) create mode 100644 src/components/ModulesSearch/ModulesSearch.module.scss create mode 100644 src/components/ModulesSearch/ModulesSearch.tsx delete mode 100644 src/components/SearchBar/SearchBar.module.scss delete mode 100644 src/components/SearchBar/SearchBar.tsx diff --git a/src/components/ModulesSearch/ModulesSearch.module.scss b/src/components/ModulesSearch/ModulesSearch.module.scss new file mode 100644 index 00000000..ca0d7947 --- /dev/null +++ b/src/components/ModulesSearch/ModulesSearch.module.scss @@ -0,0 +1,78 @@ +.search-bar { + padding: 4px 12px; + height: auto; + min-width: 18rem; + border: 1px solid #343a40; + border-radius: 22px; + box-shadow: inset 0 -2px 0 #343a40; +} + +.difficulty-dropdown { + font-size: 0.930125rem; + border-radius: 0.25rem; + background-color: #FFF; + color: #868E96; + border: 1px solid rgba(0,0,0,0.1); +} + +.icon { + display: flex; + justify-content: center; +} + +.icon:hover { + cursor: pointer; + color: maroon; +} + +.search-options { + position: absolute; + z-index: 1; + top: calc(100% + 6px); + right: 0; + width: 28rem; + padding: 24px; + background-color: #FFF; + border-radius: 0.25rem; + box-shadow: 0 0 8px rgb(175, 175, 175); +} + +.search-input { + height: calc(1.5em + 1rem + 2px); + min-width: 14rem; +} + +.input-row { + margin-bottom: 0.75rem !important; +} + +div .tags-no-border.tags-no-border { + border: none; + padding: 3px; +} + +div .tags.tags { + height: calc(1.5em + 1rem + 2px); + padding: 4px; + min-width: 110px; +} + +.selected-tag.selected-tag { + padding: 1px 3px; + font-size: 0.930125rem; + margin: 2px; +} + +.tags-search.tags-search { + padding: 3px 3px; + margin: 0; +} + +.tags-input.tags-input { + width: auto !important; + display: none; // to disable adding tags to search term +} + +.tags-suggestions.tags-suggestions { + min-width: 8rem; +} \ No newline at end of file diff --git a/src/components/ModulesSearch/ModulesSearch.tsx b/src/components/ModulesSearch/ModulesSearch.tsx new file mode 100644 index 00000000..5f264835 --- /dev/null +++ b/src/components/ModulesSearch/ModulesSearch.tsx @@ -0,0 +1,309 @@ +import {faSlidersH, faSearch} from '@fortawesome/free-solid-svg-icons'; +import React, {useEffect, useRef, useState} from 'react'; +import {Tag} from 'react-tag-autocomplete'; +import styles from './ModulesSearch.module.scss'; +import {Form, Row, Col, Button, Container} from 'react-bootstrap'; +import ToolTip from '../util/ToolTip'; +import {getTags, searchEditorsModules, searchModules, + searchOptionsEditorsModules, searchOptionsModules, + searchOptionsUserModules, searchUserModules +} from '../../api'; +import {TagEditor} from '../TagEditor/TagEditor'; +import {FontAwesomeIcon} from '@fortawesome/react-fontawesome'; +import {Formik} from 'formik'; +import {SearchOptions} from 'types/SearchOptions'; +import {makeSearchInputs} from 'factories'; +import {Module, ModuleDifficulty} from 'types/Module'; +import {UserModule} from 'types/UserModule'; +import {getModuleDifficultyLabel, moduleDifficultyOptions} from '../../util'; +import {connect} from 'react-redux'; +import {WebState} from 'redux/types/WebState'; +import {isAdmin, isAuthenticated, isCreator} from 'redux/selectors/entities'; +import {RoutePaths} from 'router/RoutePaths'; +import Select from 'react-select'; + +export type Modules = Module[] | UserModule[]; + +interface ModulesSearchProps extends ReturnType { + loadModules(): void; + showSearchedModules(modules: Modules): void; +} + +const ModulesSearch = (props: ModulesSearchProps) => { + + const initialState = makeSearchInputs(); + + const [searchInputs, setSearchInputs] = useState(initialState); + const [searchTerm, setSearchTerm] = useState(''); + const [searchTermTags, setSearchTermTags] = React.useState([]); + const [searchOptionsOn, setSearchOptionsOn] = useState(false); + const [tagSuggestions, setTagSuggestions] = React.useState([]); + + const focusRef = useRef(); + + const showSearchedModules = async (searchValue: string) => { + if (searchValue.length !== 0) { + let modules: Modules = []; + if ( (props.admin || props.creator) && window.location.pathname === RoutePaths.contentCreator ) { + modules = await searchEditorsModules(searchValue); + } + else if (props.authenticated && window.location.pathname === RoutePaths.myModules) { + modules = await searchUserModules(searchValue); + } + else { + modules = await searchModules(searchValue); + } + props.showSearchedModules(modules); + } + else { + props.loadModules(); + } + }; + + const showSearchedOptionsModules = async (searchParams: SearchOptions) => { + if (searchParams) { + let modules: Modules = []; + if ( (props.admin || props.creator) && window.location.pathname === RoutePaths.contentCreator ) { + modules = await searchOptionsEditorsModules(searchParams); + } + else if (props.authenticated && window.location.pathname === RoutePaths.myModules) { + modules = await searchOptionsUserModules(searchParams); + } + else { + modules = await searchOptionsModules(searchParams); + } + props.showSearchedModules(modules); + } + else { + props.loadModules(); + } + }; + + const onSearchTermChange = (e: any) => { + const searchValue = e.target.value; + setSearchTerm(searchValue); + + if (!searchOptionsOn) { + showSearchedModules(searchValue); + } + }; + + const onTitleChange = (e: any) => { + const title = e.target.value; + setSearchInputs({ ...searchInputs, title: title }); + }; + + const onDescriptionChange = (e: any) => { + const desc = e.target.value; + setSearchInputs({ ...searchInputs, description: desc }); + }; + + const onDifficultyChange = (option: any) => { + const diff = option.value as ModuleDifficulty; + setSearchInputs({ ...searchInputs, difficulty: diff }); + }; + + const onTagAdd = (newTag: Tag) => { + setSearchTermTags(searchTermTags.concat(newTag)); + setSearchInputs({ ...searchInputs, tags: [...searchInputs.tags, newTag] }); + }; + + const onTagDelete = (idx: number) => { + const tagTBD = searchInputs.tags[idx]; + setSearchTermTags(searchTermTags.filter(tag => tag.name !== tagTBD.name)); + setSearchInputs({ ...searchInputs, tags: searchInputs.tags.filter(tag => tag.name !== tagTBD.name) }); + }; + + const onSearchTermTagDelete = (idx: number) => { + const tagTBD = searchTermTags[idx]; + + if (tagTBD.id === 'title') { + setSearchInputs({ ...searchInputs, title: '' }); + } else if (tagTBD.id === 'desc') { + setSearchInputs({ ...searchInputs, description: '' }); + } else if (tagTBD.id === 'diff') { + setSearchInputs({ ...searchInputs, difficulty: 0 }); + } else { + setSearchInputs({ ...searchInputs, tags: searchInputs.tags.filter(tag => tag.name !== tagTBD.name) }); + } + }; + + async function handleTagSuggestions(input: string) { + setTagSuggestions(await getTags(input)); + } + + const onSearchOptionsOn = () => { + setSearchOptionsOn(!searchOptionsOn); + }; + + const onBlur = (e: any) => { + if (!e.relatedTarget || !e.currentTarget.contains(e.relatedTarget)) { + setSearchOptionsOn(false); + } + }; + + const onReset = () => { + setSearchInputs(initialState); + props.loadModules(); + }; + + const onSubmit = () => { + showSearchedOptionsModules(searchInputs); + }; + + useEffect(() => { + function updateSearchTermTags() { + let tags: Tag[] = []; + if (searchInputs.title.length !== 0) { + tags.push({ id: 'title', name: `Title: ${searchInputs.title.substring(0, 10)}` }); + } + if (searchInputs.description.length !== 0) { + tags.push({ id: 'desc', name: `Description: ${searchInputs.description.substring(0, 15)}` }); + } + if (searchInputs.difficulty !== 0) { + tags.push({ id: 'diff', name: `Difficulty: ${getModuleDifficultyLabel(searchInputs.difficulty)}` }); + } + tags = tags.concat(searchInputs.tags); + + setSearchTermTags(tags); + } + updateSearchTermTags(); + }, [searchInputs]); + + return ( + + {({ handleSubmit, handleReset }) => ( +
focusRef.current = ref} + onBlur={onBlur} + tabIndex={-1} + onReset={handleReset} + onSubmit={handleSubmit} + > + + + + + + + + {searchOptionsOn ? + {return;}} // should not directly add tags to search term + onDelete={onSearchTermTagDelete} + editing={true} + classNames={{ + root: styles['tags-no-border'], + rootFocused: styles['tags-focused'], + selectedTag: styles['selected-tag'], + search: styles['tags-search'], + suggestions: styles['tags-suggestions'], + searchInput: styles['tags-input'] + }} + /> : + + } + + + + + + + + + { searchOptionsOn && + + + Title + + + + Description + + + + Difficulty +