diff --git a/src/Documentation/Markdown/Markdown.js b/src/Documentation/Markdown/Markdown.js index 815025b887..12160744f2 100644 --- a/src/Documentation/Markdown/Markdown.js +++ b/src/Documentation/Markdown/Markdown.js @@ -2,6 +2,7 @@ import React, { Component } from 'react' // components import ReactMarkdown from 'react-markdown' import { LightButton } from '../LightButton' +import Tooltip from '../../Tooltip' // syntax highlighter import SyntaxHighlighter, { registerLanguage @@ -48,15 +49,18 @@ const HeadingRenderer = ({ level, children }) => { } const HtmlRenderer = props => { - if (props.tag !== 'details') { + if (props.tag !== 'details' && props.tag !== 'abbr') { return React.createElement(props.tag, {}, props.children) - } else { + } else if (props.tag === 'details') { const text = props.children[0].props.children[0] return ( {props.children.slice(1)} ) + } else if (props.tag === 'abbr') { + const text = props.children[0] + return } } diff --git a/src/Documentation/glossary.js b/src/Documentation/glossary.js new file mode 100644 index 0000000000..c8002f3119 --- /dev/null +++ b/src/Documentation/glossary.js @@ -0,0 +1,34 @@ +export default { + name: 'Glossary', + desc: + 'This guide is aimed to familiarize the users with definitions to ' + + 'relevant DVC concepts and terminologies which are frequently used.', + contents: [ + { + name: 'Workspace directory', + match: ['workspace'], + desc: + 'The **workspace** contains all of your DVC **project** files and ' + + "directories. It's typically also a Git **repository**. See also " + + '[`dvc init`](/doc/commands-reference/init).' + }, + { + name: 'DVC cache', + match: ['cache'], + desc: + 'DVC cache is a hidden storage which is by default found at ' + + '`.dvc/cache`. This storage is used to manage different versions of ' + + 'files which are under DVC control. For more information on cache, ' + + 'please refer to this [guide](/doc/commands-reference/config#cache).' + }, + { + name: 'Data artifact', + match: ['data artifact', 'data artifacts'], + desc: + 'Any **data** file or directory, as well as intermediate or final ' + + 'result (such as extracted features or a ML model file) that is ' + + 'under DVC control. Refer to [Data and Model Files Versioning]' + + '(/doc/use-cases/data-and-model-files-versioning) for more details.' + } + ] +} diff --git a/src/Tooltip/index.js b/src/Tooltip/index.js new file mode 100644 index 0000000000..0de90694c3 --- /dev/null +++ b/src/Tooltip/index.js @@ -0,0 +1,244 @@ +import React, { Component } from 'react' +import ReactMarkdown from 'react-markdown' +import styled from 'styled-components' + +import glossary from '../Documentation/glossary' +import { OnlyDesktop, OnlyMobile } from '../styles' + +class Tooltip extends Component { + state = { + description: '', + header: '', + hover: false, + id: null, + margin: -70, + match: false, + pointBorderAfter: 'white transparent transparent transparent', + pointBorderBefore: '#d1d5da transparent transparent transparent', + pointMargin: -15, + pointTop: 100, + pointTopAfter: -14, + pointTopBefore: 16, + timeout: null, + top: 'unset', + width: 400 + } + + componentDidMount() { + glossary.contents.forEach((glossaryItem, index) => { + if (glossaryItem.match.includes(this.props.text)) { + this.setState({ + description: glossaryItem.desc, + header: glossaryItem.name, + key: index, + match: true + }) + } + }) + } + + tooltipPositionEval = () => { + const headerHeight = document.getElementsByClassName('header')[0] + .offsetHeight + const markdownBody = document.getElementsByClassName('markdown-body')[0] + const tooltipBoundary = document + .getElementById(`tooltip-text-${this.state.key}`) + .getBoundingClientRect() + const tooltipBoxHeight = document.getElementById( + `tooltip-box-${this.state.key}` + ).offsetHeight + const tooltipHeight = tooltipBoundary.top - tooltipBoxHeight + const maxWidth = markdownBody.offsetLeft + markdownBody.clientWidth + const container = document.getElementsByClassName('tooltip-container')[0] + const tooltipWidth = container.offsetLeft + this.state.width + const vertical = tooltipHeight > headerHeight ? 'top' : 'bottom' + const horizontal = tooltipWidth > maxWidth ? 'right' : 'left' + + switch (`${horizontal} ${vertical}`) { + case 'left top': + this.setState({ + margin: -70, + pointBorderAfter: 'white transparent transparent transparent', + pointBorderBefore: '#d1d5da transparent transparent transparent', + pointMargin: -15, + pointTop: 100, + pointTopAfter: 'unset', + pointTopBefore: 'unset', + top: -tooltipBoxHeight + }) + break + case 'right top': + this.setState({ + margin: -340, + pointBorderAfter: 'white transparent transparent transparent', + pointBorderBefore: '#d1d5da transparent transparent transparent', + pointMargin: 260, + pointTop: 100, + pointTopAfter: 'unset', + pointTopBefore: 'unset', + top: -tooltipBoxHeight + }) + break + case 'left bottom': + this.setState({ + margin: -70, + pointBorderAfter: 'transparent transparent white transparent', + pointBorderBefore: 'transparent transparent #d1d5da transparent', + pointMargin: -15, + pointTop: -15, + pointTopAfter: -20, + pointTopBefore: -23, + top: 40 + }) + break + case 'right bottom': + this.setState({ + margin: -340, + pointBorderAfter: 'transparent transparent white transparent', + pointBorderBefore: 'transparent transparent #d1d5da transparent', + pointMargin: 260, + pointTop: -15, + pointTopAfter: -20, + pointTopBefore: -23, + top: 40 + }) + break + } + } + + hoverIn = () => { + if (this.state.interval) { + clearTimeout(this.state.interval) + this.setState( + { + interval: null, + hover: true + }, + this.tooltipPositionEval + ) + } else { + this.setState( + { + hover: true + }, + this.tooltipPositionEval + ) + } + } + + hoverOut = () => { + this.setState({ + interval: setTimeout(() => { + this.setState({ + hover: false + }) + }, 100) + }) + } + + render() { + if (this.state.match) { + return ( + <> + + + + {this.props.text} + + + {this.state.hover && ( + + +
{this.state.header}
+ +
+
+ )} +
+ + {this.props.text} + + + ) + } else { + return {this.props.text} + } + } +} + +const HighlightedText = styled.span` + border-bottom: 1px black dotted; +` + +const TooltipContainer = styled.div` + position: absolute; + display: inline-block; + z-index: 300000000; + background-color: white; +` + +const TooltipText = styled.div` + padding: 8px 10px; + border: 1px solid #d1d5da; + border-radius: 3px; + background-color: white; + position: absolute; + z-index: 1; + top: ${props => { + if (props.top === 'unset') { + return 'unset' + } else { + return `${props.top}px` + } + }}; + margin-left: ${props => props.margin || -70}px; + width: ${props => props.width || 400}px; + + &:after, + &:before { + content: ''; + position: absolute; + top: ${props => props.pointTop}%; + border-style: solid; + margin-left: ${props => props.pointMargin || -15}px; + } + + &:after { + top: ${props => props.pointTopAfter}px; + left: 10%; + border-width: 10px; + border-color: ${props => props.pointBorderAfter}; + } + &:before { + top: ${props => props.pointTopBefore}px; + left: 10%; + border-width: 11px; + border-color: ${props => props.pointBorderBefore}; + } + + .header { + font-size: 1.3em; + font-weight: bold; + } +` + +export default Tooltip diff --git a/src/TopMenu/index.js b/src/TopMenu/index.js index 07e15b3540..a01a63cd88 100644 --- a/src/TopMenu/index.js +++ b/src/TopMenu/index.js @@ -48,7 +48,11 @@ class TopMenu extends Component { return ( - + cache directory | +| `Filesystem type` | Shows the filesystem type (eg. ext4, FAT, etc.) and mount point of workspace and the cache directory | > If `dvc version` is executed outside a DVC workspace, the command outputs the > filesystem type of the current working directory.