From bc3c178592a6ac6827778f457bd447a27333328f Mon Sep 17 00:00:00 2001 From: danieldeng2 <danieldeng223@gmail.com> Date: Wed, 19 Aug 2020 21:28:32 +0100 Subject: [PATCH] Refactor Module resources to accomodated render prop refactoring --- .../molecules/NewQuickAccessView/index.tsx | 85 ++++++++++ .../NewQuickAccessView/style.module.scss | 34 ++++ .../molecules/SelectionView/index.tsx | 146 ++++++++++++++++++ .../molecules/SelectionView/style.module.scss | 34 ++++ .../pages/ModuleResources/index.tsx | 117 ++++++++------ 5 files changed, 366 insertions(+), 50 deletions(-) create mode 100644 src/components/molecules/NewQuickAccessView/index.tsx create mode 100644 src/components/molecules/NewQuickAccessView/style.module.scss create mode 100644 src/components/molecules/SelectionView/index.tsx create mode 100644 src/components/molecules/SelectionView/style.module.scss diff --git a/src/components/molecules/NewQuickAccessView/index.tsx b/src/components/molecules/NewQuickAccessView/index.tsx new file mode 100644 index 000000000..1903c5e60 --- /dev/null +++ b/src/components/molecules/NewQuickAccessView/index.tsx @@ -0,0 +1,85 @@ +import React from "react"; +import styles from "./style.module.scss"; + +import classNames from "classnames"; +import Row from "react-bootstrap/esm/Row"; +import Col from "react-bootstrap/esm/Col"; +import FileCard from "components/atoms/FileCard"; +import { faSquare, faCheckSquare } from "@fortawesome/free-regular-svg-icons"; +import { + faFileAlt, + IconDefinition, + faFilePdf, + faFileVideo, +} from "@fortawesome/free-solid-svg-icons"; +import { SelectionProps } from "components/molecules/SelectionView"; + +const NewQuickAccessView: React.FC<{ select: SelectionProps }> = ({ + select, +}) => { + return ( + <> + <Row + className={classNames( + "d-flex", + "flex-row", + "flex-nowrap", + styles.quickAccessRow + )} + > + {select.quickAccessItems.map(({ title, type, tags, id }) => { + let normalIcon: IconDefinition; + switch (type) { + case "pdf": + normalIcon = faFilePdf; + break; + case "video": + normalIcon = faFileVideo; + break; + default: + normalIcon = faFileAlt; + break; + } + return ( + <Col + xs={7} + sm={5} + md={5} + lg={4} + xl={3} + key={id} + style={{ + marginBottom: ".5rem", + marginTop: ".5rem", + paddingLeft: "10px", + paddingRight: "10px", + }} + > + <FileCard + title={title} + type={type} + tags={tags} + icon={ + select.isAnySelected() || select.state.isHoveringOver[id] + ? select.state.isSelected[id] + ? faCheckSquare + : faSquare + : normalIcon + } + onClick={() => select.handleCardClick(id)} + onIconClick={(e) => { + e.stopPropagation(); + select.handleIconClick(id); + }} + onMouseOver={() => select.handleMouseOver(id)} + onMouseOut={() => select.handleMouseOut(id)} + /> + </Col> + ); + })} + </Row> + </> + ); +}; + +export default NewQuickAccessView; diff --git a/src/components/molecules/NewQuickAccessView/style.module.scss b/src/components/molecules/NewQuickAccessView/style.module.scss new file mode 100644 index 000000000..be0bbee19 --- /dev/null +++ b/src/components/molecules/NewQuickAccessView/style.module.scss @@ -0,0 +1,34 @@ +@import "assets/scss/custom"; + +.quickAccessRow { + scrollbar-width: thin; + scrollbar-color: $white $white; + margin-top: 10px; + overflow-y: visible; + overflow-x: auto; + margin-right: -10px; + margin-left: -10px; + // margin-left: 0; // leave space before card +} + +.quickAccessRow::-webkit-scrollbar { + width: 1rem; + height: 0.5rem; +} +.quickAccessRow::-webkit-scrollbar-track { + background: $white; + margin-left: 10px; + margin-right: 10px; +} +.quickAccessRow::-webkit-scrollbar-thumb { + background-color: $white; + border-radius: .5rem; +} + +.quickAccessRow:hover { + scrollbar-color: $gray-400 $white; +} + +.quickAccessRow:hover::-webkit-scrollbar-thumb { + background-color: $gray-400; +} diff --git a/src/components/molecules/SelectionView/index.tsx b/src/components/molecules/SelectionView/index.tsx new file mode 100644 index 000000000..3cdffc8e1 --- /dev/null +++ b/src/components/molecules/SelectionView/index.tsx @@ -0,0 +1,146 @@ +import React from "react"; +import classNames from "classnames"; +import { api, methods } from "../../../constants/routes"; +import { request } from "../../../utils/api"; +import ResourceSectionHeader from "../ResourceSectionHeader"; +import { faSquare, faCheckSquare } from "@fortawesome/free-regular-svg-icons"; + +export interface SelectionProps { + quickAccessItems: { + title: string; + type: string; + tags: string[]; + id: number; + }[]; + state: MyState; + isAnySelected: () => boolean; + handleCardClick: (id: number) => void; + handleIconClick: (id: number) => void; + handleMouseOver: (id: number) => void; + handleMouseOut: (id: number) => void; +} + +export interface MyProps { + quickAccessItems: { + title: string; + type: string; + tags: string[]; + id: number; + }[]; + moduleCode?: string; + render: (selection: SelectionProps) => any; +} + +type idBooleanMap = { [key: number]: boolean }; + +interface MyState { + isSelected: idBooleanMap; + isHoveringOver: idBooleanMap; +} + +class SelectionView extends React.Component<MyProps, MyState> { + constructor(props: MyProps) { + super(props); + this.state = { isSelected: [], isHoveringOver: [] }; + } + + componentDidMount() { + let isSelected: idBooleanMap = []; + let isHoveringOver: idBooleanMap = []; + this.props.quickAccessItems.forEach(({ id }: { id: number }) => { + isSelected[id] = false; + isHoveringOver[id] = false; + }); + this.setState({ isSelected, isHoveringOver }); + } + + isAnySelected(): boolean { + let items = this.props.quickAccessItems; + let isSelected = this.state.isSelected; + for (let item in items) { + if (isSelected[items[item].id]) { + return true; + } + } + return false; + } + + isAllSelected(): boolean { + let items = this.props.quickAccessItems; + let isSelected = this.state.isSelected; + for (let item in items) { + if (!isSelected[items[item].id]) { + return false; + } + } + return true; + } + + handleIconClick(id: number) { + let isSelected = JSON.parse(JSON.stringify(this.state.isSelected)); + let isHoveringOver = JSON.parse(JSON.stringify(this.state.isHoveringOver)); + isSelected[id] = !isSelected[id]; + isHoveringOver[id] = false; + this.setState({ isSelected, isHoveringOver }); + } + + handleDownloadClick() {} + + handleSelectAllClick() { + let items = this.props.quickAccessItems; + let isSelected = JSON.parse(JSON.stringify(this.state.isSelected)); + let setValue = !this.isAllSelected(); + for (let item in items) { + isSelected[items[item].id] = setValue; + } + this.setState({ isSelected }); + } + + handleCardClick(id: number) { + if (this.isAnySelected()) { + this.handleIconClick(id); + return; + } + } + + handleMouseOver(id: number) { + let isHoveringOver = JSON.parse(JSON.stringify(this.state.isHoveringOver)); + isHoveringOver[id] = true; + this.setState({ isHoveringOver }); + } + + handleMouseOut(id: number) { + let isHoveringOver = JSON.parse(JSON.stringify(this.state.isHoveringOver)); + isHoveringOver[id] = false; + this.setState({ isHoveringOver }); + } + + render() { + let selection: SelectionProps = { + quickAccessItems: this.props.quickAccessItems, + state: this.state, + isAnySelected: () => this.isAnySelected(), + handleCardClick: (id: number) => this.handleCardClick(id), + handleIconClick: (id: number) => this.handleIconClick(id), + handleMouseOver: (id: number) => this.handleMouseOver(id), + handleMouseOut: (id: number) => this.handleMouseOut(id), + }; + + return ( + <> + <ResourceSectionHeader + heading="Quick Access" + onDownloadClick={() => this.handleDownloadClick()} + showDownload={this.isAnySelected()} + onSelectAllClick={() => this.handleSelectAllClick()} + selectAllIcon={this.isAllSelected() ? faCheckSquare : faSquare} + checkBoxColur={this.isAnySelected() ? "#495057" : "#dee2e6"} + /> + + {this.props.render(selection)} + </> + ); + } +} + +export default SelectionView; diff --git a/src/components/molecules/SelectionView/style.module.scss b/src/components/molecules/SelectionView/style.module.scss new file mode 100644 index 000000000..be0bbee19 --- /dev/null +++ b/src/components/molecules/SelectionView/style.module.scss @@ -0,0 +1,34 @@ +@import "assets/scss/custom"; + +.quickAccessRow { + scrollbar-width: thin; + scrollbar-color: $white $white; + margin-top: 10px; + overflow-y: visible; + overflow-x: auto; + margin-right: -10px; + margin-left: -10px; + // margin-left: 0; // leave space before card +} + +.quickAccessRow::-webkit-scrollbar { + width: 1rem; + height: 0.5rem; +} +.quickAccessRow::-webkit-scrollbar-track { + background: $white; + margin-left: 10px; + margin-right: 10px; +} +.quickAccessRow::-webkit-scrollbar-thumb { + background-color: $white; + border-radius: .5rem; +} + +.quickAccessRow:hover { + scrollbar-color: $gray-400 $white; +} + +.quickAccessRow:hover::-webkit-scrollbar-thumb { + background-color: $gray-400; +} diff --git a/src/components/pages/ModuleResources/index.tsx b/src/components/pages/ModuleResources/index.tsx index 2e9403934..5943f9048 100644 --- a/src/components/pages/ModuleResources/index.tsx +++ b/src/components/pages/ModuleResources/index.tsx @@ -31,6 +31,10 @@ export interface ResourceState { } class ModuleResources extends React.Component<ResourcesProps, ResourceState> { + moduleCode = this.props.moduleID.startsWith("CO") + ? this.props.moduleID.slice(2) + : this.props.moduleID; + constructor(props: ResourcesProps) { super(props); this.state = { @@ -41,10 +45,6 @@ class ModuleResources extends React.Component<ResourcesProps, ResourceState> { }; } - moduleCode = this.props.moduleID.startsWith("CO") - ? this.props.moduleID.slice(2) - : this.props.moduleID; - componentDidMount() { this.setState({ isLoaded: false }); const onSuccess = (data: { json: () => Promise<any> }) => { @@ -76,10 +76,6 @@ class ModuleResources extends React.Component<ResourcesProps, ResourceState> { }); } - handleSearchTextChange(searchText: string) { - this.setState({ searchText: searchText }); - } - includeInSearchResult(item: Resource, searchText: string) { let rx = /([a-z]+)\(([^)]+)\)/gi; let match: RegExpExecArray | null; @@ -106,24 +102,27 @@ class ModuleResources extends React.Component<ResourcesProps, ResourceState> { } } let rest = searchText.replace(rx, "").trim(); - for (let i in tags) { - if (tags[i].indexOf(rest) !== -1) { - return true; - } + if (tags.some((tag) => tag.indexOf(rest) !== -1)) { + return false; } return title.indexOf(rest) !== -1; } - render() { - let scope = this.props.scope || ""; - let resources = this.state.resources; + getResourcesFolderView(scope: any) { + let folders: { title: string; id: number }[] = Array.from( + new Set<string>(this.state.resources.map((res: Resource) => res.folder)) + ).map((title: string, id: number) => ({ + title: title, + id: id, + })); - let quickAccessItems = resources.filter( - ({ tags, folder }) => - tags.includes("new") && (scope === "" || scope === folder) - ); + if (this.state.searchText === "" && scope === "" && folders.length > 0) { + return <ResourcesFolderView folderItems={folders} />; + } + } - let filesContent: Resource[] = resources; + getCurrentDirectoryView(scope: any) { + let filesContent: Resource[] = this.state.resources; if (scope !== "") { filesContent = filesContent.filter(({ folder }) => folder === scope); } @@ -133,43 +132,61 @@ class ModuleResources extends React.Component<ResourcesProps, ResourceState> { ); } - let folders: { title: string; id: number }[] = Array.from( - new Set<string>(resources.map((res: Resource) => res.folder)) - ).map((title: string, id: number) => ({ - title: title, - id: id, - })); + if (scope !== "" || this.state.searchText !== "") { + return ( + <CurrentDirectoryView + documentItems={filesContent} + moduleCode={this.moduleCode} + /> + ); + } + } + + getQuickAccessView(scope: any) { + let quickAccessItems = this.state.resources.filter( + ({ tags, folder }) => + tags.includes("new") && (scope === "" || scope === folder) + ); + + if ( + this.state.searchText === "" && + scope === "" && + quickAccessItems.length > 0 + ) { + return ( + <QuickAccessView + quickAccessItems={quickAccessItems} + moduleCode={this.moduleCode} + /> + ); + } + } + + getloadedItems(pageItems: JSX.Element) { + if (!this.state.isLoaded) return <>Loading...</>; + if (this.state.error) + return <> Error retrieving data: {this.state.error} </>; + return pageItems; + } + + render() { + let scope = this.props.scope || ""; + let pageItems = ( + <> + {this.getResourcesFolderView(scope)} + {this.getCurrentDirectoryView(scope)} + {this.getQuickAccessView(scope)} + </> + ); return ( <> <MyBreadcrumbs /> <SearchBox searchText={this.state.searchText} - onSearchTextChange={(text) => this.handleSearchTextChange(text)} + onSearchTextChange={(text) => this.setState({ searchText: text })} /> - {this.state.isLoaded ? ( - this.state.error ? ( - <> Error retrieving data: {this.state.error} </> - ) : ( - <> - {this.state.searchText === "" && - scope === "" && - folders.length > 0 ? ( - <ResourcesFolderView folderItems={folders} /> - ) : null} - {scope !== "" || this.state.searchText !== "" ? ( - <CurrentDirectoryView documentItems={filesContent} moduleCode={this.moduleCode}/> - ) : null} - {this.state.searchText === "" && - scope === "" && - quickAccessItems.length > 0 ? ( - <QuickAccessView quickAccessItems={quickAccessItems} moduleCode={this.moduleCode}/> - ) : null} - </> - ) - ) : ( - <>Loading...</> - )} + {this.getloadedItems(pageItems)} </> ); } -- GitLab