Browse Source
[FIX] web_widget_darkroom: Modal, fixes, cleanup
[FIX] web_widget_darkroom: Modal, fixes, cleanup
* Fix bugs involving the crop and pan functionality by modifying crop and zoom plugins and Darkroom widget * Add Darkroom modal to normal image widget, using darkroom.modal wizard model to provide backend support for modal view * Remove res.users view changes introduced for demo purposes (not needed due to modal functionality) * Clean up existing code, removing many unnecessary DarkroomJS filespull/407/head
Oleg Bulkin
8 years ago
committed by
Dave Lasley
57 changed files with 2158 additions and 3507 deletions
-
82web_widget_darkroom/README.rst
-
4web_widget_darkroom/__init__.py
-
34web_widget_darkroom/__openerp__.py
-
24web_widget_darkroom/demo/res_users.xml
-
BINweb_widget_darkroom/static/description/modal_screenshot_1.png
-
BINweb_widget_darkroom/static/description/modal_screenshot_2.png
-
9web_widget_darkroom/static/lib/darkroomjs/.editorconfig
-
5web_widget_darkroom/static/lib/darkroomjs/.gitignore
-
23web_widget_darkroom/static/lib/darkroomjs/CHANGELOG.md
-
18web_widget_darkroom/static/lib/darkroomjs/LICENSE
-
88web_widget_darkroom/static/lib/darkroomjs/README.md
-
31web_widget_darkroom/static/lib/darkroomjs/bower.json
-
356web_widget_darkroom/static/lib/darkroomjs/core/darkroom.js
-
47web_widget_darkroom/static/lib/darkroomjs/core/plugin.js
-
43web_widget_darkroom/static/lib/darkroomjs/core/transformation.js
-
36web_widget_darkroom/static/lib/darkroomjs/core/utils.js
-
21web_widget_darkroom/static/lib/darkroomjs/gh-pages.sh
-
112web_widget_darkroom/static/lib/darkroomjs/gulpfile.js
-
12web_widget_darkroom/static/lib/darkroomjs/lib/css/_layout.scss
-
99web_widget_darkroom/static/lib/darkroomjs/lib/css/_toolbar.scss
-
2web_widget_darkroom/static/lib/darkroomjs/lib/css/darkroom.scss
-
4web_widget_darkroom/static/lib/darkroomjs/lib/icons/close.svg
-
4web_widget_darkroom/static/lib/darkroomjs/lib/icons/crop.svg
-
4web_widget_darkroom/static/lib/darkroomjs/lib/icons/done.svg
-
4web_widget_darkroom/static/lib/darkroomjs/lib/icons/redo.svg
-
4web_widget_darkroom/static/lib/darkroomjs/lib/icons/rotate-left.svg
-
4web_widget_darkroom/static/lib/darkroomjs/lib/icons/rotate-right.svg
-
4web_widget_darkroom/static/lib/darkroomjs/lib/icons/save.svg
-
4web_widget_darkroom/static/lib/darkroomjs/lib/icons/undo.svg
-
14web_widget_darkroom/static/lib/darkroomjs/lib/js/core/bootstrap.js
-
354web_widget_darkroom/static/lib/darkroomjs/lib/js/core/darkroom.js
-
43web_widget_darkroom/static/lib/darkroomjs/lib/js/core/plugin.js
-
38web_widget_darkroom/static/lib/darkroomjs/lib/js/core/transformation.js
-
91web_widget_darkroom/static/lib/darkroomjs/lib/js/core/ui.js
-
31web_widget_darkroom/static/lib/darkroomjs/lib/js/core/utils.js
-
669web_widget_darkroom/static/lib/darkroomjs/lib/js/plugins/darkroom.crop.js
-
66web_widget_darkroom/static/lib/darkroomjs/lib/js/plugins/darkroom.history.js
-
57web_widget_darkroom/static/lib/darkroomjs/lib/js/plugins/darkroom.rotate.js
-
23web_widget_darkroom/static/lib/darkroomjs/lib/js/plugins/darkroom.save.js
-
39web_widget_darkroom/static/lib/darkroomjs/package.json
-
11web_widget_darkroom/static/src/css/darkroom.css
-
20web_widget_darkroom/static/src/js/darkroom_plugins.js
-
200web_widget_darkroom/static/src/js/plugins/darkroom.crop.js
-
24web_widget_darkroom/static/src/js/plugins/darkroom.history.js
-
28web_widget_darkroom/static/src/js/plugins/darkroom.rotate.js
-
136web_widget_darkroom/static/src/js/plugins/darkroom.zoom.js
-
180web_widget_darkroom/static/src/js/widget_darkroom.js
-
246web_widget_darkroom/static/src/js/widget_darkroom.js.orig
-
64web_widget_darkroom/static/src/js/widget_darkroom_modal.js
-
11web_widget_darkroom/static/src/less/darkroom.less
-
19web_widget_darkroom/static/src/xml/field_templates.xml
-
5web_widget_darkroom/tests/__init__.py
-
203web_widget_darkroom/tests/test_darkroom_modal.py
-
17web_widget_darkroom/views/assets.xml
-
5web_widget_darkroom/wizards/__init__.py
-
82web_widget_darkroom/wizards/darkroom_modal.py
-
27web_widget_darkroom/wizards/darkroom_modal.xml
@ -1,3 +1,5 @@ |
|||||
# -*- coding: utf-8 -*- |
# -*- coding: utf-8 -*- |
||||
# Copyright 2016 LasLabs Inc. |
|
||||
|
# Copyright 2016-2017 LasLabs Inc. |
||||
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). |
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). |
||||
|
|
||||
|
from . import wizards |
@ -1,28 +1,26 @@ |
|||||
# -*- coding: utf-8 -*- |
# -*- coding: utf-8 -*- |
||||
# Copyright 2016 LasLabs Inc. |
|
||||
|
# Copyright 2016-2017 LasLabs Inc. |
||||
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). |
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). |
||||
|
|
||||
{ |
{ |
||||
"name": "Web Darkroom Image Widget", |
|
||||
"summary": "Widget provides a dynamic, editable canvas for use on any" |
|
||||
" One2many image field in backend form views.", |
|
||||
"version": "9.0.1.0.1", |
|
||||
"category": "Web", |
|
||||
"website": "https://laslabs.com/", |
|
||||
"author": "LasLabs, Odoo Community Association (OCA)", |
|
||||
"license": "LGPL-3", |
|
||||
"application": False, |
|
||||
"installable": True, |
|
||||
"depends": [ |
|
||||
"web", |
|
||||
|
'name': 'Web DarkroomJS Image Editing', |
||||
|
'summary': 'Provides web widget for image editing and adds it to standard' |
||||
|
' image widget as modal', |
||||
|
'version': '9.0.1.0.1', |
||||
|
'category': 'Web', |
||||
|
'website': 'https://laslabs.com/', |
||||
|
'author': 'LasLabs, Odoo Community Association (OCA)', |
||||
|
'license': 'LGPL-3', |
||||
|
'application': False, |
||||
|
'installable': True, |
||||
|
'depends': [ |
||||
|
'web', |
||||
], |
], |
||||
"data": [ |
|
||||
|
'data': [ |
||||
'views/assets.xml', |
'views/assets.xml', |
||||
|
'wizards/darkroom_modal.xml', |
||||
], |
], |
||||
'qweb': [ |
'qweb': [ |
||||
"static/src/xml/field_templates.xml", |
|
||||
|
'static/src/xml/field_templates.xml', |
||||
], |
], |
||||
'demo': [ |
|
||||
'demo/res_users.xml', |
|
||||
] |
|
||||
} |
} |
@ -1,24 +0,0 @@ |
|||||
<?xml version="1.0" encoding="utf-8"?> |
|
||||
|
|
||||
<!-- |
|
||||
Copyright 2016 LasLabs Inc. |
|
||||
License LGPL-3 or later (http://www.gnu.org/licenses/lgpl.html). |
|
||||
--> |
|
||||
|
|
||||
<openerp> |
|
||||
<data> |
|
||||
<record id="view_users_form" model="ir.ui.view"> |
|
||||
<field name="name">res.users.form.darkroom</field> |
|
||||
<field name="model">res.users</field> |
|
||||
<field name="inherit_id" ref="base.view_users_form" /> |
|
||||
<field name="arch" type="xml"> |
|
||||
<xpath expr="//notebook" position="inside"> |
|
||||
<page string="Darkroom"> |
|
||||
<field name="image" widget="darkroom" /> |
|
||||
</page> |
|
||||
</xpath> |
|
||||
</field> |
|
||||
</record> |
|
||||
</data> |
|
||||
</openerp> |
|
||||
|
|
After Width: 250 | Height: 250 | Size: 24 KiB |
After Width: 950 | Height: 550 | Size: 56 KiB |
@ -1,9 +0,0 @@ |
|||||
root = true |
|
||||
|
|
||||
[*] |
|
||||
charset = utf-8 |
|
||||
end_of_line = LF |
|
||||
indent_style = space |
|
||||
trim_trailing_whitespace = true |
|
||||
insert_final_newline = true |
|
||||
indent_size = 2 |
|
@ -1,5 +0,0 @@ |
|||||
/.sass-cache/ |
|
||||
/bower_components/ |
|
||||
/build/ |
|
||||
/node_modules/ |
|
||||
/docs/ |
|
@ -1,23 +0,0 @@ |
|||||
# Change Log |
|
||||
|
|
||||
All notable changes to this project will be documented in this file. |
|
||||
|
|
||||
## Unreleased |
|
||||
|
|
||||
- Add type "button" to avoid html5 submit validation (#24) |
|
||||
|
|
||||
## 2.0.0 (2015-08-01) |
|
||||
|
|
||||
- Use of **Gulp** for the build process |
|
||||
- Replace the webfont by **SVG symbols** (which are direclty included in the compiled javascript) |
|
||||
- Ability to change **canvas ratio** |
|
||||
- Original image is kept and changes are done on a clone |
|
||||
|
|
||||
## 1.0.x (2014) |
|
||||
|
|
||||
Initial release. |
|
||||
|
|
||||
- Create canvas with FabricJS from an image element |
|
||||
- Plugins: Crop, History, Rotate, Save |
|
||||
- Build process via Grunt |
|
||||
- Build webfont from SVG files to display the icons |
|
@ -1,18 +0,0 @@ |
|||||
Copyright (c) 2013 Matthieu Moquet |
|
||||
|
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of |
|
||||
this software and associated documentation files (the "Software"), to deal in |
|
||||
the Software without restriction, including without limitation the rights to |
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of |
|
||||
the Software, and to permit persons to whom the Software is furnished to do so, |
|
||||
subject to the following conditions: |
|
||||
|
|
||||
The above copyright notice and this permission notice shall be included in all |
|
||||
copies or substantial portions of the Software. |
|
||||
|
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS |
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR |
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER |
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN |
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
|
@ -1,88 +0,0 @@ |
|||||
# DarkroomJS |
|
||||
|
|
||||
DarkroomJS is a JavaScript library which provides basic image editing tools in |
|
||||
your browser, such as **rotation** or **cropping**. It is based on the awesome |
|
||||
[FabricJS](http://fabricjs.com/) library to handle images in HTML5 canvas. |
|
||||
|
|
||||
## Demo |
|
||||
|
|
||||
Try the online demo at [http://mattketmo.github.io/darkroomjs](http://mattketmo.github.io/darkroomjs/) |
|
||||
|
|
||||
The library is currently *work in progress*. |
|
||||
I know there is some bug especially when resizing the crop zone. |
|
||||
Feel free to fork the project or report issues on GitHub. |
|
||||
All ideas are also welcome. |
|
||||
|
|
||||
## Building |
|
||||
|
|
||||
- Install [Node](http://nodejs.org/) & `npm`. |
|
||||
- Run `npm install` to build dependencies. |
|
||||
- Run `npm start` to build the assets and start the demo webserver. |
|
||||
|
|
||||
## Usage |
|
||||
|
|
||||
Simply instanciate a new Darkroom object with a reference to the image element: |
|
||||
|
|
||||
```html |
|
||||
<img src="some-image.jpg" id="target"> |
|
||||
<script> |
|
||||
new Darkroom('#target'); |
|
||||
</script> |
|
||||
``` |
|
||||
|
|
||||
You can also pass some options: |
|
||||
|
|
||||
```javascript |
|
||||
new Darkroom('#target', { |
|
||||
// Canvas initialization size |
|
||||
minWidth: 100, |
|
||||
minHeight: 100, |
|
||||
maxWidth: 500, |
|
||||
maxHeight: 500, |
|
||||
|
|
||||
// Plugins options |
|
||||
plugins: { |
|
||||
crop: { |
|
||||
minHeight: 50, |
|
||||
minWidth: 50, |
|
||||
ratio: 1 |
|
||||
}, |
|
||||
save: false // disable plugin |
|
||||
}, |
|
||||
|
|
||||
// Post initialization method |
|
||||
initialize: function() { |
|
||||
// Active crop selection |
|
||||
this.plugins['crop'].requireFocus(); |
|
||||
|
|
||||
// Add custom listener |
|
||||
this.addEventListener('core:transformation', function() { /* ... */ }); |
|
||||
} |
|
||||
}); |
|
||||
``` |
|
||||
|
|
||||
## Why? |
|
||||
|
|
||||
It's easy to get a javascript script to crop an image in a web page. |
|
||||
But if your want more features like rotation or brightness adjustment, then you |
|
||||
will have to do it yourself. No more jQuery plugins here. |
|
||||
It only uses the power of HTML5 canvas to make what ever you want with your image. |
|
||||
|
|
||||
## The concept |
|
||||
|
|
||||
The library is designed to be easily extendable. The core script only transforms |
|
||||
the target image to a canvas with a FabricJS instance, and creates an empty toolbar. |
|
||||
All the features are then implemented in separate plugins. |
|
||||
|
|
||||
Each plugin is responsible for creating its own functionality. |
|
||||
Buttons can easily be added to the toolbar and binded with those features. |
|
||||
|
|
||||
## Contributing |
|
||||
|
|
||||
Run `npm develop` to build and watch the files while developing. |
|
||||
|
|
||||
## License |
|
||||
|
|
||||
DarkroomJS is released under the MIT License. See the [bundled LICENSE file](LICENSE) |
|
||||
for details. |
|
||||
|
|
@ -1,31 +0,0 @@ |
|||||
{ |
|
||||
"name": "darkroom", |
|
||||
"description": "Extensible image editing tool via HTML canvas", |
|
||||
"version": "2.0.0", |
|
||||
"homepage": "http://mattketmo.github.io/darkroomjs/", |
|
||||
"authors": [ |
|
||||
"Matthieu Moquet <matthieu@moquet.net>" |
|
||||
], |
|
||||
"license": "MIT", |
|
||||
"dependencies": { |
|
||||
"fabric": "~1.4.*" |
|
||||
}, |
|
||||
"main": [ |
|
||||
"build/darkroom.css", |
|
||||
"build/darkroom.js" |
|
||||
], |
|
||||
"moduleType": [ |
|
||||
"globals" |
|
||||
], |
|
||||
"keywords": [ |
|
||||
"image", |
|
||||
"canvas", |
|
||||
"crop" |
|
||||
], |
|
||||
"ignore": [ |
|
||||
"node_modules", |
|
||||
"bower_components", |
|
||||
"test", |
|
||||
"tests" |
|
||||
] |
|
||||
} |
|
@ -0,0 +1,356 @@ |
|||||
|
/** |
||||
|
* Copyright 2013 Matthieu Moquet |
||||
|
* Copyright 2016-2017 LasLabs Inc. |
||||
|
* License MIT (https://opensource.org/licenses/MIT)
|
||||
|
**/ |
||||
|
|
||||
|
(function() { |
||||
|
'use strict'; |
||||
|
|
||||
|
window.Darkroom = Darkroom; |
||||
|
|
||||
|
// Core object of DarkroomJS.
|
||||
|
// Basically it's a single object, instanciable via an element
|
||||
|
// (it could be a CSS selector or a DOM element), some custom options,
|
||||
|
// and a list of plugin objects (or none to use default ones).
|
||||
|
function Darkroom(element, options, plugins) { |
||||
|
return this.constructor(element, options, plugins); |
||||
|
} |
||||
|
|
||||
|
// Create an empty list of plugin objects, which will be filled by
|
||||
|
// other plugin scripts. This is the default plugin list if none is
|
||||
|
// specified in Darkroom's constructor.
|
||||
|
Darkroom.plugins = []; |
||||
|
|
||||
|
Darkroom.prototype = { |
||||
|
// Reference to the main container element
|
||||
|
containerElement: null, |
||||
|
|
||||
|
// Reference to the Fabric canvas object
|
||||
|
canvas: null, |
||||
|
|
||||
|
// Reference to the Fabric image object
|
||||
|
image: null, |
||||
|
|
||||
|
// Reference to the Fabric source canvas object
|
||||
|
sourceCanvas: null, |
||||
|
|
||||
|
// Reference to the Fabric source image object
|
||||
|
sourceImage: null, |
||||
|
|
||||
|
// Track of the original image element
|
||||
|
originalImageElement: null, |
||||
|
|
||||
|
// Stack of transformations to apply to the image source
|
||||
|
transformations: [], |
||||
|
|
||||
|
// Default options
|
||||
|
defaults: { |
||||
|
// Canvas properties (dimension, ratio, color)
|
||||
|
minWidth: null, |
||||
|
minHeight: null, |
||||
|
maxWidth: null, |
||||
|
maxHeight: null, |
||||
|
ratio: null, |
||||
|
backgroundColor: '#fff', |
||||
|
|
||||
|
// Plugins options
|
||||
|
plugins: {}, |
||||
|
|
||||
|
// Post-initialisation callback
|
||||
|
initialize: function() { /* noop */ } |
||||
|
}, |
||||
|
|
||||
|
// List of the instancied plugins
|
||||
|
plugins: {}, |
||||
|
|
||||
|
// This options are a merge between `defaults` and the options passed
|
||||
|
// through the constructor
|
||||
|
options: {}, |
||||
|
|
||||
|
constructor: function(element, options) { |
||||
|
this.options = Darkroom.Utils.extend(options, this.defaults); |
||||
|
|
||||
|
if (typeof element === 'string') |
||||
|
element = document.querySelector(element); |
||||
|
if (null === element) |
||||
|
return; |
||||
|
|
||||
|
var image = new Image(); |
||||
|
var parent = element.parentElement; |
||||
|
image.onload = function() { |
||||
|
// Initialize the DOM/Fabric elements
|
||||
|
this._initializeDOM(element, parent); |
||||
|
this._initializeImage(); |
||||
|
|
||||
|
// Then initialize the plugins
|
||||
|
this._initializePlugins(Darkroom.plugins); |
||||
|
|
||||
|
// Public method to adjust image according to the canvas
|
||||
|
this.refresh(function() { |
||||
|
// Execute a custom callback after initialization
|
||||
|
this.options.initialize.bind(this).call(); |
||||
|
}.bind(this)); |
||||
|
}.bind(this); |
||||
|
|
||||
|
image.src = element.src; |
||||
|
}, |
||||
|
|
||||
|
selfDestroy: function() { |
||||
|
var container = this.containerElement; |
||||
|
var image = new Image(); |
||||
|
image.onload = function() { |
||||
|
container.parentNode.replaceChild(image, container); |
||||
|
}; |
||||
|
|
||||
|
image.src = this.sourceImage.toDataURL(); |
||||
|
}, |
||||
|
|
||||
|
// Add ability to attach event listener on the core object.
|
||||
|
// It uses the canvas element to process events.
|
||||
|
addEventListener: function(eventName, callback) { |
||||
|
var el = this.canvas.getElement(); |
||||
|
if (el.addEventListener) { |
||||
|
el.addEventListener(eventName, callback); |
||||
|
} else if (el.attachEvent) { |
||||
|
el.attachEvent('on' + eventName, callback); |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
dispatchEvent: function(eventName) { |
||||
|
// Use the old way of creating event to be IE compatible
|
||||
|
// See https://developer.mozilla.org/en-US/docs/Web/Guide/Events/Creating_and_triggering_events
|
||||
|
var event = document.createEvent('Event'); |
||||
|
event.initEvent(eventName, true, true); |
||||
|
|
||||
|
this.canvas.getElement().dispatchEvent(event); |
||||
|
}, |
||||
|
|
||||
|
// Adjust image & canvas dimension according to min/max width/height
|
||||
|
// and ratio specified in the options.
|
||||
|
// This method should be called after each image transformation.
|
||||
|
refresh: function(next) { |
||||
|
var clone = new Image(); |
||||
|
clone.onload = function() { |
||||
|
this._replaceCurrentImage(new fabric.Image(clone)); |
||||
|
if (next) next(); |
||||
|
}.bind(this); |
||||
|
clone.src = this.sourceImage.toDataURL(); |
||||
|
}, |
||||
|
|
||||
|
_replaceCurrentImage: function(newImage) { |
||||
|
if (this.image) { |
||||
|
this.image.remove(); |
||||
|
} |
||||
|
|
||||
|
this.image = newImage; |
||||
|
this.image.selectable = false; |
||||
|
|
||||
|
// Adjust width or height according to specified ratio
|
||||
|
var viewport = Darkroom.Utils.computeImageViewPort(this.image); |
||||
|
var canvasWidth = viewport.width; |
||||
|
var canvasHeight = viewport.height; |
||||
|
|
||||
|
if (null !== this.options.ratio) { |
||||
|
var canvasRatio = +this.options.ratio; |
||||
|
var currentRatio = canvasWidth / canvasHeight; |
||||
|
|
||||
|
if (currentRatio > canvasRatio) { |
||||
|
canvasHeight = canvasWidth / canvasRatio; |
||||
|
} else if (currentRatio < canvasRatio) { |
||||
|
canvasWidth = canvasHeight * canvasRatio; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// Then scale the image to fit into dimension limits
|
||||
|
var scaleMin = 1; |
||||
|
var scaleMax = 1; |
||||
|
var scaleX = 1; |
||||
|
var scaleY = 1; |
||||
|
|
||||
|
if (null !== this.options.maxWidth && this.options.maxWidth < canvasWidth) { |
||||
|
scaleX = this.options.maxWidth / canvasWidth; |
||||
|
} |
||||
|
if (null !== this.options.maxHeight && this.options.maxHeight < canvasHeight) { |
||||
|
scaleY = this.options.maxHeight / canvasHeight; |
||||
|
} |
||||
|
scaleMin = Math.min(scaleX, scaleY); |
||||
|
|
||||
|
scaleX = 1; |
||||
|
scaleY = 1; |
||||
|
if (null !== this.options.minWidth && this.options.minWidth > canvasWidth) { |
||||
|
scaleX = this.options.minWidth / canvasWidth; |
||||
|
} |
||||
|
if (null !== this.options.minHeight && this.options.minHeight > canvasHeight) { |
||||
|
scaleY = this.options.minHeight / canvasHeight; |
||||
|
} |
||||
|
scaleMax = Math.max(scaleX, scaleY); |
||||
|
|
||||
|
var scale = scaleMax * scaleMin; // one should be equals to 1
|
||||
|
|
||||
|
canvasWidth *= scale; |
||||
|
canvasHeight *= scale; |
||||
|
|
||||
|
// Finally place the image in the center of the canvas
|
||||
|
this.image.setScaleX(1 * scale); |
||||
|
this.image.setScaleY(1 * scale); |
||||
|
this.canvas.add(this.image); |
||||
|
this.canvas.setWidth(canvasWidth); |
||||
|
this.canvas.setHeight(canvasHeight); |
||||
|
this.canvas.centerObject(this.image); |
||||
|
this.image.setCoords(); |
||||
|
}, |
||||
|
|
||||
|
// Apply the transformation on the current image and save it in the
|
||||
|
// transformations stack (in order to reconstitute the previous states
|
||||
|
// of the image).
|
||||
|
applyTransformation: function(transformation) { |
||||
|
this.transformations.push(transformation); |
||||
|
|
||||
|
transformation.applyTransformation( |
||||
|
this.sourceCanvas, |
||||
|
this.sourceImage, |
||||
|
this._postTransformation.bind(this) |
||||
|
); |
||||
|
}, |
||||
|
|
||||
|
_postTransformation: function(newImage) { |
||||
|
if (newImage) |
||||
|
this.sourceImage = newImage; |
||||
|
|
||||
|
this.refresh(function() { |
||||
|
this.dispatchEvent('core:transformation'); |
||||
|
}.bind(this)); |
||||
|
}, |
||||
|
|
||||
|
// Initialize image from original element plus re-apply every
|
||||
|
// transformations.
|
||||
|
reinitializeImage: function() { |
||||
|
this.sourceImage.remove(); |
||||
|
this._initializeImage(); |
||||
|
this._popTransformation(this.transformations.slice()); |
||||
|
}, |
||||
|
|
||||
|
_popTransformation: function(transformations) { |
||||
|
if (0 === transformations.length) { |
||||
|
this.dispatchEvent('core:reinitialized'); |
||||
|
this.refresh(); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
var transformation = transformations.shift(); |
||||
|
|
||||
|
var next = function(newImage) { |
||||
|
if (newImage) this.sourceImage = newImage; |
||||
|
this._popTransformation(transformations); |
||||
|
}; |
||||
|
|
||||
|
transformation.applyTransformation( |
||||
|
this.sourceCanvas, |
||||
|
this.sourceImage, |
||||
|
next.bind(this) |
||||
|
); |
||||
|
}, |
||||
|
|
||||
|
// Create the DOM elements and instanciate the Fabric canvas.
|
||||
|
// The image element is replaced by a new `div` element.
|
||||
|
// However the original image is re-injected in order to keep a trace of it.
|
||||
|
_initializeDOM: function(imageElement) { |
||||
|
// Container
|
||||
|
var mainContainerElement = document.createElement('div'); |
||||
|
mainContainerElement.className = 'darkroom-container'; |
||||
|
|
||||
|
// Toolbar
|
||||
|
var toolbarElement = document.createElement('div'); |
||||
|
toolbarElement.className = 'darkroom-toolbar'; |
||||
|
mainContainerElement.appendChild(toolbarElement); |
||||
|
|
||||
|
// Viewport canvas
|
||||
|
var canvasContainerElement = document.createElement('div'); |
||||
|
canvasContainerElement.className = 'darkroom-image-container'; |
||||
|
var canvasElement = document.createElement('canvas'); |
||||
|
canvasContainerElement.appendChild(canvasElement); |
||||
|
mainContainerElement.appendChild(canvasContainerElement); |
||||
|
|
||||
|
// Source canvas
|
||||
|
var sourceCanvasContainerElement = document.createElement('div'); |
||||
|
sourceCanvasContainerElement.className = 'darkroom-source-container'; |
||||
|
sourceCanvasContainerElement.style.display = 'none'; |
||||
|
var sourceCanvasElement = document.createElement('canvas'); |
||||
|
sourceCanvasContainerElement.appendChild(sourceCanvasElement); |
||||
|
mainContainerElement.appendChild(sourceCanvasContainerElement); |
||||
|
|
||||
|
// Original image
|
||||
|
imageElement.parentNode.replaceChild(mainContainerElement, imageElement); |
||||
|
imageElement.style.display = 'none'; |
||||
|
mainContainerElement.appendChild(imageElement); |
||||
|
|
||||
|
// Instanciate object from elements
|
||||
|
this.containerElement = mainContainerElement; |
||||
|
this.originalImageElement = imageElement; |
||||
|
|
||||
|
this.toolbar = new Darkroom.UI.Toolbar(toolbarElement); |
||||
|
|
||||
|
this.canvas = new fabric.Canvas(canvasElement, { |
||||
|
selection: false, |
||||
|
backgroundColor: this.options.backgroundColor, |
||||
|
}); |
||||
|
|
||||
|
this.sourceCanvas = new fabric.Canvas(sourceCanvasElement, { |
||||
|
selection: false, |
||||
|
backgroundColor: this.options.backgroundColor, |
||||
|
}); |
||||
|
}, |
||||
|
|
||||
|
// Instanciate the Fabric image object.
|
||||
|
// The image is created as a static element with no control,
|
||||
|
// then it is add in the Fabric canvas object.
|
||||
|
_initializeImage: function() { |
||||
|
this.sourceImage = new fabric.Image(this.originalImageElement, { |
||||
|
// Some options to make the image static
|
||||
|
selectable: false, |
||||
|
evented: false, |
||||
|
lockMovementX: true, |
||||
|
lockMovementY: true, |
||||
|
lockRotation: true, |
||||
|
lockScalingX: true, |
||||
|
lockScalingY: true, |
||||
|
lockUniScaling: true, |
||||
|
hasControls: false, |
||||
|
hasBorders: false, |
||||
|
}); |
||||
|
|
||||
|
this.sourceCanvas.add(this.sourceImage); |
||||
|
|
||||
|
// Adjust width or height according to specified ratio
|
||||
|
var viewport = Darkroom.Utils.computeImageViewPort(this.sourceImage); |
||||
|
var canvasWidth = viewport.width; |
||||
|
var canvasHeight = viewport.height; |
||||
|
|
||||
|
this.sourceCanvas.setWidth(canvasWidth); |
||||
|
this.sourceCanvas.setHeight(canvasHeight); |
||||
|
this.sourceCanvas.centerObject(this.sourceImage); |
||||
|
this.sourceImage.setCoords(); |
||||
|
}, |
||||
|
|
||||
|
// Initialize every plugins.
|
||||
|
// Note that plugins are instanciated in the same order than they
|
||||
|
// are declared in the parameter object.
|
||||
|
_initializePlugins: function(plugins) { |
||||
|
for (var name in plugins) { |
||||
|
var plugin = plugins[name]; |
||||
|
var options = this.options.plugins[name]; |
||||
|
|
||||
|
// Setting false into the plugin options will disable the plugin
|
||||
|
if (options === false) |
||||
|
continue; |
||||
|
|
||||
|
// Avoid any issues with _proto_
|
||||
|
if (!plugins.hasOwnProperty(name)) |
||||
|
continue; |
||||
|
|
||||
|
this.plugins[name] = new plugin(this, options); |
||||
|
} |
||||
|
}, |
||||
|
}; |
||||
|
})(); |
@ -0,0 +1,47 @@ |
|||||
|
/** |
||||
|
* Copyright 2013 Matthieu Moquet |
||||
|
* Copyright 2016-2017 LasLabs Inc. |
||||
|
* License MIT (https://opensource.org/licenses/MIT)
|
||||
|
**/ |
||||
|
|
||||
|
(function() { |
||||
|
'use strict'; |
||||
|
|
||||
|
Darkroom.Plugin = Plugin; |
||||
|
|
||||
|
// Define a plugin object. This is the (abstract) parent class which
|
||||
|
// has to be extended for each plugin.
|
||||
|
function Plugin(darkroom, options) { |
||||
|
this.darkroom = darkroom; |
||||
|
this.options = Darkroom.Utils.extend(options, this.defaults); |
||||
|
this.initialize(); |
||||
|
} |
||||
|
|
||||
|
Plugin.prototype = { |
||||
|
defaults: {}, |
||||
|
initialize: function() { /* no-op */ } |
||||
|
}; |
||||
|
|
||||
|
// Inspired by Backbone.js extend capability.
|
||||
|
Plugin.extend = function(protoProps) { |
||||
|
var parent = this; |
||||
|
var child; |
||||
|
|
||||
|
if (protoProps && protoProps.hasOwnProperty('constructor')) { |
||||
|
child = protoProps.constructor; |
||||
|
} else { |
||||
|
child = function() { return parent.apply(this, arguments); }; |
||||
|
} |
||||
|
|
||||
|
Darkroom.Utils.extend(child, parent); |
||||
|
|
||||
|
var Surrogate = function() { this.constructor = child; }; |
||||
|
Surrogate.prototype = parent.prototype; |
||||
|
child.prototype = new Surrogate(); |
||||
|
|
||||
|
if (protoProps) Darkroom.Utils.extend(child.prototype, protoProps); |
||||
|
child.__super__ = parent.prototype; |
||||
|
|
||||
|
return child; |
||||
|
}; |
||||
|
})(); |
@ -0,0 +1,43 @@ |
|||||
|
/** |
||||
|
* Copyright 2013 Matthieu Moquet |
||||
|
* Copyright 2016-2017 LasLabs Inc. |
||||
|
* License MIT (https://opensource.org/licenses/MIT)
|
||||
|
**/ |
||||
|
|
||||
|
(function() { |
||||
|
'use strict'; |
||||
|
|
||||
|
Darkroom.Transformation = Transformation; |
||||
|
|
||||
|
function Transformation(options) { |
||||
|
this.options = options; |
||||
|
} |
||||
|
|
||||
|
Transformation.prototype = { |
||||
|
applyTransformation: function() { /* no-op */ } |
||||
|
}; |
||||
|
|
||||
|
// Inspired by Backbone.js extend capability.
|
||||
|
Transformation.extend = function(protoProps) { |
||||
|
var parent = this; |
||||
|
var child; |
||||
|
|
||||
|
if (protoProps && protoProps.hasOwnProperty('constructor')) { |
||||
|
child = protoProps.constructor; |
||||
|
} else { |
||||
|
child = function() { return parent.apply(this, arguments); }; |
||||
|
} |
||||
|
|
||||
|
Darkroom.Utils.extend(child, parent); |
||||
|
|
||||
|
var Surrogate = function() { this.constructor = child; }; |
||||
|
Surrogate.prototype = parent.prototype; |
||||
|
child.prototype = new Surrogate(); |
||||
|
|
||||
|
if (protoProps) Darkroom.Utils.extend(child.prototype, protoProps); |
||||
|
|
||||
|
child.__super__ = parent.prototype; |
||||
|
|
||||
|
return child; |
||||
|
}; |
||||
|
})(); |
@ -0,0 +1,36 @@ |
|||||
|
/** |
||||
|
* Copyright 2013 Matthieu Moquet |
||||
|
* Copyright 2016-2017 LasLabs Inc. |
||||
|
* License MIT (https://opensource.org/licenses/MIT)
|
||||
|
**/ |
||||
|
|
||||
|
(function() { |
||||
|
'use strict'; |
||||
|
|
||||
|
Darkroom.Utils = { |
||||
|
extend: extend, |
||||
|
computeImageViewPort: computeImageViewPort, |
||||
|
}; |
||||
|
|
||||
|
// Utility method to easily extend objects.
|
||||
|
function extend(b, a) { |
||||
|
var prop; |
||||
|
if (b === undefined) { |
||||
|
return a; |
||||
|
} |
||||
|
|
||||
|
for (prop in a) { |
||||
|
if (a.hasOwnProperty(prop) && b.hasOwnProperty(prop) === false) { |
||||
|
b[prop] = a[prop]; |
||||
|
} |
||||
|
} |
||||
|
return b; |
||||
|
} |
||||
|
|
||||
|
function computeImageViewPort(image) { |
||||
|
return { |
||||
|
height: Math.abs(image.getWidth() * (Math.sin(image.getAngle() * Math.PI/180))) + Math.abs(image.getHeight() * (Math.cos(image.getAngle() * Math.PI/180))), |
||||
|
width: Math.abs(image.getHeight() * (Math.sin(image.getAngle() * Math.PI/180))) + Math.abs(image.getWidth() * (Math.cos(image.getAngle() * Math.PI/180))), |
||||
|
}; |
||||
|
} |
||||
|
})(); |
@ -1,21 +0,0 @@ |
|||||
#!/bin/bash |
|
||||
|
|
||||
# Update gh-pages branch |
|
||||
git branch -D gh-pages |
|
||||
git checkout -b gh-pages HEAD |
|
||||
|
|
||||
# Build assets |
|
||||
rm -rf build |
|
||||
gulp build --prod |
|
||||
|
|
||||
# Put build into demo folder |
|
||||
rm demo/build |
|
||||
cp -r build demo/build |
|
||||
|
|
||||
# Commit |
|
||||
git add -f demo |
|
||||
git commit -m "Build GH pages" |
|
||||
|
|
||||
# Push & reset |
|
||||
git push origin `git subtree split --prefix demo HEAD`:gh-pages --force |
|
||||
git checkout - |
|
@ -1,112 +0,0 @@ |
|||||
var concat = require('gulp-concat') |
|
||||
var connect = require('gulp-connect') |
|
||||
var gulp = require('gulp') |
|
||||
var gutil = require('gulp-util') |
|
||||
var htmlJsStr = require('js-string-escape') |
|
||||
var inject = require('gulp-inject') |
|
||||
var plumber = require('gulp-plumber') |
|
||||
var rimraf = require('rimraf') |
|
||||
var sass = require('gulp-sass') |
|
||||
var sourcemaps = require('gulp-sourcemaps') |
|
||||
var spawn = require("child_process").spawn |
|
||||
var streamqueue = require('streamqueue') |
|
||||
var svgmin = require('gulp-svgmin') |
|
||||
var svgstore = require('gulp-svgstore') |
|
||||
var uglify = require('gulp-uglify') |
|
||||
|
|
||||
|
|
||||
//
|
|
||||
// Variables
|
|
||||
//
|
|
||||
var srcDir = './lib'; |
|
||||
var distDir = './build'; |
|
||||
var isDebug = !gutil.env.prod; |
|
||||
|
|
||||
//
|
|
||||
// Default
|
|
||||
//
|
|
||||
gulp.task('default', ['build'], function() { |
|
||||
gulp.start('watch'); |
|
||||
}); |
|
||||
|
|
||||
//
|
|
||||
// Clean
|
|
||||
//
|
|
||||
gulp.task('clean', function(cb) { |
|
||||
rimraf(distDir, cb); |
|
||||
}); |
|
||||
|
|
||||
//
|
|
||||
// Build
|
|
||||
//
|
|
||||
gulp.task('build', ['clean'], function() { |
|
||||
gulp.start('scripts', 'styles'); |
|
||||
}); |
|
||||
|
|
||||
//
|
|
||||
// Watch
|
|
||||
//
|
|
||||
gulp.task('watch', ['server'], function() { |
|
||||
gulp.watch(srcDir + '/js/**/*.js', ['scripts']); |
|
||||
|
|
||||
gulp.watch(srcDir + '/css/**/*.scss', ['styles']); |
|
||||
}); |
|
||||
|
|
||||
//
|
|
||||
// Server
|
|
||||
//
|
|
||||
gulp.task('server', function() { |
|
||||
connect.server({ |
|
||||
root: './demo', |
|
||||
port: 2222, |
|
||||
livereload: false |
|
||||
}); |
|
||||
}); |
|
||||
|
|
||||
//
|
|
||||
// Javascript
|
|
||||
//
|
|
||||
gulp.task('scripts', function () { |
|
||||
var svgs = gulp.src(srcDir + '/icons/*.svg') |
|
||||
.pipe(svgmin()) |
|
||||
.pipe(svgstore({inlineSvg: true})) |
|
||||
// .pipe(gulp.dest(distDir));
|
|
||||
|
|
||||
function fileContents (filePath, file) { |
|
||||
return file.contents.toString(); |
|
||||
} |
|
||||
|
|
||||
var files = [ |
|
||||
srcDir + '/js/core/bootstrap.js', |
|
||||
srcDir + '/js/core/darkroom.js', |
|
||||
srcDir + '/js/core/*.js', |
|
||||
// srcDir + '/js/plugins/*.js',
|
|
||||
srcDir + '/js/plugins/darkroom.history.js', |
|
||||
srcDir + '/js/plugins/darkroom.rotate.js', |
|
||||
srcDir + '/js/plugins/darkroom.crop.js', |
|
||||
srcDir + '/js/plugins/darkroom.save.js', |
|
||||
]; |
|
||||
|
|
||||
gulp.src(files) |
|
||||
.pipe(plumber()) |
|
||||
.pipe(isDebug ? sourcemaps.init() : gutil.noop()) |
|
||||
.pipe(concat('darkroom.js', {newLine: ';'})) |
|
||||
.pipe(inject(svgs, { transform: fileContents })) |
|
||||
.pipe(isDebug ? gutil.noop() : uglify({mangle: false})) |
|
||||
.pipe(isDebug ? sourcemaps.write() : gutil.noop()) |
|
||||
.pipe(gulp.dest(distDir)) |
|
||||
}) |
|
||||
|
|
||||
//
|
|
||||
// Stylesheet
|
|
||||
//
|
|
||||
gulp.task('styles', function () { |
|
||||
gulp.src(srcDir + '/css/darkroom.scss') |
|
||||
.pipe(plumber()) |
|
||||
.pipe(isDebug ? sourcemaps.init() : gutil.noop()) |
|
||||
.pipe(sass({ |
|
||||
outputStyle: isDebug ? 'nested' : 'compressed' |
|
||||
})) |
|
||||
.pipe(isDebug ? sourcemaps.write() : gutil.noop()) |
|
||||
.pipe(gulp.dest(distDir)) |
|
||||
}) |
|
@ -1,12 +0,0 @@ |
|||||
.darkroom-container { |
|
||||
position: relative; |
|
||||
} |
|
||||
|
|
||||
.darkroom-image-container { |
|
||||
top: 0; |
|
||||
left: 0; |
|
||||
} |
|
||||
|
|
||||
.darkroom-image-container img { |
|
||||
// display: none; |
|
||||
} |
|
@ -1,99 +0,0 @@ |
|||||
// |
|
||||
// Toolbar |
|
||||
// |
|
||||
.darkroom-toolbar { |
|
||||
display: block; |
|
||||
position: absolute; |
|
||||
top: -45px; |
|
||||
left: 0; |
|
||||
background: #444; |
|
||||
height: 40px; |
|
||||
min-width: 40px; |
|
||||
z-index: 99; |
|
||||
border-radius: 2px; |
|
||||
white-space: nowrap; |
|
||||
padding: 0 5px; |
|
||||
|
|
||||
// Triangle |
|
||||
&:before { |
|
||||
content: ""; |
|
||||
position: absolute; |
|
||||
bottom: -7px; |
|
||||
left: 20px; |
|
||||
width: 0; |
|
||||
height: 0; |
|
||||
border-left: 7px solid transparent; |
|
||||
border-right: 7px solid transparent; |
|
||||
border-top: 7px solid #444; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
// |
|
||||
// Button Group |
|
||||
// |
|
||||
.darkroom-button-group { |
|
||||
display: inline-block; |
|
||||
margin: 0; |
|
||||
padding: 0; |
|
||||
// border-right: 1px solid #777; |
|
||||
|
|
||||
&:last-child { |
|
||||
border-right: none; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
|
|
||||
// |
|
||||
// Button |
|
||||
// |
|
||||
.darkroom-button { |
|
||||
box-sizing: border-box; |
|
||||
background: transparent; |
|
||||
border: none; |
|
||||
outline: none; |
|
||||
padding: 2px 0 0 0; |
|
||||
width: 40px; |
|
||||
height: 40px; |
|
||||
|
|
||||
&:hover { |
|
||||
cursor: pointer; |
|
||||
background: #555; |
|
||||
} |
|
||||
&:active { |
|
||||
cursor: pointer; |
|
||||
background: #333; |
|
||||
} |
|
||||
|
|
||||
&:disabled .darkroom-icon { |
|
||||
fill: #666; |
|
||||
} |
|
||||
&:disabled:hover { |
|
||||
cursor: default; |
|
||||
/*cursor: not-allowed;*/ |
|
||||
background: transparent; |
|
||||
} |
|
||||
&.darkroom-button-active .darkroom-icon { |
|
||||
fill: #33b5e5; |
|
||||
} |
|
||||
&.darkroom-button-hidden { |
|
||||
display: none; |
|
||||
} |
|
||||
&.darkroom-button-success .darkroom-icon { |
|
||||
fill: #99cc00; |
|
||||
} |
|
||||
&.darkroom-button-warning .darkroom-icon { |
|
||||
fill: #FFBB33; |
|
||||
} |
|
||||
&.darkroom-button-danger .darkroom-icon { |
|
||||
fill: #FF4444; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
// |
|
||||
// Icon |
|
||||
// |
|
||||
.darkroom-icon { |
|
||||
width: 24px; |
|
||||
height: 24px; |
|
||||
fill: #fff; |
|
||||
} |
|
@ -1,2 +0,0 @@ |
|||||
@import 'layout'; |
|
||||
@import 'toolbar'; |
|
@ -1,4 +0,0 @@ |
|||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"> |
|
||||
<path d="M19 6.41l-1.41-1.41-5.59 5.59-5.59-5.59-1.41 1.41 5.59 5.59-5.59 5.59 1.41 1.41 5.59-5.59 5.59 5.59 1.41-1.41-5.59-5.59z"/> |
|
||||
<path d="M0 0h24v24h-24z" fill="none"/> |
|
||||
</svg> |
|
@ -1,4 +0,0 @@ |
|||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"> |
|
||||
<path d="M0 0h24v24h-24z" fill="none"/> |
|
||||
<path d="M17 15h2v-8c0-1.1-.9-2-2-2h-8v2h8v8zm-10 2v-16h-2v4h-4v2h4v10c0 1.1.9 2 2 2h10v4h2v-4h4v-2h-16z"/> |
|
||||
</svg> |
|
@ -1,4 +0,0 @@ |
|||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"> |
|
||||
<path d="M0 0h24v24h-24z" fill="none"/> |
|
||||
<path d="M9 16.17l-4.17-4.17-1.42 1.41 5.59 5.59 12-12-1.41-1.41z"/> |
|
||||
</svg> |
|
@ -1,4 +0,0 @@ |
|||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"> |
|
||||
<path d="M0 0h24v24h-24z" fill="none"/> |
|
||||
<path d="M18.4 10.6c-1.85-1.61-4.25-2.6-6.9-2.6-4.65 0-8.58 3.03-9.96 7.22l2.36.78c1.05-3.19 4.05-5.5 7.6-5.5 1.95 0 3.73.72 5.12 1.88l-3.62 3.62h9v-9l-3.6 3.6z"/> |
|
||||
</svg> |
|
@ -1,4 +0,0 @@ |
|||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"> |
|
||||
<path d="M0 0h24v24h-24z" fill="none"/> |
|
||||
<path d="M7.11 8.53l-1.41-1.42c-.9 1.16-1.46 2.5-1.63 3.89h2.02c.14-.87.49-1.72 1.02-2.47zm-1.02 4.47h-2.02c.17 1.39.72 2.73 1.62 3.89l1.41-1.42c-.52-.75-.87-1.59-1.01-2.47zm1.01 5.32c1.16.9 2.51 1.44 3.9 1.61v-2.03c-.87-.15-1.71-.49-2.46-1.03l-1.44 1.45zm5.9-14.25v-3.07l-4.55 4.55 4.55 4.45v-3.91c2.84.48 5 2.94 5 5.91s-2.16 5.43-5 5.91v2.02c3.95-.49 7-3.85 7-7.93s-3.05-7.44-7-7.93z"/> |
|
||||
</svg> |
|
@ -1,4 +0,0 @@ |
|||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"> |
|
||||
<path d="M0 0h24v24h-24z" fill="none"/> |
|
||||
<path d="M15.55 5.55l-4.55-4.55v3.07c-3.94.49-7 3.85-7 7.93s3.05 7.44 7 7.93v-2.02c-2.84-.48-5-2.94-5-5.91s2.16-5.43 5-5.91v3.91l4.55-4.45zm4.38 5.45c-.17-1.39-.72-2.73-1.62-3.89l-1.42 1.42c.54.75.88 1.6 1.02 2.47h2.02zm-6.93 6.9v2.02c1.39-.17 2.74-.71 3.9-1.61l-1.44-1.44c-.75.54-1.59.89-2.46 1.03zm3.89-2.42l1.42 1.41c.9-1.16 1.45-2.5 1.62-3.89h-2.02c-.14.87-.48 1.72-1.02 2.48z"/> |
|
||||
</svg> |
|
@ -1,4 +0,0 @@ |
|||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"> |
|
||||
<path d="M0 0h24v24h-24z" fill="none"/> |
|
||||
<path d="M17 3h-12c-1.11 0-2 .9-2 2v14c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2v-12l-4-4zm-5 16c-1.66 0-3-1.34-3-3s1.34-3 3-3 3 1.34 3 3-1.34 3-3 3zm3-10h-10v-4h10v4z"/> |
|
||||
</svg> |
|
@ -1,4 +0,0 @@ |
|||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"> |
|
||||
<path d="M0 0h24v24h-24z" fill="none"/> |
|
||||
<path d="M12.5 8c-2.65 0-5.05.99-6.9 2.6l-3.6-3.6v9h9l-3.62-3.62c1.39-1.16 3.16-1.88 5.12-1.88 3.54 0 6.55 2.31 7.6 5.5l2.37-.78c-1.39-4.19-5.32-7.22-9.97-7.22z"/> |
|
||||
</svg> |
|
@ -1,14 +0,0 @@ |
|||||
(function() { |
|
||||
'use strict'; |
|
||||
|
|
||||
// Inject SVG icons into the DOM
|
|
||||
var element = document.createElement('div'); |
|
||||
element.id = 'darkroom-icons'; |
|
||||
element.style.height = 0; |
|
||||
element.style.width = 0; |
|
||||
element.style.position = 'absolute'; |
|
||||
element.style.visibility = 'hidden'; |
|
||||
element.innerHTML = '<!-- inject:svg --><!-- endinject -->'; |
|
||||
document.body.appendChild(element); |
|
||||
|
|
||||
})(); |
|
@ -1,354 +0,0 @@ |
|||||
(function() { |
|
||||
'use strict'; |
|
||||
|
|
||||
window.Darkroom = Darkroom; |
|
||||
|
|
||||
// Core object of DarkroomJS.
|
|
||||
// Basically it's a single object, instanciable via an element
|
|
||||
// (it could be a CSS selector or a DOM element), some custom options,
|
|
||||
// and a list of plugin objects (or none to use default ones).
|
|
||||
function Darkroom(element, options, plugins) { |
|
||||
return this.constructor(element, options, plugins); |
|
||||
} |
|
||||
|
|
||||
// Create an empty list of plugin objects, which will be filled by
|
|
||||
// other plugin scripts. This is the default plugin list if none is
|
|
||||
// specified in Darkroom'ss constructor.
|
|
||||
Darkroom.plugins = []; |
|
||||
|
|
||||
Darkroom.prototype = { |
|
||||
// Refenrece to the main container element
|
|
||||
containerElement: null, |
|
||||
|
|
||||
// Reference to the Fabric canvas object
|
|
||||
canvas: null, |
|
||||
|
|
||||
// Reference to the Fabric image object
|
|
||||
image: null, |
|
||||
|
|
||||
// Reference to the Fabric source canvas object
|
|
||||
sourceCanvas: null, |
|
||||
|
|
||||
// Reference to the Fabric source image object
|
|
||||
sourceImage: null, |
|
||||
|
|
||||
// Track of the original image element
|
|
||||
originalImageElement: null, |
|
||||
|
|
||||
// Stack of transformations to apply to the image source
|
|
||||
transformations: [], |
|
||||
|
|
||||
// Default options
|
|
||||
defaults: { |
|
||||
// Canvas properties (dimension, ratio, color)
|
|
||||
minWidth: null, |
|
||||
minHeight: null, |
|
||||
maxWidth: null, |
|
||||
maxHeight: null, |
|
||||
ratio: null, |
|
||||
backgroundColor: '#fff', |
|
||||
|
|
||||
// Plugins options
|
|
||||
plugins: {}, |
|
||||
|
|
||||
// Post-initialisation callback
|
|
||||
initialize: function() { /* noop */ } |
|
||||
}, |
|
||||
|
|
||||
// List of the instancied plugins
|
|
||||
plugins: {}, |
|
||||
|
|
||||
// This options are a merge between `defaults` and the options passed
|
|
||||
// through the constructor
|
|
||||
options: {}, |
|
||||
|
|
||||
constructor: function(element, options, plugins) { |
|
||||
this.options = Darkroom.Utils.extend(options, this.defaults); |
|
||||
|
|
||||
if (typeof element === 'string') |
|
||||
element = document.querySelector(element); |
|
||||
if (null === element) |
|
||||
return; |
|
||||
|
|
||||
var image = new Image(); |
|
||||
var parent = element.parentElement; |
|
||||
image.onload = function() { |
|
||||
// Initialize the DOM/Fabric elements
|
|
||||
this._initializeDOM(element, parent); |
|
||||
this._initializeImage(); |
|
||||
|
|
||||
// Then initialize the plugins
|
|
||||
this._initializePlugins(Darkroom.plugins); |
|
||||
|
|
||||
// Public method to adjust image according to the canvas
|
|
||||
this.refresh(function() { |
|
||||
// Execute a custom callback after initialization
|
|
||||
this.options.initialize.bind(this).call(); |
|
||||
}.bind(this)); |
|
||||
|
|
||||
}.bind(this) |
|
||||
|
|
||||
//image.crossOrigin = 'anonymous';
|
|
||||
image.src = element.src; |
|
||||
}, |
|
||||
|
|
||||
selfDestroy: function() { |
|
||||
var container = this.containerElement; |
|
||||
var image = new Image(); |
|
||||
image.onload = function() { |
|
||||
container.parentNode.replaceChild(image, container); |
|
||||
} |
|
||||
|
|
||||
image.src = this.sourceImage.toDataURL(); |
|
||||
}, |
|
||||
|
|
||||
// Add ability to attach event listener on the core object.
|
|
||||
// It uses the canvas element to process events.
|
|
||||
addEventListener: function(eventName, callback) { |
|
||||
var el = this.canvas.getElement(); |
|
||||
if (el.addEventListener){ |
|
||||
el.addEventListener(eventName, callback); |
|
||||
} else if (el.attachEvent) { |
|
||||
el.attachEvent('on' + eventName, callback); |
|
||||
} |
|
||||
}, |
|
||||
|
|
||||
dispatchEvent: function(eventName) { |
|
||||
// Use the old way of creating event to be IE compatible
|
|
||||
// See https://developer.mozilla.org/en-US/docs/Web/Guide/Events/Creating_and_triggering_events
|
|
||||
var event = document.createEvent('Event'); |
|
||||
event.initEvent(eventName, true, true); |
|
||||
|
|
||||
this.canvas.getElement().dispatchEvent(event); |
|
||||
}, |
|
||||
|
|
||||
// Adjust image & canvas dimension according to min/max width/height
|
|
||||
// and ratio specified in the options.
|
|
||||
// This method should be called after each image transformation.
|
|
||||
refresh: function(next) { |
|
||||
var clone = new Image(); |
|
||||
clone.onload = function() { |
|
||||
this._replaceCurrentImage(new fabric.Image(clone)); |
|
||||
|
|
||||
if (next) next(); |
|
||||
}.bind(this); |
|
||||
clone.src = this.sourceImage.toDataURL(); |
|
||||
}, |
|
||||
|
|
||||
_replaceCurrentImage: function(newImage) { |
|
||||
if (this.image) { |
|
||||
this.image.remove(); |
|
||||
} |
|
||||
|
|
||||
this.image = newImage; |
|
||||
this.image.selectable = false; |
|
||||
|
|
||||
// Adjust width or height according to specified ratio
|
|
||||
var viewport = Darkroom.Utils.computeImageViewPort(this.image); |
|
||||
var canvasWidth = viewport.width; |
|
||||
var canvasHeight = viewport.height; |
|
||||
|
|
||||
if (null !== this.options.ratio) { |
|
||||
var canvasRatio = +this.options.ratio; |
|
||||
var currentRatio = canvasWidth / canvasHeight; |
|
||||
|
|
||||
if (currentRatio > canvasRatio) { |
|
||||
canvasHeight = canvasWidth / canvasRatio; |
|
||||
} else if (currentRatio < canvasRatio) { |
|
||||
canvasWidth = canvasHeight * canvasRatio; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
// Then scale the image to fit into dimension limits
|
|
||||
var scaleMin = 1; |
|
||||
var scaleMax = 1; |
|
||||
var scaleX = 1; |
|
||||
var scaleY = 1; |
|
||||
|
|
||||
if (null !== this.options.maxWidth && this.options.maxWidth < canvasWidth) { |
|
||||
scaleX = this.options.maxWidth / canvasWidth; |
|
||||
} |
|
||||
if (null !== this.options.maxHeight && this.options.maxHeight < canvasHeight) { |
|
||||
scaleY = this.options.maxHeight / canvasHeight; |
|
||||
} |
|
||||
scaleMin = Math.min(scaleX, scaleY); |
|
||||
|
|
||||
scaleX = 1; |
|
||||
scaleY = 1; |
|
||||
if (null !== this.options.minWidth && this.options.minWidth > canvasWidth) { |
|
||||
scaleX = this.options.minWidth / canvasWidth; |
|
||||
} |
|
||||
if (null !== this.options.minHeight && this.options.minHeight > canvasHeight) { |
|
||||
scaleY = this.options.minHeight / canvasHeight; |
|
||||
} |
|
||||
scaleMax = Math.max(scaleX, scaleY); |
|
||||
|
|
||||
var scale = scaleMax * scaleMin; // one should be equals to 1
|
|
||||
|
|
||||
canvasWidth *= scale; |
|
||||
canvasHeight *= scale; |
|
||||
|
|
||||
// Finally place the image in the center of the canvas
|
|
||||
this.image.setScaleX(1 * scale); |
|
||||
this.image.setScaleY(1 * scale); |
|
||||
this.canvas.add(this.image); |
|
||||
this.canvas.setWidth(canvasWidth); |
|
||||
this.canvas.setHeight(canvasHeight); |
|
||||
this.canvas.centerObject(this.image); |
|
||||
this.image.setCoords(); |
|
||||
}, |
|
||||
|
|
||||
// Apply the transformation on the current image and save it in the
|
|
||||
// transformations stack (in order to reconstitute the previous states
|
|
||||
// of the image).
|
|
||||
applyTransformation: function(transformation) { |
|
||||
this.transformations.push(transformation); |
|
||||
|
|
||||
transformation.applyTransformation( |
|
||||
this.sourceCanvas, |
|
||||
this.sourceImage, |
|
||||
this._postTransformation.bind(this) |
|
||||
); |
|
||||
}, |
|
||||
|
|
||||
_postTransformation: function(newImage) { |
|
||||
if (newImage) |
|
||||
this.sourceImage = newImage; |
|
||||
|
|
||||
this.refresh(function() { |
|
||||
this.dispatchEvent('core:transformation'); |
|
||||
}.bind(this)); |
|
||||
}, |
|
||||
|
|
||||
// Initialize image from original element plus re-apply every
|
|
||||
// transformations.
|
|
||||
reinitializeImage: function() { |
|
||||
this.sourceImage.remove(); |
|
||||
this._initializeImage(); |
|
||||
this._popTransformation(this.transformations.slice()) |
|
||||
}, |
|
||||
|
|
||||
_popTransformation: function(transformations) { |
|
||||
if (0 === transformations.length) { |
|
||||
this.dispatchEvent('core:reinitialized'); |
|
||||
this.refresh(); |
|
||||
return; |
|
||||
} |
|
||||
|
|
||||
var transformation = transformations.shift(); |
|
||||
|
|
||||
var next = function(newImage) { |
|
||||
if (newImage) this.sourceImage = newImage; |
|
||||
this._popTransformation(transformations) |
|
||||
}; |
|
||||
|
|
||||
transformation.applyTransformation( |
|
||||
this.sourceCanvas, |
|
||||
this.sourceImage, |
|
||||
next.bind(this) |
|
||||
); |
|
||||
}, |
|
||||
|
|
||||
// Create the DOM elements and instanciate the Fabric canvas.
|
|
||||
// The image element is replaced by a new `div` element.
|
|
||||
// However the original image is re-injected in order to keep a trace of it.
|
|
||||
_initializeDOM: function(imageElement) { |
|
||||
// Container
|
|
||||
var mainContainerElement = document.createElement('div'); |
|
||||
mainContainerElement.className = 'darkroom-container'; |
|
||||
|
|
||||
// Toolbar
|
|
||||
var toolbarElement = document.createElement('div'); |
|
||||
toolbarElement.className = 'darkroom-toolbar'; |
|
||||
mainContainerElement.appendChild(toolbarElement); |
|
||||
|
|
||||
// Viewport canvas
|
|
||||
var canvasContainerElement = document.createElement('div'); |
|
||||
canvasContainerElement.className = 'darkroom-image-container'; |
|
||||
var canvasElement = document.createElement('canvas'); |
|
||||
canvasContainerElement.appendChild(canvasElement); |
|
||||
mainContainerElement.appendChild(canvasContainerElement); |
|
||||
|
|
||||
// Source canvas
|
|
||||
var sourceCanvasContainerElement = document.createElement('div'); |
|
||||
sourceCanvasContainerElement.className = 'darkroom-source-container'; |
|
||||
sourceCanvasContainerElement.style.display = 'none'; |
|
||||
var sourceCanvasElement = document.createElement('canvas'); |
|
||||
sourceCanvasContainerElement.appendChild(sourceCanvasElement); |
|
||||
mainContainerElement.appendChild(sourceCanvasContainerElement); |
|
||||
|
|
||||
// Original image
|
|
||||
imageElement.parentNode.replaceChild(mainContainerElement, imageElement); |
|
||||
imageElement.style.display = 'none'; |
|
||||
mainContainerElement.appendChild(imageElement); |
|
||||
|
|
||||
// Instanciate object from elements
|
|
||||
this.containerElement = mainContainerElement; |
|
||||
this.originalImageElement = imageElement; |
|
||||
|
|
||||
this.toolbar = new Darkroom.UI.Toolbar(toolbarElement); |
|
||||
|
|
||||
this.canvas = new fabric.Canvas(canvasElement, { |
|
||||
selection: false, |
|
||||
backgroundColor: this.options.backgroundColor |
|
||||
}); |
|
||||
|
|
||||
this.sourceCanvas = new fabric.Canvas(sourceCanvasElement, { |
|
||||
selection: false, |
|
||||
backgroundColor: this.options.backgroundColor |
|
||||
}); |
|
||||
}, |
|
||||
|
|
||||
// Instanciate the Fabric image object.
|
|
||||
// The image is created as a static element with no control,
|
|
||||
// then it is add in the Fabric canvas object.
|
|
||||
_initializeImage: function() { |
|
||||
this.sourceImage = new fabric.Image(this.originalImageElement, { |
|
||||
// Some options to make the image static
|
|
||||
selectable: false, |
|
||||
evented: false, |
|
||||
lockMovementX: true, |
|
||||
lockMovementY: true, |
|
||||
lockRotation: true, |
|
||||
lockScalingX: true, |
|
||||
lockScalingY: true, |
|
||||
lockUniScaling: true, |
|
||||
hasControls: false, |
|
||||
hasBorders: false, |
|
||||
}); |
|
||||
|
|
||||
this.sourceCanvas.add(this.sourceImage); |
|
||||
|
|
||||
// Adjust width or height according to specified ratio
|
|
||||
var viewport = Darkroom.Utils.computeImageViewPort(this.sourceImage); |
|
||||
var canvasWidth = viewport.width; |
|
||||
var canvasHeight = viewport.height; |
|
||||
|
|
||||
this.sourceCanvas.setWidth(canvasWidth); |
|
||||
this.sourceCanvas.setHeight(canvasHeight); |
|
||||
this.sourceCanvas.centerObject(this.sourceImage); |
|
||||
this.sourceImage.setCoords(); |
|
||||
}, |
|
||||
|
|
||||
// Initialize every plugins.
|
|
||||
// Note that plugins are instanciated in the same order than they
|
|
||||
// are declared in the parameter object.
|
|
||||
_initializePlugins: function(plugins) { |
|
||||
for (var name in plugins) { |
|
||||
var plugin = plugins[name]; |
|
||||
var options = this.options.plugins[name]; |
|
||||
|
|
||||
// Setting false into the plugin options will disable the plugin
|
|
||||
if (options === false) |
|
||||
continue; |
|
||||
|
|
||||
// Avoid any issues with _proto_
|
|
||||
if (!plugins.hasOwnProperty(name)) |
|
||||
continue; |
|
||||
|
|
||||
this.plugins[name] = new plugin(this, options); |
|
||||
} |
|
||||
}, |
|
||||
} |
|
||||
|
|
||||
})(); |
|
@ -1,43 +0,0 @@ |
|||||
(function() { |
|
||||
'use strict'; |
|
||||
|
|
||||
Darkroom.Plugin = Plugin; |
|
||||
|
|
||||
// Define a plugin object. This is the (abstract) parent class which
|
|
||||
// has to be extended for each plugin.
|
|
||||
function Plugin(darkroom, options) { |
|
||||
this.darkroom = darkroom; |
|
||||
this.options = Darkroom.Utils.extend(options, this.defaults); |
|
||||
this.initialize(); |
|
||||
} |
|
||||
|
|
||||
Plugin.prototype = { |
|
||||
defaults: {}, |
|
||||
initialize: function() { } |
|
||||
} |
|
||||
|
|
||||
// Inspired by Backbone.js extend capability.
|
|
||||
Plugin.extend = function(protoProps) { |
|
||||
var parent = this; |
|
||||
var child; |
|
||||
|
|
||||
if (protoProps && protoProps.hasOwnProperty('constructor')) { |
|
||||
child = protoProps.constructor; |
|
||||
} else { |
|
||||
child = function(){ return parent.apply(this, arguments); }; |
|
||||
} |
|
||||
|
|
||||
Darkroom.Utils.extend(child, parent); |
|
||||
|
|
||||
var Surrogate = function(){ this.constructor = child; }; |
|
||||
Surrogate.prototype = parent.prototype; |
|
||||
child.prototype = new Surrogate; |
|
||||
|
|
||||
if (protoProps) Darkroom.Utils.extend(child.prototype, protoProps); |
|
||||
|
|
||||
child.__super__ = parent.prototype; |
|
||||
|
|
||||
return child; |
|
||||
} |
|
||||
|
|
||||
})(); |
|
@ -1,38 +0,0 @@ |
|||||
(function() { |
|
||||
'use strict'; |
|
||||
|
|
||||
Darkroom.Transformation = Transformation; |
|
||||
|
|
||||
function Transformation(options) { |
|
||||
this.options = options; |
|
||||
} |
|
||||
|
|
||||
Transformation.prototype = { |
|
||||
applyTransformation: function(image) { /* no-op */ } |
|
||||
} |
|
||||
|
|
||||
// Inspired by Backbone.js extend capability.
|
|
||||
Transformation.extend = function(protoProps) { |
|
||||
var parent = this; |
|
||||
var child; |
|
||||
|
|
||||
if (protoProps && protoProps.hasOwnProperty('constructor')) { |
|
||||
child = protoProps.constructor; |
|
||||
} else { |
|
||||
child = function(){ return parent.apply(this, arguments); }; |
|
||||
} |
|
||||
|
|
||||
Darkroom.Utils.extend(child, parent); |
|
||||
|
|
||||
var Surrogate = function(){ this.constructor = child; }; |
|
||||
Surrogate.prototype = parent.prototype; |
|
||||
child.prototype = new Surrogate; |
|
||||
|
|
||||
if (protoProps) Darkroom.Utils.extend(child.prototype, protoProps); |
|
||||
|
|
||||
child.__super__ = parent.prototype; |
|
||||
|
|
||||
return child; |
|
||||
} |
|
||||
|
|
||||
})(); |
|
@ -1,91 +0,0 @@ |
|||||
(function() { |
|
||||
'use strict'; |
|
||||
|
|
||||
Darkroom.UI = { |
|
||||
Toolbar: Toolbar, |
|
||||
ButtonGroup: ButtonGroup, |
|
||||
Button: Button, |
|
||||
}; |
|
||||
|
|
||||
// Toolbar object.
|
|
||||
function Toolbar(element) { |
|
||||
this.element = element; |
|
||||
} |
|
||||
|
|
||||
Toolbar.prototype = { |
|
||||
createButtonGroup: function(options) { |
|
||||
var buttonGroup = document.createElement('div'); |
|
||||
buttonGroup.className = 'darkroom-button-group'; |
|
||||
this.element.appendChild(buttonGroup); |
|
||||
|
|
||||
return new ButtonGroup(buttonGroup); |
|
||||
} |
|
||||
}; |
|
||||
|
|
||||
// ButtonGroup object.
|
|
||||
function ButtonGroup(element) { |
|
||||
this.element = element; |
|
||||
} |
|
||||
|
|
||||
ButtonGroup.prototype = { |
|
||||
createButton: function(options) { |
|
||||
var defaults = { |
|
||||
image: 'help', |
|
||||
type: 'default', |
|
||||
group: 'default', |
|
||||
hide: false, |
|
||||
disabled: false |
|
||||
}; |
|
||||
|
|
||||
options = Darkroom.Utils.extend(options, defaults); |
|
||||
|
|
||||
var buttonElement = document.createElement('button'); |
|
||||
buttonElement.type = 'button'; |
|
||||
buttonElement.className = 'darkroom-button darkroom-button-' + options.type; |
|
||||
buttonElement.innerHTML = '<svg class="darkroom-icon"><use xlink:href="#' + options.image + '" /></svg>'; |
|
||||
this.element.appendChild(buttonElement); |
|
||||
|
|
||||
var button = new Button(buttonElement); |
|
||||
button.hide(options.hide); |
|
||||
button.disable(options.disabled); |
|
||||
|
|
||||
return button; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
// Button object.
|
|
||||
function Button(element) { |
|
||||
this.element = element; |
|
||||
} |
|
||||
|
|
||||
Button.prototype = { |
|
||||
addEventListener: function(eventName, listener) { |
|
||||
if (this.element.addEventListener){ |
|
||||
this.element.addEventListener(eventName, listener); |
|
||||
} else if (this.element.attachEvent) { |
|
||||
this.element.attachEvent('on' + eventName, listener); |
|
||||
} |
|
||||
}, |
|
||||
removeEventListener: function(eventName, listener) { |
|
||||
if (this.element.removeEventListener){ |
|
||||
this.element.removeEventListener(eventName, listener); |
|
||||
} |
|
||||
}, |
|
||||
active: function(value) { |
|
||||
if (value) |
|
||||
this.element.classList.add('darkroom-button-active'); |
|
||||
else |
|
||||
this.element.classList.remove('darkroom-button-active'); |
|
||||
}, |
|
||||
hide: function(value) { |
|
||||
if (value) |
|
||||
this.element.classList.add('darkroom-button-hidden'); |
|
||||
else |
|
||||
this.element.classList.remove('darkroom-button-hidden'); |
|
||||
}, |
|
||||
disable: function(value) { |
|
||||
this.element.disabled = (value) ? true : false; |
|
||||
} |
|
||||
}; |
|
||||
|
|
||||
})(); |
|
@ -1,31 +0,0 @@ |
|||||
(function() { |
|
||||
'use strict'; |
|
||||
|
|
||||
Darkroom.Utils = { |
|
||||
extend: extend, |
|
||||
computeImageViewPort: computeImageViewPort, |
|
||||
}; |
|
||||
|
|
||||
|
|
||||
// Utility method to easily extend objects.
|
|
||||
function extend(b, a) { |
|
||||
var prop; |
|
||||
if (b === undefined) { |
|
||||
return a; |
|
||||
} |
|
||||
for (prop in a) { |
|
||||
if (a.hasOwnProperty(prop) && b.hasOwnProperty(prop) === false) { |
|
||||
b[prop] = a[prop]; |
|
||||
} |
|
||||
} |
|
||||
return b; |
|
||||
} |
|
||||
|
|
||||
function computeImageViewPort(image) { |
|
||||
return { |
|
||||
height: Math.abs(image.getWidth() * (Math.sin(image.getAngle() * Math.PI/180))) + Math.abs(image.getHeight() * (Math.cos(image.getAngle() * Math.PI/180))), |
|
||||
width: Math.abs(image.getHeight() * (Math.sin(image.getAngle() * Math.PI/180))) + Math.abs(image.getWidth() * (Math.cos(image.getAngle() * Math.PI/180))), |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
})(); |
|
@ -1,669 +0,0 @@ |
|||||
(function() { |
|
||||
'use strict'; |
|
||||
|
|
||||
var Crop = Darkroom.Transformation.extend({ |
|
||||
applyTransformation: function(canvas, image, next) { |
|
||||
// Snapshot the image delimited by the crop zone
|
|
||||
var snapshot = new Image(); |
|
||||
snapshot.onload = function() { |
|
||||
// Validate image
|
|
||||
if (height < 1 || width < 1) |
|
||||
return; |
|
||||
|
|
||||
var imgInstance = new fabric.Image(this, { |
|
||||
// options to make the image static
|
|
||||
selectable: false, |
|
||||
evented: false, |
|
||||
lockMovementX: true, |
|
||||
lockMovementY: true, |
|
||||
lockRotation: true, |
|
||||
lockScalingX: true, |
|
||||
lockScalingY: true, |
|
||||
lockUniScaling: true, |
|
||||
hasControls: false, |
|
||||
hasBorders: false |
|
||||
}); |
|
||||
|
|
||||
var width = this.width; |
|
||||
var height = this.height; |
|
||||
|
|
||||
// Update canvas size
|
|
||||
canvas.setWidth(width); |
|
||||
canvas.setHeight(height); |
|
||||
|
|
||||
// Add image
|
|
||||
image.remove(); |
|
||||
canvas.add(imgInstance); |
|
||||
|
|
||||
next(imgInstance); |
|
||||
}; |
|
||||
|
|
||||
var viewport = Darkroom.Utils.computeImageViewPort(image); |
|
||||
var imageWidth = viewport.width; |
|
||||
var imageHeight = viewport.height; |
|
||||
|
|
||||
var left = this.options.left * imageWidth; |
|
||||
var top = this.options.top * imageHeight; |
|
||||
var width = Math.min(this.options.width * imageWidth, imageWidth - left); |
|
||||
var height = Math.min(this.options.height * imageHeight, imageHeight - top); |
|
||||
|
|
||||
snapshot.src = canvas.toDataURL({ |
|
||||
left: left, |
|
||||
top: top, |
|
||||
width: width, |
|
||||
height: height, |
|
||||
}); |
|
||||
} |
|
||||
}); |
|
||||
|
|
||||
var CropZone = fabric.util.createClass(fabric.Rect, { |
|
||||
_render: function(ctx) { |
|
||||
this.callSuper('_render', ctx); |
|
||||
|
|
||||
var canvas = ctx.canvas; |
|
||||
var dashWidth = 7; |
|
||||
|
|
||||
// Set original scale
|
|
||||
var flipX = this.flipX ? -1 : 1; |
|
||||
var flipY = this.flipY ? -1 : 1; |
|
||||
var scaleX = flipX / this.scaleX; |
|
||||
var scaleY = flipY / this.scaleY; |
|
||||
|
|
||||
ctx.scale(scaleX, scaleY); |
|
||||
|
|
||||
// Overlay rendering
|
|
||||
ctx.fillStyle = 'rgba(0, 0, 0, 0.5)'; |
|
||||
this._renderOverlay(ctx); |
|
||||
|
|
||||
// Set dashed borders
|
|
||||
if (ctx.setLineDash !== undefined) |
|
||||
ctx.setLineDash([dashWidth, dashWidth]); |
|
||||
else if (ctx.mozDash !== undefined) |
|
||||
ctx.mozDash = [dashWidth, dashWidth]; |
|
||||
|
|
||||
// First lines rendering with black
|
|
||||
ctx.strokeStyle = 'rgba(0, 0, 0, 0.2)'; |
|
||||
this._renderBorders(ctx); |
|
||||
this._renderGrid(ctx); |
|
||||
|
|
||||
// Re render lines in white
|
|
||||
ctx.lineDashOffset = dashWidth; |
|
||||
ctx.strokeStyle = 'rgba(255, 255, 255, 0.4)'; |
|
||||
this._renderBorders(ctx); |
|
||||
this._renderGrid(ctx); |
|
||||
|
|
||||
// Reset scale
|
|
||||
ctx.scale(1/scaleX, 1/scaleY); |
|
||||
}, |
|
||||
|
|
||||
_renderOverlay: function(ctx) { |
|
||||
var canvas = ctx.canvas; |
|
||||
var borderOffset = 0; |
|
||||
|
|
||||
//
|
|
||||
// x0 x1 x2 x3
|
|
||||
// y0 +------------------------+
|
|
||||
// |\\\\\\\\\\\\\\\\\\\\\\\\|
|
|
||||
// |\\\\\\\\\\\\\\\\\\\\\\\\|
|
|
||||
// y1 +------+---------+-------+
|
|
||||
// |\\\\\\| |\\\\\\\|
|
|
||||
// |\\\\\\| 0 |\\\\\\\|
|
|
||||
// |\\\\\\| |\\\\\\\|
|
|
||||
// y2 +------+---------+-------+
|
|
||||
// |\\\\\\\\\\\\\\\\\\\\\\\\|
|
|
||||
// |\\\\\\\\\\\\\\\\\\\\\\\\|
|
|
||||
// y3 +------------------------+
|
|
||||
//
|
|
||||
|
|
||||
var x0 = Math.ceil(-this.getWidth() / 2 - this.getLeft()); |
|
||||
var x1 = Math.ceil(-this.getWidth() / 2); |
|
||||
var x2 = Math.ceil(this.getWidth() / 2); |
|
||||
var x3 = Math.ceil(this.getWidth() / 2 + (canvas.width - this.getWidth() - this.getLeft())); |
|
||||
|
|
||||
var y0 = Math.ceil(-this.getHeight() / 2 - this.getTop()); |
|
||||
var y1 = Math.ceil(-this.getHeight() / 2); |
|
||||
var y2 = Math.ceil(this.getHeight() / 2); |
|
||||
var y3 = Math.ceil(this.getHeight() / 2 + (canvas.height - this.getHeight() - this.getTop())); |
|
||||
|
|
||||
// Upper rect
|
|
||||
ctx.fillRect(x0, y0, x3 - x0, y1 - y0 + borderOffset); |
|
||||
|
|
||||
// Left rect
|
|
||||
ctx.fillRect(x0, y1, x1 - x0, y2 - y1 + borderOffset); |
|
||||
|
|
||||
// Right rect
|
|
||||
ctx.fillRect(x2, y1, x3 - x2, y2 - y1 + borderOffset); |
|
||||
|
|
||||
// Down rect
|
|
||||
ctx.fillRect(x0, y2, x3 - x0, y3 - y2); |
|
||||
}, |
|
||||
|
|
||||
_renderBorders: function(ctx) { |
|
||||
ctx.beginPath(); |
|
||||
ctx.moveTo(-this.getWidth()/2, -this.getHeight()/2); // upper left
|
|
||||
ctx.lineTo(this.getWidth()/2, -this.getHeight()/2); // upper right
|
|
||||
ctx.lineTo(this.getWidth()/2, this.getHeight()/2); // down right
|
|
||||
ctx.lineTo(-this.getWidth()/2, this.getHeight()/2); // down left
|
|
||||
ctx.lineTo(-this.getWidth()/2, -this.getHeight()/2); // upper left
|
|
||||
ctx.stroke(); |
|
||||
}, |
|
||||
|
|
||||
_renderGrid: function(ctx) { |
|
||||
// Vertical lines
|
|
||||
ctx.beginPath(); |
|
||||
ctx.moveTo(-this.getWidth()/2 + 1/3 * this.getWidth(), -this.getHeight()/2); |
|
||||
ctx.lineTo(-this.getWidth()/2 + 1/3 * this.getWidth(), this.getHeight()/2); |
|
||||
ctx.stroke(); |
|
||||
ctx.beginPath(); |
|
||||
ctx.moveTo(-this.getWidth()/2 + 2/3 * this.getWidth(), -this.getHeight()/2); |
|
||||
ctx.lineTo(-this.getWidth()/2 + 2/3 * this.getWidth(), this.getHeight()/2); |
|
||||
ctx.stroke(); |
|
||||
// Horizontal lines
|
|
||||
ctx.beginPath(); |
|
||||
ctx.moveTo(-this.getWidth()/2, -this.getHeight()/2 + 1/3 * this.getHeight()); |
|
||||
ctx.lineTo(this.getWidth()/2, -this.getHeight()/2 + 1/3 * this.getHeight()); |
|
||||
ctx.stroke(); |
|
||||
ctx.beginPath(); |
|
||||
ctx.moveTo(-this.getWidth()/2, -this.getHeight()/2 + 2/3 * this.getHeight()); |
|
||||
ctx.lineTo(this.getWidth()/2, -this.getHeight()/2 + 2/3 * this.getHeight()); |
|
||||
ctx.stroke(); |
|
||||
} |
|
||||
}); |
|
||||
|
|
||||
Darkroom.plugins['crop'] = Darkroom.Plugin.extend({ |
|
||||
// Init point
|
|
||||
startX: null, |
|
||||
startY: null, |
|
||||
|
|
||||
// Keycrop
|
|
||||
isKeyCroping: false, |
|
||||
isKeyLeft: false, |
|
||||
isKeyUp: false, |
|
||||
|
|
||||
defaults: { |
|
||||
// min crop dimension
|
|
||||
minHeight: 1, |
|
||||
minWidth: 1, |
|
||||
// ensure crop ratio
|
|
||||
ratio: null, |
|
||||
// quick crop feature (set a key code to enable it)
|
|
||||
quickCropKey: false |
|
||||
}, |
|
||||
|
|
||||
initialize: function InitDarkroomCropPlugin() { |
|
||||
var buttonGroup = this.darkroom.toolbar.createButtonGroup(); |
|
||||
|
|
||||
this.cropButton = buttonGroup.createButton({ |
|
||||
image: 'crop' |
|
||||
}); |
|
||||
this.okButton = buttonGroup.createButton({ |
|
||||
image: 'done', |
|
||||
type: 'success', |
|
||||
hide: true |
|
||||
}); |
|
||||
this.cancelButton = buttonGroup.createButton({ |
|
||||
image: 'close', |
|
||||
type: 'danger', |
|
||||
hide: true |
|
||||
}); |
|
||||
|
|
||||
// Buttons click
|
|
||||
this.cropButton.addEventListener('click', this.toggleCrop.bind(this)); |
|
||||
this.okButton.addEventListener('click', this.cropCurrentZone.bind(this)); |
|
||||
this.cancelButton.addEventListener('click', this.releaseFocus.bind(this)); |
|
||||
|
|
||||
// Canvas events
|
|
||||
this.darkroom.canvas.on('mouse:down', this.onMouseDown.bind(this)); |
|
||||
this.darkroom.canvas.on('mouse:move', this.onMouseMove.bind(this)); |
|
||||
this.darkroom.canvas.on('mouse:up', this.onMouseUp.bind(this)); |
|
||||
this.darkroom.canvas.on('object:moving', this.onObjectMoving.bind(this)); |
|
||||
this.darkroom.canvas.on('object:scaling', this.onObjectScaling.bind(this)); |
|
||||
|
|
||||
fabric.util.addListener(fabric.document, 'keydown', this.onKeyDown.bind(this)); |
|
||||
fabric.util.addListener(fabric.document, 'keyup', this.onKeyUp.bind(this)); |
|
||||
|
|
||||
this.darkroom.addEventListener('core:transformation', this.releaseFocus.bind(this)); |
|
||||
}, |
|
||||
|
|
||||
// Avoid crop zone to go beyond the canvas edges
|
|
||||
onObjectMoving: function(event) { |
|
||||
if (!this.hasFocus()) { |
|
||||
return; |
|
||||
} |
|
||||
|
|
||||
var currentObject = event.target; |
|
||||
if (currentObject !== this.cropZone) |
|
||||
return; |
|
||||
|
|
||||
var canvas = this.darkroom.canvas; |
|
||||
var x = currentObject.getLeft(), y = currentObject.getTop(); |
|
||||
var w = currentObject.getWidth(), h = currentObject.getHeight(); |
|
||||
var maxX = canvas.getWidth() - w; |
|
||||
var maxY = canvas.getHeight() - h; |
|
||||
|
|
||||
if (x < 0) |
|
||||
currentObject.set('left', 0); |
|
||||
if (y < 0) |
|
||||
currentObject.set('top', 0); |
|
||||
if (x > maxX) |
|
||||
currentObject.set('left', maxX); |
|
||||
if (y > maxY) |
|
||||
currentObject.set('top', maxY); |
|
||||
|
|
||||
this.darkroom.dispatchEvent('crop:update'); |
|
||||
}, |
|
||||
|
|
||||
// Prevent crop zone from going beyond the canvas edges (like mouseMove)
|
|
||||
onObjectScaling: function(event) { |
|
||||
if (!this.hasFocus()) { |
|
||||
return; |
|
||||
} |
|
||||
|
|
||||
var preventScaling = false; |
|
||||
var currentObject = event.target; |
|
||||
if (currentObject !== this.cropZone) |
|
||||
return; |
|
||||
|
|
||||
var canvas = this.darkroom.canvas; |
|
||||
var pointer = canvas.getPointer(event.e); |
|
||||
var x = pointer.x; |
|
||||
var y = pointer.y; |
|
||||
|
|
||||
var minX = currentObject.getLeft(); |
|
||||
var minY = currentObject.getTop(); |
|
||||
var maxX = currentObject.getLeft() + currentObject.getWidth(); |
|
||||
var maxY = currentObject.getTop() + currentObject.getHeight(); |
|
||||
|
|
||||
if (null !== this.options.ratio) { |
|
||||
if (minX < 0 || maxX > canvas.getWidth() || minY < 0 || maxY > canvas.getHeight()) { |
|
||||
preventScaling = true; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
if (minX < 0 || maxX > canvas.getWidth() || preventScaling) { |
|
||||
var lastScaleX = this.lastScaleX || 1; |
|
||||
currentObject.setScaleX(lastScaleX); |
|
||||
} |
|
||||
if (minX < 0) { |
|
||||
currentObject.setLeft(0); |
|
||||
} |
|
||||
|
|
||||
if (minY < 0 || maxY > canvas.getHeight() || preventScaling) { |
|
||||
var lastScaleY = this.lastScaleY || 1; |
|
||||
currentObject.setScaleY(lastScaleY); |
|
||||
} |
|
||||
if (minY < 0) { |
|
||||
currentObject.setTop(0); |
|
||||
} |
|
||||
|
|
||||
if (currentObject.getWidth() < this.options.minWidth) { |
|
||||
currentObject.scaleToWidth(this.options.minWidth); |
|
||||
} |
|
||||
if (currentObject.getHeight() < this.options.minHeight) { |
|
||||
currentObject.scaleToHeight(this.options.minHeight); |
|
||||
} |
|
||||
|
|
||||
this.lastScaleX = currentObject.getScaleX(); |
|
||||
this.lastScaleY = currentObject.getScaleY(); |
|
||||
|
|
||||
this.darkroom.dispatchEvent('crop:update'); |
|
||||
}, |
|
||||
|
|
||||
// Init crop zone
|
|
||||
onMouseDown: function(event) { |
|
||||
if (!this.hasFocus()) { |
|
||||
return; |
|
||||
} |
|
||||
|
|
||||
var canvas = this.darkroom.canvas; |
|
||||
|
|
||||
// recalculate offset, in case canvas was manipulated since last `calcOffset`
|
|
||||
canvas.calcOffset(); |
|
||||
var pointer = canvas.getPointer(event.e); |
|
||||
var x = pointer.x; |
|
||||
var y = pointer.y; |
|
||||
var point = new fabric.Point(x, y); |
|
||||
|
|
||||
// Check if user want to scale or drag the crop zone.
|
|
||||
var activeObject = canvas.getActiveObject(); |
|
||||
if (activeObject === this.cropZone || this.cropZone.containsPoint(point)) { |
|
||||
return; |
|
||||
} |
|
||||
|
|
||||
canvas.discardActiveObject(); |
|
||||
this.cropZone.setWidth(0); |
|
||||
this.cropZone.setHeight(0); |
|
||||
this.cropZone.setScaleX(1); |
|
||||
this.cropZone.setScaleY(1); |
|
||||
|
|
||||
this.startX = x; |
|
||||
this.startY = y; |
|
||||
}, |
|
||||
|
|
||||
// Extend crop zone
|
|
||||
onMouseMove: function(event) { |
|
||||
// Quick crop feature
|
|
||||
if (this.isKeyCroping) |
|
||||
return this.onMouseMoveKeyCrop(event); |
|
||||
|
|
||||
if (null === this.startX || null === this.startY) { |
|
||||
return; |
|
||||
} |
|
||||
|
|
||||
var canvas = this.darkroom.canvas; |
|
||||
var pointer = canvas.getPointer(event.e); |
|
||||
var x = pointer.x; |
|
||||
var y = pointer.y; |
|
||||
|
|
||||
this._renderCropZone(this.startX, this.startY, x, y); |
|
||||
}, |
|
||||
|
|
||||
onMouseMoveKeyCrop: function(event) { |
|
||||
var canvas = this.darkroom.canvas; |
|
||||
var zone = this.cropZone; |
|
||||
|
|
||||
var pointer = canvas.getPointer(event.e); |
|
||||
var x = pointer.x; |
|
||||
var y = pointer.y; |
|
||||
|
|
||||
if (!zone.left || !zone.top) { |
|
||||
zone.setTop(y); |
|
||||
zone.setLeft(x); |
|
||||
} |
|
||||
|
|
||||
this.isKeyLeft = x < zone.left + zone.width / 2 ; |
|
||||
this.isKeyUp = y < zone.top + zone.height / 2 ; |
|
||||
|
|
||||
this._renderCropZone( |
|
||||
Math.min(zone.left, x), |
|
||||
Math.min(zone.top, y), |
|
||||
Math.max(zone.left+zone.width, x), |
|
||||
Math.max(zone.top+zone.height, y) |
|
||||
); |
|
||||
}, |
|
||||
|
|
||||
// Finish crop zone
|
|
||||
onMouseUp: function(event) { |
|
||||
if (null === this.startX || null === this.startY) { |
|
||||
return; |
|
||||
} |
|
||||
|
|
||||
var canvas = this.darkroom.canvas; |
|
||||
this.cropZone.setCoords(); |
|
||||
canvas.setActiveObject(this.cropZone); |
|
||||
canvas.calcOffset(); |
|
||||
|
|
||||
this.startX = null; |
|
||||
this.startY = null; |
|
||||
}, |
|
||||
|
|
||||
onKeyDown: function(event) { |
|
||||
if (false === this.options.quickCropKey || event.keyCode !== this.options.quickCropKey || this.isKeyCroping) |
|
||||
return; |
|
||||
|
|
||||
// Active quick crop flow
|
|
||||
this.isKeyCroping = true ; |
|
||||
this.darkroom.canvas.discardActiveObject(); |
|
||||
this.cropZone.setWidth(0); |
|
||||
this.cropZone.setHeight(0); |
|
||||
this.cropZone.setScaleX(1); |
|
||||
this.cropZone.setScaleY(1); |
|
||||
this.cropZone.setTop(0); |
|
||||
this.cropZone.setLeft(0); |
|
||||
}, |
|
||||
|
|
||||
onKeyUp: function(event) { |
|
||||
if (false === this.options.quickCropKey || event.keyCode !== this.options.quickCropKey || !this.isKeyCroping) |
|
||||
return; |
|
||||
|
|
||||
// Unactive quick crop flow
|
|
||||
this.isKeyCroping = false; |
|
||||
this.startX = 1; |
|
||||
this.startY = 1; |
|
||||
this.onMouseUp(); |
|
||||
}, |
|
||||
|
|
||||
selectZone: function(x, y, width, height, forceDimension) { |
|
||||
if (!this.hasFocus()) |
|
||||
this.requireFocus(); |
|
||||
|
|
||||
if (!forceDimension) { |
|
||||
this._renderCropZone(x, y, x+width, y+height); |
|
||||
} else { |
|
||||
this.cropZone.set({ |
|
||||
'left': x, |
|
||||
'top': y, |
|
||||
'width': width, |
|
||||
'height': height |
|
||||
}); |
|
||||
} |
|
||||
|
|
||||
var canvas = this.darkroom.canvas; |
|
||||
canvas.bringToFront(this.cropZone); |
|
||||
this.cropZone.setCoords(); |
|
||||
canvas.setActiveObject(this.cropZone); |
|
||||
canvas.calcOffset(); |
|
||||
|
|
||||
this.darkroom.dispatchEvent('crop:update'); |
|
||||
}, |
|
||||
|
|
||||
toggleCrop: function() { |
|
||||
if (!this.hasFocus()) |
|
||||
this.requireFocus(); |
|
||||
else |
|
||||
this.releaseFocus(); |
|
||||
}, |
|
||||
|
|
||||
cropCurrentZone: function() { |
|
||||
if (!this.hasFocus()) |
|
||||
return; |
|
||||
|
|
||||
// Avoid croping empty zone
|
|
||||
if (this.cropZone.width < 1 && this.cropZone.height < 1) |
|
||||
return; |
|
||||
|
|
||||
var image = this.darkroom.image; |
|
||||
|
|
||||
// Compute crop zone dimensions
|
|
||||
var top = this.cropZone.getTop() - image.getTop(); |
|
||||
var left = this.cropZone.getLeft() - image.getLeft(); |
|
||||
var width = this.cropZone.getWidth(); |
|
||||
var height = this.cropZone.getHeight(); |
|
||||
|
|
||||
// Adjust dimensions to image only
|
|
||||
if (top < 0) { |
|
||||
height += top; |
|
||||
top = 0; |
|
||||
} |
|
||||
|
|
||||
if (left < 0) { |
|
||||
width += left; |
|
||||
left = 0; |
|
||||
} |
|
||||
|
|
||||
// Apply crop transformation.
|
|
||||
// Make sure to use relative dimension since the crop will be applied
|
|
||||
// on the source image.
|
|
||||
this.darkroom.applyTransformation(new Crop({ |
|
||||
top: top / image.getHeight(), |
|
||||
left: left / image.getWidth(), |
|
||||
width: width / image.getWidth(), |
|
||||
height: height / image.getHeight(), |
|
||||
})); |
|
||||
}, |
|
||||
|
|
||||
// Test wether crop zone is set
|
|
||||
hasFocus: function() { |
|
||||
return this.cropZone !== undefined; |
|
||||
}, |
|
||||
|
|
||||
// Create the crop zone
|
|
||||
requireFocus: function() { |
|
||||
this.cropZone = new CropZone({ |
|
||||
fill: 'transparent', |
|
||||
hasBorders: false, |
|
||||
originX: 'left', |
|
||||
originY: 'top', |
|
||||
//stroke: '#444',
|
|
||||
//strokeDashArray: [5, 5],
|
|
||||
//borderColor: '#444',
|
|
||||
cornerColor: '#444', |
|
||||
cornerSize: 8, |
|
||||
transparentCorners: false, |
|
||||
lockRotation: true, |
|
||||
hasRotatingPoint: false, |
|
||||
}); |
|
||||
|
|
||||
if (null !== this.options.ratio) { |
|
||||
this.cropZone.set('lockUniScaling', true); |
|
||||
} |
|
||||
|
|
||||
this.darkroom.canvas.add(this.cropZone); |
|
||||
this.darkroom.canvas.defaultCursor = 'crosshair'; |
|
||||
|
|
||||
this.cropButton.active(true); |
|
||||
this.okButton.hide(false); |
|
||||
this.cancelButton.hide(false); |
|
||||
}, |
|
||||
|
|
||||
// Remove the crop zone
|
|
||||
releaseFocus: function() { |
|
||||
if (undefined === this.cropZone) |
|
||||
return; |
|
||||
|
|
||||
this.cropZone.remove(); |
|
||||
this.cropZone = undefined; |
|
||||
|
|
||||
this.cropButton.active(false); |
|
||||
this.okButton.hide(true); |
|
||||
this.cancelButton.hide(true); |
|
||||
|
|
||||
this.darkroom.canvas.defaultCursor = 'default'; |
|
||||
|
|
||||
this.darkroom.dispatchEvent('crop:update'); |
|
||||
}, |
|
||||
|
|
||||
_renderCropZone: function(fromX, fromY, toX, toY) { |
|
||||
var canvas = this.darkroom.canvas; |
|
||||
|
|
||||
var isRight = (toX > fromX); |
|
||||
var isLeft = !isRight; |
|
||||
var isDown = (toY > fromY); |
|
||||
var isUp = !isDown; |
|
||||
|
|
||||
var minWidth = Math.min(+this.options.minWidth, canvas.getWidth()); |
|
||||
var minHeight = Math.min(+this.options.minHeight, canvas.getHeight()); |
|
||||
|
|
||||
// Define corner coordinates
|
|
||||
var leftX = Math.min(fromX, toX); |
|
||||
var rightX = Math.max(fromX, toX); |
|
||||
var topY = Math.min(fromY, toY); |
|
||||
var bottomY = Math.max(fromY, toY); |
|
||||
|
|
||||
// Replace current point into the canvas
|
|
||||
leftX = Math.max(0, leftX); |
|
||||
rightX = Math.min(canvas.getWidth(), rightX); |
|
||||
topY = Math.max(0, topY) |
|
||||
bottomY = Math.min(canvas.getHeight(), bottomY); |
|
||||
|
|
||||
// Recalibrate coordinates according to given options
|
|
||||
if (rightX - leftX < minWidth) { |
|
||||
if (isRight) |
|
||||
rightX = leftX + minWidth; |
|
||||
else |
|
||||
leftX = rightX - minWidth; |
|
||||
} |
|
||||
if (bottomY - topY < minHeight) { |
|
||||
if (isDown) |
|
||||
bottomY = topY + minHeight; |
|
||||
else |
|
||||
topY = bottomY - minHeight; |
|
||||
} |
|
||||
|
|
||||
// Truncate truncate according to canvas dimensions
|
|
||||
if (leftX < 0) { |
|
||||
// Translate to the left
|
|
||||
rightX += Math.abs(leftX); |
|
||||
leftX = 0 |
|
||||
} |
|
||||
if (rightX > canvas.getWidth()) { |
|
||||
// Translate to the right
|
|
||||
leftX -= (rightX - canvas.getWidth()); |
|
||||
rightX = canvas.getWidth(); |
|
||||
} |
|
||||
if (topY < 0) { |
|
||||
// Translate to the bottom
|
|
||||
bottomY += Math.abs(topY); |
|
||||
topY = 0 |
|
||||
} |
|
||||
if (bottomY > canvas.getHeight()) { |
|
||||
// Translate to the right
|
|
||||
topY -= (bottomY - canvas.getHeight()); |
|
||||
bottomY = canvas.getHeight(); |
|
||||
} |
|
||||
|
|
||||
var width = rightX - leftX; |
|
||||
var height = bottomY - topY; |
|
||||
var currentRatio = width / height; |
|
||||
|
|
||||
if (this.options.ratio && +this.options.ratio !== currentRatio) { |
|
||||
var ratio = +this.options.ratio; |
|
||||
|
|
||||
if(this.isKeyCroping) { |
|
||||
isLeft = this.isKeyLeft; |
|
||||
isUp = this.isKeyUp; |
|
||||
} |
|
||||
|
|
||||
if (currentRatio < ratio) { |
|
||||
var newWidth = height * ratio; |
|
||||
if (isLeft) { |
|
||||
leftX -= (newWidth - width); |
|
||||
} |
|
||||
width = newWidth; |
|
||||
} else if (currentRatio > ratio) { |
|
||||
var newHeight = height / (ratio * height/width); |
|
||||
if (isUp) { |
|
||||
topY -= (newHeight - height); |
|
||||
} |
|
||||
height = newHeight; |
|
||||
} |
|
||||
|
|
||||
if (leftX < 0) { |
|
||||
leftX = 0; |
|
||||
//TODO
|
|
||||
} |
|
||||
if (topY < 0) { |
|
||||
topY = 0; |
|
||||
//TODO
|
|
||||
} |
|
||||
if (leftX + width > canvas.getWidth()) { |
|
||||
var newWidth = canvas.getWidth() - leftX; |
|
||||
height = newWidth * height / width; |
|
||||
width = newWidth; |
|
||||
if (isUp) { |
|
||||
topY = fromY - height; |
|
||||
} |
|
||||
} |
|
||||
if (topY + height > canvas.getHeight()) { |
|
||||
var newHeight = canvas.getHeight() - topY; |
|
||||
width = width * newHeight / height; |
|
||||
height = newHeight; |
|
||||
if (isLeft) { |
|
||||
leftX = fromX - width; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
// Apply coordinates
|
|
||||
this.cropZone.left = leftX; |
|
||||
this.cropZone.top = topY; |
|
||||
this.cropZone.width = width; |
|
||||
this.cropZone.height = height; |
|
||||
|
|
||||
this.darkroom.canvas.bringToFront(this.cropZone); |
|
||||
|
|
||||
this.darkroom.dispatchEvent('crop:update'); |
|
||||
} |
|
||||
}); |
|
||||
|
|
||||
})(); |
|
@ -1,66 +0,0 @@ |
|||||
;(function(window, document, Darkroom, fabric) { |
|
||||
'use strict'; |
|
||||
|
|
||||
Darkroom.plugins['history'] = Darkroom.Plugin.extend({ |
|
||||
undoTransformations: [], |
|
||||
|
|
||||
initialize: function InitDarkroomHistoryPlugin() { |
|
||||
this._initButtons(); |
|
||||
|
|
||||
this.darkroom.addEventListener('core:transformation', this._onTranformationApplied.bind(this)); |
|
||||
}, |
|
||||
|
|
||||
undo: function() { |
|
||||
if (this.darkroom.transformations.length === 0) { |
|
||||
return; |
|
||||
} |
|
||||
|
|
||||
var lastTransformation = this.darkroom.transformations.pop(); |
|
||||
this.undoTransformations.unshift(lastTransformation); |
|
||||
|
|
||||
this.darkroom.reinitializeImage(); |
|
||||
this._updateButtons(); |
|
||||
}, |
|
||||
|
|
||||
redo: function() { |
|
||||
if (this.undoTransformations.length === 0) { |
|
||||
return; |
|
||||
} |
|
||||
|
|
||||
var cancelTransformation = this.undoTransformations.shift(); |
|
||||
this.darkroom.transformations.push(cancelTransformation); |
|
||||
|
|
||||
this.darkroom.reinitializeImage(); |
|
||||
this._updateButtons(); |
|
||||
}, |
|
||||
|
|
||||
_initButtons: function() { |
|
||||
var buttonGroup = this.darkroom.toolbar.createButtonGroup(); |
|
||||
|
|
||||
this.backButton = buttonGroup.createButton({ |
|
||||
image: 'undo', |
|
||||
disabled: true |
|
||||
}); |
|
||||
|
|
||||
this.forwardButton = buttonGroup.createButton({ |
|
||||
image: 'redo', |
|
||||
disabled: true |
|
||||
}); |
|
||||
|
|
||||
this.backButton.addEventListener('click', this.undo.bind(this)); |
|
||||
this.forwardButton.addEventListener('click', this.redo.bind(this)); |
|
||||
|
|
||||
return this; |
|
||||
}, |
|
||||
|
|
||||
_updateButtons: function() { |
|
||||
this.backButton.disable((this.darkroom.transformations.length === 0)) |
|
||||
this.forwardButton.disable((this.undoTransformations.length === 0)) |
|
||||
}, |
|
||||
|
|
||||
_onTranformationApplied: function() { |
|
||||
this.undoTransformations = []; |
|
||||
this._updateButtons(); |
|
||||
} |
|
||||
}); |
|
||||
})(window, document, Darkroom, fabric); |
|
@ -1,57 +0,0 @@ |
|||||
(function() { |
|
||||
'use strict'; |
|
||||
|
|
||||
var Rotation = Darkroom.Transformation.extend({ |
|
||||
applyTransformation: function(canvas, image, next) { |
|
||||
var angle = (image.getAngle() + this.options.angle) % 360; |
|
||||
image.rotate(angle); |
|
||||
|
|
||||
var width, height; |
|
||||
height = Math.abs(image.getWidth()*(Math.sin(angle*Math.PI/180)))+Math.abs(image.getHeight()*(Math.cos(angle*Math.PI/180))); |
|
||||
width = Math.abs(image.getHeight()*(Math.sin(angle*Math.PI/180)))+Math.abs(image.getWidth()*(Math.cos(angle*Math.PI/180))); |
|
||||
|
|
||||
canvas.setWidth(width); |
|
||||
canvas.setHeight(height); |
|
||||
|
|
||||
canvas.centerObject(image); |
|
||||
image.setCoords(); |
|
||||
canvas.renderAll(); |
|
||||
|
|
||||
next(); |
|
||||
} |
|
||||
}); |
|
||||
|
|
||||
Darkroom.plugins['rotate'] = Darkroom.Plugin.extend({ |
|
||||
|
|
||||
initialize: function InitDarkroomRotatePlugin() { |
|
||||
var buttonGroup = this.darkroom.toolbar.createButtonGroup(); |
|
||||
|
|
||||
var leftButton = buttonGroup.createButton({ |
|
||||
image: 'rotate-left' |
|
||||
}); |
|
||||
|
|
||||
var rightButton = buttonGroup.createButton({ |
|
||||
image: 'rotate-right' |
|
||||
}); |
|
||||
|
|
||||
leftButton.addEventListener('click', this.rotateLeft.bind(this)); |
|
||||
rightButton.addEventListener('click', this.rotateRight.bind(this)); |
|
||||
}, |
|
||||
|
|
||||
rotateLeft: function rotateLeft() { |
|
||||
this.rotate(-90); |
|
||||
}, |
|
||||
|
|
||||
rotateRight: function rotateRight() { |
|
||||
this.rotate(90); |
|
||||
}, |
|
||||
|
|
||||
rotate: function rotate(angle) { |
|
||||
this.darkroom.applyTransformation( |
|
||||
new Rotation({angle: angle}) |
|
||||
); |
|
||||
} |
|
||||
|
|
||||
}); |
|
||||
|
|
||||
})(); |
|
@ -1,23 +0,0 @@ |
|||||
(function() { |
|
||||
'use strict'; |
|
||||
|
|
||||
Darkroom.plugins['save'] = Darkroom.Plugin.extend({ |
|
||||
|
|
||||
defaults: { |
|
||||
callback: function() { |
|
||||
this.darkroom.selfDestroy(); |
|
||||
} |
|
||||
}, |
|
||||
|
|
||||
initialize: function InitializeDarkroomSavePlugin() { |
|
||||
var buttonGroup = this.darkroom.toolbar.createButtonGroup(); |
|
||||
|
|
||||
this.destroyButton = buttonGroup.createButton({ |
|
||||
image: 'save' |
|
||||
}); |
|
||||
|
|
||||
this.destroyButton.addEventListener('click', this.options.callback.bind(this)); |
|
||||
}, |
|
||||
}); |
|
||||
|
|
||||
})(); |
|
@ -1,39 +0,0 @@ |
|||||
{ |
|
||||
"name": "darkroom", |
|
||||
"description": "Extensible image editing tool via HTML canvas", |
|
||||
"version": "2.0.1", |
|
||||
"license": "MIT", |
|
||||
"homepage": "https://mattketmo.github.io/darkroomjs", |
|
||||
"repository": { |
|
||||
"type": "git", |
|
||||
"url": "https://github.com/mattketmo/darkroomjs.git" |
|
||||
}, |
|
||||
"author": "Matthieu Moquet <matthieu@moquet.net> (http://moquet.net/)", |
|
||||
"dependencies": {}, |
|
||||
"devDependencies": { |
|
||||
"cheerio": "^0.18.0", |
|
||||
"gulp": "^3.8.6", |
|
||||
"gulp-concat": "^2.3.4", |
|
||||
"gulp-connect": "^2.0.6", |
|
||||
"gulp-inject": "^1.2.0", |
|
||||
"gulp-plumber": "^0.6.4", |
|
||||
"gulp-sass": "^0.7.2", |
|
||||
"gulp-sourcemaps": "^1.1.0", |
|
||||
"gulp-svgmin": "^1.1.1", |
|
||||
"gulp-svgstore": "^5.0.0", |
|
||||
"gulp-uglify": "^0.3.1", |
|
||||
"gulp-util": "^3.0.0", |
|
||||
"js-string-escape": "^1.0.0", |
|
||||
"rimraf": "^2.2.8", |
|
||||
"streamqueue": "^0.1.1" |
|
||||
}, |
|
||||
"scripts": { |
|
||||
"start": "node_modules/.bin/gulp server build --prod", |
|
||||
"develop": "node_modules/.bin/gulp" |
|
||||
}, |
|
||||
"ignore": [ |
|
||||
"**/.*", |
|
||||
"node_modules", |
|
||||
"bower_components" |
|
||||
] |
|
||||
} |
|
@ -1,11 +0,0 @@ |
|||||
/*.darkroom-container{ |
|
||||
padding-top: 50px; |
|
||||
} |
|
||||
.darkroom-toolbar{ |
|
||||
top: 5px; |
|
||||
} |
|
||||
*/ |
|
||||
|
|
||||
.darkroom-button-group{ |
|
||||
display: inline; |
|
||||
} |
|
@ -1,20 +0,0 @@ |
|||||
/* Copyright 2016 LasLabs Inc. |
|
||||
* License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html).
|
|
||||
*/ |
|
||||
|
|
||||
odoo.define('web_widget_darkroom.darkroom_plugins', function(require){ |
|
||||
"use strict"; |
|
||||
|
|
||||
var DarkroomPlugins = Object; |
|
||||
DarkroomPlugins.extend = function(destination, source) { |
|
||||
for (var property in source) { |
|
||||
if (source.hasOwnProperty(property)) { |
|
||||
destination[property] = source[property]; |
|
||||
} |
|
||||
} |
|
||||
return destination; |
|
||||
}; |
|
||||
|
|
||||
return DarkroomPlugins |
|
||||
|
|
||||
}); |
|
@ -1,246 +0,0 @@ |
|||||
/* © 2016-TODAY LasLabs Inc. |
|
||||
* License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). |
|
||||
*/ |
|
||||
|
|
||||
odoo.define('web_widget_darkroom.darkroom_widget', function(require){ |
|
||||
"use strict"; |
|
||||
|
|
||||
var core = require('web.core'); |
|
||||
var common = require('web.form_common'); |
|
||||
var session = require('web.session'); |
|
||||
var utils = require('web.utils'); |
|
||||
var framework = require('web.framework'); |
|
||||
var crash_manager = require('web.crash_manager'); |
|
||||
|
|
||||
var QWeb = core.qweb; |
|
||||
var _t = core._t; |
|
||||
|
|
||||
var FieldDarkroomImage = common.AbstractField.extend(common.ReinitializeFieldMixin, { |
|
||||
className: 'darkroom-widget', |
|
||||
template: 'FieldDarkroomImage', |
|
||||
placeholder: "/web/static/src/img/placeholder.png", |
|
||||
darkroom: null, |
|
||||
no_rerender: false, |
|
||||
|
|
||||
_init_darkroom_icons: function() { |
|
||||
var element = document.createElement('div'); |
|
||||
element.id = 'darkroom-icons'; |
|
||||
element.style.height = 0; |
|
||||
element.style.width = 0; |
|
||||
element.style.position = 'absolute'; |
|
||||
element.style.visibility = 'hidden'; |
|
||||
element.innerHTML = '<!-- inject:svg --><!-- endinject -->'; |
|
||||
this.el.appendChild(element); |
|
||||
}, |
|
||||
|
|
||||
_init_darkroom_plugins: function(){ |
|
||||
require('web_widget_darkroom.darkroom_crop').DarkroomPluginCrop(); |
|
||||
require('web_widget_darkroom.darkroom_history').DarkroomPluginHistory(); |
|
||||
require('web_widget_darkroom.darkroom_rotate').DarkroomPluginRotate(); |
|
||||
require('web_widget_darkroom.darkroom_zoom').DarkroomPluginZoom(); |
|
||||
require('web_widget_darkroom.darkroom_save').DarkroomPluginSave(); |
|
||||
}, |
|
||||
|
|
||||
_init_darkroom_ui: function() { |
|
||||
|
|
||||
this._init_darkroom_icons(); |
|
||||
|
|
||||
Darkroom.UI = { |
|
||||
Toolbar: Toolbar, |
|
||||
ButtonGroup: ButtonGroup, |
|
||||
Button: Button, |
|
||||
}; |
|
||||
|
|
||||
// Toolbar object. |
|
||||
function Toolbar(element) { |
|
||||
this.element = element; |
|
||||
} |
|
||||
|
|
||||
Toolbar.prototype = { |
|
||||
createButtonGroup: function(options) { |
|
||||
var buttonGroup = document.createElement('div'); |
|
||||
buttonGroup.className = 'darkroom-button-group'; |
|
||||
this.element.appendChild(buttonGroup); |
|
||||
|
|
||||
return new ButtonGroup(buttonGroup); |
|
||||
} |
|
||||
}; |
|
||||
|
|
||||
// ButtonGroup object. |
|
||||
function ButtonGroup(element) { |
|
||||
this.element = element; |
|
||||
} |
|
||||
|
|
||||
ButtonGroup.prototype = { |
|
||||
createButton: function(options) { |
|
||||
var defaults = { |
|
||||
image: 'fa fa-question-circle', |
|
||||
type: 'default', |
|
||||
group: 'default', |
|
||||
hide: false, |
|
||||
disabled: false, |
|
||||
editOnly: false, |
|
||||
addClass: '', |
|
||||
}; |
|
||||
|
|
||||
options = Darkroom.Utils.extend(options, defaults); |
|
||||
|
|
||||
var buttonElement = document.createElement('button'); |
|
||||
buttonElement.type = 'button'; |
|
||||
buttonElement.className = 'darkroom-button darkroom-button-' + options.type; |
|
||||
buttonElement.innerHTML = '<i class="' + options.image + ' fa-2x"></i>'; |
|
||||
if (options.editOnly) { |
|
||||
buttonElement.classList.add('oe_edit_only'); |
|
||||
} |
|
||||
<<<<<<< Updated upstream |
|
||||
if (options.addClass) { |
|
||||
buttonElement.classList.add(options.addClass); |
|
||||
} |
|
||||
// buttonElement.innerHTML = '<svg class="darkroom-icon"><use xlink:href="#' + options.image + '" /></svg>'; |
|
||||
======= |
|
||||
>>>>>>> Stashed changes |
|
||||
this.element.appendChild(buttonElement); |
|
||||
|
|
||||
var button = new Button(buttonElement); |
|
||||
button.hide(options.hide); |
|
||||
button.disable(options.disabled); |
|
||||
|
|
||||
return button; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
// Button object. |
|
||||
function Button(element) { |
|
||||
this.element = element; |
|
||||
} |
|
||||
|
|
||||
Button.prototype = { |
|
||||
addEventListener: function(eventName, listener) { |
|
||||
if (this.element.addEventListener){ |
|
||||
this.element.addEventListener(eventName, listener); |
|
||||
} else if (this.element.attachEvent) { |
|
||||
this.element.attachEvent('on' + eventName, listener); |
|
||||
} |
|
||||
}, |
|
||||
removeEventListener: function(eventName, listener) { |
|
||||
if (this.element.removeEventListener){ |
|
||||
this.element.removeEventListener(eventName, listener); |
|
||||
} |
|
||||
}, |
|
||||
active: function(value) { |
|
||||
if (value){ |
|
||||
this.element.classList.add('darkroom-button-active'); |
|
||||
this.element.disabled = false; |
|
||||
} else { |
|
||||
this.element.classList.remove('darkroom-button-active'); |
|
||||
this.element.disabled = true; |
|
||||
} |
|
||||
}, |
|
||||
hide: function(value) { |
|
||||
if (value) |
|
||||
this.element.classList.add('hidden'); |
|
||||
else |
|
||||
this.element.classList.remove('hidden'); |
|
||||
}, |
|
||||
disable: function(value) { |
|
||||
this.element.disabled = (value) ? true : false; |
|
||||
} |
|
||||
}; |
|
||||
|
|
||||
}, |
|
||||
|
|
||||
destroy_content: function() { |
|
||||
console.log('Destroying Darkroom Obj'); |
|
||||
this.darkroom.selfDestroy(); |
|
||||
}, |
|
||||
|
|
||||
render_value: function() { |
|
||||
console.log('Rerendering'); |
|
||||
var url; |
|
||||
if (this.get('value') && !utils.is_bin_size(this.get('value'))) { |
|
||||
url = 'data:image/png;base64,' + this.get('value'); |
|
||||
} else if (this.get('value')) { |
|
||||
var id = JSON.stringify(this.view.datarecord.id || null); |
|
||||
var field = this.name; |
|
||||
if (this.options.preview_image) |
|
||||
field = this.options.preview_image; |
|
||||
url = session.url('/web/image', { |
|
||||
model: this.view.dataset.model, |
|
||||
id: id, |
|
||||
field: field, |
|
||||
unique: (this.view.datarecord.__last_update || '').replace(/[^0-9]/g, ''), |
|
||||
}); |
|
||||
} else { |
|
||||
url = this.placeholder; |
|
||||
} |
|
||||
|
|
||||
var $img = $(QWeb.render("FieldBinaryImage-img", { widget: this, url: url })); |
|
||||
this.$el.find('> img').remove(); |
|
||||
this.$el.append($img); |
|
||||
|
|
||||
if (!this.darkroom) { |
|
||||
this._init_darkroom_ui(); |
|
||||
this._init_darkroom_plugins(); |
|
||||
} |
|
||||
this.darkroom = new Darkroom($img.get(0)); |
|
||||
this.darkroom.widget = this; |
|
||||
}, |
|
||||
|
|
||||
on_save_as: function(e) { |
|
||||
|
|
||||
framework.blockUI(); |
|
||||
var value = this.darkroom.sourceImage.toDataURL(); |
|
||||
var c = crash_manager; |
|
||||
var filename_fieldname = this.node.attrs.filename; |
|
||||
var filename_field = this.view.fields && this.view.fields[filename_fieldname]; |
|
||||
|
|
||||
var filereader = new FileReader(); |
|
||||
filereader.onload = function(upload) { |
|
||||
var data = upload.target.result; |
|
||||
data = data.split(',')[1]; |
|
||||
$.post({ |
|
||||
url: '/web/binary/upload', |
|
||||
|
|
||||
}) |
|
||||
}; |
|
||||
filereader.readAsDataURL(new Blob(value)); |
|
||||
|
|
||||
this.$el.find('form.o_form_darkroom_form input[name=ufile]').val(value); |
|
||||
this.$el.find('form.o_form_darkroom_form input[name=session_id]').val(this.session.session_id); |
|
||||
this.$el.find('form.o_form_darkroom_form').submit(); |
|
||||
|
|
||||
var $form = $(parentEl).find('form'); |
|
||||
$form.find('input[name=ufile]').val(value); |
|
||||
|
|
||||
}, |
|
||||
|
|
||||
init: function(field_manager, node) { |
|
||||
var self = this; |
|
||||
this._super(field_manager, node); |
|
||||
this.binary_value = false; |
|
||||
this.useFileAPI = !!window.FileReader; |
|
||||
this.max_upload_size = 25 * 1024 * 1024; // 25Mo |
|
||||
if (!this.useFileAPI) { |
|
||||
this.fileupload_id = _.uniqueId('oe_fileupload'); |
|
||||
$(window).on(this.fileupload_id, function() { |
|
||||
var args = [].slice.call(arguments).slice(1); |
|
||||
self.on_file_uploaded.apply(self, args); |
|
||||
}); |
|
||||
} |
|
||||
}, |
|
||||
stop: function() { |
|
||||
if (!this.useFileAPI) { |
|
||||
$(window).off(this.fileupload_id); |
|
||||
} |
|
||||
this._super.apply(this, arguments); |
|
||||
}, |
|
||||
|
|
||||
}); |
|
||||
|
|
||||
core.form_widget_registry.add("darkroom", FieldDarkroomImage); |
|
||||
|
|
||||
return { |
|
||||
FieldDarkroomImage: FieldDarkroomImage, |
|
||||
} |
|
||||
|
|
||||
}); |
|
@ -0,0 +1,64 @@ |
|||||
|
/** |
||||
|
* Copyright 2017 LasLabs Inc. |
||||
|
* License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html).
|
||||
|
**/ |
||||
|
|
||||
|
odoo.define('web_widget_darkroom.darkroom_modal_button', function(require) { |
||||
|
'use strict'; |
||||
|
|
||||
|
var core = require('web.core'); |
||||
|
var DataModel = require('web.DataModel'); |
||||
|
|
||||
|
core.form_widget_registry.get('image').include({ |
||||
|
// Used in template to prevent Darkroom buttons from being added to
|
||||
|
// forms for new records, which are not supported
|
||||
|
darkroom_supported: function() { |
||||
|
if (this.field_manager.dataset.index === null) { |
||||
|
return false; |
||||
|
} |
||||
|
return true; |
||||
|
}, |
||||
|
|
||||
|
render_value: function() { |
||||
|
this._super(); |
||||
|
|
||||
|
var imageWidget = this; |
||||
|
var activeModel = imageWidget.field_manager.dataset._model.name; |
||||
|
var activeRecordId = imageWidget.field_manager.datarecord.id; |
||||
|
var activeField = imageWidget.node.attrs.name; |
||||
|
|
||||
|
var updateImage = function() { |
||||
|
var ActiveModel = new DataModel(activeModel); |
||||
|
ActiveModel.query([activeField]). |
||||
|
filter([['id', '=', activeRecordId]]). |
||||
|
all(). |
||||
|
then(function(result) { |
||||
|
imageWidget.set_value(result[0].image); |
||||
|
}); |
||||
|
}; |
||||
|
|
||||
|
var openModal = function() { |
||||
|
var context = { |
||||
|
active_model: activeModel, |
||||
|
active_record_id: activeRecordId, |
||||
|
active_field: activeField, |
||||
|
}; |
||||
|
var modalAction = { |
||||
|
type: 'ir.actions.act_window', |
||||
|
res_model: 'darkroom.modal', |
||||
|
name: 'Darkroom', |
||||
|
views: [[false, 'form']], |
||||
|
target: 'new', |
||||
|
context: context, |
||||
|
}; |
||||
|
var options = {on_close: updateImage}; |
||||
|
imageWidget.do_action(modalAction, options); |
||||
|
}; |
||||
|
|
||||
|
var $button = this.$('.oe_form_binary_image_darkroom_modal'); |
||||
|
if ($button.length > 0) { |
||||
|
$button.click(openModal); |
||||
|
} |
||||
|
}, |
||||
|
}); |
||||
|
}); |
@ -0,0 +1,11 @@ |
|||||
|
.darkroom-button-group { |
||||
|
display: inline; |
||||
|
} |
||||
|
|
||||
|
.darkroom-button-active { |
||||
|
color: @odoo-brand-primary; |
||||
|
} |
||||
|
|
||||
|
.oe_form_field_image_controls i { |
||||
|
margin: 0 5%; |
||||
|
} |
@ -1,17 +1,30 @@ |
|||||
<?xml version="1.0" encoding="utf-8"?> |
<?xml version="1.0" encoding="utf-8"?> |
||||
|
|
||||
<!-- |
<!-- |
||||
Copyright 2016 LasLabs Inc. |
|
||||
|
Copyright 2016-2017 LasLabs Inc. |
||||
License LGPL-3 or later (http://www.gnu.org/licenses/lgpl.html). |
License LGPL-3 or later (http://www.gnu.org/licenses/lgpl.html). |
||||
--> |
--> |
||||
|
|
||||
<templates id="field_templates" xml:space="preserve"> |
<templates id="field_templates" xml:space="preserve"> |
||||
<t t-name="FieldDarkroomImage"> |
<t t-name="FieldDarkroomImage"> |
||||
<span class="oe_form_field o_form_field_darkroom" |
|
||||
t-att-style="widget.node.attrs.style"> |
|
||||
|
<span class="oe_form_field o_form_field_darkroom" t-att-style="widget.node.attrs.style"> |
||||
<t t-if="!widget.get('effective_readonly')"> |
<t t-if="!widget.get('effective_readonly')"> |
||||
<div class="darkroom-toolbar"/> |
<div class="darkroom-toolbar"/> |
||||
</t> |
</t> |
||||
</span> |
</span> |
||||
</t> |
</t> |
||||
|
|
||||
|
<t t-extend="FieldBinaryImage"> |
||||
|
<t t-jquery=".oe_form_binary_file_edit" t-operation="after"> |
||||
|
<t t-if="widget.darkroom_supported()"> |
||||
|
<i class="fa fa-picture-o fa-lg oe_form_binary_image_darkroom_modal" title="Darkroom"></i> |
||||
|
</t> |
||||
|
</t> |
||||
|
<t t-jquery=".oe_form_binary_file_edit" t-operation="replace"> |
||||
|
<i class="fa fa-pencil fa-lg oe_form_binary_file_edit" title="Edit"></i> |
||||
|
</t> |
||||
|
<t t-jquery=".oe_form_binary_file_clear" t-operation="replace"> |
||||
|
<i class="fa fa-trash-o fa-lg oe_form_binary_file_clear" title="Clear"></i> |
||||
|
</t> |
||||
|
</t> |
||||
</templates> |
</templates> |
@ -0,0 +1,5 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
# Copyright 2017 LasLabs Inc. |
||||
|
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). |
||||
|
|
||||
|
from . import test_darkroom_modal |
@ -0,0 +1,203 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
# Copyright 2017 LasLabs Inc. |
||||
|
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). |
||||
|
|
||||
|
from openerp.tests.common import TransactionCase |
||||
|
|
||||
|
|
||||
|
class TestDarkroomModal(TransactionCase): |
||||
|
|
||||
|
def test_default_res_model_id_model_in_context(self): |
||||
|
"""Should return correct ir.model record when context has model name""" |
||||
|
active_model = 'res.users' |
||||
|
test_model = self.env['darkroom.modal'].with_context({ |
||||
|
'active_model': active_model, |
||||
|
}) |
||||
|
test_result = test_model._default_res_model_id() |
||||
|
|
||||
|
expected = self.env['ir.model'].search([('model', '=', active_model)]) |
||||
|
self.assertEqual(test_result, expected) |
||||
|
|
||||
|
def test_default_res_model_id_no_valid_info_in_context(self): |
||||
|
"""Should return empty ir.model recordset when missing/invalid info""" |
||||
|
test_model = self.env['darkroom.modal'].with_context({}) |
||||
|
test_result = test_model._default_res_model_id() |
||||
|
|
||||
|
self.assertEqual(test_result, self.env['ir.model']) |
||||
|
|
||||
|
def test_default_res_record_id_id_in_context(self): |
||||
|
"""Should return correct value when ID in context""" |
||||
|
active_record_id = 5 |
||||
|
test_model = self.env['darkroom.modal'].with_context({ |
||||
|
'active_record_id': active_record_id, |
||||
|
}) |
||||
|
test_result = test_model._default_res_record_id() |
||||
|
|
||||
|
self.assertEqual(test_result, active_record_id) |
||||
|
|
||||
|
def test_default_res_record_id_no_id_in_context(self): |
||||
|
"""Should return 0 when no ID in context""" |
||||
|
test_model = self.env['darkroom.modal'].with_context({}) |
||||
|
test_result = test_model._default_res_record_id() |
||||
|
|
||||
|
self.assertEqual(test_result, 0) |
||||
|
|
||||
|
def test_default_res_record_model_and_id_in_context(self): |
||||
|
"""Should return correct record when context has model name and ID""" |
||||
|
active_model = 'res.users' |
||||
|
active_record_id = 1 |
||||
|
test_model = self.env['darkroom.modal'].with_context({ |
||||
|
'active_model': active_model, |
||||
|
'active_record_id': active_record_id, |
||||
|
}) |
||||
|
test_result = test_model._default_res_record() |
||||
|
|
||||
|
expected = self.env[active_model].browse(active_record_id) |
||||
|
self.assertEqual(test_result, expected) |
||||
|
|
||||
|
def test_default_res_record_model_but_no_id_in_context(self): |
||||
|
"""Should return right empty recordset if model but no ID in context""" |
||||
|
active_model = 'res.users' |
||||
|
test_model = self.env['darkroom.modal'].with_context({ |
||||
|
'active_model': active_model, |
||||
|
}) |
||||
|
test_result = test_model._default_res_record() |
||||
|
|
||||
|
self.assertEqual(test_result, self.env[active_model]) |
||||
|
|
||||
|
def test_default_res_record_no_valid_model_info_in_context(self): |
||||
|
"""Should return None if context has missing/invalid model info""" |
||||
|
active_model = 'bad.model.name' |
||||
|
test_model = self.env['darkroom.modal'].with_context({ |
||||
|
'active_model': active_model, |
||||
|
}) |
||||
|
test_result = test_model._default_res_record() |
||||
|
|
||||
|
self.assertIsNone(test_result) |
||||
|
|
||||
|
def test_default_res_field_id_model_and_field_in_context(self): |
||||
|
"""Should return correct ir.model.fields record when info in context""" |
||||
|
active_model = 'res.users' |
||||
|
active_field = 'name' |
||||
|
test_model = self.env['darkroom.modal'].with_context({ |
||||
|
'active_model': active_model, |
||||
|
'active_field': active_field, |
||||
|
}) |
||||
|
test_result = test_model._default_res_field_id() |
||||
|
|
||||
|
self.assertEqual(test_result.name, active_field) |
||||
|
self.assertEqual(test_result.model_id.model, active_model) |
||||
|
|
||||
|
def test_default_res_field_id_no_valid_field_in_context(self): |
||||
|
"""Should return empty recordset if field info missing/invalid""" |
||||
|
active_model = 'res.users' |
||||
|
active_field = 'totally.not.a.real.field.name' |
||||
|
test_model = self.env['darkroom.modal'].with_context({ |
||||
|
'active_model': active_model, |
||||
|
'active_field': active_field, |
||||
|
}) |
||||
|
test_result = test_model._default_res_field_id() |
||||
|
|
||||
|
self.assertEqual(test_result, self.env['ir.model.fields']) |
||||
|
|
||||
|
def test_default_res_field_id_no_valid_model_in_context(self): |
||||
|
"""Should return empty recordset if model info missing/invalid""" |
||||
|
active_field = 'name' |
||||
|
test_model = self.env['darkroom.modal'].with_context({ |
||||
|
'active_field': active_field, |
||||
|
}) |
||||
|
test_result = test_model._default_res_field_id() |
||||
|
|
||||
|
self.assertEqual(test_result, self.env['ir.model.fields']) |
||||
|
|
||||
|
def test_default_image_all_info_in_context(self): |
||||
|
"""Should return value of correct field if all info in context""" |
||||
|
active_model = 'res.users' |
||||
|
active_record_id = 1 |
||||
|
active_field = 'name' |
||||
|
test_model = self.env['darkroom.modal'].with_context({ |
||||
|
'active_model': active_model, |
||||
|
'active_record_id': active_record_id, |
||||
|
'active_field': active_field, |
||||
|
}) |
||||
|
test_result = test_model._default_image() |
||||
|
|
||||
|
expected = self.env[active_model].browse(active_record_id).name |
||||
|
self.assertEqual(test_result, expected) |
||||
|
|
||||
|
def test_default_image_no_valid_field_in_context(self): |
||||
|
"""Should return None if missing/invalid field info in context""" |
||||
|
active_model = 'res.users' |
||||
|
active_record_id = 1 |
||||
|
test_model = self.env['darkroom.modal'].with_context({ |
||||
|
'active_model': active_model, |
||||
|
'active_record_id': active_record_id, |
||||
|
}) |
||||
|
test_result = test_model._default_image() |
||||
|
|
||||
|
self.assertIsNone(test_result) |
||||
|
|
||||
|
def test_default_image_no_valid_id_in_context(self): |
||||
|
"""Should return False/None if missing/invalid record ID in context""" |
||||
|
active_model = 'res.users' |
||||
|
active_field = 'name' |
||||
|
test_model = self.env['darkroom.modal'].with_context({ |
||||
|
'active_model': active_model, |
||||
|
'active_field': active_field, |
||||
|
}) |
||||
|
test_result = test_model._default_image() |
||||
|
|
||||
|
self.assertFalse(test_result) |
||||
|
|
||||
|
def test_default_image_no_valid_model_in_context(self): |
||||
|
"""Should return None if missing/invalid model info in context""" |
||||
|
active_record_id = 1 |
||||
|
active_field = 'name' |
||||
|
test_model = self.env['darkroom.modal'].with_context({ |
||||
|
'active_record_id': active_record_id, |
||||
|
'active_field': active_field, |
||||
|
}) |
||||
|
test_result = test_model._default_image() |
||||
|
|
||||
|
self.assertIsNone(test_result) |
||||
|
|
||||
|
def test_action_save_record_count_in_self(self): |
||||
|
"""Should raise correct error if not called on recordset of 1""" |
||||
|
test_wizard = self.env['darkroom.modal'].with_context({ |
||||
|
'active_model': 'res.users', |
||||
|
'active_record_id': 1, |
||||
|
'active_field': 'name', |
||||
|
}).create({}) |
||||
|
test_wizard_set = test_wizard + test_wizard.copy() |
||||
|
|
||||
|
with self.assertRaises(ValueError): |
||||
|
self.env['darkroom.modal'].action_save() |
||||
|
with self.assertRaises(ValueError): |
||||
|
test_wizard_set.action_save() |
||||
|
|
||||
|
def test_action_save_update_source(self): |
||||
|
"""Should update source record correctly""" |
||||
|
active_model = 'res.users' |
||||
|
active_record_id = 1 |
||||
|
test_wizard = self.env['darkroom.modal'].with_context({ |
||||
|
'active_model': active_model, |
||||
|
'active_record_id': active_record_id, |
||||
|
'active_field': 'name', |
||||
|
}).create({}) |
||||
|
test_name = 'Test Name' |
||||
|
test_wizard.image = test_name |
||||
|
test_wizard.action_save() |
||||
|
|
||||
|
result = self.env[active_model].browse(active_record_id).name |
||||
|
self.assertEqual(result, test_name) |
||||
|
|
||||
|
def test_action_save_return_action(self): |
||||
|
"""Should return correct action""" |
||||
|
test_wizard = self.env['darkroom.modal'].with_context({ |
||||
|
'active_model': 'res.users', |
||||
|
'active_record_id': 1, |
||||
|
'active_field': 'name', |
||||
|
}).create({}) |
||||
|
test_value = test_wizard.action_save() |
||||
|
|
||||
|
self.assertEqual(test_value, {'type': 'ir.actions.act_window_close'}) |
@ -1,31 +1,28 @@ |
|||||
<?xml version="1.0" encoding="utf-8"?> |
<?xml version="1.0" encoding="utf-8"?> |
||||
|
|
||||
<!-- |
<!-- |
||||
Copyright 2016 LasLabs Inc. |
|
||||
|
Copyright 2016-2017 LasLabs Inc. |
||||
License LGPL-3 or later (http://www.gnu.org/licenses/lgpl.html). |
License LGPL-3 or later (http://www.gnu.org/licenses/lgpl.html). |
||||
--> |
--> |
||||
|
|
||||
<odoo> |
<odoo> |
||||
<template id="assets_darkroom" name="web_widget_darkroom Assets" inherit_id="web.assets_backend"> |
<template id="assets_darkroom" name="web_widget_darkroom Assets" inherit_id="web.assets_backend"> |
||||
<xpath expr="//script[last()]" position="after"> |
<xpath expr="//script[last()]" position="after"> |
||||
<!--<link href="/web_widget_darkroom/static/lib/darkroomjs/build/darkroom.css" rel="stylesheet" type="text/css" />--> |
|
||||
<link href="/web_widget_darkroom/static/src/css/darkroom.css" rel="stylesheet" type="text/css" /> |
|
||||
|
<link href="/web_widget_darkroom/static/src/less/darkroom.less" rel="stylesheet" type="text/less"/> |
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.5.0/fabric.require.min.js"/> |
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.5.0/fabric.require.min.js"/> |
||||
|
|
||||
<script src="/web_widget_darkroom/static/lib/darkroomjs/lib/js/core/darkroom.js" /> |
|
||||
<script src="/web_widget_darkroom/static/lib/darkroomjs/lib/js/core/plugin.js" /> |
|
||||
<script src="/web_widget_darkroom/static/lib/darkroomjs/lib/js/core/transformation.js" /> |
|
||||
<!--<script src="/web_widget_darkroom/static/lib/darkroomjs/lib/js/core/ui.js" />--> |
|
||||
<script src="/web_widget_darkroom/static/lib/darkroomjs/lib/js/core/utils.js" /> |
|
||||
|
<script src="/web_widget_darkroom/static/lib/darkroomjs/core/darkroom.js"/> |
||||
|
<script src="/web_widget_darkroom/static/lib/darkroomjs/core/plugin.js"/> |
||||
|
<script src="/web_widget_darkroom/static/lib/darkroomjs/core/transformation.js"/> |
||||
|
<script src="/web_widget_darkroom/static/lib/darkroomjs/core/utils.js"/> |
||||
|
|
||||
<script src="/web_widget_darkroom/static/src/js/plugins/darkroom.crop.js"/> |
<script src="/web_widget_darkroom/static/src/js/plugins/darkroom.crop.js"/> |
||||
<script src="/web_widget_darkroom/static/src/js/plugins/darkroom.history.js"/> |
<script src="/web_widget_darkroom/static/src/js/plugins/darkroom.history.js"/> |
||||
<script src="/web_widget_darkroom/static/src/js/plugins/darkroom.rotate.js"/> |
<script src="/web_widget_darkroom/static/src/js/plugins/darkroom.rotate.js"/> |
||||
<script src="/web_widget_darkroom/static/src/js/plugins/darkroom.save.js" /> |
|
||||
<script src="/web_widget_darkroom/static/src/js/plugins/darkroom.zoom.js"/> |
<script src="/web_widget_darkroom/static/src/js/plugins/darkroom.zoom.js"/> |
||||
|
|
||||
<script src="/web_widget_darkroom/static/src/js/widget_darkroom.js"/> |
<script src="/web_widget_darkroom/static/src/js/widget_darkroom.js"/> |
||||
|
|
||||
|
<script src="/web_widget_darkroom/static/src/js/widget_darkroom_modal.js"/> |
||||
</xpath> |
</xpath> |
||||
</template> |
</template> |
||||
</odoo> |
</odoo> |
@ -0,0 +1,5 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
# Copyright 2017 LasLabs Inc. |
||||
|
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). |
||||
|
|
||||
|
from . import darkroom_modal |
@ -0,0 +1,82 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
# Copyright 2017 LasLabs Inc. |
||||
|
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). |
||||
|
|
||||
|
from openerp import api, fields, models |
||||
|
from openerp.exceptions import MissingError |
||||
|
|
||||
|
|
||||
|
class DarkroomModal(models.TransientModel): |
||||
|
_name = 'darkroom.modal' |
||||
|
_description = 'Darkroom Modal - Wizard Model' |
||||
|
|
||||
|
@api.model |
||||
|
def _default_res_model_id(self): |
||||
|
res_model_name = self.env.context.get('active_model') |
||||
|
return self.env['ir.model'].search([('model', '=', res_model_name)]) |
||||
|
|
||||
|
@api.model |
||||
|
def _default_res_record_id(self): |
||||
|
return self.env.context.get('active_record_id', 0) |
||||
|
|
||||
|
@api.model |
||||
|
def _default_res_record(self): |
||||
|
res_model_name = self._default_res_model_id().model |
||||
|
try: |
||||
|
res_model_model = self.env[res_model_name] |
||||
|
except KeyError: |
||||
|
return None |
||||
|
|
||||
|
return res_model_model.browse(self._default_res_record_id()) |
||||
|
|
||||
|
@api.model |
||||
|
def _default_res_field_id(self): |
||||
|
res_model_id = self._default_res_model_id() |
||||
|
res_field_name = self.env.context.get('active_field') |
||||
|
return self.env['ir.model.fields'].search([ |
||||
|
('model_id', '=', res_model_id.id), |
||||
|
('name', '=', res_field_name), |
||||
|
]) |
||||
|
|
||||
|
@api.model |
||||
|
def _default_image(self): |
||||
|
res_record = self._default_res_record() |
||||
|
res_field_name = self._default_res_field_id().name |
||||
|
|
||||
|
try: |
||||
|
return getattr(res_record, res_field_name, None) |
||||
|
except (TypeError, MissingError): |
||||
|
return None |
||||
|
|
||||
|
res_model_id = fields.Many2one( |
||||
|
comodel_name='ir.model', |
||||
|
string='Source Model', |
||||
|
required=True, |
||||
|
default=lambda s: s._default_res_model_id(), |
||||
|
) |
||||
|
res_record_id = fields.Integer( |
||||
|
string='Source Record ID', |
||||
|
required=True, |
||||
|
default=lambda s: s._default_res_record_id(), |
||||
|
) |
||||
|
res_field_id = fields.Many2one( |
||||
|
comodel_name='ir.model.fields', |
||||
|
string='Source Field', |
||||
|
required=True, |
||||
|
default=lambda s: s._default_res_field_id(), |
||||
|
) |
||||
|
image = fields.Binary( |
||||
|
string='Darkroom Image', |
||||
|
required=True, |
||||
|
default=lambda s: s._default_image(), |
||||
|
) |
||||
|
|
||||
|
@api.multi |
||||
|
def action_save(self): |
||||
|
self.ensure_one() |
||||
|
|
||||
|
res_record = self._default_res_record() |
||||
|
res_field_name = self._default_res_field_id().name |
||||
|
setattr(res_record, res_field_name, self.image) |
||||
|
|
||||
|
return {'type': 'ir.actions.act_window_close'} |
@ -0,0 +1,27 @@ |
|||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||
|
|
||||
|
<!-- |
||||
|
Copyright 2017 LasLabs Inc. |
||||
|
License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). |
||||
|
--> |
||||
|
|
||||
|
<odoo> |
||||
|
<record id="darkroom_modal_view_form" model="ir.ui.view"> |
||||
|
<field name="name">Darkroom Modal Wizard</field> |
||||
|
<field name="model">darkroom.modal</field> |
||||
|
<field name="arch" type="xml"> |
||||
|
<form string="Darkroom Modal"> |
||||
|
<header /> |
||||
|
<sheet> |
||||
|
<group name="data"> |
||||
|
<field name="image" widget="darkroom" nolabel="1"/> |
||||
|
</group> |
||||
|
</sheet> |
||||
|
<footer> |
||||
|
<button special="cancel" string="Cancel" class="pull-left"/> |
||||
|
<button name="action_save" type="object" string="Save" class="oe_highlight pull-right"/> |
||||
|
</footer> |
||||
|
</form> |
||||
|
</field> |
||||
|
</record> |
||||
|
</odoo> |
Write
Preview
Loading…
Cancel
Save
Reference in new issue