Skip to content

Commit c3721ea

Browse files
committed
批量导入扩展
1 parent 1761789 commit c3721ea

13 files changed

Lines changed: 509 additions & 72 deletions

File tree

src/components/library-item/library-item.css

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,3 +240,41 @@
240240
.library-item:hover .favorite-container {
241241
display: block;
242242
}
243+
244+
.selection-container {
245+
display: flex;
246+
align-items: center;
247+
justify-content: center;
248+
background: none;
249+
border: none;
250+
padding: 0;
251+
margin: 0;
252+
position: absolute;
253+
right: 0.75rem;
254+
bottom: 0.75rem;
255+
cursor: pointer;
256+
}
257+
258+
.selection-checkbox {
259+
width: 22px;
260+
height: 22px;
261+
border-radius: 6px;
262+
border: 2px solid $motion-primary;
263+
background: $ui-white;
264+
display: flex;
265+
align-items: center;
266+
justify-content: center;
267+
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.12);
268+
}
269+
270+
.selection-active .selection-checkbox {
271+
background: $motion-primary;
272+
}
273+
274+
.selection-checkmark {
275+
width: 6px;
276+
height: 10px;
277+
border-right: 2px solid $ui-white;
278+
border-bottom: 2px solid $ui-white;
279+
transform: rotate(45deg) translate(-1px, -1px);
280+
}

src/components/library-item/library-item.jsx

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,19 @@ class LibraryItemComponent extends React.PureComponent {
4545
/>
4646
</button>
4747
);
48+
const selectionCheckbox = this.props.isSelectable ? (
49+
<button
50+
className={classNames(styles.selectionContainer, {
51+
[styles.selectionActive]: this.props.isSelected
52+
})}
53+
onClick={this.props.onSelectionToggle}
54+
title={this.props.isSelected ? 'Cancel selection' : 'Select'}
55+
>
56+
<span className={styles.selectionCheckbox}>
57+
{this.props.isSelected ? <span className={styles.selectionCheckmark} /> : null}
58+
</span>
59+
</button>
60+
) : null;
4861

4962
return this.props.featured ? (
5063
<div
@@ -203,6 +216,7 @@ class LibraryItemComponent extends React.PureComponent {
203216
) : null}
204217

205218
{favorite}
219+
{selectionCheckbox}
206220
</div>
207221
) : (
208222
<Box
@@ -245,6 +259,7 @@ class LibraryItemComponent extends React.PureComponent {
245259
) : null}
246260

247261
{favorite}
262+
{selectionCheckbox}
248263
</Box>
249264
);
250265
}
@@ -268,6 +283,8 @@ LibraryItemComponent.propTypes = {
268283
insetIconURL: PropTypes.string,
269284
internetConnectionRequired: PropTypes.bool,
270285
isPlaying: PropTypes.bool,
286+
isSelectable: PropTypes.bool,
287+
isSelected: PropTypes.bool,
271288
name: PropTypes.oneOfType([
272289
PropTypes.string,
273290
PropTypes.node
@@ -290,6 +307,7 @@ LibraryItemComponent.propTypes = {
290307
onMouseEnter: PropTypes.func.isRequired,
291308
onMouseLeave: PropTypes.func.isRequired,
292309
onPlay: PropTypes.func.isRequired,
310+
onSelectionToggle: PropTypes.func,
293311
onStop: PropTypes.func.isRequired,
294312
showPlayButton: PropTypes.bool
295313
};

src/components/library/library.css

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,27 @@
6161
overflow: hidden;
6262
}
6363

64+
.header-action {
65+
margin-left: auto;
66+
display: flex;
67+
align-items: center;
68+
}
69+
70+
.header-action-button {
71+
background: $motion-primary;
72+
color: $ui-white;
73+
border: none;
74+
border-radius: 999px;
75+
padding: 0.6rem 1rem;
76+
font-size: 0.875rem;
77+
font-weight: bold;
78+
cursor: pointer;
79+
}
80+
81+
.header-action-button:hover {
82+
background: $motion-primary;
83+
}
84+
6485
.spinner-wrapper {
6586
width: 100%;
6687
height: 100%;

src/components/library/library.jsx

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ class LibraryComponent extends React.Component {
4141
'handleFilterClear',
4242
'handleMouseEnter',
4343
'handleMouseLeave',
44+
'handleItemSelectionToggle',
4445
'handlePlayingEnd',
4546
'handleSelect',
4647
'handleFavorite',
@@ -85,6 +86,11 @@ class LibraryComponent extends React.Component {
8586
this.handleClose();
8687
this.props.onItemSelected(this.getFilteredData()[id]);
8788
}
89+
handleItemSelectionToggle (id) {
90+
if (this.props.onItemSelectionToggle) {
91+
this.props.onItemSelectionToggle(this.getFilteredData()[id]);
92+
}
93+
}
8894
readFavoritesFromStorage () {
8995
let data;
9096
try {
@@ -290,6 +296,11 @@ class LibraryComponent extends React.Component {
290296
))}
291297
</div>
292298
}
299+
{this.props.headerAction ? (
300+
<div className={styles.headerAction}>
301+
{this.props.headerAction}
302+
</div>
303+
) : null}
293304
</div>
294305
)}
295306
<div
@@ -321,6 +332,8 @@ class LibraryComponent extends React.Component {
321332
insetIconURL={dataItem.insetIconURL}
322333
internetConnectionRequired={dataItem.internetConnectionRequired}
323334
isPlaying={this.state.playingItem === index}
335+
isSelectable={this.props.isItemSelectable && this.props.isItemSelectable(dataItem)}
336+
isSelected={this.props.isItemSelected && this.props.isItemSelected(dataItem)}
324337
key={dataItem.key || (
325338
typeof dataItem.name === 'string' ?
326339
dataItem.name :
@@ -333,6 +346,7 @@ class LibraryComponent extends React.Component {
333346
showPlayButton={this.props.showPlayButton}
334347
onMouseEnter={this.handleMouseEnter}
335348
onMouseLeave={this.handleMouseLeave}
349+
onSelectionToggle={this.handleItemSelectionToggle}
336350
onSelect={this.handleSelect}
337351
/>
338352
)
@@ -379,11 +393,15 @@ LibraryComponent.propTypes = {
379393
PropTypes.instanceOf(Promise)
380394
]),
381395
filterable: PropTypes.bool,
396+
headerAction: PropTypes.node,
382397
id: PropTypes.string.isRequired,
398+
isItemSelectable: PropTypes.func,
399+
isItemSelected: PropTypes.func,
383400
persistableKey: PropTypes.string,
384401
intl: intlShape.isRequired,
385402
onItemMouseEnter: PropTypes.func,
386403
onItemMouseLeave: PropTypes.func,
404+
onItemSelectionToggle: PropTypes.func,
387405
onItemSelected: PropTypes.func,
388406
onRequestClose: PropTypes.func,
389407
setStopHandler: PropTypes.func,

src/components/tw-extension-import-modal/extension-import-modal.css

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,13 @@
2424
color: $text-primary-transparent;
2525
}
2626

27+
.progressText {
28+
margin: -0.75rem 0 1rem 0;
29+
line-height: 1.4;
30+
font-size: 0.8125rem;
31+
color: $text-primary-transparent;
32+
}
33+
2734
.errorMessage {
2835
color: $error-primary;
2936
margin: 0 0 1.5rem 0;
@@ -97,4 +104,4 @@
97104
.cancelButton:disabled {
98105
background-color: $ui-tertiary;
99106
cursor: not-allowed;
100-
}
107+
}

src/components/tw-extension-import-modal/extension-import-modal.jsx

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,16 @@ const messages = defineMessages({
1717
description: 'Description for extension import method selection',
1818
id: 'tw.extensionImportModal.description'
1919
},
20+
batchDescription: {
21+
defaultMessage: 'How would you like to import these {count} extensions?',
22+
description: 'Description for batch extension import method selection',
23+
id: 'tw.extensionImportModal.batchDescription'
24+
},
25+
progress: {
26+
defaultMessage: 'Importing {current} / {total}',
27+
description: 'Progress text shown while importing extensions in batch',
28+
id: 'tw.extensionImportModal.progress'
29+
},
2030
normalImport: {
2131
defaultMessage: 'Normal Import',
2232
description: 'Button to import extension normally',
@@ -46,9 +56,25 @@ const ExtensionImportModal = props => (
4656
<FormattedMessage {...messages.title} />
4757
</h3>
4858
<p className={styles.description}>
49-
<FormattedMessage {...messages.description} />
59+
{props.batchMode ? (
60+
<FormattedMessage {...messages.batchDescription} values={{count: props.itemCount}} />
61+
) : (
62+
<FormattedMessage {...messages.description} />
63+
)}
5064
</p>
5165

66+
{props.loading && props.batchMode && props.itemCount > 1 ? (
67+
<p className={styles.progressText}>
68+
<FormattedMessage
69+
{...messages.progress}
70+
values={{
71+
current: props.progressIndex || 1,
72+
total: props.itemCount
73+
}}
74+
/>
75+
</p>
76+
) : null}
77+
5278
{props.error && (
5379
<div className={styles.errorMessage}>
5480
{props.error}
@@ -91,13 +117,16 @@ const ExtensionImportModal = props => (
91117
);
92118

93119
ExtensionImportModal.propTypes = {
120+
batchMode: PropTypes.bool,
94121
extensionName: PropTypes.string,
122+
itemCount: PropTypes.number,
95123
loading: PropTypes.bool,
96124
error: PropTypes.string,
97125
onClose: PropTypes.func,
98126
onNormalImport: PropTypes.func,
99127
onTextImport: PropTypes.func,
128+
progressIndex: PropTypes.number,
100129
intl: intlShape.isRequired
101130
};
102131

103-
export default injectIntl(ExtensionImportModal);
132+
export default injectIntl(ExtensionImportModal);

src/containers/blocks.jsx

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,8 @@ import {
3232
openConnectionModal,
3333
openCustomExtensionModal,
3434
openExtensionImportMethodModal,
35-
setSelectedExtension
35+
setSelectedExtension,
36+
setSelectedExtensions
3637
} from '../reducers/modals';
3738
import { activateCustomProcedures, deactivateCustomProcedures } from '../reducers/custom-procedures';
3839
import { setConnectionModalExtensionId } from '../reducers/connection-modal';
@@ -199,6 +200,10 @@ class Blocks extends React.Component {
199200
this.workspace = this.ScratchBlocks.inject(this.blocks, workspaceConfig);
200201
AddonHooks.blocklyWorkspace = this.workspace;
201202
this.syncWorkspaceCullingState();
203+
window.__twEnableProcedureReturns = () => {
204+
this.handleEnableProcedureReturns();
205+
this.handleCategorySelected('myBlocks');
206+
};
202207

203208
// Register buttons under new callback keys for creating variables,
204209
// lists, and procedures from extensions.
@@ -346,6 +351,9 @@ class Blocks extends React.Component {
346351
cancelAnimationFrame(this.toolboxStateSyncFrame);
347352
this.toolboxStateSyncFrame = null;
348353
}
354+
if (window.__twEnableProcedureReturns) {
355+
delete window.__twEnableProcedureReturns;
356+
}
349357
this.workspace.dispose();
350358

351359
// Clear the flyout blocks so that they can be recreated on mount.
@@ -1204,6 +1212,7 @@ class Blocks extends React.Component {
12041212
onOpenCustomExtensionModal,
12051213
onOpenExtensionImportMethodModal,
12061214
onSetSelectedExtension,
1215+
onSetSelectedExtensions,
12071216
reduxOnOpenCustomExtensionModal,
12081217
updateToolboxState,
12091218
onActivateCustomProcedures,
@@ -1246,6 +1255,7 @@ class Blocks extends React.Component {
12461255
onOpenCustomExtensionModal={onOpenCustomExtensionModal || reduxOnOpenCustomExtensionModal}
12471256
onOpenExtensionImportMethodModal={onOpenExtensionImportMethodModal}
12481257
onSetSelectedExtension={onSetSelectedExtension}
1258+
onSetSelectedExtensions={onSetSelectedExtensions}
12491259
/>
12501260
) : null}
12511261
{customProceduresVisible ? (
@@ -1282,6 +1292,7 @@ Blocks.propTypes = {
12821292
onOpenCustomExtensionModal: PropTypes.func,
12831293
onOpenExtensionImportMethodModal: PropTypes.func,
12841294
onSetSelectedExtension: PropTypes.func,
1295+
onSetSelectedExtensions: PropTypes.func,
12851296
reduxOnOpenCustomExtensionModal: PropTypes.func,
12861297
onRequestCloseCustomProcedures: PropTypes.func,
12871298
onRequestCloseExtensionLibrary: PropTypes.func,
@@ -1359,6 +1370,7 @@ const mapDispatchToProps = dispatch => ({
13591370
reduxOnOpenCustomExtensionModal: () => dispatch(openCustomExtensionModal()),
13601371
onOpenExtensionImportMethodModal: () => dispatch(openExtensionImportMethodModal()),
13611372
onSetSelectedExtension: extension => dispatch(setSelectedExtension(extension)),
1373+
onSetSelectedExtensions: extensions => dispatch(setSelectedExtensions(extensions)),
13621374
onRequestCloseExtensionLibrary: () => {
13631375
dispatch(closeExtensionLibrary());
13641376
},

0 commit comments

Comments
 (0)