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