@bigfishtv/cockpit
Version:
892 lines (794 loc) • 29.2 kB
JavaScript
var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; };
var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
var _dec, _class, _class2, _temp;
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { Fieldset, createValue } from '@bigfishtv/react-forms';
import classnames from 'classnames';
import deepEqual from 'deep-equal';
import get from 'lodash/get';
import $ from 'jquery';
import { getImageUrl } from '../../utils/fileUtils';
import { titleCase } from '../../utils/stringUtils';
import { curvesHashTable } from '../../utils/colorUtils';
import { post } from '../../api/xhrUtils';
import { notifyWarning } from '../../actions/notifications';
import Spinner from '../Spinner';
import PropTypes from 'prop-types';
import Icon from '../Icon';
import MainContent from '../container/MainContent';
import Bulkhead from '../page/Bulkhead';
import Button from '../button/Button';
import Field from '../form/Field';
import Meter from '../input/MeterInput';
import Cropper from '../editor/Cropper';
import ProgressBarPredictive from '../ProgressBarPredictive';
import DropdownAction from '../button/dropdown/DropdownAction';
import DropdownItem from '../button/dropdown/DropdownItem';
import * as tankCaman from '../../utils/tankCaman';
var presetNames = [];
var adjustmentOrder = ['temperature', 'exposure', 'contrast', 'curves', 'fade', 'vibrance', 'saturation', 'sharpen', 'grain'];
var defaultFormValue = {
ratio: null,
rotation: 0,
crop: { top: 0, left: 0, width: 1, height: 1 },
temperature: 6600,
exposure: 0,
contrast: 0,
darks: 0,
lights: 0,
fade: 0,
vibrance: 0,
saturation: 0,
sharpen: 0,
blur: 0,
grain: 0
};
var filterTimingEstimate = {
exposure: 1,
contrast: 1,
curves: 1,
vibrance: 0.5,
saturation: 0.5,
sharpen: 1.5,
blur: 2,
noise: 3.8
};
var HeaderToolbar = function HeaderToolbar(props) {
return React.createElement(
'div',
null,
props.saving && props.estimatedTime && React.createElement(ProgressBarPredictive, { estimatedTime: props.estimatedTime }),
props.standalone && React.createElement(Button, { text: 'Close', size: 'large', onClick: props.onClose }),
!props.saving && props.savedImageUrl && React.createElement(Button, { text: 'View full-resolution', size: 'large', onClick: function onClick() {
return window.open(props.savedImageUrl);
} }),
React.createElement(
DropdownAction,
{
text: props.saving ? 'Saving...' : 'Save',
style: 'secondary',
size: 'large',
pullRight: true,
onClick: props.onSave,
disabled: props.saving },
React.createElement(DropdownItem, { text: 'Save as a copy', onClick: props.onSaveCopy })
)
);
};
function estimatedSaveDuration(asset) {
var steps = [].concat(asset.transform_instructions.editSteps, asset.transform_instructions.filterSteps);
var megapixels = asset.width * asset.transform_instructions.settings.crop.width * (asset.height * asset.transform_instructions.settings.crop.height) / 1000000;
var equivalentSteps = steps.reduce(function (counter, step) {
return counter + (filterTimingEstimate[step.key] || 0.5);
}, 0);
// 0.3s for base image edit, 0.5s for round-trip
// stats can be found here https://docs.google.com/spreadsheets/d/1qosKrfK9pnxC3rkiJ6VVEh-QZ3PhfclfezBGNrTraAQ/edit?usp=sharing
var estimatedSeconds = 0.55 * megapixels / 7 * equivalentSteps + 0.3 + 0.5;
return estimatedSeconds * 1000;
}
function getSavedImageUrl(asset) {
return asset.transform_instructions ? '/uploads/_transformed/' + asset.filename + '?' + new Date().valueOf() : '/uploads/' + asset.filename;
}
/**
* Template for image editor, is also used in a modal
*/
var ImageEdit = (_dec = connect(function (_ref) {
var imageFilterPresets = _ref.imageFilterPresets;
return { imageFilterPresets: imageFilterPresets };
}), _dec(_class = (_temp = _class2 = function (_Component) {
_inherits(ImageEdit, _Component);
function ImageEdit(props) {
_classCallCheck(this, ImageEdit);
var _this2 = _possibleConstructorReturn(this, _Component.call(this, props));
_this2.handleSave = function () {
var saveAsCopy = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
if (_this2.state.cropping) {
_this2.handleCrop();
}
var data = _extends({}, _this2.props.asset, { transform_instructions: _this2.generateInstructions() });
_this2.setState({ saving: true, estimatedSaveDuration: estimatedSaveDuration(data) });
var action = saveAsCopy === true ? 'saveAs' : 'edit';
post({
url: '/tank/assets/' + action + '/' + data.id + '.json',
data: data,
subject: 'image',
callback: function callback(response) {
// need this in case it's a belongsToMany with join data
response._joinData = _this2.props.asset._joinData;
_this2.props.onAssetChange(response, _this2.props.asset);
_this2.setState({
saving: false,
savedImageUrl: getSavedImageUrl(response)
});
},
callbackError: function callbackError(response) {
if (response.status === 404) {
_this2.props.dispatch(notifyWarning('"Save as a copy" feature requires Tank 3.0.4 or greater'));
}
_this2.setState({ saving: false });
console.warn('Error saving image', response);
}
});
};
_this2.handleClose = function () {
_this2.props.onClose();
_this2.props.closeModal();
};
_this2.handleFormValueChange = function (nextFormValue) {
_this2.setState({ formValue: nextFormValue });
};
_this2.handleReset = function () {
_this2.state.formValue.select('filter').update(null);
_this2.setState({ currentFilter: null }, function () {
_this2.applyImageFilter();
});
};
_this2.toggleCrop = function () {
_this2.setState({ cropping: !_this2.state.cropping });
_this2.toggleTray('crop');
};
_this2.updateCrop = function (crop) {
_this2.crop = crop;
};
_this2.handleCrop = function () {
_this2.state.formValue.select('crop').update(_this2.crop);
_this2.setState({
cropping: false,
cropped: true,
currentTray: null
});
};
_this2.cancelCrop = function () {
_this2.crop = _this2.state.formValue.value.crop;
_this2.setState({
cropping: false,
cropped: !!_this2.crop,
currentTray: null
});
};
_this2.handleParamsReset = function () {
var newFormValue = _extends({}, defaultFormValue);
newFormValue.crop = _this2.state.formValue.value.crop;
_this2.state.formValue.update(newFormValue);
_this2.setState({ steps: [] }, function () {
_this2.applyImageFilter();
});
};
tankCaman.init(window.Caman);
presetNames = props.imageFilterPresets.map(function (preset) {
return preset.title;
});
var formValue = _extends({}, defaultFormValue, get(props.asset, 'transform_instructions.settings', {}));
var cropped = !!get(props.asset, 'transform_instructions.settings.crop');
var steps = [];
get(props.asset, 'transform_instructions.editSteps', []).map(function (step) {
if (step.value) steps[step.key] = step.value;
});
_this2.state = {
loading: false,
cropping: false,
saving: false,
savedImageUrl: getSavedImageUrl(props.asset),
cropped: cropped,
currentTray: null,
currentFilter: formValue.filter,
lights: get(formValue, 'lights', 0),
darks: get(formValue, 'darks', 0),
steps: steps,
url: props.host + getImageUrl(props.asset, 'cockpit-edit', null, true),
previewUrl: props.host + getImageUrl(props.asset, 'cockpit-small', null, true),
width: 640,
height: 480,
formValue: createValue({
schema: undefined,
value: formValue,
onChange: _this2.handleFormValueChange
})
};
_this2.crop = formValue.crop;
_this2.origWidth = _this2.state.width;
_this2.origHeight = _this2.state.height;
_this2.stageHeight = 1000;
var img = new Image();
img.onload = function () {
_this2.setState({ width: img.width, height: img.height });
_this2.origWidth = img.width;
_this2.origHeight = img.height;
// Go ahead and apply any previous edits, idk why it doesn't work immediately...
setTimeout(function () {
return _this2.applyImageFilter();
}, 500);
};
img.src = _this2.state.url;
return _this2;
}
ImageEdit.prototype.componentDidMount = function componentDidMount() {
// Set current filter selection based on saved selection
if (this.state.currentFilter) this.handleFilterClick(this.state.currentFilter);
// @refactor to not use jquery
this.stageHeight = $(this.refs.edit_inner).height();
};
// generates filter preview thumbnail, staggered for performance
ImageEdit.prototype.handleFilterImageLoad = function handleFilterImageLoad(elementId, filterName) {
var _this3 = this;
var delay = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
var doIt = function doIt() {
return _this3.applyPreviewFilter(elementId, filterName);
};
if (delay) setTimeout(doIt, delay);else doIt();
};
ImageEdit.prototype.applyPreviewFilter = function applyPreviewFilter(elementId, filterName) {
var _this = this;
if (typeof Caman != 'undefined') Caman(this.props.currentDocument.getElementById(elementId), function () {
this.resize({ width: 200, height: 150 });
this.render();
var steps = _this.generateFilterSteps(filterName);
_this.applyImageSteps.apply(this, [steps, false]);
this.render();
});
};
ImageEdit.prototype.applyImageFilter = function applyImageFilter() {
// make ref to this because caman binds its own
var _this = this;
var editSteps = this.generateEditSteps();
var filterSteps = this.generateFilterSteps();
this.setState({ loading: true });
if (typeof Caman != 'undefined') Caman(this.props.currentDocument.getElementById('mainImage'), function () {
var _this4 = this;
this.revert();
// if any edits have been made do them first
if (editSteps.length) {
// apply edit steps and render
_this.applyImageSteps.apply(this, [editSteps]);
this.render(function () {
// if filter applied then execute those steps and render
if (filterSteps.length) _this.applyImageSteps.apply(_this4, [filterSteps]);
_this4.render(function () {
_this.setState({ loading: false });
});
});
} else {
// if filter applied then execute those steps and render
if (filterSteps.length) _this.applyImageSteps.apply(this, [filterSteps]);
this.render(function () {
_this.setState({ loading: false });
});
}
});
};
// this function has scope of caman image applied to it
ImageEdit.prototype.applyImageSteps = function applyImageSteps() {
var _steps = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
var verbose = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true;
// copy array
var steps = [].concat(_steps);
for (var _iterator = steps, _isArray = Array.isArray(_iterator), _i = 0, _iterator = _isArray ? _iterator : _iterator[Symbol.iterator]();;) {
var _ref2;
if (_isArray) {
if (_i >= _iterator.length) break;
_ref2 = _iterator[_i++];
} else {
_i = _iterator.next();
if (_i.done) break;
_ref2 = _i.value;
}
var step = _ref2;
var _step = _extends({}, step);
var key = _step.key,
value = _step.value;
// if value is an array the step's function requires more than 1 argument so run apply on it
if (value instanceof Array) {
if (verbose) console.log('Applying (with args)', key, value);
if (key == 'curves' && (value[value.length - 1] === 'curvesHashTable' || value[value.length - 1] === null)) value[value.length - 1] = curvesHashTable;
this[key].apply(this, value);
// step's function takes no argument so just call it
} else if (typeof value == 'undefined') {
if (verbose) console.log('Applying (without args)', key, value);
this[key]();
// in all other cases apply the step with 1 argument, except if value is a numeric 0
} else if (value !== 0) {
if (verbose) console.log('Applying (as only arg)', key, value);
if ((typeof value === 'undefined' ? 'undefined' : _typeof(value)) == 'object') value = _extends({}, value); // if it's an object then copy it
this[key](value);
}
}
};
ImageEdit.prototype.generateEditSteps = function generateEditSteps() {
var fullSteps = [];
var steps = this.state.steps;
var keys = Object.keys(steps);
adjustmentOrder.map(function (key) {
if (keys.indexOf(key) >= 0 && steps[key] !== 0) {
var value = steps[key];
if (value instanceof Array && value.length == 1) value = value[0];
if (value !== 0) {
var _value = steps[key];
fullSteps.push({ key: key, value: _value });
}
}
});
return fullSteps;
};
ImageEdit.prototype.generateFilterSteps = function generateFilterSteps() {
var filterName = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null;
if (filterName === null) filterName = this.state.currentFilter;
var fullSteps = [];
if (filterName !== null && presetNames.indexOf(filterName) >= 0) {
this.props.imageFilterPresets.map(function (preset) {
if (preset.title === filterName) fullSteps = [].concat(preset.steps);
});
}
return fullSteps;
};
ImageEdit.prototype.generateInstructions = function generateInstructions() {
var formValue = this.state.formValue;
var editSteps = this.generateEditSteps();
var filterSteps = this.generateFilterSteps();
var instructions = {
settings: _extends({}, formValue.value),
editSteps: editSteps,
filterSteps: filterSteps
};
return instructions;
};
ImageEdit.prototype.handleFilterClick = function handleFilterClick(filterName) {
var _this5 = this;
if (this.state.loading) return;
this.state.formValue.select('filter').update(filterName);
this.setState({ currentFilter: filterName, loading: true }, function () {
_this5.applyImageFilter();
});
};
ImageEdit.prototype.handleRotate = function handleRotate(deg90s) {
// keep track of rotation in state
var rotation = (this.state.rotation + deg90s * 90) % 360;
this.state.formValue.select('rotation').update(rotation);
};
ImageEdit.prototype.toggleTray = function toggleTray(trayName) {
var _state = this.state,
currentTray = _state.currentTray,
cropping = _state.cropping;
if (currentTray == trayName) currentTray = null;else currentTray = trayName;
this.setState({ currentTray: currentTray });
if (cropping && trayName != 'crop') this.setState({ cropping: false });
};
ImageEdit.prototype.handleParamChange = function handleParamChange(param, value) {
var _this6 = this;
var steps = this.state.steps;
var newSteps = steps;
newSteps[param] = (typeof value === 'undefined' ? 'undefined' : _typeof(value)) !== 'object' ? [value] : value;
this.setState({ steps: newSteps }, function () {
_this6.applyImageFilter();
});
};
ImageEdit.prototype.handleCurveChange = function handleCurveChange(tone, value) {
var _setState,
_this7 = this;
this.setState((_setState = {}, _setState[tone] = value, _setState), function () {
var _state2 = _this7.state,
lights = _state2.lights,
darks = _state2.darks;
_this7.handleParamChange('curves', ['rgb', [0, darks], [100, 100], [180, 180], [255, 255 - lights], curvesHashTable]);
});
};
ImageEdit.prototype.render = function render() {
var _this8 = this,
_React$createElement;
var _state3 = this.state,
loading = _state3.loading,
cropping = _state3.cropping,
saving = _state3.saving,
savedImageUrl = _state3.savedImageUrl,
currentTray = _state3.currentTray,
currentFilter = _state3.currentFilter,
formValue = _state3.formValue,
url = _state3.url,
previewUrl = _state3.previewUrl,
height = _state3.height,
cropped = _state3.cropped;
var resetable = deepEqual(formValue.value, defaultFormValue);
var ratio = formValue.value.ratio ? ratioToDecimal(formValue.value.ratio) : null;
var _formValue$value = formValue.value,
rotation = _formValue$value.rotation,
crop = _formValue$value.crop;
var originalImageStyles = {
position: 'absolute',
width: this.origWidth,
height: this.origHeight,
backgroundSize: '100%',
backgroundImage: 'url("' + url + '")',
transform: 'translate(-50%, -50%) rotate(' + rotation + 'deg)'
// this is not ideal as it messes with cropping but this whole thing needs to be redone soon
};if (this.stageHeight < this.origHeight) originalImageStyles.zoom = this.stageHeight / (this.origHeight * 1.1);
if (cropped && !cropping) {
var cropTop = crop.top * this.origHeight;
var cropLeft = crop.left * this.origWidth;
var cropWidth = this.origWidth * crop.width;
var cropHeight = this.origHeight * crop.height;
var cropRight = cropLeft + cropWidth;
var cropBottom = cropTop + cropHeight;
var negativeWidth = cropLeft - (this.origWidth - cropRight);
var negativeHeight = cropTop - (this.origHeight - cropBottom);
originalImageStyles.clip = 'rect(' + cropTop + 'px ' + cropRight + 'px ' + cropBottom + 'px ' + cropLeft + 'px)';
originalImageStyles.transform = 'translate(calc(-50% - ' + negativeWidth / 2 + 'px), calc(-50% - ' + negativeHeight / 2 + 'px)) rotate(' + rotation + 'deg)';
}
return React.createElement(
MainContent,
{ size: 'full' },
React.createElement(Bulkhead, {
title: 'Edit Image',
Toolbar: HeaderToolbar,
onSave: this.handleSave,
onSaveCopy: this.handleSave.bind(this, true),
onClose: this.handleClose,
saving: saving,
savedImageUrl: savedImageUrl,
estimatedTime: this.state.estimatedSaveDuration,
standalone: this.props.standalone
}),
React.createElement(
'div',
{ style: { display: 'flex', flex: 'auto' } },
React.createElement(
'div',
{ className: 'edit', ref: 'edit_inner' },
React.createElement(
'div',
{ className: 'edit-toolbar' },
React.createElement(
Button,
{ style: 'icon', onClick: this.toggleCrop },
React.createElement(Icon, { name: 'crop' })
),
React.createElement(
Button,
{ style: 'icon', onClick: function onClick() {
return _this8.toggleTray('advanced');
} },
React.createElement(Icon, { name: 'advanced' })
)
),
currentTray == 'advanced' ? React.createElement(
'div',
{ className: 'edit-toolbar edit-toolbar-advanced' },
React.createElement(
Fieldset,
{ formValue: formValue },
React.createElement(
Field,
{ select: 'temperature', autoLabel: false },
React.createElement(Meter, {
min: 4600,
max: 8600,
icon: 'temperature',
text: 'Temperature',
onAfterChange: this.handleParamChange.bind(this, 'temperature')
})
),
React.createElement(
Field,
{ select: 'exposure', autoLabel: false },
React.createElement(Meter, {
min: -50,
max: 50,
icon: 'sunny',
text: 'Exposure',
onAfterChange: this.handleParamChange.bind(this, 'exposure')
})
),
React.createElement(
Field,
{ select: 'contrast', autoLabel: false },
React.createElement(Meter, {
min: -20,
max: 20,
icon: 'tonality',
text: 'Contrast',
onAfterChange: this.handleParamChange.bind(this, 'contrast')
})
),
React.createElement(
Field,
{ select: 'darks', autoLabel: false },
React.createElement(Meter, {
min: -80,
max: 80,
icon: 'bring-forward',
text: 'Shadow recovery',
onAfterChange: this.handleCurveChange.bind(this, 'darks')
})
),
React.createElement(
Field,
{ select: 'lights', autoLabel: false },
React.createElement(Meter, {
min: -50,
max: 50,
icon: 'send-backwards',
text: 'Highlight recovery',
onAfterChange: this.handleCurveChange.bind(this, 'lights')
})
),
React.createElement(
Field,
{ select: 'fade', autoLabel: false },
React.createElement(Meter, {
min: 0,
max: 100,
icon: 'moon-crescent',
text: 'Fade',
onAfterChange: this.handleParamChange.bind(this, 'fade')
})
),
React.createElement(
Field,
{ select: 'vibrance', autoLabel: false },
React.createElement(Meter, {
min: -100,
max: 100,
icon: 'beach',
text: 'Vibrance',
onAfterChange: this.handleParamChange.bind(this, 'vibrance')
})
),
React.createElement(
Field,
{ select: 'saturation', autoLabel: false },
React.createElement(Meter, {
min: -100,
max: 100,
icon: 'barrel-drop',
text: 'Saturation',
onAfterChange: this.handleParamChange.bind(this, 'saturation')
})
),
React.createElement(
Field,
{ select: 'sharpen', autoLabel: false },
React.createElement(Meter, {
min: 0,
max: 100,
icon: 'magic-wand',
text: 'Sharpen',
onAfterChange: this.handleParamChange.bind(this, 'sharpen')
})
),
React.createElement(
Field,
{ select: 'grain', autoLabel: false },
React.createElement(Meter, {
min: 0,
max: 10,
icon: 'grain',
text: 'Grain',
onAfterChange: this.handleParamChange.bind(this, 'noise')
})
),
React.createElement(Button, {
text: React.createElement(
'span',
null,
React.createElement(Icon, { name: 'reset', size: 14 }),
' Reset'
),
size: 'medium',
style: 'margin-top-small',
onClick: this.handleParamsReset,
disabled: resetable
})
)
) : currentTray == 'crop' ? React.createElement(
'div',
{ className: 'edit-toolbar edit-toolbar-crop' },
React.createElement(
Fieldset,
{ formValue: formValue },
React.createElement(
Field,
{ select: 'ratio', autoLabel: false },
React.createElement(RatioButton, { text: '3:2' })
),
React.createElement(
Field,
{ select: 'ratio', autoLabel: false },
React.createElement(RatioButton, { text: '4:3' })
),
React.createElement(
Field,
{ select: 'ratio', autoLabel: false },
React.createElement(RatioButton, { text: '5:4' })
),
React.createElement(
Field,
{ select: 'ratio', autoLabel: false },
React.createElement(RatioButton, { text: '1:1' })
),
React.createElement(
Field,
{ select: 'ratio', autoLabel: false },
React.createElement(RatioButton, { text: '4:5' })
),
React.createElement(
Field,
{ select: 'ratio', autoLabel: false },
React.createElement(RatioButton, { text: '3:4' })
),
React.createElement(
Field,
{ select: 'ratio', autoLabel: false },
React.createElement(RatioButton, { text: '2:3' })
),
React.createElement(
Field,
{ select: 'ratio', autoLabel: false },
React.createElement(RatioButton, { text: '2:1' })
),
React.createElement(
Field,
{ select: 'ratio', autoLabel: false },
React.createElement(RatioButton, { text: '1:2' })
),
React.createElement(
Field,
{ select: 'ratio', autoLabel: false },
React.createElement(RatioButton, { text: '16:9' })
),
React.createElement(
Field,
{ select: 'ratio', autoLabel: false },
React.createElement(RatioButton, { text: '9:16' })
)
)
) : null,
React.createElement(
'div',
{ className: '', style: { flex: 'auto', display: 'flex', flexDirection: 'column' } },
React.createElement(
'div',
{ className: 'edit-inner' },
React.createElement(
'div',
{ style: originalImageStyles },
React.createElement('img', { src: url, id: 'mainImage' }),
cropping && React.createElement(Cropper, {
width: this.origWidth,
height: height,
ApplyButton: null,
fixedRatio: ratio,
onChange: this.updateCrop,
defaultCrop: this.crop
})
),
loading && React.createElement(
'div',
{ className: 'loader-center' },
React.createElement(Spinner, { size: 32 })
)
),
cropping && React.createElement(
'div',
{ className: '', style: { flex: 'none', display: 'flex', background: '#ECECEF' } },
React.createElement(
'div',
{ style: { margin: '1.6rem auto' } },
React.createElement(Button, { text: 'Cancel', onClick: this.cancelCrop }),
React.createElement(Button, (_React$createElement = { text: 'Apply Crop', style: 'primary', onClick: this.handleCrop }, _React$createElement['style'] = 'primary', _React$createElement))
)
)
)
)
),
React.createElement(
'div',
{ className: 'image-filters' },
React.createElement(
'div',
{ className: classnames('image-filter', { active: currentFilter === null }) },
React.createElement(
'div',
{ className: 'media media-4-3' },
React.createElement('img', { src: previewUrl, onClick: this.handleReset })
),
React.createElement(
'h4',
null,
'Original'
)
),
presetNames.map(function (filterName, i) {
return React.createElement(
'div',
{
key: i,
className: classnames('image-filter', { active: currentFilter == filterName }),
onClick: function onClick() {
return _this8.handleFilterClick(filterName);
} },
React.createElement(
'div',
{ className: 'media media-4-3' },
React.createElement('img', {
src: previewUrl,
id: 'filter' + i,
onLoad: function onLoad() {
return _this8.handleFilterImageLoad('filter' + i, filterName, i * 200);
}
})
),
React.createElement(
'h4',
null,
titleCase(filterName)
)
);
})
)
);
};
return ImageEdit;
}(Component), _class2.propTypes = {
/** document that component is on -- hangover from attempting to open editor in new window */
currentDocument: PropTypes.any,
/** Host string to append before image url e.g. http://dev.project, default is empty string so image urls will just be /imageurlwhatever */
host: PropTypes.string,
/** Is component used outside a page template? If true displays things like a close button */
standalone: PropTypes.bool,
/** Called on image save -- useful for updating an asset url if being used in a modal */
onAssetChange: PropTypes.func
}, _class2.defaultProps = {
currentDocument: window.document,
host: '',
standalone: false,
onAssetChange: function onAssetChange() {
return console.warn('[ImageEdit] no onAssetChange prop');
}
}, _temp)) || _class);
export { ImageEdit as default };
var RatioButton = function RatioButton(props) {
return React.createElement(Button, {
text: props.text,
style: props.value == props.text ? 'primary' : null,
onClick: function onClick() {
return props.onChange(props.value !== props.text ? props.text : null);
}
});
};
/**
* Converts a string ratio to a decimal fraction. Returns null if doesn't match format
*
* ratioToDecimal('3:2') // 1.5
*
* @param {string} ratio
* @returns {number|null}
*/
function ratioToDecimal(ratio) {
var matches = ratio.match(/^(\d+):(\d+)$/);
if (matches) {
return parseInt(matches[1]) / parseInt(matches[2]);
}
return null;
}