integrate Embedly thumbnail inside EmbedlyURL component (not using ThumbnailURL anymore)

This commit is contained in:
SachaG 2017-05-06 16:08:01 +09:00
parent f9148219cc
commit 6cff2b19fd
8 changed files with 162 additions and 39 deletions

View file

@ -1,6 +1,7 @@
import { Components, registerComponent, Utils } from 'meteor/vulcan:core';
import { Components, registerComponent, Utils, getSetting } from 'meteor/vulcan:core';
import { withMutation } from 'meteor/vulcan:core';
import React, { PropTypes, Component } from 'react';
import { FormattedMessage, intlShape } from 'react-intl';
import FRC from 'formsy-react-components';
const Input = FRC.Input;
@ -10,10 +11,13 @@ class EmbedlyURL extends Component {
constructor(props) {
super(props);
this.handleBlur = this.handleBlur.bind(this);
this.editThumbnail = this.editThumbnail.bind(this);
this.clearThumbnail = this.clearThumbnail.bind(this);
this.state = {
loading: false,
value: props.value || '',
thumbnailUrl: props.document.thumbnailUrl || ''
};
}
@ -28,6 +32,22 @@ class EmbedlyURL extends Component {
}
}
editThumbnail() {
const newThumbnailUrl = prompt(this.context.intl.formatMessage({id: 'posts.enter_thumbnail_url'}), this.state.thumbnailUrl);
if (newThumbnailUrl) {
this.setState({thumbnailUrl: newThumbnailUrl});
// this.context.updateCurrentValues({thumbnailUrl: newThumbnailUrl});
}
}
clearThumbnail() {
if (confirm(this.context.intl.formatMessage({id: 'posts.clear_thumbnail?'}))) {
this.setState({thumbnailUrl: ''});
this.context.addToDeletedValues('thumbnailUrl');
// this.context.updateCurrentValues({thumbnailUrl: ''});
}
}
// called whenever the URL input field loses focus
async handleBlur() {
try {
@ -48,16 +68,16 @@ class EmbedlyURL extends Component {
// extract the relevant data, for easier consumption
const { data: { getEmbedlyData: { title, description, thumbnailUrl } } } = result;
// update the form
await this.context.updateCurrentValues({
title: title || "",
body: description || "",
thumbnailUrl: thumbnailUrl || "",
// thumbnailUrl: thumbnailUrl || "",
});
// embedly component is done
await this.setState({loading: false});
await this.setState({loading: false, thumbnailUrl});
// remove errors & keep the current values
await this.context.clearForm({clearErrors: true});
@ -75,6 +95,40 @@ class EmbedlyURL extends Component {
}
}
getDimensions() {
const width = getSetting('thumbnailWidth', 80);
const height = getSetting('thumbnailHeight', 60);
const ratio = width/height;
return {
width,
height,
ratio
}
}
renderThumbnail() {
return (
<div className="embedly-thumbnail">
<img className="embedly-thumbnail-image" src={this.state.thumbnailUrl} />
<div className="embedly-thumbnail-actions">
<a className="thumbnail-edit" onClick={this.editThumbnail}><Components.Icon name="edit"/> <FormattedMessage id="posts.enter_thumbnail_url"/></a>
<a className="thumbnail-clear" onClick={this.clearThumbnail}><Components.Icon name="delete"/> <FormattedMessage id="posts.clear_thumbnail"/></a>
</div>
</div>
)
}
renderNoThumbnail() {
return (
<div className="embedly-thumbnail">
<div style={{width: `${Math.round(60 * this.getDimensions().ratio)}px`, height: `60px`}} onClick={this.editThumbnail} className="embedly-thumbnail-placeholder">
<Components.Icon name="image" />
<FormattedMessage id="posts.enter_thumbnail_url"/>
</div>
</div>
)
}
render() {
const wrapperStyle = {
@ -94,15 +148,29 @@ class EmbedlyURL extends Component {
const {document, control, getEmbedlyData, ...rest} = this.props; // eslint-disable-line
return (
<div className="embedly-url-field" style={wrapperStyle}>
<Input
{...rest}
onBlur={this.handleBlur}
type="text"
ref={ref => this.input = ref}
/>
<div className="embedly-url-field-loading" style={loadingStyle}>
<Components.Loading />
<div className="form-group row embedly-form-group" style={wrapperStyle}>
<label className="control-label col-sm-3">{this.props.label}</label>
<div className="col-sm-9 embedly-form-control">
<div className="embedly-url-field">
<Input
{...rest}
onBlur={this.handleBlur}
type="text"
ref={ref => this.input = ref}
layout="elementOnly"
/>
<div className="embedly-url-field-loading" style={loadingStyle}>
<Components.Loading />
</div>
</div>
{this.state.thumbnailUrl ? this.renderThumbnail() : this.renderNoThumbnail()}
<Input
type="hidden"
name="thumbnailUrl"
value={this.state.thumbnailUrl}
/>
</div>
</div>
);
@ -117,8 +185,10 @@ EmbedlyURL.propTypes = {
EmbedlyURL.contextTypes = {
updateCurrentValues: PropTypes.func,
addToDeletedValues: PropTypes.func,
throwError: PropTypes.func,
clearForm: PropTypes.func,
intl: intlShape
}
export default withMutation({

View file

@ -29,15 +29,15 @@ class ThumbnailURL extends Component {
renderThumbnail() {
return (
<div>
<img
className="embedly-thumbnail"
src={this.props.value}
style={{
"width": 150,
"height": getSetting('thumbnailHeight', 150) * 150 / getSetting('thumbnailWidth', 150)
}}
/>
<a className="thumbnail-url-clear" onClick={this.clearThumbnail}><FormattedMessage id="posts.clear_thumbnail"/></a>
<img
className="embedly-thumbnail"
src={this.props.value}
style={{
"width": 150,
"height": getSetting('thumbnailHeight', 150) * 150 / getSetting('thumbnailWidth', 150)
}}
/>
<a className="thumbnail-url-clear" onClick={this.clearThumbnail}><FormattedMessage id="posts.clear_thumbnail"/></a>
</div>
)
}

View file

@ -17,7 +17,7 @@ Posts.addField([
insertableBy: ['members'],
editableBy: ['members'],
viewableBy: ['guests'],
control: ThumbnailURL
hidden: true
}
},
{

View file

@ -5,8 +5,8 @@ function getEmbedlyData(url) {
var extractBase = 'http://api.embed.ly/1/extract';
var embedlyKey = getSetting('embedlyKey');
// 200 x 200 is the minimum size accepted by facebook
var thumbnailWidth = getSetting('thumbnailWidth', 200);
var thumbnailHeight = getSetting('thumbnailHeight', 200);
var thumbnailWidth = getSetting('thumbnailWidth', 400);
var thumbnailHeight = getSetting('thumbnailHeight', 300);
if(!embedlyKey) {
// fail silently to still let the post be submitted as usual

View file

@ -1,9 +1,43 @@
.embedly-thumbnail{
display: block;
margin-bottom: 5px;
border: 3px solid #ddd;
.embedly-form-control{
display: flex;
}
.thumbnail-url-clear{
.embedly-url-field{
position: relative;
flex: 1;
margin-right: 10px;
}
.embedly-thumbnail-placeholder{
background: #eee;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
.icon{
display: block;
color: rgba(0,0,0,0.5);
}
span{
display: none;
}
}
.embedly-thumbnail-image{
height: 60px;
width: auto;
display: block;
}
.embedly-thumbnail-actions{
display: flex;
align-items: center;
justify-content: center;
.thumbnail-edit{
margin-right: 5px;
}
span{
display: none;
}
}

View file

@ -58,6 +58,7 @@ class Form extends Component {
this.mutationSuccessCallback = this.mutationSuccessCallback.bind(this);
this.mutationErrorCallback = this.mutationErrorCallback.bind(this);
this.addToAutofilledValues = this.addToAutofilledValues.bind(this);
this.addToDeletedValues = this.addToDeletedValues.bind(this);
this.throwError = this.throwError.bind(this);
this.clearForm = this.clearForm.bind(this);
this.updateCurrentValues = this.updateCurrentValues.bind(this);
@ -71,6 +72,7 @@ class Form extends Component {
disabled: false,
errors: [],
autofilledValues: props.prefilledProps || {},
deletedValues: [],
currentValues: {}
};
}
@ -338,7 +340,7 @@ class Form extends Component {
}));
}
// add something to prefilled values
// add something to autofilled values
addToAutofilledValues(property) {
this.setState(prevState => ({
autofilledValues: {
@ -348,6 +350,13 @@ class Form extends Component {
}));
}
// add something to deleted values
addToDeletedValues(name) {
this.setState(prevState => ({
deletedValues: [...prevState.deletedValues, name]
}));
}
setFormState(fn) {
this.setState(fn);
}
@ -359,6 +368,7 @@ class Form extends Component {
clearForm: this.clearForm,
autofilledValues: this.state.autofilledValues,
addToAutofilledValues: this.addToAutofilledValues,
addToDeletedValues: this.addToDeletedValues,
updateCurrentValues: this.updateCurrentValues,
getDocument: this.getDocument,
setFormState: this.setFormState,
@ -453,15 +463,20 @@ class Form extends Component {
const set = _.compactObject(flatten(data));
// put all keys without data on $unset
const unsetKeys = _.difference(fields, _.keys(set));
const unset = _.object(unsetKeys, unsetKeys.map(()=>true));
const setKeys = _.keys(set);
let unsetKeys = _.difference(fields, setKeys);
// build modifier
const modifier = {$set: set};
if (!_.isEmpty(unset)) modifier.$unset = unset;
// add all keys to delete (minus those that have data associated)
unsetKeys = _.unique(unsetKeys.concat(_.difference(this.state.deletedValues, setKeys)));
// build mutation arguments object
const args = {documentId: document._id, set: set};
if (unsetKeys.length > 0) {
const unset = _.object(unsetKeys, unsetKeys.map(() => true));
args.unset = unset;
}
// call method with _id of document being edited and modifier
// Meteor.call(this.props.methodName, document._id, modifier, this.methodCallback);
this.props.editMutation({documentId: document._id, set: set, unset: unset}).then(this.editMutationSuccessCallback).catch(this.mutationErrorCallback);
this.props.editMutation(args).then(this.editMutationSuccessCallback).catch(this.mutationErrorCallback);
}
}
@ -565,6 +580,7 @@ Form.contextTypes = {
Form.childContextTypes = {
autofilledValues: PropTypes.object,
addToAutofilledValues: PropTypes.func,
addToDeletedValues: PropTypes.func,
updateCurrentValues: PropTypes.func,
setFormState: PropTypes.func,
throwError: PropTypes.func,

View file

@ -68,6 +68,7 @@ addStrings('en', {
"posts.scheduled": "Scheduled",
"posts.daily": "Daily",
"posts.clear_thumbnail": "Clear Thumbnail",
"posts.clear_thumbnail?": "Clear thumbnail?",
"posts.enter_thumbnail_url": "Enter URL",
"posts.created_message": "Post created.",
"posts.rate_limit_error": "Please wait {value} seconds before posting again.",

View file

@ -55,4 +55,6 @@ Utils.icons = {
spinner: "spinner",
new: "plus",
user: "user",
like: "heart",
image: "picture-o",
};