github.com/minio/console@v1.4.1/web-app/src/screens/Console/HelpMenu.tsx (about)

     1  // This file is part of MinIO Console Server
     2  // Copyright (c) 2023 MinIO, Inc.
     3  //
     4  // This program is free software: you can redistribute it and/or modify
     5  // it under the terms of the GNU Affero General Public License as published by
     6  // the Free Software Foundation, either version 3 of the License, or
     7  // (at your option) any later version.
     8  //
     9  // This program is distributed in the hope that it will be useful,
    10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    12  // GNU Affero General Public License for more details.
    13  //
    14  // You should have received a copy of the GNU Affero General Public License
    15  // along with this program.  If not, see <http://www.gnu.org/licenses/>.
    16  
    17  import React, { Fragment, useEffect, useRef, useState } from "react";
    18  import ReactMarkdown from "react-markdown";
    19  import styled from "styled-components";
    20  import get from "lodash/get";
    21  import {
    22    AlertCloseIcon,
    23    Box,
    24    Button,
    25    HelpIcon,
    26    HelpIconFilled,
    27    IconButton,
    28    MinIOTierIcon,
    29    TabItemProps,
    30    Tabs,
    31  } from "mds";
    32  import { useSelector } from "react-redux";
    33  import { AppState, useAppDispatch } from "../../store";
    34  import { setHelpTabName } from "../../systemSlice";
    35  import { DocItem } from "./HelpMenu.types";
    36  import HelpItem from "./HelpItem";
    37  import MoreLink from "../../common/MoreLink";
    38  
    39  const HelpMenuContainer = styled.div(({ theme }) => ({
    40    backgroundColor: get(theme, "bgColor", "#FFF"),
    41    position: "absolute",
    42    zIndex: "10",
    43    border: `${get(theme, "borderColor", "#E2E2E2")} 1px solid`,
    44    borderRadius: 4,
    45    boxShadow: "rgba(0, 0, 0, 0.1) 0px 0px 10px",
    46    width: 754,
    47    "& .tabsPanels": {
    48      padding: "15px 0 0",
    49    },
    50    "& .helpContainer": {
    51      maxHeight: 400,
    52      overflowY: "auto",
    53      "& .helpItemBlock": {
    54        padding: 5,
    55        "&:hover": {
    56          backgroundColor: get(
    57            theme,
    58            "buttons.regular.hover.background",
    59            "#E6EAEB",
    60          ),
    61        },
    62      },
    63    },
    64  }));
    65  
    66  const HelpMenu = () => {
    67    const helpTopics = require("../Console/helpTopics.json");
    68  
    69    const [helpItems, setHelpItems] = useState<DocItem[]>([]);
    70    const [headerDocs, setHeaderDocs] = useState<string | null>(null);
    71    const [helpItemsVideo, setHelpItemsVideo] = useState<DocItem[]>([]);
    72    const [headerVideo, setHeaderVideo] = useState<string | null>(null);
    73    const [helpItemsBlog, setHelpItemsBlog] = useState<DocItem[]>([]);
    74    const [headerBlog, setHeaderBlog] = useState<string | null>(null);
    75    const [helpMenuOpen, setHelpMenuOpen] = useState<boolean>(false);
    76  
    77    const systemHelpName = useSelector(
    78      (state: AppState) => state.system.helpName,
    79    );
    80  
    81    const helpTabName = useSelector(
    82      (state: AppState) => state.system.helpTabName,
    83    );
    84  
    85    const toggleHelpMenu = () => {
    86      setHelpMenuOpen(!helpMenuOpen);
    87    };
    88    const dispatch = useAppDispatch();
    89  
    90    function useOutsideAlerter(ref: any) {
    91      useEffect(() => {
    92        function handleClickOutside(event: any) {
    93          if (ref.current && !ref.current.contains(event.target)) {
    94            setHelpMenuOpen(false);
    95          }
    96        }
    97  
    98        document.addEventListener("mousedown", handleClickOutside);
    99        return () => {
   100          document.removeEventListener("mousedown", handleClickOutside);
   101        };
   102      }, [ref]);
   103    }
   104  
   105    const wrapperRef = useRef<HTMLDivElement>(null);
   106    useOutsideAlerter(wrapperRef);
   107  
   108    useEffect(() => {
   109      let docsTotal = 0;
   110      let blogTotal = 0;
   111      let videoTotal = 0;
   112      if (helpTopics[systemHelpName]) {
   113        if (helpTopics[systemHelpName]["docs"]) {
   114          setHeaderDocs(helpTopics[systemHelpName]["docs"]["header"]);
   115          setHelpItems(helpTopics[systemHelpName]["docs"]["links"]);
   116          docsTotal = helpTopics[systemHelpName]["docs"]["links"].length;
   117        }
   118  
   119        if (helpTopics[systemHelpName]["blog"]) {
   120          setHeaderBlog(helpTopics[systemHelpName]["blog"]["header"]);
   121          setHelpItemsBlog(helpTopics[systemHelpName]["blog"]["links"]);
   122          blogTotal = helpTopics[systemHelpName]["blog"]["links"].length;
   123        }
   124  
   125        if (helpTopics[systemHelpName]["video"]) {
   126          setHeaderVideo(helpTopics[systemHelpName]["video"]["header"]);
   127          setHelpItemsVideo(helpTopics[systemHelpName]["video"]["links"]);
   128          videoTotal = helpTopics[systemHelpName]["video"]["links"].length;
   129        }
   130  
   131        let autoSelect = "docs";
   132        let hadToFlip = false;
   133        // if no docs, eval video o blog
   134        if (docsTotal === 0 && headerDocs === null && helpTabName === "docs") {
   135          // if no blog, default video?
   136          if (videoTotal !== 0 || headerVideo !== null) {
   137            autoSelect = "video";
   138          } else {
   139            autoSelect = "blog";
   140          }
   141          hadToFlip = true;
   142        }
   143        if (videoTotal === 0 && headerVideo === null && helpTabName === "video") {
   144          // if no blog, default video?
   145          if (docsTotal !== 0 || headerDocs !== null) {
   146            autoSelect = "docs";
   147          } else {
   148            autoSelect = "blog";
   149          }
   150          hadToFlip = true;
   151        }
   152        if (blogTotal === 0 && headerBlog === null && helpTabName === "blog") {
   153          // if no blog, default video?
   154          if (docsTotal !== 0 || headerDocs !== null) {
   155            autoSelect = "docs";
   156          } else {
   157            autoSelect = "video";
   158          }
   159          hadToFlip = true;
   160        }
   161        if (hadToFlip) {
   162          dispatch(setHelpTabName(autoSelect));
   163        }
   164      } else {
   165        setHelpItems(helpTopics["help"]["docs"]["links"]);
   166        setHelpItemsBlog([]);
   167        setHelpItemsVideo([]);
   168      }
   169    }, [
   170      systemHelpName,
   171      helpTabName,
   172      dispatch,
   173      helpTopics,
   174      headerBlog,
   175      headerDocs,
   176      headerVideo,
   177    ]);
   178  
   179    const helpContent = (
   180      <Box className={"helpContainer"}>
   181        {headerDocs && (
   182          <div style={{ paddingLeft: 16, paddingRight: 16 }}>
   183            <div>
   184              <ReactMarkdown>{`${headerDocs}`}</ReactMarkdown>
   185            </div>
   186            <div style={{ borderBottom: "1px solid #dedede" }} />
   187          </div>
   188        )}
   189        {helpItems &&
   190          helpItems.map((aHelpItem, idx) => (
   191            <Box className={"helpItemBlock"} key={`help-item-${aHelpItem}`}>
   192              <HelpItem item={aHelpItem} displayImage={false} />
   193            </Box>
   194          ))}
   195        <div style={{ padding: 16 }}>
   196          <MoreLink
   197            LeadingIcon={MinIOTierIcon}
   198            text={"Visit MinIO Documentation"}
   199            link={"https://docs.min.io/?ref=con"}
   200            color={"#C5293F"}
   201          />
   202        </div>
   203      </Box>
   204    );
   205    const helpContentVideo = (
   206      <Box className={"helpContainer"}>
   207        {headerVideo && (
   208          <Fragment>
   209            <div style={{ paddingLeft: 16, paddingRight: 16 }}>
   210              <ReactMarkdown>{`${headerVideo}`}</ReactMarkdown>
   211            </div>
   212            <div style={{ borderBottom: "1px solid #dedede" }} />
   213          </Fragment>
   214        )}
   215        {helpItemsVideo &&
   216          helpItemsVideo.map((aHelpItem, idx) => (
   217            <Box className={"helpItemBlock"} key={`help-item-${aHelpItem}`}>
   218              <HelpItem item={aHelpItem} />
   219            </Box>
   220          ))}
   221        <div style={{ padding: 16 }}>
   222          <MoreLink
   223            LeadingIcon={MinIOTierIcon}
   224            text={"Visit MinIO Videos"}
   225            link={"https://min.io/videos?ref=con"}
   226            color={"#C5293F"}
   227          />
   228        </div>
   229      </Box>
   230    );
   231    const helpContentBlog = (
   232      <Box className={"helpContainer"}>
   233        {headerBlog && (
   234          <Fragment>
   235            <div style={{ paddingLeft: 16, paddingRight: 16 }}>
   236              <ReactMarkdown>{`${headerBlog}`}</ReactMarkdown>
   237            </div>
   238            <div style={{ borderBottom: "1px solid #dedede" }} />
   239          </Fragment>
   240        )}
   241        {helpItemsBlog &&
   242          helpItemsBlog.map((aHelpItem, idx) => (
   243            <Box className={"helpItemBlock"} key={`help-item-${aHelpItem}`}>
   244              <HelpItem item={aHelpItem} />
   245            </Box>
   246          ))}
   247        <div style={{ padding: 16 }}>
   248          <MoreLink
   249            LeadingIcon={MinIOTierIcon}
   250            text={"Visit MinIO Blog"}
   251            link={"https://blog.min.io/?ref=con"}
   252            color={"#C5293F"}
   253          />
   254        </div>
   255      </Box>
   256    );
   257  
   258    const constructHMTabs = () => {
   259      const helpMenuElements: TabItemProps[] = [];
   260  
   261      if (helpItems.length !== 0) {
   262        helpMenuElements.push({
   263          tabConfig: { label: "Documentation", id: "docs" },
   264          content: helpContent,
   265        });
   266      }
   267  
   268      if (helpItemsVideo.length !== 0) {
   269        helpMenuElements.push({
   270          tabConfig: { label: "Video", id: "video" },
   271          content: helpContentVideo,
   272        });
   273      }
   274  
   275      if (helpItemsBlog.length !== 0) {
   276        helpMenuElements.push({
   277          tabConfig: { label: "Blog", id: "blog" },
   278          content: helpContentBlog,
   279        });
   280      }
   281  
   282      return helpMenuElements;
   283    };
   284  
   285    return (
   286      <Fragment>
   287        {helpMenuOpen && (
   288          <HelpMenuContainer ref={wrapperRef}>
   289            <Tabs
   290              options={constructHMTabs()}
   291              currentTabOrPath={helpTabName}
   292              onTabClick={(item) => dispatch(setHelpTabName(item))}
   293              optionsInitialComponent={
   294                <Box sx={{ margin: "10px 10px 10px 15px" }}>
   295                  <HelpIconFilled
   296                    style={{ color: "#3874A6", width: 16, height: 16 }}
   297                  />
   298                </Box>
   299              }
   300              optionsEndComponent={
   301                <Box sx={{ marginRight: 15 }}>
   302                  <IconButton
   303                    onClick={() => {
   304                      setHelpMenuOpen(false);
   305                    }}
   306                    size="small"
   307                  >
   308                    <AlertCloseIcon style={{ color: "#919191", width: 12 }} />
   309                  </IconButton>
   310                </Box>
   311              }
   312              horizontalBarBackground
   313              horizontal
   314            />
   315          </HelpMenuContainer>
   316        )}
   317        <Button
   318          id={systemHelpName ?? "help_button"}
   319          icon={<HelpIcon />}
   320          onClick={toggleHelpMenu}
   321        ></Button>
   322      </Fragment>
   323    );
   324  };
   325  
   326  export default HelpMenu;