uploadcare-widget-tab-effects
Version:
Effects Tab for Uploadcare Widget. Edit images in desktop and mobile browsers.
374 lines (289 loc) • 8.62 kB
JavaScript
import {createNode, getModifiersByEffects, getNextRotateValue, getCropResize} from 'tools'
import template from './Tab.html'
import {Content, Crops, Effects, Footer, Header, Image, Range} from 'components'
const ranges = {
enhance: [0, 100],
sharp: [0, 20],
blur: [0, 100, 5],
}
const Tab = (props) => {
let $element
let _header
let _content
let _footer
let _image
let _effects
let _crops
const {
uc,
store,
settings,
onDone,
onFail,
} = props
let state = {
cropWidget: null,
currentCrop: 0,
}
const t = uc.locale.t
const CropWidget = uc.crop.CropWidget
const previewModifiers = '-/preview/1162x693/-/setfill/ffffff/-/format/jpeg/-/progressive/yes/'
const getElement = () => {
if (!$element) {
render()
}
return $element
}
const render = () => {
const {view, appliedEffects, image} = store.getState()
$element = createNode(template())
_header = new Header({title: headerTitle(view)})
_footer = new Footer({
locale: {
done: t('dialog.tabs.preview.done'),
cancel: t('dialog.tabs.preview.image.change'),
},
onDone: handleDone,
onCancel: handleCancel,
})
_content = new Content()
let imageUrl = image.cdnUrl + previewModifiers
if (settings.previewUrlCallback) {
imageUrl = settings.previewUrlCallback(imageUrl, image)
}
_image = new Image({
imageUrl: imageUrl,
onUpdate: () => store.setImageLoad('start'),
onLoad: () => store.setImageLoad('load'),
onFail: () => store.setImageLoad('fail'),
})
$element.appendChild(_header.getElement())
$element.appendChild(_content.getElement())
$element.appendChild(_footer.getElement())
_content.appendChild(_image.getElement())
const {effects} = settings
const titles = effects.reduce((titles, effect) => {
titles[effect] = effectTitle(effect)
return titles
}, {})
_effects = new Effects({
effects,
titles,
appliedEffects,
onEffectClick: handleEffectClick,
})
_footer.appendChild(_effects.getElement())
store.subscribeToAppliedEffects(handleAppliedEffectsChange)
store.subscribeToView(handleViewChange)
store.subscribeToImage(handleImageChange)
store.subscribeToImageLoad(handleImageLoadChange)
}
const headerTitle = (view) => (view === 'preview') ? t('dialog.tabs.preview.image.title') : effectTitle(view)
const effectTitle = (effect) => t(`effects.captions.${effect}`)
const deleteCropWidget = () => {
if (!state.cropWidget || !state.cropWidget.__api) return
state.cropWidget.__api.release()
state.cropWidget.__api.destroy()
_image.getImg().removeAttribute('style')
}
const handleDone = (e) => {
const {view} = store.getState()
if (view === 'preview') {
onDone()
}
else {
e.stopPropagation()
if (view === 'crop') {
finishCrop()
}
store.setView('preview')
}
}
const handleCancel = (e) => {
const {view, appliedEffects} = store.getState()
const {crop: cropSettings} = settings
if (view !== 'preview') {
e.stopPropagation()
let value = 0
if (view === 'crop') {
const freeAllowed = cropSettings.some(c => !c.preferedSize)
value = freeAllowed ? null : appliedEffects[view]
deleteCropWidget()
}
store.setAppliedEffect({[view]: value})
store.setView('preview')
}
}
const handleEffectClick = (effect) => {
const {appliedEffects} = store.getState()
if (effect === 'rotate') {
store.setAppliedEffect({rotate: getNextRotateValue(appliedEffects.rotate)})
return
}
if (typeof appliedEffects[effect] === 'boolean') {
store.setAppliedEffect({[effect]: !appliedEffects[effect]})
return
}
if (effect === 'crop' || typeof appliedEffects[effect] === 'number') {
store.setView(effect)
}
}
const handleRangeChange = (value) => {
const {view} = store.getState()
store.setAppliedEffect({[view]: parseInt(value)})
}
const handleAppliedEffectsChange = () => {
store.rebuildImage()
const {appliedEffects} = store.getState()
_effects.updateApplied(appliedEffects)
}
const handleViewChange = () => {
const {view, appliedEffects, image} = store.getState()
_header.updateTitle(headerTitle(view))
if (view === 'preview') {
_footer.updateDoneTitle()
}
else {
_footer.updateDoneTitle(t('effects.apply'))
}
_footer.empty()
if (view === 'preview') {
_footer.appendChild(_effects.getElement())
return
}
if (view === 'crop') {
showCrops()
let imageUrl = image.originalUrl +
(getModifiersByEffects([{crop: null}], false, false) || '') +
previewModifiers
if (settings.previewUrlCallback) {
imageUrl = settings.previewUrlCallback(imageUrl, image)
}
_image.updateImageUrl(imageUrl)
return
}
if (ranges[view]) {
let value = appliedEffects[view]
if (value === 0) {
value = ranges[view][1] / 2
store.setAppliedEffect({[view]: value})
}
const _range = new Range({
min: ranges[view][0],
max: ranges[view][1],
step: ranges[view][2],
value,
onChange: handleRangeChange,
})
_footer.appendChild(_range.getElement())
}
}
const handleImageChange = () => {
const {image} = store.getState()
let imageUrl = image.cdnUrl + previewModifiers
if (settings.previewUrlCallback) {
imageUrl = settings.previewUrlCallback(imageUrl, image)
}
_image.updateImageUrl(imageUrl)
}
const handleImageLoadChange = () => {
const {view, imageLoad} = store.getState()
switch (imageLoad) {
case 'start':
_footer.toggleDisabled(true)
if (view === 'preview') {
_effects.toggleDisabled(true)
}
if (view === 'crop') {
_crops.toggleDisabled(true)
}
break
case 'load':
_footer.toggleDisabled(false)
if (view === 'preview') {
_effects.toggleDisabled(false)
}
else if (view === 'crop') {
startCrop()
_crops.toggleDisabled(false)
}
break
case 'fail':
onFail()
break
}
}
const showCrops = () => {
const {crop: cropSettings} = settings
const {appliedEffects} = store.getState()
const {crop} = appliedEffects
state.currentCrop = (crop && crop.index !== undefined) ? crop.index : 0
_crops = new Crops({
crops: cropSettings.map((c, i) => {
const {description, width, height} = getCropSizeInfo(c)
return {
index: i,
settings: c,
title: description,
size: {
width,
height,
},
}
}),
currentCrop: state.currentCrop,
onCropClick: (crop) => {
state.currentCrop = crop.index
if (state.cropWidget) {
state.cropWidget.setCrop(crop.settings)
}
},
})
_footer.appendChild(_crops.getElement())
}
const startCrop = () => {
const {crop: cropSettings} = settings
const {appliedEffects, image} = store.getState()
const {crop} = appliedEffects
const {width, height} = image.originalImageInfo
const size = [width, height]
state.cropWidget = new CropWidget(uc.jQuery(_image.getImg()), size, cropSettings[state.currentCrop])
if (crop && crop.coords) {
state.cropWidget.setSelection(crop.coords)
}
}
const finishCrop = () => {
const {crop, originalSize} = state.cropWidget
const coords = state.cropWidget.getSelection()
deleteCropWidget()
store.setAppliedEffect({
crop: {
originalSize,
coords,
resizeTo: getCropResize(crop, [coords.width, coords.height]),
settings: crop,
index: state.currentCrop,
},
})
}
const getCropSizeInfo = (crop) => {
const {preferedSize} = crop
let description = t('dialog.tabs.preview.crop.free')
let width
let height
if (preferedSize) {
const gcd = uc.utils.gcd(preferedSize[0], preferedSize[1])
const size = uc.utils.fitSize(preferedSize, [30, 30], true)
description = `${preferedSize[0] / gcd}:${preferedSize[1] / gcd}`
width = `${Math.max(20, size[0])}px`
height = `${Math.max(12, size[1])}px`
}
return {
description,
width,
height,
}
}
return {getElement}
}
export default Tab