diff --git a/ui/v2.5/src/components/Changelog/Version.tsx b/ui/v2.5/src/components/Changelog/Version.tsx index 7808ef803..9f60781e8 100644 --- a/ui/v2.5/src/components/Changelog/Version.tsx +++ b/ui/v2.5/src/components/Changelog/Version.tsx @@ -49,7 +49,7 @@ const Version: React.FC = ({ -
{children}
+
{children}
diff --git a/ui/v2.5/src/components/Changelog/versions/v030.tsx b/ui/v2.5/src/components/Changelog/versions/v030.tsx index f5e716aaf..d2dae6b82 100644 --- a/ui/v2.5/src/components/Changelog/versions/v030.tsx +++ b/ui/v2.5/src/components/Changelog/versions/v030.tsx @@ -3,6 +3,7 @@ import ReactMarkdown from "react-markdown"; const markup = ` ### ✨ New Features +* Add in-app help manual. * Add support for custom served folders. * Add support for parent/child studios. diff --git a/ui/v2.5/src/components/Help/Manual.tsx b/ui/v2.5/src/components/Help/Manual.tsx new file mode 100644 index 000000000..86edb4b9b --- /dev/null +++ b/ui/v2.5/src/components/Help/Manual.tsx @@ -0,0 +1,150 @@ +import React, { useState } from "react"; +import { Modal, Container, Row, Col, Nav, Tab } from "react-bootstrap"; +import Introduction from "src/docs/en/Introduction.md"; +import Tasks from "src/docs/en/Tasks.md"; +import AutoTagging from "src/docs/en/AutoTagging.md"; +import JSONSpec from "src/docs/en/JSONSpec.md"; +import Configuration from "src/docs/en/Configuration.md"; +import Interface from "src/docs/en/Interface.md"; +import Galleries from "src/docs/en/Galleries.md"; +import Scraping from "src/docs/en/Scraping.md"; +import Contributing from "src/docs/en/Contributing.md"; +import SceneFilenameParser from "src/docs/en/SceneFilenameParser.md"; +import Help from "src/docs/en/Help.md"; +import { Page } from "./Page"; + +interface IManualProps { + show: boolean; + onClose: () => void; +} + +export const Manual: React.FC = ({ show, onClose }) => { + const content = [ + { + key: "Introduction.md", + title: "Introduction", + content: Introduction, + }, + { + key: "Configuration.md", + title: "Configuration", + content: Configuration, + }, + { + key: "Interface.md", + title: "Interface", + content: Interface, + }, + { + key: "Tasks.md", + title: "Tasks", + content: Tasks, + }, + { + key: "AutoTagging.md", + title: "Auto Tagging", + content: AutoTagging, + className: "indent-1", + }, + { + key: "SceneFilenameParser.md", + title: "Scene Filename Parser", + content: SceneFilenameParser, + className: "indent-1", + }, + { + key: "JSONSpec.md", + title: "JSON Specification", + content: JSONSpec, + className: "indent-1", + }, + { + key: "Galleries.md", + title: "Image Galleries", + content: Galleries, + }, + { + key: "Scraping.md", + title: "Metadata Scraping", + content: Scraping, + }, + { + key: "Contributing.md", + title: "Contributing", + content: Contributing, + }, + { + key: "Help.md", + title: "Further Help", + content: Help, + }, + ]; + + const [activeTab, setActiveTab] = useState(content[0].key); + + // links to other manual pages are specified as "/help/page.md" + // intercept clicks to these pages and set the tab accordingly + function interceptLinkClick( + event: React.MouseEvent + ) { + if (event.target instanceof HTMLAnchorElement) { + const href = (event.target as HTMLAnchorElement).getAttribute("href"); + if (href && href.startsWith("/help")) { + const newKey = (event.target as HTMLAnchorElement).pathname.substring( + "/help/".length + ); + setActiveTab(newKey); + event.preventDefault(); + } + } + } + + return ( + + + Help + + + + setActiveTab(k)} + id="manual-tabs" + > + + + + + + + {content.map((c) => { + return ( + + + + ); + })} + + + + + + + + ); +}; diff --git a/ui/v2.5/src/components/Help/Page.tsx b/ui/v2.5/src/components/Help/Page.tsx new file mode 100644 index 000000000..1ab94109a --- /dev/null +++ b/ui/v2.5/src/components/Help/Page.tsx @@ -0,0 +1,22 @@ +import React, { useEffect, useState } from "react"; +import ReactMarkdown from "react-markdown"; + +interface IPageProps { + // page is a markdown module + // eslint-disable-next-line @typescript-eslint/no-explicit-any + page: any; +} + +export const Page: React.FC = ({ page }) => { + const [markdown, setMarkdown] = useState(""); + + useEffect(() => { + if (!markdown) { + fetch(page) + .then((res) => res.text()) + .then((text) => setMarkdown(text)); + } + }, [page, markdown]); + + return ; +}; diff --git a/ui/v2.5/src/components/Help/styles.scss b/ui/v2.5/src/components/Help/styles.scss new file mode 100644 index 000000000..e52417025 --- /dev/null +++ b/ui/v2.5/src/components/Help/styles.scss @@ -0,0 +1,40 @@ +.manual { + background-color: #30404d; + color: $text-color; + + .close { + color: $text-color; + } + + .manual-container { + padding-left: 1px; + padding-right: 5px; + } + + .modal-header, + .modal-body { + background-color: #30404d; + color: $text-color; + overflow-y: hidden; + } +} + +.manual .manual-content { + max-height: calc(100vh - 10rem); + overflow-y: auto; + + .indent-1 { + padding-left: 2rem; + } +} + +@media (max-width: 992px) { + .manual .modal-body { + overflow-y: auto; + + .manual-content { + max-height: inherit; + overflow-y: hidden; + } + } +} diff --git a/ui/v2.5/src/components/MainNavbar.tsx b/ui/v2.5/src/components/MainNavbar.tsx index 851230c7c..e9382efba 100644 --- a/ui/v2.5/src/components/MainNavbar.tsx +++ b/ui/v2.5/src/components/MainNavbar.tsx @@ -12,6 +12,7 @@ import { Link, NavLink, useLocation } from "react-router-dom"; import { SessionUtils } from "src/utils"; import { Icon } from "src/components/Shared"; +import { Manual } from "./Help/Manual"; interface IMenuItem { message: MessageDescriptor; @@ -91,6 +92,8 @@ const menuItems: IMenuItem[] = [ export const MainNavbar: React.FC = () => { const location = useLocation(); const [expanded, setExpanded] = useState(false); + const [showManual, setShowManual] = useState(false); + // react-bootstrap typing bug // eslint-disable-next-line @typescript-eslint/no-explicit-any const navbarRef = useRef(); @@ -147,55 +150,65 @@ export const MainNavbar: React.FC = () => { } return ( - - setExpanded(false)} + <> + setShowManual(false)} /> + - - + + + + + + + +