Skip to content

Commit b57fce8

Browse files
committed
add exporting restore points
1 parent 6f80fae commit b57fce8

File tree

5 files changed

+82
-5
lines changed

5 files changed

+82
-5
lines changed

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,16 @@
122122
background-color: $ui-black-transparent;
123123
}
124124

125+
.export-button {
126+
margin: 0 4px;
127+
padding: 2px 4px;
128+
background-color: $data-primary;
129+
border: 1px solid $ui-black-transparent;
130+
border-radius: 0.25rem;
131+
font-size: small;
132+
color: $ui-white;
133+
}
134+
125135
.disabled {
126136
padding: 0.5rem;
127137
border-radius: 0.5rem;

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,7 @@ const RestorePointModal = props => (
167167
key={restorePoint.id}
168168
onClickDelete={props.onClickDelete}
169169
onClickLoad={props.onClickLoad}
170+
onClickExport={props.onClickExport}
170171
{...restorePoint}
171172
/>
172173
))}
@@ -222,6 +223,7 @@ RestorePointModal.propTypes = {
222223
onClickDelete: PropTypes.func.isRequired,
223224
onClickDeleteAll: PropTypes.func.isRequired,
224225
onClickLoad: PropTypes.func.isRequired,
226+
onClickExport: PropTypes.func.isRequired,
225227
isLoading: PropTypes.bool.isRequired,
226228
totalSize: PropTypes.number.isRequired,
227229
restorePoints: PropTypes.arrayOf(PropTypes.shape({})),

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

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ class RestorePoint extends React.Component {
1616
super(props);
1717
bindAll(this, [
1818
'handleClickDelete',
19-
'handleClickLoad'
19+
'handleClickLoad',
20+
'handleClickExport'
2021
]);
2122
this.state = {
2223
thumbnail: null,
@@ -73,6 +74,11 @@ class RestorePoint extends React.Component {
7374
this.props.onClickLoad(this.props.id);
7475
}
7576

77+
handleClickExport (e) {
78+
e.stopPropagation();
79+
this.props.onClickExport(this.props.id);
80+
}
81+
7682
render () {
7783
const createdDate = new Date(this.props.created * 1000);
7884
return (
@@ -125,6 +131,13 @@ class RestorePoint extends React.Component {
125131
n: Object.keys(this.props.assets).length
126132
}}
127133
/>
134+
135+
<button
136+
className={styles.exportButton}
137+
onClick={this.handleClickExport}
138+
>
139+
Export
140+
</button>
128141
</div>
129142
</div>
130143

@@ -151,7 +164,8 @@ RestorePoint.propTypes = {
151164
thumbnailSize: PropTypes.number.isRequired,
152165
assets: PropTypes.shape({}).isRequired, // Record<string, number>
153166
onClickDelete: PropTypes.func.isRequired,
154-
onClickLoad: PropTypes.func.isRequired
167+
onClickLoad: PropTypes.func.isRequired,
168+
onClickExport: PropTypes.func.isRequired
155169
};
156170

157171
export default RestorePoint;

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

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {LoadingStates, getIsShowingProject, onLoadedProject, requestProjectUploa
99
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';
12+
import downloadBlob from '../lib/download-blob';
1213
import log from '../lib/log';
1314

1415
/* eslint-disable no-alert */
@@ -49,10 +50,12 @@ class TWRestorePointManager extends React.Component {
4950
'handleClickDelete',
5051
'handleClickDeleteAll',
5152
'handleChangeInterval',
52-
'handleClickLoad'
53+
'handleClickLoad',
54+
'handleClickExport'
5355
]);
5456
this.state = {
5557
loading: true,
58+
exporting: false,
5659
totalSize: 0,
5760
restorePoints: [],
5861
error: null,
@@ -161,6 +164,9 @@ class TWRestorePointManager extends React.Component {
161164
if (!this.canLoadProject()) {
162165
return;
163166
}
167+
if (this.state.exporting) {
168+
return;
169+
}
164170

165171
this.props.onCloseModal();
166172
this.props.onStartLoadingRestorePoint(this.props.loadingState);
@@ -181,6 +187,41 @@ class TWRestorePointManager extends React.Component {
181187
});
182188
}
183189

190+
handleClickExport (id) {
191+
if (!this.props.isShowingProject) {
192+
// this might break the state machine if we dont do this? not sure so we won't risk it & just return
193+
return;
194+
}
195+
if (this.state.exporting) {
196+
return;
197+
}
198+
199+
this.setState({
200+
exporting: true
201+
});
202+
203+
// specifically add true so dontLoadProject is true
204+
RestorePointAPI.loadRestorePoint(this.props.vm, id, true)
205+
.then((arrayBuffer) => {
206+
this.setState({
207+
exporting: false
208+
});
209+
210+
const blob = new Blob([arrayBuffer], { type: "application/x.scratch.sb3" });
211+
downloadBlob("restore-point.pmp", blob);
212+
})
213+
.catch(error => {
214+
log.error(error);
215+
alert(this.props.intl.formatMessage(messages.loadError, {
216+
error
217+
}));
218+
219+
this.setState({
220+
exporting: false
221+
});
222+
});
223+
}
224+
184225
handleChangeInterval (e) {
185226
const interval = +e.target.value;
186227
RestorePointAPI.setInterval(interval);
@@ -282,6 +323,7 @@ class TWRestorePointManager extends React.Component {
282323
onClickDelete={this.handleClickDelete}
283324
onClickDeleteAll={this.handleClickDeleteAll}
284325
onClickLoad={this.handleClickLoad}
326+
onClickExport={this.handleClickExport}
285327
interval={this.state.interval}
286328
onChangeInterval={this.handleChangeInterval}
287329
isLoading={this.state.loading}

src/lib/tw-restore-point-api.js

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -362,9 +362,10 @@ const deleteAllRestorePoints = () => openDB().then(db => new Promise((resolveTra
362362
/**
363363
* @param {VirtualMachine} vm scratch-vm instance
364364
* @param {number} id the restore point's ID
365+
* @param {boolean} dontLoadProject if true, the project will not be loaded and this will simply return the sb3 file
365366
* @returns {Promise<ArrayBuffer>} Resolves with sb3 file
366367
*/
367-
const loadRestorePoint = (vm, id) => openDB().then(db => new Promise((resolveTransaction, rejectTransaction) => {
368+
const loadRestorePoint = (vm, id, dontLoadProject) => openDB().then(db => new Promise((resolveTransaction, rejectTransaction) => {
368369
const transaction = db.transaction([METADATA_STORE, PROJECT_STORE, ASSET_STORE], 'readonly');
369370
transaction.onerror = () => {
370371
rejectTransaction(new Error(`Transaction error: ${transaction.error}`));
@@ -378,6 +379,12 @@ const loadRestorePoint = (vm, id) => openDB().then(db => new Promise((resolveTra
378379
// zip in memory.
379380

380381
const loadVM = () => {
382+
if (dontLoadProject) {
383+
return resolveTransaction(zip.generateAsync({
384+
type: 'arraybuffer'
385+
}));
386+
}
387+
381388
resolveTransaction(
382389
zip.generateAsync({
383390
// Don't bother compressing it since it will be immediately decompressed
@@ -426,7 +433,9 @@ const loadRestorePoint = (vm, id) => openDB().then(db => new Promise((resolveTra
426433
};
427434
};
428435

429-
vm.stop();
436+
if (!dontLoadProject) {
437+
vm.stop();
438+
}
430439

431440
loadMetadata();
432441
}));

0 commit comments

Comments
 (0)