GetmeUK / ContentTools

A JS library for building WYSIWYG editors for HTML content.
http://getcontenttools.com
MIT License
3.95k stars 395 forks source link

How to crop images ? #354

Closed mohamedveron closed 7 years ago

mohamedveron commented 7 years ago

Hello, First of all thank you for this amazing library.

I can't crop images in dialog i followed the http://getcontenttools.com/tutorials/handling-image-uploads tutorial and the following link contain the image upload function ----> http://ideone.com/br0wlV

i can't update the crop Region and crop buttons didn't work with me. Thanks in advance.

anthonyjb commented 7 years ago

Hi @mohamedveron,

Thanks for the kind words. So if you can't drag the crop region around that sounds like a JS issue, could you let me know what browser/version you are using and also can you check to see if there's any error output in the console please?

Ant

mohamedveron commented 7 years ago

I use the latest version of Firefox and chrome and there is no errors in inspector .

anthonyjb commented 7 years ago

@mohamedveron are you able to move the crop pins on the demo page here: http://getcontenttools.com/demo

I'm using the latest stable release of chromium currently and I can click on the blue pins and drag them when I select the crop button on the demo page.

mohamedveron commented 7 years ago

it is working in both browsers

anthonyjb commented 7 years ago

Can you post your code that's not working gist or JS fiddle, or send me a zip of the code so I can see if I can spot what's going wrong?

mohamedveron commented 7 years ago

http://ideone.com/br0wlV

mohamedveron commented 7 years ago

And the following code which i use to crop image .. // Check if a crop region has been defined by the user if (dialog.cropRegion()) { alert(dialog.cropRegion()); formData.append('crop', dialog.cropRegion()); }

anthonyjb commented 7 years ago

OK that's just the uploader code, the image uploader code you write shouldn't impact on whether crop pins can be dragged or the crop tool can be selected.

Can I just confirm is the issue that you can't select the crop tool and change the crop region using the pins in the browser, and your questions doesn't refer to a server side issue?

mohamedveron commented 7 years ago

Yes, there is no server side issue, i can insert image in server and ajax return images path and size successfully .

anthonyjb commented 7 years ago

OK well I'm a little stumped then since the uploader code shouldn't impact on the crop region code, the uploader code only queries for the final crop region size, it doesn't implement the code to activate or resize the crop region, and like you say the demo page works correctly for you. Any chance of a live link so I can see the issue in action you can email to me if you prefer (ant@getme.co.uk)?

mohamedveron commented 7 years ago

I will but first i need to ask if the following code is enough to crop images..

if (dialog.cropRegion()) { formData.append('crop', dialog.cropRegion()); }

mohamedveron commented 7 years ago

And following screen shot may help .. shot

anthonyjb commented 7 years ago

So that's enough to collect the crop region data, however it might help you if I provide a working handler example, I only have a CoffeeScript example but it should show you an approach we've used for a live application:

class ImageUploader
    # An image uploader for the in-page site editor.

    constructor: (dialog) ->
        # Initialize the dialog to support image uploads

        # The image dialog the uploader is providing functionality for
        @_dialog = dialog

        # The image being edited (when the dialog is populated)
        @_image = null

        # The operations applied to the image
        @_imageOps = []

        # The XHR used to upload an image (when the dialog is uploading)
        @_xhr = null

        # Listen to key events from the dialog and assign handlers to each
        @_dialog.addEventListener 'imageuploader.cancelupload', () =>
            @_onCancelUpload()

        @_dialog.addEventListener 'imageuploader.clear', () =>
            @_onClear()

        @_dialog.addEventListener 'imageuploader.fileready', (ev) =>
            @_onFileReady(ev.detail().file)

        @_dialog.addEventListener 'imageuploader.rotateccw', () =>
            @_onRotateCCW()

        @_dialog.addEventListener 'imageuploader.rotatecw', () =>
            @_onRotateCW()

        @_dialog.addEventListener 'imageuploader.save', () =>
            @_onSave()

    # Event handlers

    _onCancelUpload: () ->
        # Handle an upload being cancelled

        # Stop the upload
        if @_xhr
            @_xhr.upload.removeEventListener 'progress', @_xhrProgress
            @_xhr.removeEventListener 'readystatechange', @_xhrComplete
            @_xhr.abort()

        # Set the dialog to empty
        @_dialog.state('empty')

    _onClear: () ->
        # Handle the current image being cleared
        @_dialog.clear()
        @_image = null
        @_imageOps = null

    _onFileReady: (file) ->
        # Handle a file being selected by the user

        # Set the dialog state to uploading
        @_dialog.progress(0)
        @_dialog.state('uploading')

        # Build the form data to post to the server
        formData = new FormData()
        formData.append('__file__', file)

        # Build a request to send the file
        @_xhr = new XMLHttpRequest();

        # Handle progress
        @_xhrProgress = (ev) =>
            @_dialog.progress((ev.loaded / ev.total) * 100)

        @_xhr.upload.addEventListener('progress', @_xhrProgress)

        # Handle completion
        @_xhrComplete = (ev) =>
            readyState = ev.target.readyState
            text = ev.target.responseText
            status = ev.target.status

            # Look for done response
            if readyState != 4
                return

            # Clear the XHR reference
            @_xhr = null

            # Handle the result of the upload
            if parseInt(status) is 200
                response = JSON.parse(text)

                # Handle failed response
                if response.status is 'fail'
                    new ContentTools.FlashUI('no')
                    return

                # Handle successful response
                @_image = response.payload.image
                @_dialog.populate(@_image.url, @_image.size)

            else
                # Handle error response
                new ContentTools.FlashUI('no')

        @_xhr.addEventListener('readystatechange', @_xhrComplete)

        # Send the file
        @_xhr.open('POST', '/cms/image-upload', true)
        @_xhr.send(formData)

    _onRotateCCW: () ->
        # Handle a request by the user to rotate the image counter-clockwise
        @_imageOps.push(['Rotate', {'direction': 'ccw'}])
        @_applyOps (image) =>
            @_image = image
            @_dialog.populate(@_image.url, @_image.size)

    _onRotateCW: () ->
        # Handle a request by the user to rotate the image clockwise
        @_imageOps.push(['Rotate', {'direction': 'cw'}])
        @_applyOps (image) =>
            @_image = image
            @_dialog.populate(@_image.url, @_image.size)

    _onSave: () ->
        # Handle the user saving the image
        cropRegion = @_dialog.cropRegion()

        # Check if the crop is the full size of the image, if so no need to
        # apply a crop.
        if JSON.stringify(cropRegion) is JSON.stringify([0, 0, 1, 1])
            @_dialog.save(@_image.url, @_image.size, @_buildImageAttrs())
            return

        # Apply a crop to the image before inserting it into the page.
        crop = [
            [cropRegion[1], cropRegion[0]],
            [cropRegion[3], cropRegion[0]],
            [cropRegion[3], cropRegion[2]],
            [cropRegion[1], cropRegion[2]]
            ]

        @_imageOps.push(['Crop', {'crop_marks': crop}])
        @_applyOps (image) =>
            @_image = image
            @_dialog.save(@_image.url, @_image.size, @_buildImageAttrs())

    # Private methods

    _applyOps: (callback) ->
        # Apply the current list of image ops to the image, any callback will be
        # called on successful completion with the new image information.

        # Send the save data to the CMS
        payload = new FormData();
        payload.append('uid', @_image.uid)
        payload.append('ops', JSON.stringify(@_imageOps))

        onStateChange = (ev) ->
            # Check if the request is finished
            unless ev.target.readyState == 4
                return

            # Finished saving the content

            if ev.target.status is 200
                text = ev.target.responseText
                response = JSON.parse(text)

                # Call the callback
                callback(response.payload.image)

                # Save was successful, notify the user with a flash
                new ContentTools.FlashUI('ok');

            else
                # Save failed, notify the user with a flash
                new ContentTools.FlashUI('no');

        xhr = new XMLHttpRequest()
        xhr.addEventListener('readystatechange', onStateChange)
        xhr.open('POST', '/cms/image-upload')
        xhr.send(payload)

    _buildImageAttrs: () ->
        # Build a list of attributes to save the image with
        return {
            'alt': @_image.alt,
            'data-uid': @_image.uid,
            'data-ce-max-width': @_image.max_size
            }

    # Class methods

    @createImageUploader: (dialog) ->
        return new ImageUploader(dialog)

window.ImageUploader = ImageUploader
mohamedveron commented 7 years ago

But it still give me array of Nan when i make an alert for crop region..

if (dialog.cropRegion()) {

        alert(dialog.cropRegion());
        formData.append('crop', dialog.cropRegion());
    } 
anthonyjb commented 7 years ago

If you don't attempt to crop the image does the method return [0, 0, 1, 1]?

anthonyjb commented 7 years ago

Just to confirm I've tested (in Chrome) calling cropRegion using the sandox demo with the latest CT release (1.3.1 - just to check you're using the latest release I assume?). The output is the expected array of 4 floats (between 0-1).

Without access to a demo or your code it's really difficult to investigate the issue as I can't reproduce it myself.

mohamedveron commented 7 years ago

yes it returns [0, 0, 1, 1]

mohamedveron commented 7 years ago

But i didn't use sandbox.js file i just use content-tools.min.js and followed the Getting start and image upload tutorials .

mohamedveron commented 7 years ago

Is sandbox.js file contain crop methods which is not in content-tool.js ?

anthonyjb commented 7 years ago

OK so then it would appear the issue is specific to the CropMarksUI component and it not being able to query the position of the crop pins/handles in order to return a valid crop region. Here's the code that builds the crop region: https://github.com/GetmeUK/ContentTools/blob/master/src/scripts/ui/dialogs/image.coffee#L402

I suspect what's happening here is that the values in _bounds are not valid numbers, and they are in turn set when you set the width and height of the image, so when you call populate you should pass in an image size which is an array with 2 values [width, height]. I suspect your not passing it an array with 2 values, can you check that when you call populate the value of image.size is actually an array containing the width and height?

anthonyjb commented 7 years ago

No the sandbox JS uses the CT library in the same directory.

mohamedveron commented 7 years ago

It worked with me when sandbox.js imported in my js file. Thanks and sorry for wasting your time.

mohamedveron commented 7 years ago

And i will check what is the wrong by myself :)

anthonyjb commented 7 years ago

No problem - it's often tricky to find these problems :) I hope this points you in the right direction and you can resolve the issue. If I can be any further help though I'm happy to help where I can :)

baldram commented 7 years ago

I had similar issue. The "crop feature" didn't work on client side. Now fixed!

The problem was with server-side response while 'imageuploader.fileready' event called an /upload-image server action. Response was invalid. Instead of expected eg.: {size: [320, 200], url: "/uploads/something.png"}, by mistake the size was returned with a file size ;-) I think also for others this a bit misleading name size instead of "dimensions" could lead to similar issue ;-)

When I fixed the "size" response, the crop feature worked perfectly!