diff --git a/src/components/library-item/library-item.css b/src/components/library-item/library-item.css
index fc57d33e282..71d89388a49 100644
--- a/src/components/library-item/library-item.css
+++ b/src/components/library-item/library-item.css
@@ -116,6 +116,7 @@
.featured-image-container {
position: relative;
+ width: 100%;
}
.featured-image {
@@ -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;
diff --git a/src/components/library-item/library-item.jsx b/src/components/library-item/library-item.jsx
index 24772813590..7789a2968eb 100644
--- a/src/components/library-item/library-item.jsx
+++ b/src/components/library-item/library-item.jsx
@@ -38,6 +38,10 @@ class LibraryItemComponent extends React.PureComponent {
) : null}
@@ -58,6 +62,31 @@ class LibraryItemComponent extends React.PureComponent {
{this.props.description}
+
+ {this.props.credits && this.props.credits.length > 0 && (
+
+
+
+
+
+
+ {this.props.credits.map((credit, index) => (
+
+ {credit}
+ {index !== this.props.credits.length - 1 && (
+ ', '
+ )}
+
+ ))}
+
+
+
+ )}
+
{this.props.bluetoothRequired || this.props.internetConnectionRequired || this.props.collaborator ? (
@@ -103,16 +132,6 @@ class LibraryItemComponent extends React.PureComponent {
) : null}
- {this.props.incompatibleWithScratch && (
-
-
-
- )}
) : (
(
- (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 &&
@@ -233,30 +234,38 @@ class LibraryComponent extends React.Component {
ref={this.setFilteredDataRef}
>
{this.state.loaded ? this.getFilteredData().map((dataItem, index) => (
-
+ React.isValidElement(dataItem) ? (
+
+ {dataItem}
+
+ ) : (
+
+ )
)) : (
(
+
+);
+
+export default Separator;
diff --git a/src/containers/extension-library.jsx b/src/containers/extension-library.jsx
index 5b820a3cdb1..ab39a6dc4ee 100644
--- a/src/containers/extension-library.jsx
+++ b/src/containers/extension-library.jsx
@@ -5,32 +5,98 @@ import VM from 'scratch-vm';
import {defineMessages, injectIntl, intlShape} from 'react-intl';
import log from '../lib/log';
-import extensionLibraryContent from '../lib/libraries/extensions/index.jsx';
+import extensionLibraryContent, {
+ galleryError,
+ galleryLoading,
+ galleryMore
+} from '../lib/libraries/extensions/index.jsx';
import extensionTags from '../lib/libraries/tw-extension-tags';
import LibraryComponent from '../components/library/library.jsx';
import extensionIcon from '../components/action-menu/icon--sprite.svg';
+import Separator from '../components/tw-extension-separator/separator.jsx';
const messages = defineMessages({
extensionTitle: {
defaultMessage: 'Choose an Extension',
description: 'Heading for the extension library',
id: 'gui.extensionLibrary.chooseAnExtension'
- },
- incompatible: {
- // eslint-disable-next-line max-len
- defaultMessage: 'This extension is incompatible with Scratch. Projects made with it cannot be uploaded to the Scratch website. Are you sure you want to enable it?',
- description: 'Confirm loading Scratch-incompatible extension',
- id: 'tw.confirmIncompatibleExtension'
}
});
+const toLibraryItem = extension => ({
+ rawURL: extension.iconURL || extensionIcon,
+ ...extension
+});
+
+let cachedGallery = null;
+
+const fetchLibrary = async () => {
+ const res = await fetch('https://extensions.turbowarp.org/generated-metadata/extensions-v0.json');
+ if (!res.ok) {
+ throw new Error(`HTTP status ${res.status}`);
+ }
+ const data = await res.json();
+ return data.extensions.map(extension => {
+ const allCredits = [
+ ...(extension.by || []),
+ ...(extension.original || [])
+ ];
+ return {
+ name: extension.name,
+ description: extension.description,
+ extensionId: extension.id,
+ extensionURL: `https://extensions.turbowarp.org/${extension.slug}.js`,
+ iconURL: `https://extensions.turbowarp.org/${extension.image || 'images/unknown.svg'}`,
+ iconAspectRatio: 2,
+ tags: ['tw'],
+ credits: allCredits.map(credit => {
+ if (credit.link) {
+ return (
+
+ {credit.name}
+
+ );
+ }
+ return credit.name;
+ }),
+ incompatibleWithScratch: true,
+ featured: true
+ };
+ });
+};
+
class ExtensionLibrary extends React.PureComponent {
constructor (props) {
super(props);
bindAll(this, [
'handleItemSelect'
]);
+ this.state = {
+ gallery: cachedGallery,
+ galleryError: null
+ };
+ }
+ componentDidMount () {
+ if (!this.state.gallery) {
+ fetchLibrary()
+ .then(gallery => {
+ cachedGallery = gallery;
+ this.setState({
+ gallery
+ });
+ })
+ .catch(error => {
+ log.error(error);
+ this.setState({
+ galleryError: error
+ });
+ });
+ }
}
handleItemSelect (item) {
if (item.href) {
@@ -46,11 +112,6 @@ class ExtensionLibrary extends React.PureComponent {
return;
}
- // eslint-disable-next-line no-alert
- if (item.incompatibleWithScratch && !confirm(this.props.intl.formatMessage(messages.incompatible))) {
- return;
- }
-
if (extensionId === 'procedures_enable_return') {
this.props.onEnableProcedureReturns();
this.props.onCategorySelected('myBlocks');
@@ -75,14 +136,21 @@ class ExtensionLibrary extends React.PureComponent {
}
}
render () {
- const extensionLibraryThumbnailData = extensionLibraryContent.map(extension => ({
- rawURL: extension.iconURL || extensionIcon,
- ...extension
- }));
+ const library = extensionLibraryContent.map(toLibraryItem);
+ library.push();
+ if (this.state.gallery) {
+ library.push(...this.state.gallery.map(toLibraryItem));
+ library.push(toLibraryItem(galleryMore));
+ } else if (this.state.galleryError) {
+ library.push(toLibraryItem(galleryError));
+ } else {
+ library.push(toLibraryItem(galleryLoading));
+ }
+
return (
\ No newline at end of file
diff --git a/src/lib/libraries/extensions/gallery/bigint.svg b/src/lib/libraries/extensions/gallery/bigint.svg
deleted file mode 100644
index e9db2ebe213..00000000000
--- a/src/lib/libraries/extensions/gallery/bigint.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/src/lib/libraries/extensions/gallery/bitwise.svg b/src/lib/libraries/extensions/gallery/bitwise.svg
deleted file mode 100644
index 241fa60f86f..00000000000
--- a/src/lib/libraries/extensions/gallery/bitwise.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/src/lib/libraries/extensions/gallery/box2d.svg b/src/lib/libraries/extensions/gallery/box2d.svg
deleted file mode 100644
index 79c5bcff1f8..00000000000
--- a/src/lib/libraries/extensions/gallery/box2d.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/src/lib/libraries/extensions/gallery/clippingblending.svg b/src/lib/libraries/extensions/gallery/clippingblending.svg
deleted file mode 100644
index f8f9fc09fd9..00000000000
--- a/src/lib/libraries/extensions/gallery/clippingblending.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/src/lib/libraries/extensions/gallery/clonesplus.svg b/src/lib/libraries/extensions/gallery/clonesplus.svg
deleted file mode 100644
index 71dde55d54c..00000000000
--- a/src/lib/libraries/extensions/gallery/clonesplus.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/src/lib/libraries/extensions/gallery/cursor.svg b/src/lib/libraries/extensions/gallery/cursor.svg
deleted file mode 100644
index b0242c2d9e6..00000000000
--- a/src/lib/libraries/extensions/gallery/cursor.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/src/lib/libraries/extensions/gallery/fetch.svg b/src/lib/libraries/extensions/gallery/fetch.svg
deleted file mode 100644
index 39f1716c8d8..00000000000
--- a/src/lib/libraries/extensions/gallery/fetch.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/src/lib/libraries/extensions/gallery/files.svg b/src/lib/libraries/extensions/gallery/files.svg
deleted file mode 100644
index 0b0459d5d50..00000000000
--- a/src/lib/libraries/extensions/gallery/files.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/src/lib/libraries/extensions/gallery/gallery.svg b/src/lib/libraries/extensions/gallery/gallery.svg
index 8003d333952..1a9b8d4b097 100644
--- a/src/lib/libraries/extensions/gallery/gallery.svg
+++ b/src/lib/libraries/extensions/gallery/gallery.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/src/lib/libraries/extensions/gallery/gamepad.svg b/src/lib/libraries/extensions/gallery/gamepad.svg
deleted file mode 100644
index 026a148f9a3..00000000000
--- a/src/lib/libraries/extensions/gallery/gamepad.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/src/lib/libraries/extensions/gallery/json.svg b/src/lib/libraries/extensions/gallery/json.svg
deleted file mode 100644
index 28e3b6c2fc0..00000000000
--- a/src/lib/libraries/extensions/gallery/json.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/src/lib/libraries/extensions/gallery/local-storage.svg b/src/lib/libraries/extensions/gallery/local-storage.svg
deleted file mode 100644
index c4b923d2fdb..00000000000
--- a/src/lib/libraries/extensions/gallery/local-storage.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/src/lib/libraries/extensions/gallery/looksplus.svg b/src/lib/libraries/extensions/gallery/looksplus.svg
deleted file mode 100644
index 455c8d24c72..00000000000
--- a/src/lib/libraries/extensions/gallery/looksplus.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/src/lib/libraries/extensions/gallery/pointerlock.svg b/src/lib/libraries/extensions/gallery/pointerlock.svg
deleted file mode 100644
index f5993789751..00000000000
--- a/src/lib/libraries/extensions/gallery/pointerlock.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/src/lib/libraries/extensions/gallery/regex.svg b/src/lib/libraries/extensions/gallery/regex.svg
deleted file mode 100644
index 191cc7d7a33..00000000000
--- a/src/lib/libraries/extensions/gallery/regex.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/src/lib/libraries/extensions/gallery/runtime-options.svg b/src/lib/libraries/extensions/gallery/runtime-options.svg
deleted file mode 100644
index 82a28042894..00000000000
--- a/src/lib/libraries/extensions/gallery/runtime-options.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/src/lib/libraries/extensions/gallery/sensingplus.svg b/src/lib/libraries/extensions/gallery/sensingplus.svg
deleted file mode 100644
index 5904df0a0ff..00000000000
--- a/src/lib/libraries/extensions/gallery/sensingplus.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/src/lib/libraries/extensions/gallery/stretch.svg b/src/lib/libraries/extensions/gallery/stretch.svg
deleted file mode 100644
index e117eb7c0b0..00000000000
--- a/src/lib/libraries/extensions/gallery/stretch.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/src/lib/libraries/extensions/gallery/text.svg b/src/lib/libraries/extensions/gallery/text.svg
deleted file mode 100644
index a62785220bb..00000000000
--- a/src/lib/libraries/extensions/gallery/text.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/src/lib/libraries/extensions/gallery/unknown.svg b/src/lib/libraries/extensions/gallery/unknown.svg
deleted file mode 100644
index 8d58519a538..00000000000
--- a/src/lib/libraries/extensions/gallery/unknown.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/src/lib/libraries/extensions/gallery/utilities.svg b/src/lib/libraries/extensions/gallery/utilities.svg
deleted file mode 100644
index 4f062909a32..00000000000
--- a/src/lib/libraries/extensions/gallery/utilities.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/src/lib/libraries/extensions/index.jsx b/src/lib/libraries/extensions/index.jsx
index 7546e4297b3..4b153cb6c95 100644
--- a/src/lib/libraries/extensions/index.jsx
+++ b/src/lib/libraries/extensions/index.jsx
@@ -47,40 +47,9 @@ import gdxforConnectionIconURL from './gdxfor/gdxfor-illustration.svg';
import gdxforConnectionSmallIconURL from './gdxfor/gdxfor-small.svg';
import twIcon from './tw/tw.svg';
-
import customExtensionIcon from './custom/custom.svg';
-
-// eslint-disable-next-line no-unused-vars
-import unknownIcon from './gallery/unknown.svg';
-import galleryIcon from './gallery/gallery.svg';
-import animatedTextIcon from './gallery/animated-text.svg';
-import stretchIcon from './gallery/stretch.svg';
-import gamepadIcon from './gallery/gamepad.svg';
-import cursorIcon from './gallery/cursor.svg';
-import filesIcon from './gallery/files.svg';
-import pointerlockIcon from './gallery/pointerlock.svg';
-import runtimeOptionsIcon from './gallery/runtime-options.svg';
-import utilitiesIcon from './gallery/utilities.svg';
-import sensingPlusIcon from './gallery/sensingplus.svg';
-import clonesPlusIcon from './gallery/clonesplus.svg';
-import looksPlusIcon from './gallery/looksplus.svg';
-import clippingBlendingIcon from './gallery/clippingblending.svg';
-import regexIcon from './gallery/regex.svg';
-import bitwiseIcon from './gallery/bitwise.svg';
-import textIcon from './gallery/text.svg';
-import fetchIcon from './gallery/fetch.svg';
-import box2dIcon from './gallery/box2d.svg';
-import localStorageIcon from './gallery/local-storage.svg';
-import bigIntIcon from './gallery/bigint.svg';
-import jsonIcon from './gallery/json.svg';
import returnIcon from './custom/return.svg';
-
-const galleryItem = object => ({
- ...object,
- tags: ['tw'],
- incompatibleWithScratch: true,
- featured: true
-});
+import galleryIcon from './gallery/gallery.svg';
export default [
{
@@ -93,6 +62,7 @@ export default [
),
extensionId: 'music',
iconURL: musicIconURL,
+ iconAspectRatio: 600 / 372,
insetIconURL: musicInsetIconURL,
description: (
- ),
- href: 'https://extensions.turbowarp.org/',
- extensionId: '',
- iconURL: galleryIcon,
- description: (
-
- ),
- tags: ['tw'],
- incompatibleWithScratch: true,
- featured: true
- },
{
name: (
),
tags: ['tw'],
- incompatibleWithScratch: true,
featured: true
+ // Not marked as incompatible with Scratch so that clicking on it doesn't show a prompt
}
];
+
+export const galleryLoading = {
+ name: (
+
+ ),
+ href: 'https://extensions.turbowarp.org/',
+ extensionId: '',
+ iconURL: galleryIcon,
+ iconAspectRatio: 2,
+ description: (
+
+ ),
+ tags: ['tw'],
+ featured: true
+};
+
+export const galleryMore = {
+ name: (
+
+ ),
+ href: 'https://extensions.turbowarp.org/',
+ extensionId: '',
+ iconURL: galleryIcon,
+ iconAspectRatio: 2,
+ description: (
+
+ ),
+ tags: ['tw'],
+ featured: true
+};
+
+export const galleryError = {
+ name: (
+
+ ),
+ href: 'https://extensions.turbowarp.org/',
+ extensionId: '',
+ iconURL: galleryIcon,
+ iconAspectRatio: 2,
+ description: (
+
+ ),
+ tags: ['tw'],
+ featured: true
+};