diff --git a/src/components/gui/gui.jsx b/src/components/gui/gui.jsx index 261eda83b65..907213481ca 100644 --- a/src/components/gui/gui.jsx +++ b/src/components/gui/gui.jsx @@ -364,6 +364,7 @@ const GUIComponent = props => { diff --git a/src/containers/ruby-tab.jsx b/src/containers/ruby-tab.jsx index 06d21eaf4e9..ec80ac5874d 100644 --- a/src/containers/ruby-tab.jsx +++ b/src/containers/ruby-tab.jsx @@ -1,7 +1,7 @@ import bindAll from 'lodash.bindall'; import PropTypes from 'prop-types'; import React from 'react'; -import {injectIntl, intlShape} from 'react-intl'; +import {FormattedMessage, injectIntl, intlShape} from 'react-intl'; import {connect} from 'react-redux'; import AceEditor from 'react-ace'; import { @@ -21,12 +21,20 @@ import 'ace-builds/src-noconflict/ext-language_tools'; import SnippetsCompleter from './ruby-tab/snippets-completer'; +import rubyIcon from './ruby-tab/icon--ruby.svg'; +import RubyDownloader from './ruby-downloader.jsx'; +import collectMetadata from '../lib/collect-metadata.js'; +import {closeFileMenu} from '../reducers/menus.js'; +import styles from './ruby-tab/ruby-tab.css'; +import ReactTooltip from 'react-tooltip'; + class RubyTab extends React.Component { constructor (props) { super(props); bindAll(this, [ 'setAceEditorRef' ]); + this.mainTooltipId = 'ruby-downloader-tooltip'; } componentDidUpdate (prevProps) { @@ -79,6 +87,17 @@ class RubyTab extends React.Component { this.aceEditorRef = ref; } + getSaveToComputerHandler (downloadProjectCallback) { + return () => { + this.props.onRequestCloseFile(); + downloadProjectCallback(); + if (this.props.onProjectTelemetryEvent) { + const metadata = collectMetadata(this.props.vm, this.props.projectTitle, this.props.locale); + this.props.onProjectTelemetryEvent('projectDidSave', metadata); + } + }; + } + render () { const { onChange, @@ -93,34 +112,66 @@ class RubyTab extends React.Component { const completers = [new SnippetsCompleter()]; return ( - + <> + +
+ {(_, downloadProjectCallback) => ( + + )} + + + + +
+ ); } } @@ -131,21 +182,29 @@ RubyTab.propTypes = { intl: intlShape.isRequired, isVisible: PropTypes.bool, onChange: PropTypes.func, + onRequestCloseFile: PropTypes.func, + onProjectTelemetryEvent: PropTypes.func, rubyCode: rubyCodeShape, targetCodeToBlocks: PropTypes.func, updateRubyCodeTargetState: PropTypes.func, - vm: PropTypes.instanceOf(VM).isRequired + vm: PropTypes.instanceOf(VM).isRequired, + projectTitle: PropTypes.string, + locale: PropTypes.string.isRequired }; const mapStateToProps = state => ({ blocksTabVisible: state.scratchGui.editorTab.activeTabIndex === BLOCKS_TAB_INDEX, editingTarget: state.scratchGui.targets.editingTarget, - rubyCode: state.scratchGui.rubyCode + rubyCode: state.scratchGui.rubyCode, + vm: state.scratchGui.vm, + projectTitle: state.scratchGui.projectTitle, + locale: state.locales.local }); const mapDispatchToProps = dispatch => ({ onChange: code => dispatch(updateRubyCode(code)), - updateRubyCodeTargetState: target => dispatch(updateRubyCodeTarget(target)) + updateRubyCodeTargetState: target => dispatch(updateRubyCodeTarget(target)), + onRequestCloseFile: () => dispatch(closeFileMenu()) }); export default RubyToBlocksConverterHOC(injectIntl(connect( diff --git a/src/containers/ruby-tab/icon--ruby.svg b/src/containers/ruby-tab/icon--ruby.svg new file mode 100644 index 00000000000..0e063e7f941 --- /dev/null +++ b/src/containers/ruby-tab/icon--ruby.svg @@ -0,0 +1,83 @@ + + + + diff --git a/src/containers/ruby-tab/ruby-tab.css b/src/containers/ruby-tab/ruby-tab.css new file mode 100644 index 00000000000..f55152f40eb --- /dev/null +++ b/src/containers/ruby-tab/ruby-tab.css @@ -0,0 +1,67 @@ +@import "../../css/colors.css"; +@import "../../css/units.css"; +@import "../../css/z-index.css"; + +.button { + z-index: $z-index-add-button; + width: 2.75rem; + height: 2.75rem; + border: none; + border-radius: 100%; + background-color: $looks-secondary; + box-shadow: 0 0 0 4px $looks-transparent; + transition: transform, box-shadow 0.5s; +} +.wrapper { + position: absolute; + width: 2.75rem; + height: 2.75rem; + bottom: 1rem; + right: 1rem; + border-radius: 100%; +} +.button:hover { + transform: scale(1.1); + border-radius: 100%; + box-shadow: 0 0 0 6px $looks-transparent; + background-color: $extensions-primary; +} + +.img { + width: 2rem; +} + +.tooltip { + background-color: $extensions-primary !important; + opacity: 1 !important; + border: 1px solid hsla(0, 0%, 0%, .1) !important; + border-radius: $form-radius !important; + box-shadow: 0 0 .5rem hsla(0, 0%, 0%, .25) !important; + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif !important; + z-index: $z-index-tooltip !important; +} + +.tooltip:after { + background-color: $extensions-primary; +} + +$arrow-size: 0.5rem; +$arrow-inset: -0.25rem; +$arrow-rounding: 0.125rem; + +.tooltip:after { + content: ""; + border-top: 1px solid hsla(0, 0%, 0%, .1) !important; + border-left: 0 !important; + border-bottom: 0 !important; + border-right: 1px solid hsla(0, 0%, 0%, .1) !important; + border-radius: $arrow-rounding; + height: $arrow-size !important; + width: $arrow-size !important; +} + +.tooltip:global(.place-left):after { + margin-top: $arrow-inset !important; + right: $arrow-inset !important; + transform: rotate(45deg) !important; +} \ No newline at end of file