Skip to content

Commit 869f1c6

Browse files
committed
Add export restore point button
1 parent afe00d5 commit 869f1c6

8 files changed

Lines changed: 214 additions & 18 deletions

File tree

‎package-lock.json‎

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎package.json‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
},
3434
"dependencies": {
3535
"@microbit/microbit-universal-hex": "0.2.2",
36+
"@turbowarp/jszip": "^3.11.1",
3637
"@turbowarp/nanolog": "^0.2.0",
3738
"@turbowarp/scratch-l10n": "^3.1001.0-202401241456-994097a5",
3839
"@turbowarp/scratch-storage": "^0.0.202403251715",
Lines changed: 2 additions & 0 deletions
Loading

‎src/components/tw-restore-point-modal/restore-point-modal.css‎

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
@import "../../css/filters.css";
33

44
.modal-content {
5-
max-width: 550px;
5+
max-width: 600px;
66
margin-top: 50px;
77
}
88

@@ -96,25 +96,34 @@
9696
font-size: 2em;
9797
}
9898

99-
.delete-button {
99+
.restore-point-buttons {
100+
margin-left: auto;
101+
display: flex;
102+
flex-direction: row;
103+
gap: 0.5rem;
104+
}
105+
106+
.restore-point-button {
100107
appearance: none;
101108
background: none;
102109
border: none;
103110
border-radius: 100%;
104111
width: 2rem;
105112
height: 2rem;
106-
margin-left: auto;
107113
display: flex;
108114
align-items: center;
109115
justify-content: center;
110116
}
111-
.delete-button img {
117+
.restore-point-button[disabled] {
118+
opacity: 0.5;
119+
}
120+
.restore-point-button img {
112121
display: block;
113122
width: 75%;
114123
height: 75%;
115124
filter: $filter-icon-gray;
116125
}
117-
.delete-button:hover {
126+
.restore-point-button:not([disabled]):hover {
118127
background-color: $ui-black-transparent;
119128
}
120129

‎src/components/tw-restore-point-modal/restore-point-modal.jsx‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,9 @@ const RestorePointModal = props => (
166166
<RestorePoint
167167
key={restorePoint.id}
168168
onClickDelete={props.onClickDelete}
169+
onClickExport={props.onClickExport}
169170
onClickLoad={props.onClickLoad}
171+
isExporting={props.isExporting(restorePoint.id)}
170172
{...restorePoint}
171173
/>
172174
))}
@@ -221,7 +223,9 @@ RestorePointModal.propTypes = {
221223
onClickCreate: PropTypes.func.isRequired,
222224
onClickDelete: PropTypes.func.isRequired,
223225
onClickDeleteAll: PropTypes.func.isRequired,
226+
onClickExport: PropTypes.func.isRequired,
224227
onClickLoad: PropTypes.func.isRequired,
228+
isExporting: PropTypes.func.isRequired,
225229
isLoading: PropTypes.bool.isRequired,
226230
totalSize: PropTypes.number.isRequired,
227231
restorePoints: PropTypes.arrayOf(PropTypes.shape({})),

‎src/components/tw-restore-point-modal/restore-point.jsx‎

Lines changed: 34 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import styles from './restore-point-modal.css';
66
import {formatBytes} from '../../lib/tw-bytes-utils';
77
import RestorePointAPI from '../../lib/tw-restore-point-api';
88
import log from '../../lib/log';
9+
import exportIcon from './export.svg';
910
import deleteIcon from './delete.svg';
1011

1112
// Browser support is not perfect yet
@@ -16,6 +17,7 @@ class RestorePoint extends React.Component {
1617
super(props);
1718
bindAll(this, [
1819
'handleClickDelete',
20+
'handleClickExport',
1921
'handleClickLoad'
2022
]);
2123
this.state = {
@@ -69,6 +71,11 @@ class RestorePoint extends React.Component {
6971
this.props.onClickDelete(this.props.id);
7072
}
7173

74+
handleClickExport (e) {
75+
e.stopPropagation();
76+
this.props.onClickExport(this.props.id);
77+
}
78+
7279
handleClickLoad () {
7380
this.props.onClickLoad(this.props.id);
7481
}
@@ -129,16 +136,31 @@ class RestorePoint extends React.Component {
129136
</div>
130137
</div>
131138

132-
<button
133-
className={styles.deleteButton}
134-
onClick={this.handleClickDelete}
135-
>
136-
<img
137-
src={deleteIcon}
138-
alt="Delete"
139-
draggable={false}
140-
/>
141-
</button>
139+
<div className={styles.restorePointButtons}>
140+
<button
141+
className={styles.restorePointButton}
142+
onClick={this.handleClickExport}
143+
disabled={this.props.isExporting}
144+
>
145+
<img
146+
src={exportIcon}
147+
alt="Export"
148+
draggable={false}
149+
/>
150+
</button>
151+
152+
<button
153+
className={styles.restorePointButton}
154+
onClick={this.handleClickDelete}
155+
disabled={this.props.isExporting}
156+
>
157+
<img
158+
src={deleteIcon}
159+
alt="Delete"
160+
draggable={false}
161+
/>
162+
</button>
163+
</div>
142164
</div>
143165
);
144166
}
@@ -151,7 +173,9 @@ RestorePoint.propTypes = {
151173
projectSize: PropTypes.number.isRequired,
152174
thumbnailSize: PropTypes.number.isRequired,
153175
assets: PropTypes.shape({}).isRequired, // Record<string, number>
176+
isExporting: PropTypes.bool.isRequired,
154177
onClickDelete: PropTypes.func.isRequired,
178+
onClickExport: PropTypes.func.isRequired,
155179
onClickLoad: PropTypes.func.isRequired
156180
};
157181

‎src/containers/tw-restore-point-manager.jsx‎

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {setFileHandle} from '../reducers/tw';
1010
import TWRestorePointModal from '../components/tw-restore-point-modal/restore-point-modal.jsx';
1111
import RestorePointAPI from '../lib/tw-restore-point-api';
1212
import log from '../lib/log';
13+
import downloadBlob from '../lib/download-blob.js';
1314

1415
/* eslint-disable no-alert */
1516

@@ -38,6 +39,11 @@ const messages = defineMessages({
3839
defaultMessage: 'Error loading restore point: {error}',
3940
description: 'Error message when a restore point could not be loaded',
4041
id: 'tw.restorePoints.error'
42+
},
43+
exportError: {
44+
defaultMessage: 'Error exporting restore point: {error}',
45+
description: 'Error message when a restore point could not be exported',
46+
id: 'tw.restorePoints.exportError'
4147
}
4248
});
4349

@@ -50,14 +56,17 @@ class TWRestorePointManager extends React.Component {
5056
'handleClickDelete',
5157
'handleClickDeleteAll',
5258
'handleChangeInterval',
53-
'handleClickLoad'
59+
'handleClickExport',
60+
'handleClickLoad',
61+
'isExportingRestorePoint'
5462
]);
5563
this.state = {
5664
loading: true,
5765
totalSize: 0,
5866
restorePoints: [],
5967
error: null,
60-
interval: RestorePointAPI.readInterval()
68+
interval: RestorePointAPI.readInterval(),
69+
exportingRestorePoints: []
6170
};
6271
this.timeout = null;
6372
}
@@ -148,6 +157,39 @@ class TWRestorePointManager extends React.Component {
148157
return true;
149158
}
150159

160+
handleClickExport (id) {
161+
if (this.isExportingRestorePoint(id)) {
162+
return;
163+
}
164+
165+
this.setState(oldState => ({
166+
exportingRestorePoints: [...oldState.exportingRestorePoints, id]
167+
}));
168+
169+
const removeFromExportingList = () => {
170+
this.setState(oldState => ({
171+
exportingRestorePoints: oldState.exportingRestorePoints.filter(i => i !== id)
172+
}));
173+
};
174+
175+
RestorePointAPI.exportRestorePoint(id)
176+
.then(result => {
177+
downloadBlob(`${result.title}.sb3`, result.blob);
178+
removeFromExportingList();
179+
})
180+
.catch(error => {
181+
log.error(error);
182+
alert(this.props.intl.formatMessage(messages.exportError, {
183+
error
184+
}));
185+
removeFromExportingList();
186+
});
187+
}
188+
189+
isExportingRestorePoint (id) {
190+
return this.state.exportingRestorePoints.includes(id);
191+
}
192+
151193
handleClickLoad (id) {
152194
if (!this.canLoadProject()) {
153195
return;
@@ -271,9 +313,11 @@ class TWRestorePointManager extends React.Component {
271313
onClickCreate={this.handleClickCreate}
272314
onClickDelete={this.handleClickDelete}
273315
onClickDeleteAll={this.handleClickDeleteAll}
316+
onClickExport={this.handleClickExport}
274317
onClickLoad={this.handleClickLoad}
275318
interval={this.state.interval}
276319
onChangeInterval={this.handleChangeInterval}
320+
isExporting={this.isExportingRestorePoint}
277321
isLoading={this.state.loading}
278322
totalSize={this.state.totalSize}
279323
restorePoints={this.state.restorePoints}

0 commit comments

Comments
 (0)