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;