@mongodb-js/mongodb-ui-components
Version:
A collection of frequently used functional UI components found on mongodb properties
369 lines (315 loc) • 10.9 kB
JavaScript
'use strict';
const React = require('react');
const Dropdown = require('../dropdown');
const clipboardCopy = require('../shared/clipboard-copy');
const easing = require('../shared/easing');
const style = require('../shared/style');
const script = require('../shared/script');
const widgetStyle = require('./style');
class CodeWidget extends React.PureComponent {
constructor(props) {
super(props);
this.state = {
activeTab: 0,
activeStage: 0,
isClipboardSupported: true,
copyBtnText: 'Copy Snippet'
};
this.minimaps = [];
this.stageTitles = props.descriptions.map(item => item.title);
this.onTabSelect = this.onTabSelect.bind(this);
this.onNavigateStage = this.onNavigateStage.bind(this);
this.onDropdownSelect = this.onDropdownSelect.bind(this);
this.onCopyBtnClick = this.onCopyBtnClick.bind(this);
this.revertCopiedText = this.revertCopiedText.bind(this);
this.renderTabBtn = this.renderTabBtn.bind(this);
this.renderTabs = this.renderTabs.bind(this);
}
componentDidMount() {
if (!clipboardCopy.isSupported()) {
this.setState({ isClipboardSupported: false });
}
const cdn = 'https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.12.0';
style('code-widget', widgetStyle());
script('link', `${cdn}/styles/default.min.css`);
script('script', `${cdn}/highlight.min.js`).then(() => Promise.all((this.props.languages || []).map(language => script('script', `${cdn}/languages/${language}.min.js`)))).then(() => {
this.minimaps.forEach((minimap, i) => {
minimap.innerHTML = this.renderMinimap(minimap.innerHTML, minimap.className, this.stageTitles.length);
});
this.setHighlightedCode(0);
this.initHLJS();
}).catch(console.error.bind(console));
}
initHLJS() {
if (!window.hljs || !window.hljs.initHighlighting) {
return setTimeout(this.initHLJS.bind(this), 300);
}
window.hljs.configure({ tabReplace: ' ' }); // 2 spaces
window.hljs.initHighlighting();
}
alertCopied(success) {
this.setState({
copyBtnText: success ? 'Copied!' : 'Copy Unsuccessful'
});
setTimeout(this.revertCopiedText, 1000);
}
revertCopiedText() {
this.setState({ copyBtnText: 'Copy Snippet' });
}
getFileExtension(lang) {
switch (lang) {
case 'Python':
return 'py';
case 'Java':
return 'java';
case 'C++':
return 'cpp';
case 'C#':
return 'cs';
case 'Node.JS':
return 'js';
}
}
getCurrentSnippet() {
return this.props.snippets[this.state.activeTab].stages[this.state.activeStage];
}
setHighlightedCode(newStage, shouldAnimate) {
this.minimaps.forEach((minimap, i) => {
const last = minimap.querySelector('.minimap-section--active');
if (last) {
last.classList.remove('minimap-section--active');
}
const sections = minimap.querySelectorAll('.minimap-section');
sections[newStage].classList.add('minimap-section--active');
if (shouldAnimate === false) {
newStage === 0 ? minimap.scrollTop = 0 : minimap.scrollTop = sections[newStage].offsetTop - 20;
return;
}
return newStage === 0 ? this.scrollToSection(minimap, 0) : this.scrollToSection(minimap, sections[newStage].offsetTop - 20);
});
}
scrollToSection(minimap, dest) {
if (dest === minimap.scrollTop) return;
let steps = 0;
const start = minimap.scrollTop;
const totalSteps = 30;
const diff = dest - start;
function tick() {
steps += 1;
minimap.scrollTop = start + diff * easing.easeOutQuart(steps / totalSteps);
if (steps >= totalSteps) {
minimap.scrollTop = dest;
return;
}
window.requestAnimationFrame(tick);
}
window.requestAnimationFrame(tick);
}
onTabSelect(e) {
const tab = Number(e.currentTarget.getAttribute('data-i'));
if (tab === this.state.activeTab) return;
this.setState({ activeTab: tab }, () => {
this.setHighlightedCode(this.state.activeStage, false);
});
}
onDropdownSelect(newStage) {
this.setState({ activeStage: newStage });
this.setHighlightedCode(newStage);
}
onNavigateStage(e) {
const direction = Number(e.currentTarget.getAttribute('data-direction'));
const newStage = this.state.activeStage + direction;
if (newStage < 0 || newStage === this.props.snippets.length) return;
this.setState({ activeStage: newStage });
this.setHighlightedCode(newStage);
}
onCopyBtnClick(e) {
if (clipboardCopy(this.getCurrentSnippet())) {
this.alertCopied(true);
} else {
this.alertCopied(false);
}
const cl = e.currentTarget.classList;
cl.add('animation--bounce-inwards');
setTimeout(() => cl.remove('animation--bounce-inwards'), 600);
}
render() {
return React.createElement(
'div',
{ className: 'code-widget' },
React.createElement(
'div',
{ className: 'flex flex--wrap' },
this.props.snippets.map(this.renderTabBtn)
),
React.createElement(
'div',
{ className: 'code-widget__shadow' },
this.props.snippets.map(this.renderTabs)
)
);
}
renderTabBtn(snippet, i) {
return React.createElement(
'button',
{
key: i,
className: `code-widget__tab ${i === this.state.activeTab ? 'code-widget__tab--active' : ''}`,
onClick: this.onTabSelect,
'data-i': i },
snippet.language
);
}
renderTabs(snippet, i) {
const stage = this.state.activeStage;
const tab = this.state.activeTab;
const downloadUrl = snippet.sourceUrl || `/assets/files/what-is-mongodb.${this.getFileExtension(snippet.language)}`;
return React.createElement(
'div',
{
key: i,
style: tab === i ? null : { display: 'none' },
className: 'flex flex--wrap' },
React.createElement(
'div',
{ className: 'code-widget__column code-widget__column-1' },
React.createElement(
'div',
{ style: { margin: '20px 20px 0' } },
React.createElement(
'p',
null,
React.createElement(
'strong',
null,
'Choose your step'
)
),
React.createElement(Dropdown, {
onSelect: this.onDropdownSelect,
activeIndex: stage,
options: this.stageTitles })
),
React.createElement(
'div',
{ className: 'code-widget__navigation' },
React.createElement(
'button',
{
className: `code-widget__navigation-btn ${stage === 0 ? '' : 'code-widget__navigation-btn--active'}`,
onClick: this.onNavigateStage,
'data-direction': '-1' },
'Back'
),
React.createElement(
'button',
{
className: `code-widget__navigation-btn ${stage === this.props.snippets.length - 1 ? '' : 'code-widget__navigation-btn--active'}`,
onClick: this.onNavigateStage,
'data-direction': '1' },
'Next'
)
),
React.createElement(
'div',
{ className: 'code-widget__progress' },
React.createElement('div', { className: `code-widget__progress-bar code-widget__progress-bar--stage-${stage}` })
),
React.createElement('p', { className: 'code-widget__description', dangerouslySetInnerHTML: {
__html: this.props.descriptions[this.state.activeStage].content
} }),
React.createElement(
'div',
{ className: 'code-widget__column-1-options' },
this.state.isClipboardSupported && React.createElement(
'button',
{
onClick: this.onCopyBtnClick,
className: 'clipboard-btn btn-hollow btn-hollow--gray' },
this.state.copyBtnText
),
React.createElement('div', { className: 'divider--horizontal' }),
React.createElement(
'a',
{ href: snippet.documentation[stage] },
React.createElement(
'p',
null,
React.createElement(
'strong',
null,
'Go to Documentation'
),
React.createElement(
'svg',
{ width: '20', height: '10', viewBox: '0 0 20 10', style: { marginLeft: '7px' } },
React.createElement('path', {
fill: '#589636',
fillRule: 'evenodd',
d: 'M14.707.586L13.293 2l2.293 2.293H0v2h15.586l-2.293 2.293L14.707 10l4.707-4.707z' })
)
)
)
)
),
React.createElement(
'div',
{ className: 'code-widget__column code-widget__column-2' },
snippet.stages.map((code, j) => React.createElement(
'pre',
{ key: `${i}.${j}`, style: { display: j === stage ? 'inherit' : 'none' } },
React.createElement(
'code',
{ className: snippet.language.toLowerCase() },
code.trim()
)
))
),
React.createElement(
'div',
{ className: 'code-widget__column code-widget__column-3' },
React.createElement(
'pre',
null,
React.createElement(
'code',
{
ref: el => {
if (el) this.minimaps.push(el);
},
className: snippet.language.toLowerCase() },
snippet.sourceCode
)
),
React.createElement(
'a',
{ href: downloadUrl, download: downloadUrl },
React.createElement(
'button',
{ className: 'download-file-btn btn-hollow btn-hollow--gray' },
'Download File'
)
)
)
);
}
renderMinimap(code, language, numStages) {
const commentToken = language === 'python' ? '#' : '//';
let result = code;
for (let i = 1, l = numStages + 2; i < l; ++i) {
const startIndex = result.indexOf(`${commentToken} ${i}.`);
if (i < l - 1) {
result = this.insert(result, `<span class='minimap-section' id='minimap-${i}'>`, startIndex);
}
if (i < 3) continue;
const endIndex = result.indexOf(`<span class='minimap-section' id='minimap-${i - 1}'>`) - commentToken.length;
result = this.insert(result, '</span>', endIndex);
if (i === l - 1) result += '</span>';
}
return result;
}
insert(source, text, index) {
return `${source.slice(0, index)}${text}${source.slice(index)}`;
}
}
module.exports = CodeWidget;