From 5ad603ad69cd47bc97203d9a834f7ad237044b98 Mon Sep 17 00:00:00 2001 From: Bartek Szopka Date: Wed, 3 Apr 2019 12:56:01 +0200 Subject: [PATCH 1/3] Reorder screenshots using drag and drop --- package.json | 1 + static/js/publisher/form/media.js | 78 ++++--------- static/js/publisher/form/mediaItem.js | 7 +- static/js/publisher/form/mediaList.js | 85 ++++++++++++++ .../sass/_snapcraft_market_screenshots.scss | 104 ++++++++++-------- yarn.lock | 30 +++++ 6 files changed, 197 insertions(+), 108 deletions(-) create mode 100644 static/js/publisher/form/mediaList.js diff --git a/package.json b/package.json index 7cb7da1f4..701351a52 100644 --- a/package.json +++ b/package.json @@ -78,6 +78,7 @@ "react": "^16.4.1", "react-dom": "^16.4.1", "react-redux": "^5.1.1", + "react-sortable-hoc": "^1.8.3", "react-testing-library": "^6.0.0", "redux": "^4.0.1", "redux-mock-store": "^1.5.3", diff --git a/static/js/publisher/form/media.js b/static/js/publisher/form/media.js index a18f30e64..be3c4660e 100644 --- a/static/js/publisher/form/media.js +++ b/static/js/publisher/form/media.js @@ -2,8 +2,7 @@ import React, { Fragment } from "react"; import { PropTypes } from "prop-types"; -import MediaItem, { mediaClasses } from "./mediaItem"; -import FileInput from "./fileInput"; +import SortableMediaList from "./mediaList"; class Media extends React.Component { constructor(props) { @@ -146,46 +145,15 @@ class Media extends React.Component { ); } - renderInputs() { - const { mediaLimit, restrictions } = this.props; - const currentMedia = this.state.mediaData.length; - const inputs = []; - - for (let i = 0; i < mediaLimit; i++) { - const classes = [...mediaClasses]; - classes.push("is-empty"); - - let isActive = false; - - if (i === currentMedia) { - classes.push("p-listing-images__add-image"); - isActive = true; - } else if (i < currentMedia) { - classes.push("u-hide"); - } - - inputs.push( - - - {isActive && ( - - -
- Add image -
- )} -
-
- ); - } + onSortEnd(params) { + const { oldIndex, newIndex } = params; + const newMediaData = [...this.state.mediaData]; + const moved = newMediaData.splice(oldIndex, 1); + newMediaData.splice(newIndex, 0, moved[0]); - return inputs; + this.setState({ + mediaData: newMediaData + }); } render() { @@ -195,24 +163,16 @@ class Media extends React.Component { {this.renderOverLimit()} {this.renderErrors()} -
(this.holder = item)} - > - {mediaList.map((item, i) => { - return ( - 4} - /> - ); - })} - {this.renderInputs()} -
+ {this.renderRescrictions()}
); diff --git a/static/js/publisher/form/mediaItem.js b/static/js/publisher/form/mediaItem.js index b926849d1..3bf719d3c 100644 --- a/static/js/publisher/form/mediaItem.js +++ b/static/js/publisher/form/mediaItem.js @@ -7,6 +7,8 @@ const mediaClasses = [ "js-media-item-holder" ]; +import { SortableElement } from "react-sortable-hoc"; + class MediaItem extends React.Component { constructor(props) { super(props); @@ -52,4 +54,7 @@ MediaItem.propTypes = { markForDeletion: PropTypes.func }; -export { MediaItem as default, mediaClasses }; +MediaItem.displayName = "MediaItem"; + +const SortableMediaItem = SortableElement(MediaItem); +export { SortableMediaItem as default, mediaClasses }; diff --git a/static/js/publisher/form/mediaList.js b/static/js/publisher/form/mediaList.js new file mode 100644 index 000000000..badf5fcfc --- /dev/null +++ b/static/js/publisher/form/mediaList.js @@ -0,0 +1,85 @@ +import React, { Fragment } from "react"; + +import { PropTypes } from "prop-types"; + +import MediaItem, { mediaClasses } from "./mediaItem"; +import FileInput from "./fileInput"; + +import { SortableContainer } from "react-sortable-hoc"; + +class MediaList extends React.Component { + renderInputs() { + const { mediaLimit, restrictions } = this.props; + const currentMedia = this.props.mediaData.length; + const inputs = []; + + for (let i = 0; i < mediaLimit; i++) { + const classes = [...mediaClasses]; + classes.push("is-empty"); + + let isActive = false; + + if (i === currentMedia) { + classes.push("p-listing-images__add-image"); + isActive = true; + } else if (i < currentMedia) { + classes.push("u-hide"); + } + + inputs.push( + + + {isActive && ( + + +
+ Add image +
+ )} +
+
+ ); + } + + return inputs; + } + + render() { + const mediaList = this.props.mediaData; + + return ( +
+ {mediaList.map((item, i) => { + return ( + 4} + /> + ); + })} + {this.renderInputs()} +
+ ); + } +} + +MediaList.propTypes = { + mediaLimit: PropTypes.number, + mediaData: PropTypes.array, + restrictions: PropTypes.object, + mediaChanged: PropTypes.func, + markForDeletion: PropTypes.func +}; + +export default SortableContainer(MediaList); diff --git a/static/sass/_snapcraft_market_screenshots.scss b/static/sass/_snapcraft_market_screenshots.scss index 74099b93b..a4e75e935 100644 --- a/static/sass/_snapcraft_market_screenshots.scss +++ b/static/sass/_snapcraft_market_screenshots.scss @@ -2,62 +2,56 @@ .p-listing-images { min-height: 120px; position: relative; + } - .p-listing-images__image { - align-items: center; - background: $color-light; - border: 1px solid $color-light; - border-radius: $border-radius; - color: $color-link; - display: flex; - flex-grow: 0; - height: 120px; - justify-content: center; - margin-bottom: $spv-intra; - min-width: auto; - position: relative; - width: 120px; + .p-listing-images__image { + align-items: center; + background: $color-light; + border: 1px solid $color-light; + color: $color-link; + cursor: grab; + display: flex; + flex-grow: 0; + height: 120px; + justify-content: center; + margin-bottom: $spv-intra; + min-width: auto; + position: relative; + width: 120px; - .p-listing-images__delete-image { - background: $color-x-light; - box-sizing: content-box; - cursor: pointer; - height: 1rem; - opacity: .5; - padding: .25rem; - position: absolute; - right: 0; - top: 0; - width: 1rem; - z-index: 1; - } + .p-listing-images__delete-image { + background: $color-x-light; + box-sizing: content-box; + cursor: pointer; + height: 1rem; + opacity: .5; + padding: .25rem; + position: absolute; + right: 0; + top: 0; + width: 1rem; + z-index: 1; + } - .p-listing-images__delete-image .p-icon--delete { - display: block; - top: 0; - } + .p-listing-images__delete-image .p-icon--delete { + display: block; + top: 0; + } - &--no-show { - justify-self: flex-start; + &--no-show { + justify-self: flex-start; - img { - filter: sepia(100%) hue-rotate(300deg); - } + img { + filter: sepia(100%) hue-rotate(300deg); } + } - &:not(.is-empty):hover, - &:not(.is-empty):focus { - border: 1px solid $color-link; + &:not(.is-empty):hover, + &:not(.is-empty):focus { + border: 1px solid $color-link; - .p-listing-images__delete-image { - opacity: 1; - } - } - - img { - align-self: center; - max-height: 100%; - max-width: 100%; + .p-listing-images__delete-image { + opacity: 1; } } @@ -68,5 +62,19 @@ position: static; z-index: 1; } + + img { + align-self: center; + max-height: 100%; + max-width: 100%; + pointer-events: none; + user-select: none; + } + } + + .p-listing-images__add-image { + background: $color-x-light; + border: 1px solid $color-mid-light; + cursor: pointer; } } diff --git a/yarn.lock b/yarn.lock index 2028337d8..8de97a27f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -70,6 +70,12 @@ dependencies: regenerator-runtime "^0.12.0" +"@babel/runtime@^7.2.0": + version "7.4.2" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.4.2.tgz#f5ab6897320f16decd855eed70b705908a313fe8" + dependencies: + regenerator-runtime "^0.13.2" + "@babel/runtime@^7.3.1", "@babel/runtime@^7.3.4": version "7.3.4" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.3.4.tgz#73d12ba819e365fcf7fd152aed56d6df97d21c83" @@ -5923,6 +5929,14 @@ promise@^7.1.1: dependencies: asap "~2.0.3" +prop-types@^15.5.7: + version "15.7.2" + resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5" + dependencies: + loose-envify "^1.4.0" + object-assign "^4.1.1" + react-is "^16.8.1" + prop-types@^15.6.0, prop-types@^15.6.1, prop-types@^15.6.2: version "15.6.2" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.6.2.tgz#05d5ca77b4453e985d60fc7ff8c859094a497102" @@ -6054,6 +6068,10 @@ react-is@^16.3.2, react-is@^16.6.0: version "16.6.3" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.6.3.tgz#d2d7462fcfcbe6ec0da56ad69047e47e56e7eac0" +react-is@^16.8.1: + version "16.8.6" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.8.6.tgz#5bbc1e2d29141c9fbdfed456343fe2bc430a6a16" + react-is@^16.8.4: version "16.8.4" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.8.4.tgz#90f336a68c3a29a096a3d648ab80e87ec61482a2" @@ -6074,6 +6092,14 @@ react-redux@^5.1.1: react-is "^16.6.0" react-lifecycles-compat "^3.0.0" +react-sortable-hoc@^1.8.3: + version "1.8.3" + resolved "https://registry.yarnpkg.com/react-sortable-hoc/-/react-sortable-hoc-1.8.3.tgz#0c80cda29f305038f985cbdb34d3030220e1ab5d" + dependencies: + "@babel/runtime" "^7.2.0" + invariant "^2.2.4" + prop-types "^15.5.7" + react-testing-library@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/react-testing-library/-/react-testing-library-6.0.0.tgz#81edfcfae8a795525f48685be9bf561df45bb35d" @@ -6217,6 +6243,10 @@ regenerator-runtime@^0.12.0: version "0.12.1" resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.12.1.tgz#fa1a71544764c036f8c49b13a08b2594c9f8a0de" +regenerator-runtime@^0.13.2: + version "0.13.2" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.2.tgz#32e59c9a6fb9b1a4aff09b4930ca2d4477343447" + regenerator-transform@^0.10.0: version "0.10.1" resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.10.1.tgz#1e4996837231da8b7f3cf4114d71b5691a0680dd" From 831cc655302e4e970a556fc03991f5718b4ec626 Mon Sep 17 00:00:00 2001 From: Bartek Szopka Date: Wed, 3 Apr 2019 13:27:54 +0200 Subject: [PATCH 2/3] Remove flexible screenshots spacing to avoid drag and drop shifts --- static/sass/_snapcraft_market_screenshots.scss | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/static/sass/_snapcraft_market_screenshots.scss b/static/sass/_snapcraft_market_screenshots.scss index a4e75e935..cce26f64d 100644 --- a/static/sass/_snapcraft_market_screenshots.scss +++ b/static/sass/_snapcraft_market_screenshots.scss @@ -1,10 +1,14 @@ @mixin snapcraft-market-screenshots { .p-listing-images { + justify-content: flex-start; + margin-right: -$spv-intra; min-height: 120px; position: relative; } .p-listing-images__image { + $screenshot-thumb-size: 123px; // make it fit exactly on full screen width + align-items: center; background: $color-light; border: 1px solid $color-light; @@ -12,12 +16,13 @@ cursor: grab; display: flex; flex-grow: 0; - height: 120px; + height: $screenshot-thumb-size; justify-content: center; margin-bottom: $spv-intra; + margin-right: $spv-intra; min-width: auto; position: relative; - width: 120px; + width: $screenshot-thumb-size; .p-listing-images__delete-image { background: $color-x-light; From bfa62c613a41142c70d6fc63a2adac1d4861e5be Mon Sep 17 00:00:00 2001 From: Bartek Szopka Date: Thu, 4 Apr 2019 09:41:35 +0200 Subject: [PATCH 3/3] Keep ordering of newly added screenshots --- webapp/publisher/snaps/logic.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/webapp/publisher/snaps/logic.py b/webapp/publisher/snaps/logic.py index 9ff7df5ab..c7fba02bd 100644 --- a/webapp/publisher/snaps/logic.py +++ b/webapp/publisher/snaps/logic.py @@ -163,6 +163,8 @@ def build_changed_images( info = [] images_files = [] images_json = None + + # Get screenshots info (existing and new) while keeping the order recieved for changed_screenshot in changed_screenshots: for current_screenshot in current_screenshots: if ( @@ -171,15 +173,7 @@ def build_changed_images( ): info.append(current_screenshot) break - - # Add new icon - if icon is not None: - info.append(build_image_info(icon, "icon")) - images_files.append(icon) - - # Add new screenshots - for new_screenshot in new_screenshots: - for changed_screenshot in changed_screenshots: + for new_screenshot in new_screenshots: is_same = ( changed_screenshot["status"] == "new" and changed_screenshot["name"] == new_screenshot.filename @@ -190,6 +184,12 @@ def build_changed_images( if image_built not in info: info.append(image_built) images_files.append(new_screenshot) + break + + # Add new icon + if icon is not None: + info.append(build_image_info(icon, "icon")) + images_files.append(icon) images_json = {"info": dumps(info)}