From 82f8b651810ac290fb9fe51e405b67825c3dbec2 Mon Sep 17 00:00:00 2001 From: Aquino Luane Date: Tue, 15 Oct 2024 11:24:06 +0900 Subject: [PATCH 1/3] feat: add a sprite --- src/containers/library-item.jsx | 2 +- src/lib/libraries/costumes.json | 12815 +--------------------- src/lib/libraries/sprites.json | 17230 +----------------------------- src/lib/storage.js | 2 +- 4 files changed, 30 insertions(+), 30019 deletions(-) diff --git a/src/containers/library-item.jsx b/src/containers/library-item.jsx index 51e105053..e871f1daa 100644 --- a/src/containers/library-item.jsx +++ b/src/containers/library-item.jsx @@ -106,7 +106,7 @@ class LibraryItem extends React.PureComponent { render () { const iconMd5 = this.curIconMd5(); const iconURL = iconMd5 ? - `https://cdn.assets.scratch.mit.edu/internalapi/asset/${iconMd5}/get/` : + `http://localhost:3000/image/${iconMd5}` : this.props.iconRawURL; return ( Date: Wed, 23 Oct 2024 16:30:56 +0900 Subject: [PATCH 2/3] feat: setup assets store + consume sprites and keep in store --- package.json | 2 +- src/containers/gui.jsx | 27 ++++++++++++++++++- src/containers/sprite-library.jsx | 19 +++++++++---- src/containers/target-pane.jsx | 41 +++++++++++++++------------- src/index.js | 5 +++- src/lib/app-state-hoc.jsx | 7 +++-- src/lib/libraries/sprites.json | 24 ----------------- src/reducers/assets.js | 44 +++++++++++++++++++++++++++++++ 8 files changed, 117 insertions(+), 52 deletions(-) delete mode 100644 src/lib/libraries/sprites.json create mode 100644 src/reducers/assets.js diff --git a/package.json b/package.json index 59e0f98c5..81edf4dc7 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ "prune": "./prune-gh-pages.sh", "i18n:push": "tx-push-src scratch-editor interface translations/en.json", "i18n:src": "rimraf ./translations/messages/src && babel src > tmp.js && rimraf tmp.js && build-i18n-src ./translations/messages/src ./translations/ && npm run i18n:push", - "start": "webpack-dev-server", + "start": "NODE_OPTIONS=--openssl-legacy-provider webpack-dev-server", "test": "npm run test:lint && npm run test:unit && npm run build && npm run test:integration", "test:integration": "jest --runInBand test[\\\\/]integration", "test:lint": "eslint . --ext .js,.jsx", diff --git a/src/containers/gui.jsx b/src/containers/gui.jsx index c10df0fb3..c745046a6 100644 --- a/src/containers/gui.jsx +++ b/src/containers/gui.jsx @@ -24,6 +24,7 @@ import { closeTelemetryModal, openExtensionLibrary } from '../reducers/modals'; +import { setSprites } from '../reducers/assets.js'; import FontLoaderHOC from '../lib/font-loader-hoc.jsx'; import LocalizationHOC from '../lib/localization-hoc.jsx'; @@ -45,6 +46,7 @@ class GUI extends React.Component { setIsScratchDesktop(this.props.isScratchDesktop); this.props.onStorageInit(storage); this.props.onVmInit(this.props.vm); + this.getSpritesFromApi(); } componentDidUpdate (prevProps) { if (this.props.projectId !== prevProps.projectId && this.props.projectId !== null) { @@ -56,6 +58,26 @@ class GUI extends React.Component { this.props.onProjectLoaded(); } } + getSpritesFromApi () { + return fetch( + 'http://localhost:3000/ccm/scratch-api/sprites' + ) + .then((response) => { + if (!response.ok) { + throw new Error('Network response was not ok'); + } + return response.json(); + }) + .then((data) => { + this.props.onSetSprites(data); + }) + .catch((error) => { + console.error( + 'There was a problem fetching the sprites:', + error + ); + }); + } render () { if (this.props.isError) { throw new Error( @@ -73,6 +95,7 @@ class GUI extends React.Component { onStorageInit, onUpdateProjectId, onVmInit, + onSetSprites, projectHost, projectId, /* eslint-enable no-unused-vars */ @@ -110,6 +133,7 @@ GUI.propTypes = { onStorageInit: PropTypes.func, onUpdateProjectId: PropTypes.func, onVmInit: PropTypes.func, + onSetSprites: PropTypes.func, projectHost: PropTypes.string, projectId: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), telemetryModalVisible: PropTypes.bool, @@ -161,7 +185,8 @@ const mapDispatchToProps = dispatch => ({ onActivateSoundsTab: () => dispatch(activateTab(SOUNDS_TAB_INDEX)), onRequestCloseBackdropLibrary: () => dispatch(closeBackdropLibrary()), onRequestCloseCostumeLibrary: () => dispatch(closeCostumeLibrary()), - onRequestCloseTelemetryModal: () => dispatch(closeTelemetryModal()) + onRequestCloseTelemetryModal: () => dispatch(closeTelemetryModal()), + onSetSprites: (sprites) => dispatch(setSprites(sprites)), }); const ConnectedGUI = injectIntl(connect( diff --git a/src/containers/sprite-library.jsx b/src/containers/sprite-library.jsx index 9fe0604c9..0cd736bf4 100644 --- a/src/containers/sprite-library.jsx +++ b/src/containers/sprite-library.jsx @@ -3,8 +3,8 @@ import PropTypes from 'prop-types'; import React from 'react'; import {injectIntl, intlShape, defineMessages} from 'react-intl'; import VM from 'scratch-vm'; +import {connect} from 'react-redux'; -import spriteLibraryContent from '../lib/libraries/sprites.json'; import randomizeSpritePosition from '../lib/randomize-sprite-position'; import spriteTags from '../lib/libraries/sprite-tags'; @@ -34,14 +34,16 @@ class SpriteLibrary extends React.PureComponent { } render () { return ( - + : null ); } } @@ -50,7 +52,14 @@ SpriteLibrary.propTypes = { intl: intlShape.isRequired, onActivateBlocksTab: PropTypes.func.isRequired, onRequestClose: PropTypes.func, - vm: PropTypes.instanceOf(VM).isRequired + vm: PropTypes.instanceOf(VM).isRequired, + sprites: PropTypes.arrayOf(PropTypes.object) }; -export default injectIntl(SpriteLibrary); +const mapStateToProps = state => { + return { + sprites: state.assets.sprites, + }; +}; + +export default injectIntl(connect(mapStateToProps,null)(SpriteLibrary)); diff --git a/src/containers/target-pane.jsx b/src/containers/target-pane.jsx index 010cf12dd..9ec54df05 100644 --- a/src/containers/target-pane.jsx +++ b/src/containers/target-pane.jsx @@ -15,7 +15,6 @@ import {setRestore} from '../reducers/restore-deletion'; import DragConstants from '../lib/drag-constants'; import TargetPaneComponent from '../components/target-pane/target-pane.jsx'; import {BLOCKS_DEFAULT_SCALE} from '../lib/layout-constants'; -import spriteLibraryContent from '../lib/libraries/sprites.json'; import {handleFileUpload, spriteUpload} from '../lib/file-uploader.js'; import sharedMessages from '../lib/shared-messages'; import {emptySprite} from '../lib/empty-assets'; @@ -106,13 +105,15 @@ class TargetPane extends React.Component { } } handleSurpriseSpriteClick () { - const surpriseSprites = spriteLibraryContent.filter(sprite => - (sprite.tags.indexOf('letters') === -1) && (sprite.tags.indexOf('numbers') === -1) - ); - const item = surpriseSprites[Math.floor(Math.random() * surpriseSprites.length)]; - randomizeSpritePosition(item); - this.props.vm.addSprite(JSON.stringify(item)) - .then(this.handleActivateBlocksTab); + if(this.props.spritesAsset){ + const surpriseSprites = this.props.spritesAsset.filter(sprite => + (sprite.tags.indexOf('letters') === -1) && (sprite.tags.indexOf('numbers') === -1) + ); + const item = surpriseSprites[Math.floor(Math.random() * surpriseSprites.length)]; + randomizeSpritePosition(item); + this.props.vm.addSprite(JSON.stringify(item)) + .then(this.handleActivateBlocksTab); + } } handlePaintSpriteClick () { const formatMessage = this.props.intl.formatMessage; @@ -244,6 +245,7 @@ class TargetPane extends React.Component { onReceivedBlocks, onShowImporting, workspaceMetrics, + spritesAsset, ...componentProps } = this.props; /* eslint-enable no-unused-vars */ @@ -286,16 +288,19 @@ TargetPane.propTypes = { ...targetPaneProps }; -const mapStateToProps = state => ({ - editingTarget: state.scratchGui.targets.editingTarget, - hoveredTarget: state.scratchGui.hoveredTarget, - isRtl: state.locales.isRtl, - spriteLibraryVisible: state.scratchGui.modals.spriteLibrary, - sprites: state.scratchGui.targets.sprites, - stage: state.scratchGui.targets.stage, - raiseSprites: state.scratchGui.blockDrag, - workspaceMetrics: state.scratchGui.workspaceMetrics -}); +const mapStateToProps = state => { + return { + editingTarget: state.scratchGui.targets.editingTarget, + hoveredTarget: state.scratchGui.hoveredTarget, + isRtl: state.locales.isRtl, + spriteLibraryVisible: state.scratchGui.modals.spriteLibrary, + sprites: state.scratchGui.targets.sprites, + stage: state.scratchGui.targets.stage, + raiseSprites: state.scratchGui.blockDrag, + workspaceMetrics: state.scratchGui.workspaceMetrics, + spritesAsset: state.assets.sprites, + } +} const mapDispatchToProps = dispatch => ({ onNewSpriteClick: e => { diff --git a/src/index.js b/src/index.js index 70dda5a90..fba085b60 100644 --- a/src/index.js +++ b/src/index.js @@ -2,6 +2,7 @@ import GUI from './containers/gui.jsx'; import AppStateHOC from './lib/app-state-hoc.jsx'; import GuiReducer, {guiInitialState, guiMiddleware, initEmbedded, initFullScreen, initPlayer} from './reducers/gui'; import LocalesReducer, {localesInitialState, initLocale} from './reducers/locales'; +import AssetsReducer, { assetsInitialState } from "./reducers/assets.js"; import {ScratchPaintReducer} from 'scratch-paint'; import {setFullScreen, setPlayer} from './reducers/mode'; import {remixProject} from './reducers/project-state'; @@ -10,7 +11,8 @@ import {setAppElement} from 'react-modal'; const guiReducers = { locales: LocalesReducer, scratchGui: GuiReducer, - scratchPaint: ScratchPaintReducer + scratchPaint: ScratchPaintReducer, + assets: AssetsReducer }; export { @@ -25,6 +27,7 @@ export { initFullScreen, initLocale, localesInitialState, + assetsInitialState, remixProject, setFullScreen, setPlayer diff --git a/src/lib/app-state-hoc.jsx b/src/lib/app-state-hoc.jsx index ff0ee0b5b..7fad167b0 100644 --- a/src/lib/app-state-hoc.jsx +++ b/src/lib/app-state-hoc.jsx @@ -7,6 +7,7 @@ import ConnectedIntlProvider from './connected-intl-provider.jsx'; import localesReducer, {initLocale, localesInitialState} from '../reducers/locales'; import {setPlayer, setFullScreen} from '../reducers/mode.js'; +import assetsReducer, { assetsInitialState } from "../reducers/assets.js"; import locales from 'scratch-l10n'; import {detectLocale} from './detect-locale'; @@ -69,11 +70,13 @@ const AppStateHOC = function (WrappedComponent, localesOnly) { reducers = { locales: localesReducer, scratchGui: guiReducer, - scratchPaint: ScratchPaintReducer + scratchPaint: ScratchPaintReducer, + assets: assetsReducer }; initialState = { locales: initializedLocales, - scratchGui: initializedGui + scratchGui: initializedGui, + assets: assetsInitialState }; enhancer = composeEnhancers(guiMiddleware); } diff --git a/src/lib/libraries/sprites.json b/src/lib/libraries/sprites.json deleted file mode 100644 index 32d5e9a6e..000000000 --- a/src/lib/libraries/sprites.json +++ /dev/null @@ -1,24 +0,0 @@ -[{ - "name": "Neko", - "tags": [ - "people", - "dance" - ], - "isStage": false, - "variables": {}, - "costumes": [ - { - "assetId": "60f720956ab1840431dcf0616ce98f14", - "name": "neko", - "bitmapResolution": 2, - "md5ext": "60f720956ab1840431dcf0616ce98f14.svg", - "dataFormat": "svg", - "rotationCenterX": 174, - "rotationCenterY": 162 - } - ], - "sounds": [ - ], - "blocks": {} -} -] diff --git a/src/reducers/assets.js b/src/reducers/assets.js new file mode 100644 index 000000000..a00984831 --- /dev/null +++ b/src/reducers/assets.js @@ -0,0 +1,44 @@ +const SET_SPRITES = 'SET_SPRITES'; +const SET_COSTUMES = 'GET_COSTUMES'; + +const initialState = { + sprites: [{}], + costumes: [{}], +}; + +const reducer = function (state, action) { + if (typeof state === 'undefined') state = initialState; + switch (action.type) { + case SET_SPRITES: + return Object.assign({}, state, { + sprites: action.payload, + }); + case SET_COSTUMES: + return Object.assign({}, state, { + costumes: action.payload, + }); + default: + return state; + } +}; + +const setSprites = function (sprites) { + return { + type: SET_SPRITES, + payload: sprites + }; +}; + +const setCostumes = function (costumes) { + return { + type: SET_COSTUMES, + payload: costumes + }; +}; + +export { + reducer as default, + initialState as sessionInitialState, + setSprites, + setCostumes +}; From 06996072f1cb482303d3836965d5229916b500aa Mon Sep 17 00:00:00 2001 From: Aquino Luane Date: Wed, 23 Oct 2024 16:57:40 +0900 Subject: [PATCH 3/3] feat: setup store for costumes + consume data from api --- src/containers/costume-library.jsx | 16 ++++++++++++---- src/containers/costume-tab.jsx | 29 ++++++++++++++++------------- src/containers/gui.jsx | 26 +++++++++++++++++++++++++- src/lib/libraries/costumes.json | 15 --------------- 4 files changed, 53 insertions(+), 33 deletions(-) delete mode 100644 src/lib/libraries/costumes.json diff --git a/src/containers/costume-library.jsx b/src/containers/costume-library.jsx index 83c24b191..9025298b7 100644 --- a/src/containers/costume-library.jsx +++ b/src/containers/costume-library.jsx @@ -3,8 +3,8 @@ import PropTypes from 'prop-types'; import React from 'react'; import {defineMessages, injectIntl, intlShape} from 'react-intl'; import VM from 'scratch-vm'; +import {connect} from 'react-redux'; -import costumeLibraryContent from '../lib/libraries/costumes.json'; import spriteTags from '../lib/libraries/sprite-tags'; import LibraryComponent from '../components/library/library.jsx'; @@ -36,14 +36,16 @@ class CostumeLibrary extends React.PureComponent { } render () { return ( - + : null ); } } @@ -54,4 +56,10 @@ CostumeLibrary.propTypes = { vm: PropTypes.instanceOf(VM).isRequired }; -export default injectIntl(CostumeLibrary); +const mapStateToProps = state => { + return { + costumes: state.assets.costumes, + }; +}; + +export default injectIntl(connect(mapStateToProps,null)(CostumeLibrary)); diff --git a/src/containers/costume-tab.jsx b/src/containers/costume-tab.jsx index be9b6d44b..b4aa6d4b3 100644 --- a/src/containers/costume-tab.jsx +++ b/src/containers/costume-tab.jsx @@ -34,7 +34,6 @@ import paintIcon from '../components/action-menu/icon--paint.svg'; import surpriseIcon from '../components/action-menu/icon--surprise.svg'; import searchIcon from '../components/action-menu/icon--search.svg'; -import costumeLibraryContent from '../lib/libraries/costumes.json'; import backdropLibraryContent from '../lib/libraries/backdrops.json'; let messages = defineMessages({ @@ -168,16 +167,18 @@ class CostumeTab extends React.Component { this.handleNewCostume(emptyCostume(name)); } handleSurpriseCostume () { - const item = costumeLibraryContent[Math.floor(Math.random() * costumeLibraryContent.length)]; - const vmCostume = { - name: item.name, - md5: item.md5ext, - rotationCenterX: item.rotationCenterX, - rotationCenterY: item.rotationCenterY, - bitmapResolution: item.bitmapResolution, - skinId: null - }; - this.handleNewCostume(vmCostume, true /* fromCostumeLibrary */); + if(this.props.costumes){ + const item = this.props.costumes[Math.floor(Math.random() * this.props.costumes.length)]; + const vmCostume = { + name: item.name, + md5: item.md5ext, + rotationCenterX: item.rotationCenterX, + rotationCenterY: item.rotationCenterY, + bitmapResolution: item.bitmapResolution, + skinId: null + }; + this.handleNewCostume(vmCostume, true /* fromCostumeLibrary */); + } } handleSurpriseBackdrop () { const item = backdropLibraryContent[Math.floor(Math.random() * backdropLibraryContent.length)]; @@ -349,7 +350,8 @@ CostumeTab.propTypes = { name: PropTypes.string.isRequired })) }), - vm: PropTypes.instanceOf(VM) + vm: PropTypes.instanceOf(VM), + costumes: PropTypes.arrayOf(PropTypes.object) }; const mapStateToProps = state => ({ @@ -357,7 +359,8 @@ const mapStateToProps = state => ({ isRtl: state.locales.isRtl, sprites: state.scratchGui.targets.sprites, stage: state.scratchGui.targets.stage, - dragging: state.scratchGui.assetDrag.dragging + dragging: state.scratchGui.assetDrag.dragging, + costumes: state.assets.costumes }); const mapDispatchToProps = dispatch => ({ diff --git a/src/containers/gui.jsx b/src/containers/gui.jsx index c745046a6..10904d8b0 100644 --- a/src/containers/gui.jsx +++ b/src/containers/gui.jsx @@ -24,7 +24,7 @@ import { closeTelemetryModal, openExtensionLibrary } from '../reducers/modals'; -import { setSprites } from '../reducers/assets.js'; +import { setCostumes, setSprites } from '../reducers/assets.js'; import FontLoaderHOC from '../lib/font-loader-hoc.jsx'; import LocalizationHOC from '../lib/localization-hoc.jsx'; @@ -47,6 +47,7 @@ class GUI extends React.Component { this.props.onStorageInit(storage); this.props.onVmInit(this.props.vm); this.getSpritesFromApi(); + this.getCostumesFromApi(); } componentDidUpdate (prevProps) { if (this.props.projectId !== prevProps.projectId && this.props.projectId !== null) { @@ -78,6 +79,26 @@ class GUI extends React.Component { ); }); } + getCostumesFromApi () { + return fetch( + 'http://localhost:3000/ccm/scratch-api/costumes' + ) + .then((response) => { + if (!response.ok) { + throw new Error('Network response was not ok'); + } + return response.json(); + }) + .then((data) => { + this.props.onSetCostumes(data); + }) + .catch((error) => { + console.error( + 'There was a problem fetching the costumes:', + error + ); + }); + } render () { if (this.props.isError) { throw new Error( @@ -96,6 +117,7 @@ class GUI extends React.Component { onUpdateProjectId, onVmInit, onSetSprites, + onSetCostumes, projectHost, projectId, /* eslint-enable no-unused-vars */ @@ -134,6 +156,7 @@ GUI.propTypes = { onUpdateProjectId: PropTypes.func, onVmInit: PropTypes.func, onSetSprites: PropTypes.func, + onSetCostumes: PropTypes.func, projectHost: PropTypes.string, projectId: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), telemetryModalVisible: PropTypes.bool, @@ -187,6 +210,7 @@ const mapDispatchToProps = dispatch => ({ onRequestCloseCostumeLibrary: () => dispatch(closeCostumeLibrary()), onRequestCloseTelemetryModal: () => dispatch(closeTelemetryModal()), onSetSprites: (sprites) => dispatch(setSprites(sprites)), + onSetCostumes: (costumes) => dispatch(setCostumes(costumes)), }); const ConnectedGUI = injectIntl(connect( diff --git a/src/lib/libraries/costumes.json b/src/lib/libraries/costumes.json deleted file mode 100644 index 5a7675a82..000000000 --- a/src/lib/libraries/costumes.json +++ /dev/null @@ -1,15 +0,0 @@ -[ - { - "name": "Neko", - "tags": [ - "people", - "dance" - ], - "assetId": "60f720956ab1840431dcf0616ce98f14", - "bitmapResolution": 2, - "dataFormat": "svg", - "md5ext": "60f720956ab1840431dcf0616ce98f14.svg", - "rotationCenterX": 174, - "rotationCenterY": 162 - } -]