{"version":3,"file":"full-screen-spinner-base-0fd4a107.js","sources":["../../../client/src/javascripts/customer_pages/_utils/image-editor.jsx","../../../client/src/javascripts/customer_pages/_utils/tools/slider-control-conversions.js","../../../client/src/javascripts/customer_pages/_utils/string-parameterize.js","../../../client/src/javascripts/customer_pages/_utils/manana-slider.jsx","../../../client/src/javascripts/customer_pages/_prism-builder/common/notices/full-screen-spinner-base.jsx"],"sourcesContent":["import createReactClass from 'create-react-class';\nimport React from 'react';\nimport ReactDOM from 'react-dom';\nimport fetchImage from './fetch-image';\nimport Rectangle from './img/geometry';\nimport {\n getDefaultCrop,\n getDefaultCropRatio,\n getFixedCrop,\n scaleRectAroundOrigin,\n} from './img/canvas';\nimport IU from './img/img-utils';\nimport CropCords from './img/thumbnail-crop-coords';\n\nconst imageProcessors = (function() {\n /** ************************************************************************************* */\n /* Constants */\n const { X, Y, WIDTH, HEIGHT, RIGHT_ANGLE, imgHelpers } = IU;\n\n /** ************************************************************************************* */\n /* Private Functions */\n\n const LocalThumbCache = {\n getThumb(url) {\n if (this.thumbs[url]) {\n return this.thumbs[url];\n }\n\n const img = fetchImage(url, 'anonymous');\n this.thumbs[url] = img;\n return img;\n },\n thumbs: {},\n };\n\n const PanHandle = new Image();\n PanHandle.src = '/wm/images/move-photo.png';\n const canvasId = 'pandaview-canvas';\n\n\n // -----------------------------------------------------------\n // PandaView\n // -----------------------------------------------------------\n\n const PandaView = createReactClass({\n getInitialState() {\n return {\n forceCrop: false,\n drag: false,\n dragX: 0,\n dragY: 0,\n lastCrop: false,\n editMode: 'drag'\n };\n },\n componentDidMount() {\n this.checkImage();\n this.draw();\n },\n componentDidUpdate() {\n this.checkImage();\n if (this.simple_update) {\n this.draw();\n } else {\n this.queue_draw();\n }\n },\n UNSAFE_componentWillReceiveProps(nextProps) {\n const url_prop = 'large_url';\n\n if (this.props.editMode != nextProps.editMode) {\n this.setState({ editMode: nextProps.editMode });\n }\n\n this.pImg = null;\n\n if (\n nextProps.crop != this.props.crop ||\n nextProps.size_key != this.props.size_key ||\n nextProps.angle != this.props.angle ||\n nextProps[url_prop] != this.props[url_prop]\n ) {\n this.img_width = null;\n this.img_height = null;\n this.cacheKey = null;\n this.setState({ forceCrop: null });\n }\n\n this.simple_update =\n nextProps.crop != this.props.crop &&\n nextProps.angle == this.props.angle &&\n nextProps[url_prop] == this.props[url_prop];\n\n this.checkImage();\n },\n fullResoluton() {\n return this.prints_EL2() || this.printEditPreview();\n },\n prints_EL2() {\n return this.props.resolution === 'full';\n },\n printEditPreview() {\n return this.props.resolution === 'full-edit-preview';\n },\n ratio() {\n const {\n constraints: { ratio, prism },\n large_dimensions\n } = this.props;\n\n if (prism) {\n return ratio;\n }\n\n // if portrait or square image, then invert the ratio, as for those we apply the size ratio as portrait\n if (large_dimensions && large_dimensions.w <= large_dimensions.h) {\n return 1 / ratio;\n }\n\n return ratio;\n },\n render_EL2() {\n const maxWidth = IU.EL2_WIDTH;\n const maxHeight = IU.EL2_HEIGHT;\n const ratio = this.ratio();\n\n this.rect = Rectangle.fitLargestRectangle(ratio, maxWidth, maxHeight);\n\n this.rect[WIDTH] = Math.round(this.rect[WIDTH]);\n this.rect[HEIGHT] = Math.round(this.rect[HEIGHT]);\n\n // noinspection CheckTagEmptyBody\n return (\n \n );\n },\n renderPrintEditPreview() {\n const { cropEdges, filename, style } = this.props;\n let width = IU.EL2_WIDTH;\n let height = IU.EL2_HEIGHT;\n\n if (cropEdges) {\n const img = this.getImage();\n\n if (img) {\n const cropCords = new CropCords(\n img.crop,\n IU.imgHelpers.getPhotoAspectRatio(this.props)\n );\n width = cropCords.canvasWidth;\n height = cropCords.canvasHeight;\n }\n }\n\n return (\n \n );\n },\n render() {\n if (this.prints_EL2()) {\n return this.render_EL2();\n }\n\n if (this.printEditPreview()) {\n return this.renderPrintEditPreview();\n }\n\n let width = IU.CANVAS_W;\n let height = IU.CANVAS_H;\n let classname = '';\n\n if (this.props.cropEdges) {\n const img = this.getImage();\n\n if (img) {\n const cropCords = new CropCords(\n img.crop,\n IU.imgHelpers.getPhotoAspectRatio(this.props)\n );\n width = cropCords.canvasWidth;\n height = cropCords.canvasHeight;\n\n classname = ' sized';\n }\n }\n\n // noinspection CheckTagEmptyBody\n return (\n \n );\n },\n fixTouchEvent(e) {\n const [touch] = e.touches;\n\n if (touch) {\n return {\n ...e,\n pageX: touch.pageX,\n pageY: touch.pageY,\n };\n }\n },\n handleMouseDown(e) {\n // Only if we have a handler\n let viewScale;\n const zoom = this.getZoom();\n const {\n onClose,\n mode,\n editMode,\n raw_dimensions,\n large_dimensions,\n angle,\n } = this.props;\n const offset = $(this.refs.canvas).offset();\n let x = e.pageX - offset.left;\n let y = e.pageY - offset.top;\n\n if (this.activeArea) {\n if (x < this.activeArea[X]) {\n if (onClose) onClose();\n return;\n }\n if (x > this.activeArea[X] + this.activeArea[WIDTH]) {\n if (onClose) onClose();\n return;\n }\n if (y > this.activeArea[Y] + this.activeArea[HEIGHT]) {\n if (onClose) onClose();\n return;\n }\n }\n\n //* **************************************************************\n // Editing Mode\n if (mode === 'editing') {\n this.setState({\n forceCrop: this.getCrop(),\n lastCrop: this.getCrop(),\n drag: true,\n dragX: e.pageX,\n dragY: e.pageY,\n });\n }\n },\n handleTouchStart(e) {\n this.handleMouseDown(this.fixTouchEvent(e));\n },\n shouldHandleMove() {\n return this.props.mode == 'editing' && this.state.drag;\n },\n handleMouseMove(e) {\n if (this.shouldHandleMove()) {\n e.preventDefault();\n const dX = e.pageX - this.state.dragX;\n const dY = e.pageY - this.state.dragY;\n const scale = this.viewScale();\n\n this.handlePan(-1 * dX * scale, -1 * dY * scale);\n this.setState({\n dragX: e.pageX,\n dragY: e.pageY,\n });\n }\n },\n handleTouchMove(e) {\n if (this.shouldHandleMove()) {\n this.handleMouseMove(this.fixTouchEvent(e));\n }\n },\n handleMouseGone(e) {\n this.setState({ drag: false });\n if (this.overlay) {\n this.overlay.off();\n this.overlay.remove();\n jQuery(document.body).css('user-select', '');\n this.overlay = null;\n }\n if (this.timer) {\n clearTimeout(this.timer);\n this.timer = null;\n }\n\n if (this.state.lastCrop) {\n this.props.onCropChanged &&\n this.props.onCropChanged(this.state.lastCrop);\n this.setState({ lastCrop: false });\n }\n },\n handleTouchEnd(e) {\n this.handleMouseGone(this.fixTouchEvent(e));\n },\n checkImage() {\n let url;\n let cache = false;\n const url_prop = 'large_url';\n\n if (this.fullResoluton()) {\n if (!this.props[url_prop]) {\n return;\n }\n url = this.props[url_prop].replace(/^https?:/, '');\n } else if (this.props.thumb_url) {\n url = this.props.thumb_url.replace(/^https?:/, '');\n cache = true;\n }\n\n if (this.img && this.url == url) {\n } else {\n this.url = url;\n if (cache) {\n this.img = LocalThumbCache.getThumb(url);\n } else if (window.RawCache) {\n this.img = RawCache.get(url);\n } else {\n this.img = fetchImage(url, 'anonymous');\n }\n\n const self = this;\n const queue_draw = function() {\n // Needs to re-render the component after the image have been succesfully loaded in order for the canvas to\n // be resized correctly.\n self.forceUpdate(function() {\n self.queue_draw();\n\n if (self.props.onImageLoaded) {\n self.props.onImageLoaded();\n }\n });\n };\n if (this.img.complete) {\n window.requestAnimationFrame(queue_draw);\n }\n this.img.addEventListener('load', queue_draw);\n }\n },\n viewScale() {\n const canvas = this.refs.canvas;\n if (canvas) {\n return canvas.width / canvas.clientWidth;\n }\n return 1;\n },\n queue_draw() {\n if (!this.tID) {\n this.bound_draw = this.bound_draw || this.draw;\n this.tID = setTimeout(this.bound_draw, 150);\n }\n },\n getCacheKey() {\n let cacheKey = `-img-${[\n this.img.src,\n this.props.size_key,\n this.props.angle,\n this.props.straighten,\n this.props.gamma,\n this.props.fx,\n ].join('-')}`;\n\n return cacheKey;\n },\n getProcessedImage() {\n let img = this.img;\n\n if (img && img.complete) {\n const cacheKey = this.getCacheKey();\n\n if (this.cachedImage && this.cacheKey == cacheKey) {\n return this.cachedImage;\n }\n\n const crop = this.getCrop();\n const ratio = crop[WIDTH] / crop[HEIGHT];\n const scale = 1;\n\n const props = {\n angle: this.props.angle,\n straighten: this.props.straighten,\n gamma: this.props.gamma,\n fx: this.props.fx,\n };\n\n if (this.props.constraints && this.props.constraints.prism) {\n props.angle = RIGHT_ANGLE * 4 - props.angle;\n }\n\n props.eye_scale = this.getScale();\n img = imgHelpers.doProcessPhoto(img, props, ratio, scale);\n\n this.cachedImage = img;\n this.cacheKey = cacheKey;\n\n return img;\n }\n },\n getImage() {\n if (this.img && this.img.src && this.img.complete) {\n this.pImg = this.pImg || this.getProcessedImage();\n\n const scale = (() => {\n const scale = this.getScale();\n\n return value => {\n value = parseInt(value, 10) * scale;\n\n return isNaN(value) ? 1 : value;\n };\n })();\n\n const img = this.pImg;\n\n img.crop = features?.disableAutoPrintCropping && !this.props.crop\n ? [0, 0, img.width, img.height]\n : this.getCrop().map(scale);\n\n img.cropped = false;\n\n return img;\n }\n\n this.pImg = null;\n },\n draw() {\n const canvas = this.refs.canvas;\n if (!canvas) {\n return;\n }\n\n const img = this.getImage();\n this.tID = null;\n\n if (!this.img || !this.img.complete || !img) {\n const ctx = canvas.getContext('2d');\n ctx.save();\n ctx.rect(\n canvas.width / 4,\n canvas.height / 4,\n canvas.width / 2,\n canvas.height / 2\n );\n ctx.fillStyle = 'transparent';\n ctx.fill();\n\n ctx.restore();\n return;\n }\n\n if (this.prints_EL2()) {\n return this.draw_EL2(canvas, img);\n }\n\n try {\n this.props.mode === 'editing'\n ? this.draw_editable()\n : this.draw_preview();\n } catch (e) {\n console.error(e);\n }\n },\n get_CropRatio() {},\n draw_EL2(canvas, img) {\n if (!canvas) return;\n if (!img) return;\n\n const dx = 1;\n const dy = 1;\n const dw = this.rect[WIDTH];\n const dh = this.rect[HEIGHT];\n\n const crop =\n img.crop ||\n getDefaultCrop(\n this.props.size_area[X] / this.props.size_area[Y],\n img.width,\n img.height\n );\n\n const ctx = canvas.getContext('2d');\n\n this.last_draw = {\n x: dx,\n y: dy,\n crop,\n };\n\n ctx.drawImage(\n img,\n Math.floor(crop[X]),\n Math.floor(crop[Y]),\n Math.floor(crop[WIDTH]),\n Math.floor(crop[HEIGHT]),\n dx,\n dy,\n dw,\n dh\n );\n\n ctx.beginPath();\n ctx.rect(dx, dy, dw, dh);\n ctx.strokeStyle = '#ffc120';\n ctx.lineWidth = 3;\n ctx.stroke();\n this.draw_handle({ ctx, canvas, x: dx, y: dy, cropFrame: this.rect });\n },\n draw_outline(ctx, rectangle, dimensions) {},\n draw_preview() {\n const canvas = ReactDOM.findDOMNode(this.refs.canvas);\n if (!canvas) return;\n\n let img = this.getImage();\n if (!img) return;\n\n const crop =\n img.crop ||\n getDefaultCrop(\n this.props.size_area[X] / this.props.size_area[Y],\n img.width,\n img.height\n );\n img = img.cloneRectangle(\n crop,\n IU.imgHelpers.getPhotoAspectRatio(this.props)\n );\n\n const ctx = canvas.getContext('2d');\n ctx.beginPath();\n ctx.save();\n\n let x = 0;\n const y = 0;\n\n const scale =\n img.height / img.width < canvas.height / canvas.width\n ? canvas.width / img.width\n : canvas.height / img.height;\n if (scale * img.width < canvas.width) {\n x = (canvas.width - scale * img.width) / 2;\n ctx.translate(x, 0);\n }\n ctx.scale(scale, scale);\n\n ctx.drawImage(img, 0, 0);\n ctx.restore();\n\n const viewScale = canvas.clientWidth / canvas.width;\n const outline = [x, y, scale * img.width, scale * img.height];\n this.reportSize(\n viewScale * x,\n viewScale * y,\n viewScale * (x + outline[WIDTH]),\n viewScale * (y + outline[HEIGHT])\n );\n },\n reportSize(x1, y1, x2, y2) {\n if (\n this.last_size &&\n this.last_size[X] === x2 &&\n this.last_size[Y] === y2\n ) {\n return;\n }\n\n this.widgetSize = [x1, y1, x2, y2];\n if (this.props.onSize) {\n const self = this;\n this.last_size = [x2, y2];\n setTimeout(function() {\n self.props.onSize(x2, y2, x1, y1);\n self.forceUpdate();\n }, 5);\n }\n },\n draw_editable() {\n const canvas = ReactDOM.findDOMNode(this.refs.canvas);\n if (!canvas) return;\n\n let img = this.getImage();\n if (!img) return;\n\n const crop =\n img.crop ||\n getDefaultCrop(\n this.props.size_area[X] / this.props.size_area[Y],\n img.width,\n img.height\n );\n img = img.cloneRectangle(\n crop,\n IU.imgHelpers.getPhotoAspectRatio(this.props)\n );\n\n const ctx = canvas.getContext('2d');\n ctx.beginPath();\n ctx.save();\n\n let x = 0;\n const y = 0;\n\n const scale =\n img.height / img.width < canvas.height / canvas.width\n ? canvas.width / img.width\n : canvas.height / img.height;\n if (scale * img.width < canvas.width) {\n x = (canvas.width - scale * img.width) / 2;\n ctx.translate(x, 0);\n }\n ctx.scale(scale, scale);\n\n ctx.drawImage(img, 0, 0);\n ctx.restore();\n\n const viewScale = canvas.clientWidth / canvas.width;\n const outline = [x, y, scale * img.width, scale * img.height];\n this.reportSize(\n viewScale * x,\n viewScale * y,\n viewScale * (x + outline[WIDTH]),\n viewScale * (y + outline[HEIGHT])\n );\n\n ctx.rect(outline[X], outline[Y], outline[WIDTH], outline[HEIGHT]);\n ctx.strokeStyle = '#ffc120';\n ctx.lineWidth = 3 / viewScale;\n ctx.stroke();\n\n const draw = {\n canvas,\n img,\n outline,\n scale,\n cropFrame: outline,\n ctx,\n };\n this.draw_handle(draw);\n\n this.last_draw = draw;\n },\n draw_handle(draw) {\n if (\n this.props.mode == 'editing' &&\n this.canPan() &&\n PanHandle.complete &&\n !PanHandle.error &&\n !this.state.drag\n ) {\n if (!this.panHandleSince) {\n this.panHandleSince = new Date().getTime();\n }\n let phw = draw.canvas.width;\n if (draw.x) {\n phw -= draw.x * 2;\n }\n const phh = draw.cropFrame[HEIGHT] + 2 * draw.cropFrame[Y];\n let handle_scale = 1.0;\n if (this.fullResoluton()) {\n handle_scale = draw.canvas.width / ( draw.canvas.clientWidth * 2 );\n }\n const now = new Date().getTime();\n\n const fade = 5;\n const alpha = fade - ( now - this.panHandleSince ) / 1000;\n if (alpha < 0) {\n return;\n }\n draw.ctx.save();\n draw.ctx.globalAlpha = alpha;\n draw.ctx.translate(phw / 2, phh / 2, 'pwh,pwh - draw_handle');\n draw.ctx.scale(handle_scale, handle_scale);\n\n draw.ctx.drawImage(\n PanHandle,\n (0 - PanHandle.width) / 2,\n (0 - PanHandle.height) / 2\n );\n draw.ctx.restore();\n const self = this;\n setTimeout(function() {\n self.draw();\n }, 50);\n } else if (this.canPan() === false) {\n // reset pan status\n this.panHandleSince = null;\n }\n },\n defaultCrop() {\n return this.defaultCropForAngle(this.props.angle);\n },\n defaultCropForAngle(angle) {\n const { raw_dimensions: rawDimensions } = this.props;\n\n if (!rawDimensions) {\n return [0, 0, 1, 1];\n }\n\n const { size_area, size, constraints = {} } = this.props;\n const area = ( size_area || size.area );\n let { w, h } = rawDimensions;\n\n const ratio = constraints.prism\n ? constraints.ratio // already rotated\n : getDefaultCropRatio(area[X] / area[Y], w, h);\n\n if (angle % ( 2 * RIGHT_ANGLE ) === RIGHT_ANGLE) {\n [h, w] = [rawDimensions.w, rawDimensions.h];\n }\n\n return getFixedCrop(ratio, w, h);\n },\n getCrop() {\n if (this.state.forceCrop) {\n return this.state.forceCrop;\n }\n\n if (typeof this.props.crop === 'string') {\n return this.props.crop.split(/,/).map(parseInt);\n }\n\n if (Array.isArray(this.props.crop) && this.props.crop.length == 4) {\n return this.props.crop;\n }\n\n return this.defaultCrop();\n },\n getZoom() {\n const defaultCrop = this.defaultCrop();\n const crop = this.getCrop();\n const zoom = defaultCrop[WIDTH] / crop[WIDTH]; // TODO - is this broken ??\n\n if (isNaN(zoom)) return IU.DEF_ZOOM;\n if (zoom > IU.MAX_ZOOM) return IU.MAX_ZOOM;\n if (zoom < IU.MIN_ZOOM) return IU.MIN_ZOOM;\n\n return zoom;\n },\n getScale() {\n if (this.img && this.img.complete && this.img.width) {\n return this.img.width / this.props.raw_dimensions.w;\n }\n\n return this.fullResoluton()\n ? this.props.large_dimensions.w / this.props.raw_dimensions.w\n : this.props.thumb_dimensions.w / this.props.raw_dimensions.w;\n },\n canPan() {\n if (!this.img) return null;\n const crop = this.getCrop();\n const area = this.props.raw_dimensions.w * this.props.raw_dimensions.h;\n const crop_area = crop[WIDTH] * crop[HEIGHT];\n const percentage = (crop_area / area) * 100;\n return percentage < 98 || percentage > 102;\n },\n\n handlePan(dx, dy) {\n const crop = this.getCrop();\n const scale = this.getScale();\n const { raw_dimensions, angle } = this.props;\n let w;\n let h;\n\n w = raw_dimensions.w;\n h = raw_dimensions.h;\n\n if (angle % (2 * RIGHT_ANGLE) === RIGHT_ANGLE) {\n h = raw_dimensions.w;\n w = raw_dimensions.h;\n }\n\n const crop2 = [\n crop[X] + dx / scale,\n crop[Y] + dy / scale,\n crop[WIDTH],\n crop[HEIGHT],\n ];\n if (crop2[X] + crop2[WIDTH] > w) {\n crop2[X] = w - crop2[WIDTH];\n }\n if (crop2[Y] + crop[HEIGHT] > h) {\n crop2[Y] = h - crop2[HEIGHT];\n }\n if (crop2[X] < 0) {\n crop2[X] = 0;\n }\n if (crop2[Y] < 0) {\n crop2[Y] = 0;\n }\n\n this.setState({ forceCrop: crop2, lastCrop: crop2 });\n },\n componentWillUnmount() {\n clearTimeout(this.tID);\n },\n });\n\n // -----------------------------------------------------------\n // PandaTweaker\n // -----------------------------------------------------------\n\n const PandaTweaker = createReactClass({\n getInitialState() {\n return {};\n },\n init() {\n // no-op\n },\n render() {\n const props = {};\n for (const k in this.state) props[k] = this.state[k];\n props.cropRatio = this.props.constraints.crop_ratio;\n const styles = {\n width: 'auto',\n maxHeight: this.props.height,\n };\n\n return (\n