Skip to content

Commit bfb9562

Browse files
authored
Merge pull request #349 from kiharu3112/feat/ruby-download-button
feat: add ruby code download button
2 parents 28a3963 + 68783fe commit bfb9562

File tree

4 files changed

+242
-32
lines changed

4 files changed

+242
-32
lines changed

src/components/gui/gui.jsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -364,6 +364,7 @@ const GUIComponent = props => {
364364
<RubyTab
365365
isVisible={rubyTabVisible}
366366
vm={vm}
367+
onProjectTelemetryEvent={onProjectTelemetryEvent}
367368
/>
368369
</TabPanel>
369370
</Tabs>

src/containers/ruby-tab.jsx

Lines changed: 91 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import bindAll from 'lodash.bindall';
22
import PropTypes from 'prop-types';
33
import React from 'react';
4-
import {injectIntl, intlShape} from 'react-intl';
4+
import {FormattedMessage, injectIntl, intlShape} from 'react-intl';
55
import {connect} from 'react-redux';
66
import AceEditor from 'react-ace';
77
import {
@@ -21,12 +21,20 @@ import 'ace-builds/src-noconflict/ext-language_tools';
2121

2222
import SnippetsCompleter from './ruby-tab/snippets-completer';
2323

24+
import rubyIcon from './ruby-tab/icon--ruby.svg';
25+
import RubyDownloader from './ruby-downloader.jsx';
26+
import collectMetadata from '../lib/collect-metadata.js';
27+
import {closeFileMenu} from '../reducers/menus.js';
28+
import styles from './ruby-tab/ruby-tab.css';
29+
import ReactTooltip from 'react-tooltip';
30+
2431
class RubyTab extends React.Component {
2532
constructor (props) {
2633
super(props);
2734
bindAll(this, [
2835
'setAceEditorRef'
2936
]);
37+
this.mainTooltipId = 'ruby-downloader-tooltip';
3038
}
3139

3240
componentDidUpdate (prevProps) {
@@ -79,6 +87,17 @@ class RubyTab extends React.Component {
7987
this.aceEditorRef = ref;
8088
}
8189

90+
getSaveToComputerHandler (downloadProjectCallback) {
91+
return () => {
92+
this.props.onRequestCloseFile();
93+
downloadProjectCallback();
94+
if (this.props.onProjectTelemetryEvent) {
95+
const metadata = collectMetadata(this.props.vm, this.props.projectTitle, this.props.locale);
96+
this.props.onProjectTelemetryEvent('projectDidSave', metadata);
97+
}
98+
};
99+
}
100+
82101
render () {
83102
const {
84103
onChange,
@@ -93,34 +112,66 @@ class RubyTab extends React.Component {
93112
const completers = [new SnippetsCompleter()];
94113

95114
return (
96-
<AceEditor
97-
annotations={errors}
98-
editorProps={{$blockScrolling: true}}
99-
fontSize={16}
100-
height="inherit"
101-
markers={markers}
102-
mode="ruby"
103-
name="ruby-editor"
104-
ref={this.setAceEditorRef}
105-
setOptions={{
106-
tabSize: 2,
107-
useSoftTabs: true,
108-
showInvisibles: true,
109-
enableAutoIndent: true,
110-
enableBasicAutocompletion: completers,
111-
enableLiveAutocompletion: true
112-
}}
113-
style={{
114-
border: '1px solid hsla(0, 0%, 0%, 0.15)',
115-
borderBottomRightRadius: '0.5rem',
116-
borderTopRightRadius: '0.5rem',
117-
fontFamily: ['Monaco', 'Menlo', 'Consolas', 'source-code-pro', 'monospace']
118-
}}
119-
theme="clouds"
120-
value={code}
121-
width="100%"
122-
onChange={onChange}
123-
/>
115+
<>
116+
<AceEditor
117+
annotations={errors}
118+
editorProps={{$blockScrolling: true}}
119+
fontSize={16}
120+
height="inherit"
121+
markers={markers}
122+
mode="ruby"
123+
name="ruby-editor"
124+
ref={this.setAceEditorRef}
125+
setOptions={{
126+
tabSize: 2,
127+
useSoftTabs: true,
128+
showInvisibles: true,
129+
enableAutoIndent: true,
130+
enableBasicAutocompletion: completers,
131+
enableLiveAutocompletion: true
132+
}}
133+
style={{
134+
border: '1px solid hsla(0, 0%, 0%, 0.15)',
135+
borderBottomRightRadius: '0.5rem',
136+
borderTopRightRadius: '0.5rem',
137+
fontFamily: ['Monaco', 'Menlo', 'Consolas', 'source-code-pro', 'monospace']
138+
}}
139+
theme="clouds"
140+
value={code}
141+
width="100%"
142+
onChange={onChange}
143+
/>
144+
<div className={styles.wrapper}>
145+
<RubyDownloader>{(_, downloadProjectCallback) => (
146+
<button
147+
className={styles.button}
148+
onClick={this.getSaveToComputerHandler(downloadProjectCallback)}
149+
data-tip
150+
data-for={'ruby-downloader-tooltip'}
151+
>
152+
<img
153+
src={rubyIcon}
154+
alt="ruby download"
155+
className={styles.img}
156+
/>
157+
158+
</button>
159+
)}
160+
</RubyDownloader>
161+
<ReactTooltip
162+
id={this.mainTooltipId}
163+
place="left"
164+
effect="solid"
165+
className={styles.tooltip}
166+
>
167+
<FormattedMessage
168+
defaultMessage="Download Ruby code to your compute"
169+
description="Menu bar item for downloading Ruby code to your computer"
170+
id="gui.smalruby3.menuBar.downloadRubyCodeToComputer"
171+
/>
172+
</ReactTooltip>
173+
</div>
174+
</>
124175
);
125176
}
126177
}
@@ -131,21 +182,29 @@ RubyTab.propTypes = {
131182
intl: intlShape.isRequired,
132183
isVisible: PropTypes.bool,
133184
onChange: PropTypes.func,
185+
onRequestCloseFile: PropTypes.func,
186+
onProjectTelemetryEvent: PropTypes.func,
134187
rubyCode: rubyCodeShape,
135188
targetCodeToBlocks: PropTypes.func,
136189
updateRubyCodeTargetState: PropTypes.func,
137-
vm: PropTypes.instanceOf(VM).isRequired
190+
vm: PropTypes.instanceOf(VM).isRequired,
191+
projectTitle: PropTypes.string,
192+
locale: PropTypes.string.isRequired
138193
};
139194

140195
const mapStateToProps = state => ({
141196
blocksTabVisible: state.scratchGui.editorTab.activeTabIndex === BLOCKS_TAB_INDEX,
142197
editingTarget: state.scratchGui.targets.editingTarget,
143-
rubyCode: state.scratchGui.rubyCode
198+
rubyCode: state.scratchGui.rubyCode,
199+
vm: state.scratchGui.vm,
200+
projectTitle: state.scratchGui.projectTitle,
201+
locale: state.locales.local
144202
});
145203

146204
const mapDispatchToProps = dispatch => ({
147205
onChange: code => dispatch(updateRubyCode(code)),
148-
updateRubyCodeTargetState: target => dispatch(updateRubyCodeTarget(target))
206+
updateRubyCodeTargetState: target => dispatch(updateRubyCodeTarget(target)),
207+
onRequestCloseFile: () => dispatch(closeFileMenu())
149208
});
150209

151210
export default RubyToBlocksConverterHOC(injectIntl(connect(
Lines changed: 83 additions & 0 deletions
Loading
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
@import "../../css/colors.css";
2+
@import "../../css/units.css";
3+
@import "../../css/z-index.css";
4+
5+
.button {
6+
z-index: $z-index-add-button;
7+
width: 2.75rem;
8+
height: 2.75rem;
9+
border: none;
10+
border-radius: 100%;
11+
background-color: $looks-secondary;
12+
box-shadow: 0 0 0 4px $looks-transparent;
13+
transition: transform, box-shadow 0.5s;
14+
}
15+
.wrapper {
16+
position: absolute;
17+
width: 2.75rem;
18+
height: 2.75rem;
19+
bottom: 1rem;
20+
right: 1rem;
21+
border-radius: 100%;
22+
}
23+
.button:hover {
24+
transform: scale(1.1);
25+
border-radius: 100%;
26+
box-shadow: 0 0 0 6px $looks-transparent;
27+
background-color: $extensions-primary;
28+
}
29+
30+
.img {
31+
width: 2rem;
32+
}
33+
34+
.tooltip {
35+
background-color: $extensions-primary !important;
36+
opacity: 1 !important;
37+
border: 1px solid hsla(0, 0%, 0%, .1) !important;
38+
border-radius: $form-radius !important;
39+
box-shadow: 0 0 .5rem hsla(0, 0%, 0%, .25) !important;
40+
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif !important;
41+
z-index: $z-index-tooltip !important;
42+
}
43+
44+
.tooltip:after {
45+
background-color: $extensions-primary;
46+
}
47+
48+
$arrow-size: 0.5rem;
49+
$arrow-inset: -0.25rem;
50+
$arrow-rounding: 0.125rem;
51+
52+
.tooltip:after {
53+
content: "";
54+
border-top: 1px solid hsla(0, 0%, 0%, .1) !important;
55+
border-left: 0 !important;
56+
border-bottom: 0 !important;
57+
border-right: 1px solid hsla(0, 0%, 0%, .1) !important;
58+
border-radius: $arrow-rounding;
59+
height: $arrow-size !important;
60+
width: $arrow-size !important;
61+
}
62+
63+
.tooltip:global(.place-left):after {
64+
margin-top: $arrow-inset !important;
65+
right: $arrow-inset !important;
66+
transform: rotate(45deg) !important;
67+
}

0 commit comments

Comments
 (0)