Skip to content

Commit

Permalink
Put the entire extension gallery into the library (#804)
Browse files Browse the repository at this point in the history
  • Loading branch information
GarboMuffin authored Aug 20, 2023
1 parent de138fe commit 065d42a
Show file tree
Hide file tree
Showing 31 changed files with 356 additions and 316 deletions.
26 changes: 25 additions & 1 deletion src/components/library-item/library-item.css
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@

.featured-image-container {
position: relative;
width: 100%;
}

.featured-image {
Expand Down Expand Up @@ -184,9 +185,32 @@
font-weight: bold;
}

.featured-extension-metadata-detail img{
.featured-extension-metadata-detail img {
margin-right: 0.25rem;
width: 20px;
height: 20px;
}

.credits-outer {
width: 300px;
padding: 0 1.25rem 1rem 1.25rem;
display: flex;
flex-direction: row;
opacity: 0.75;
text-align: left;
}
.credits-inner {
display: flex;
flex-direction: row;
}
.credits-title {
margin-right: 0.25rem;
}
.credits-list a {
color: inherit !important;
text-decoration: underline;
}


.coming-soon-text {
position: absolute;
Expand Down
45 changes: 34 additions & 11 deletions src/components/library-item/library-item.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ class LibraryItemComponent extends React.PureComponent {
) : null}
<img
className={styles.featuredImage}
style={{
aspectRatio: this.props.iconAspectRatio ? this.props.iconAspectRatio.toString() : ''
}}
loading="lazy"
src={this.props.iconURL}
/>
</div>
Expand All @@ -58,6 +62,31 @@ class LibraryItemComponent extends React.PureComponent {
<br />
<span className={styles.featuredDescription}>{this.props.description}</span>
</div>

{this.props.credits && this.props.credits.length > 0 && (
<div className={styles.creditsOuter}>
<div className={styles.creditsInner}>
<div className={styles.creditsTitle}>
<FormattedMessage
defaultMessage="Created by:"
description="Appears in the extension list. Followed by a list of names."
id="tw.createdBy"
/>
</div>
<div className={styles.creditsList}>
{this.props.credits.map((credit, index) => (
<React.Fragment key={index}>
{credit}
{index !== this.props.credits.length - 1 && (
', '
)}
</React.Fragment>
))}
</div>
</div>
</div>
)}

{this.props.bluetoothRequired || this.props.internetConnectionRequired || this.props.collaborator ? (
<div className={styles.featuredExtensionMetadata}>
<div className={styles.featuredExtensionRequirement}>
Expand Down Expand Up @@ -103,16 +132,6 @@ class LibraryItemComponent extends React.PureComponent {
</div>
</div>
) : null}
{this.props.incompatibleWithScratch && (
<div className={styles.incompatibleWithScratch}>
<FormattedMessage
// eslint-disable-next-line max-len
defaultMessage="Not compatible with Scratch."
description="Warning that appears on extensions that won't work in Scratch."
id="tw.extensions.incompatible"
/>
</div>
)}
</div>
) : (
<Box
Expand Down Expand Up @@ -171,14 +190,18 @@ LibraryItemComponent.propTypes = {
featured: PropTypes.bool,
hidden: PropTypes.bool,
iconURL: PropTypes.string,
incompatibleWithScratch: PropTypes.bool,
iconAspectRatio: PropTypes.number,
insetIconURL: PropTypes.string,
internetConnectionRequired: PropTypes.bool,
isPlaying: PropTypes.bool,
name: PropTypes.oneOfType([
PropTypes.string,
PropTypes.node
]),
credits: PropTypes.arrayOf(PropTypes.oneOfType([
PropTypes.string,
PropTypes.node
])),
onBlur: PropTypes.func.isRequired,
onClick: PropTypes.func.isRequired,
onFocus: PropTypes.func.isRequired,
Expand Down
131 changes: 72 additions & 59 deletions src/components/library/library.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import bindAll from 'lodash.bindall';
import PropTypes from 'prop-types';
import React from 'react';
import {defineMessages, injectIntl, intlShape} from 'react-intl';
import {isScratchDesktop} from '../../lib/isScratchDesktop';

import LibraryItem from '../../containers/library-item.jsx';
import Modal from '../../containers/modal.jsx';
Expand Down Expand Up @@ -82,13 +81,7 @@ class LibraryComponent extends React.Component {
}
}
handleSelect (id) {
const extension = this.getFilteredData()[id];
if (extension.href) {
window.open(extension.href);
}
if (!extension.href || isScratchDesktop()) {
this.handleClose();
}
this.handleClose();
this.props.onItemSelected(this.getFilteredData()[id]);
}
handleClose () {
Expand Down Expand Up @@ -154,19 +147,27 @@ class LibraryComponent extends React.Component {
getFilteredData () {
if (this.state.selectedTag === 'all') {
if (!this.state.filterQuery) return this.state.data;
return this.state.data.filter(dataItem => (
(dataItem.tags || [])
// Second argument to map sets `this`
.map(String.prototype.toLowerCase.call, String.prototype.toLowerCase)
.concat(dataItem.name ?
(typeof dataItem.name === 'string' ?
// Use the name if it is a string, else use formatMessage to get the translated name
dataItem.name : this.props.intl.formatMessage(dataItem.name.props)
).toLowerCase() :
null)
.join('\n') // unlikely to partially match newlines
.indexOf(this.state.filterQuery.toLowerCase()) !== -1
));
return this.state.data.filter(dataItem => {
if (React.isValidElement(dataItem)) {
return false;
}
const search = [...dataItem.tags];
if (dataItem.name) {
// Use the name if it is a string, else use formatMessage to get the translated name
if (typeof dataItem.name === 'string') {
search.push(dataItem.name);
} else {
search.push(this.props.intl.formatMessage(dataItem.name.props));
}
}
if (dataItem.description) {
search.push(dataItem.description);
}
return search
.join('\n')
.toLowerCase()
.includes(this.state.filterQuery.toLowerCase());
});
}
return this.state.data.filter(dataItem => (
dataItem.tags &&
Expand Down Expand Up @@ -233,30 +234,38 @@ class LibraryComponent extends React.Component {
ref={this.setFilteredDataRef}
>
{this.state.loaded ? this.getFilteredData().map((dataItem, index) => (
<LibraryItem
bluetoothRequired={dataItem.bluetoothRequired}
collaborator={dataItem.collaborator}
description={dataItem.description}
disabled={dataItem.disabled}
extensionId={dataItem.extensionId}
featured={dataItem.featured}
hidden={dataItem.hidden}
href={dataItem.href}
iconMd5={dataItem.costumes ? dataItem.costumes[0].md5ext : dataItem.md5ext}
iconRawURL={dataItem.rawURL}
icons={dataItem.costumes}
id={index}
incompatibleWithScratch={dataItem.incompatibleWithScratch}
insetIconURL={dataItem.insetIconURL}
internetConnectionRequired={dataItem.internetConnectionRequired}
isPlaying={this.state.playingItem === index}
key={typeof dataItem.name === 'string' ? dataItem.name : dataItem.rawURL}
name={dataItem.name}
showPlayButton={this.props.showPlayButton}
onMouseEnter={this.handleMouseEnter}
onMouseLeave={this.handleMouseLeave}
onSelect={this.handleSelect}
/>
React.isValidElement(dataItem) ? (
<React.Fragment key={index}>
{dataItem}
</React.Fragment>
) : (
<LibraryItem
bluetoothRequired={dataItem.bluetoothRequired}
collaborator={dataItem.collaborator}
description={dataItem.description}
disabled={dataItem.disabled}
extensionId={dataItem.extensionId}
href={dataItem.href}
featured={dataItem.featured}
hidden={dataItem.hidden}
iconMd5={dataItem.costumes ? dataItem.costumes[0].md5ext : dataItem.md5ext}
iconRawURL={dataItem.rawURL}
iconAspectRatio={dataItem.iconAspectRatio}
icons={dataItem.costumes}
id={index}
incompatibleWithScratch={dataItem.incompatibleWithScratch}
insetIconURL={dataItem.insetIconURL}
internetConnectionRequired={dataItem.internetConnectionRequired}
isPlaying={this.state.playingItem === index}
key={typeof dataItem.name === 'string' ? dataItem.name : dataItem.rawURL}
name={dataItem.name}
credits={dataItem.credits}
showPlayButton={this.props.showPlayButton}
onMouseEnter={this.handleMouseEnter}
onMouseLeave={this.handleMouseLeave}
onSelect={this.handleSelect}
/>
)
)) : (
<div className={styles.spinnerWrapper}>
<Spinner
Expand All @@ -272,20 +281,24 @@ class LibraryComponent extends React.Component {
}

LibraryComponent.propTypes = {
data: PropTypes.oneOfType([PropTypes.arrayOf(
/* eslint-disable react/no-unused-prop-types, lines-around-comment */
// An item in the library
PropTypes.shape({
// @todo remove md5/rawURL prop from library, refactor to use storage
md5: PropTypes.string,
name: PropTypes.oneOfType([
PropTypes.string,
PropTypes.node
]),
rawURL: PropTypes.string
})
/* eslint-enable react/no-unused-prop-types, lines-around-comment */
), PropTypes.instanceOf(Promise)]),
data: PropTypes.oneOfType([
PropTypes.arrayOf(
/* eslint-disable react/no-unused-prop-types, lines-around-comment */
// An item in the library
PropTypes.shape({
// @todo remove md5/rawURL prop from library, refactor to use storage
md5: PropTypes.string,
name: PropTypes.oneOfType([
PropTypes.string,
PropTypes.node
]),
rawURL: PropTypes.string
})
/* eslint-enable react/no-unused-prop-types, lines-around-comment */
),
PropTypes.node,
PropTypes.instanceOf(Promise)
]),
filterable: PropTypes.bool,
id: PropTypes.string.isRequired,
intl: intlShape.isRequired,
Expand Down
8 changes: 8 additions & 0 deletions src/components/tw-extension-separator/separator.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
@import "../../css/colors.css";

.separator {
width: 100%;
border: none;
border-top: 2px solid $ui-black-transparent;
margin: 0.5rem 0;
}
8 changes: 8 additions & 0 deletions src/components/tw-extension-separator/separator.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import React from 'react';
import styles from './separator.css';

const Separator = () => (
<hr className={styles.separator} />
);

export default Separator;
Loading

0 comments on commit 065d42a

Please sign in to comment.