Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Drag and drop screenshots to reorder #1772

Merged
merged 3 commits into from
Apr 5, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
78 changes: 19 additions & 59 deletions static/js/publisher/form/media.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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(
<Fragment key={`add-screenshot-${i}`}>
<FileInput
restrictions={restrictions}
className={classes.join(" ")}
inputName="screenshots"
fileChangedCallback={this.mediaChanged}
active={isActive}
>
{isActive && (
<span role="button" className="u-align-text--center">
<i className="p-icon--plus" />
<br />
Add image
</span>
)}
</FileInput>
</Fragment>
);
}
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() {
Expand All @@ -195,24 +163,16 @@ class Media extends React.Component {
<Fragment>
{this.renderOverLimit()}
{this.renderErrors()}
<div
className="p-listing-images p-fluid-grid"
ref={item => (this.holder = item)}
>
{mediaList.map((item, i) => {
return (
<MediaItem
key={`${item.url}-${i}`}
url={item.url}
type={item.type}
status={item.status}
markForDeletion={this.markForDeletion}
overflow={i > 4}
/>
);
})}
{this.renderInputs()}
</div>
<SortableMediaList
distance={10}
onSortEnd={this.onSortEnd.bind(this)}
axis="xy"
mediaData={mediaList}
mediaLimit={this.props.mediaLimit}
restrictions={this.props.restrictions}
markForDeletion={this.markForDeletion}
mediaChanged={this.mediaChanged}
/>
{this.renderRescrictions()}
</Fragment>
);
Expand Down
7 changes: 6 additions & 1 deletion static/js/publisher/form/mediaItem.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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 };
85 changes: 85 additions & 0 deletions static/js/publisher/form/mediaList.js
Original file line number Diff line number Diff line change
@@ -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(
<Fragment key={`add-screenshot-${i}`}>
<FileInput
restrictions={restrictions}
className={classes.join(" ")}
inputName="screenshots"
fileChangedCallback={this.props.mediaChanged}
active={isActive}
>
{isActive && (
<span role="button" className="u-align-text--center">
<i className="p-icon--plus" />
<br />
Add image
</span>
)}
</FileInput>
</Fragment>
);
}

return inputs;
}

render() {
const mediaList = this.props.mediaData;

return (
<div className="p-listing-images p-fluid-grid">
{mediaList.map((item, i) => {
return (
<MediaItem
key={`${item.url}-${i}`}
index={i}
url={item.url}
type={item.type}
status={item.status}
markForDeletion={this.props.markForDeletion}
overflow={i > 4}
/>
);
})}
{this.renderInputs()}
</div>
);
}
}

MediaList.propTypes = {
mediaLimit: PropTypes.number,
mediaData: PropTypes.array,
restrictions: PropTypes.object,
mediaChanged: PropTypes.func,
markForDeletion: PropTypes.func
};

export default SortableContainer(MediaList);
109 changes: 61 additions & 48 deletions static/sass/_snapcraft_market_screenshots.scss
Original file line number Diff line number Diff line change
@@ -1,63 +1,62 @@
@mixin snapcraft-market-screenshots {
.p-listing-images {
justify-content: flex-start;
margin-right: -$spv-intra;
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__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__image {
$screenshot-thumb-size: 123px; // make it fit exactly on full screen width

.p-listing-images__delete-image .p-icon--delete {
display: block;
top: 0;
}
align-items: center;
background: $color-light;
border: 1px solid $color-light;
color: $color-link;
cursor: grab;
display: flex;
flex-grow: 0;
height: $screenshot-thumb-size;
justify-content: center;
margin-bottom: $spv-intra;
margin-right: $spv-intra;
min-width: auto;
position: relative;
width: $screenshot-thumb-size;

&--no-show {
justify-self: flex-start;
.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;
}

img {
filter: sepia(100%) hue-rotate(300deg);
}
}
.p-listing-images__delete-image .p-icon--delete {
display: block;
top: 0;
}

&:not(.is-empty):hover,
&:not(.is-empty):focus {
border: 1px solid $color-link;
&--no-show {
justify-self: flex-start;

.p-listing-images__delete-image {
opacity: 1;
}
img {
filter: sepia(100%) hue-rotate(300deg);
}
}

img {
align-self: center;
max-height: 100%;
max-width: 100%;
&:not(.is-empty):hover,
&:not(.is-empty):focus {
border: 1px solid $color-link;

.p-listing-images__delete-image {
opacity: 1;
}
}

Expand All @@ -68,5 +67,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;
}
}
18 changes: 9 additions & 9 deletions webapp/publisher/snaps/logic.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand All @@ -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
Expand All @@ -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)}

Expand Down