diff --git a/src/components/asset-panel/icon--sound.svg b/src/components/asset-panel/icon--sound.svg index c7e6081b50edbea029005aa7a483318912d3874f..152272535f732e16f1bdb88d458563481f7daf09 100644 Binary files a/src/components/asset-panel/icon--sound.svg and b/src/components/asset-panel/icon--sound.svg differ diff --git a/src/components/asset-panel/selector.jsx b/src/components/asset-panel/selector.jsx index ff818415a44851d1049cfd674e40f40c4ca6987b..6c3f72edb19f70f6c35958b62bd40a19058139cf 100644 --- a/src/components/asset-panel/selector.jsx +++ b/src/components/asset-panel/selector.jsx @@ -42,6 +42,7 @@ const Selector = props => { assetId={item.assetId} className={styles.listItem} costumeURL={item.url} + details={item.details} id={index} key={`asset-${index}`} name={item.name} diff --git a/src/components/sprite-selector-item/sprite-selector-item.css b/src/components/sprite-selector-item/sprite-selector-item.css index 8567d1352a4012cf57477141f5d38964a011be7c..b02b4bfdb84728160a081dcc3ebb0a094b74e5b2 100644 --- a/src/components/sprite-selector-item/sprite-selector-item.css +++ b/src/components/sprite-selector-item/sprite-selector-item.css @@ -10,24 +10,26 @@ font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; font-size: 0.8rem; - background: white; color: $text-primary; border-width: 2px; border-style: solid; border-color: $ui-black-transparent; border-radius: $space; + text-align: center; cursor: pointer; transition: 0.25s ease-out; } .sprite-selector-item.is-selected { - border: 2px solid $motion-primary; box-shadow: 0px 0px 0px 4px $motion-transparent; + border: 2px solid $motion-primary; + background: $ui-white; } .sprite-selector-item:hover { border: 2px solid $motion-primary; + background: $ui-white; } .sprite-image { @@ -35,9 +37,13 @@ user-select: none; } -.sprite-name { +.sprite-info { + padding: 0.2rem 0.2rem 0.1rem ; + border-bottom-left-radius: 0.25rem; + border-bottom-right-radius: 0.25rem; + font-size: 0.625rem; - margin: 0.15rem; + color: $text-primary; user-select: none; /* @@ -50,6 +56,10 @@ min-width: 0; } +.is-selected .sprite-info { + background: $motion-primary; + color: $ui-white; +} .delete-button { position: absolute; top: 0.125rem; @@ -59,8 +69,9 @@ .number { position: absolute; - top: 0.125rem; - left: 0.125rem; + top: 0.15rem; + left: 0.15rem; font-size: 0.625rem; + font-weight: bold; z-index: 2; } diff --git a/src/components/sprite-selector-item/sprite-selector-item.jsx b/src/components/sprite-selector-item/sprite-selector-item.jsx index 35eeebe005cba166743ed763dbd518f1deafc43a..2cfdd1e26d5db23b17a0f1ba4869aa5ed69ddd5a 100644 --- a/src/components/sprite-selector-item/sprite-selector-item.jsx +++ b/src/components/sprite-selector-item/sprite-selector-item.jsx @@ -42,7 +42,12 @@ const SpriteSelectorItem = props => ( width={32} /> ) : null} - <div className={styles.spriteName}>{props.name}</div> + <div className={styles.spriteInfo}> + <div className={styles.spriteName}>{props.name}</div> + {props.details ? ( + <div className={styles.spriteDetails}>{props.details}</div> + ) : null} + </div> {props.onDuplicateButtonClick || props.onDeleteButtonClick ? ( <ContextMenu id={`${props.name}-${contextMenuId++}`}> {props.onDuplicateButtonClick ? ( @@ -73,6 +78,7 @@ SpriteSelectorItem.propTypes = { costumeURL: PropTypes.string, name: PropTypes.string.isRequired, number: PropTypes.number, + details: PropTypes.string, onClick: PropTypes.func, onDeleteButtonClick: PropTypes.func, onDuplicateButtonClick: PropTypes.func, diff --git a/src/containers/costume-tab.jsx b/src/containers/costume-tab.jsx index 37fd8a12e1e490933ec5f843eb12e667c2316b10..61337c540b6fb62e0bbcd904ddb9bb36d6b41b12 100644 --- a/src/containers/costume-tab.jsx +++ b/src/containers/costume-tab.jsx @@ -158,6 +158,11 @@ class CostumeTab extends React.Component { }; this.props.vm.addCostume(item.md5, vmCostume); } + formatCostumeDetails (size) { + // Round up width and height for scratch-flash compatibility + // https://github.com/LLK/scratch-flash/blob/9fbac92ef3d09ceca0c0782f8a08deaa79e4df69/src/ui/media/MediaInfo.as#L224-L237 + return `${Math.ceil(size[0])} x ${Math.ceil(size[1])}` + } render () { const { intl, @@ -184,6 +189,12 @@ class CostumeTab extends React.Component { const addLibraryFunc = target.isStage ? onNewLibraryBackdropClick : onNewLibraryCostumeClick; const addLibraryIcon = target.isStage ? addLibraryBackdropIcon : addLibraryCostumeIcon; + const costumeData = (target.costumes || []).map((costume) => ({ + name: costume.name, + assetId: costume.assetId, + details: costume.size ? this.formatCostumeDetails(costume.size) : null + })); + return ( <AssetPanel buttons={[ @@ -211,7 +222,7 @@ class CostumeTab extends React.Component { onClick: this.handleNewBlankCostume } ]} - items={target.costumes || []} + items={costumeData} selectedItemIndex={this.state.selectedCostumeIndex} onDeleteClick={target.costumes.length > 1 ? this.handleDeleteCostume : null} onDuplicateClick={this.handleDuplicateCostume} diff --git a/src/containers/sound-tab.jsx b/src/containers/sound-tab.jsx index b0b4e3d2209f27ec4725ee33639c6c926e14f26d..3662b1f66e9557e3c115931b73d064fa0e4d0582 100644 --- a/src/containers/sound-tab.jsx +++ b/src/containers/sound-tab.jsx @@ -115,7 +115,8 @@ class SoundTab extends React.Component { const sounds = sprite.sounds ? sprite.sounds.map(sound => ( { url: soundIcon, - name: sound.name + name: sound.name, + details: (sound.sampleCount / sound.rate).toFixed(2) } )) : []; diff --git a/test/unit/components/__snapshots__/sprite-selector-item.test.jsx.snap b/test/unit/components/__snapshots__/sprite-selector-item.test.jsx.snap index eaca8d9366004e9eccf9bf21fa69da48b877e662..2c276ff65660dc9eb2b04090a6e705b5b2b6a377 100644 --- a/test/unit/components/__snapshots__/sprite-selector-item.test.jsx.snap +++ b/test/unit/components/__snapshots__/sprite-selector-item.test.jsx.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`SpriteSelectorItemComponent matches snapshot when given a number to show 1`] = ` +exports[`SpriteSelectorItemComponent matches snapshot when given a number and details to show 1`] = ` <div className="react-contextmenu-wrapper ponies undefined" onClick={[Function]} @@ -44,7 +44,16 @@ exports[`SpriteSelectorItemComponent matches snapshot when given a number to sho <div className={undefined} > - Pony sprite + <div + className={undefined} + > + Pony sprite + </div> + <div + className={undefined} + > + 480 x 360 + </div> </div> <nav className="react-contextmenu" @@ -118,7 +127,11 @@ exports[`SpriteSelectorItemComponent matches snapshot when selected 1`] = ` <div className={undefined} > - Pony sprite + <div + className={undefined} + > + Pony sprite + </div> </div> <nav className="react-contextmenu" diff --git a/test/unit/components/sprite-selector-item.test.jsx b/test/unit/components/sprite-selector-item.test.jsx index 5848e0ca951ffea8f1a893c2cc20c7d51ef93954..1bf52fe97e4c7c83eded75c6a0fc9b29bdc97d66 100644 --- a/test/unit/components/sprite-selector-item.test.jsx +++ b/test/unit/components/sprite-selector-item.test.jsx @@ -12,6 +12,7 @@ describe('SpriteSelectorItemComponent', () => { let onDeleteButtonClick; let selected; let number; + let details; // Wrap this in a function so it gets test specific states and can be reused. const getComponent = function () { @@ -19,6 +20,7 @@ describe('SpriteSelectorItemComponent', () => { <SpriteSelectorItemComponent className={className} costumeURL={costumeURL} + details={details} name={name} number={number} selected={selected} @@ -35,8 +37,9 @@ describe('SpriteSelectorItemComponent', () => { onClick = jest.fn(); onDeleteButtonClick = jest.fn(); selected = true; - // Reset number to undefined since it is an optional prop + // Reset to undefined since they are optional props number = undefined; // eslint-disable-line no-undefined + details = undefined; // eslint-disable-line no-undefined }); test('matches snapshot when selected', () => { @@ -44,8 +47,9 @@ describe('SpriteSelectorItemComponent', () => { expect(component.toJSON()).toMatchSnapshot(); }); - test('matches snapshot when given a number to show', () => { + test('matches snapshot when given a number and details to show', () => { number = 5; + details = '480 x 360'; const component = componentWithIntl(getComponent()); expect(component.toJSON()).toMatchSnapshot(); });