From 112eba3e12a24a495c03882d3460d0a38957483e Mon Sep 17 00:00:00 2001 From: Dave Lasley Date: Tue, 23 Aug 2016 16:29:12 -0700 Subject: [PATCH] web_widget_darkroom: Add o2m DarkroomJS widget --- web_widget_darkroom/README.rst | 95 +++ web_widget_darkroom/__init__.py | 3 + web_widget_darkroom/__openerp__.py | 28 + web_widget_darkroom/demo/res_users.xml | 24 + .../static/description/icon.png | Bin 0 -> 9455 bytes .../static/lib/darkroomjs/.editorconfig | 9 + .../static/lib/darkroomjs/.gitignore | 5 + .../static/lib/darkroomjs/CHANGELOG.md | 23 + .../static/lib/darkroomjs/LICENSE | 18 + .../static/lib/darkroomjs/README.md | 88 +++ .../static/lib/darkroomjs/bower.json | 31 + .../static/lib/darkroomjs/gh-pages.sh | 21 + .../static/lib/darkroomjs/gulpfile.js | 112 +++ .../lib/darkroomjs/lib/css/_layout.scss | 12 + .../lib/darkroomjs/lib/css/_toolbar.scss | 99 +++ .../lib/darkroomjs/lib/css/darkroom.scss | 2 + .../static/lib/darkroomjs/lib/icons/close.svg | 4 + .../static/lib/darkroomjs/lib/icons/crop.svg | 4 + .../static/lib/darkroomjs/lib/icons/done.svg | 4 + .../static/lib/darkroomjs/lib/icons/redo.svg | 4 + .../lib/darkroomjs/lib/icons/rotate-left.svg | 4 + .../lib/darkroomjs/lib/icons/rotate-right.svg | 4 + .../static/lib/darkroomjs/lib/icons/save.svg | 4 + .../static/lib/darkroomjs/lib/icons/undo.svg | 4 + .../lib/darkroomjs/lib/js/core/bootstrap.js | 14 + .../lib/darkroomjs/lib/js/core/darkroom.js | 354 +++++++++ .../lib/darkroomjs/lib/js/core/plugin.js | 43 ++ .../darkroomjs/lib/js/core/transformation.js | 38 + .../static/lib/darkroomjs/lib/js/core/ui.js | 91 +++ .../lib/darkroomjs/lib/js/core/utils.js | 31 + .../lib/js/plugins/darkroom.crop.js | 669 +++++++++++++++++ .../lib/js/plugins/darkroom.history.js | 66 ++ .../lib/js/plugins/darkroom.rotate.js | 57 ++ .../lib/js/plugins/darkroom.save.js | 23 + .../static/lib/darkroomjs/package.json | 39 + .../static/src/css/darkroom.css | 11 + .../static/src/js/darkroom_plugins.js | 20 + .../static/src/js/plugins/darkroom.crop.js | 683 ++++++++++++++++++ .../static/src/js/plugins/darkroom.history.js | 80 ++ .../static/src/js/plugins/darkroom.rotate.js | 70 ++ .../static/src/js/plugins/darkroom.zoom.js | 198 +++++ .../static/src/js/widget_darkroom.js | 238 ++++++ .../static/src/js/widget_darkroom.js.orig | 246 +++++++ .../static/src/xml/field_templates.xml | 17 + web_widget_darkroom/views/assets.xml | 31 + 45 files changed, 3621 insertions(+) create mode 100755 web_widget_darkroom/README.rst create mode 100755 web_widget_darkroom/__init__.py create mode 100755 web_widget_darkroom/__openerp__.py create mode 100644 web_widget_darkroom/demo/res_users.xml create mode 100644 web_widget_darkroom/static/description/icon.png create mode 100755 web_widget_darkroom/static/lib/darkroomjs/.editorconfig create mode 100755 web_widget_darkroom/static/lib/darkroomjs/.gitignore create mode 100755 web_widget_darkroom/static/lib/darkroomjs/CHANGELOG.md create mode 100755 web_widget_darkroom/static/lib/darkroomjs/LICENSE create mode 100755 web_widget_darkroom/static/lib/darkroomjs/README.md create mode 100755 web_widget_darkroom/static/lib/darkroomjs/bower.json create mode 100755 web_widget_darkroom/static/lib/darkroomjs/gh-pages.sh create mode 100755 web_widget_darkroom/static/lib/darkroomjs/gulpfile.js create mode 100755 web_widget_darkroom/static/lib/darkroomjs/lib/css/_layout.scss create mode 100755 web_widget_darkroom/static/lib/darkroomjs/lib/css/_toolbar.scss create mode 100755 web_widget_darkroom/static/lib/darkroomjs/lib/css/darkroom.scss create mode 100755 web_widget_darkroom/static/lib/darkroomjs/lib/icons/close.svg create mode 100755 web_widget_darkroom/static/lib/darkroomjs/lib/icons/crop.svg create mode 100755 web_widget_darkroom/static/lib/darkroomjs/lib/icons/done.svg create mode 100755 web_widget_darkroom/static/lib/darkroomjs/lib/icons/redo.svg create mode 100755 web_widget_darkroom/static/lib/darkroomjs/lib/icons/rotate-left.svg create mode 100755 web_widget_darkroom/static/lib/darkroomjs/lib/icons/rotate-right.svg create mode 100755 web_widget_darkroom/static/lib/darkroomjs/lib/icons/save.svg create mode 100755 web_widget_darkroom/static/lib/darkroomjs/lib/icons/undo.svg create mode 100755 web_widget_darkroom/static/lib/darkroomjs/lib/js/core/bootstrap.js create mode 100755 web_widget_darkroom/static/lib/darkroomjs/lib/js/core/darkroom.js create mode 100755 web_widget_darkroom/static/lib/darkroomjs/lib/js/core/plugin.js create mode 100755 web_widget_darkroom/static/lib/darkroomjs/lib/js/core/transformation.js create mode 100755 web_widget_darkroom/static/lib/darkroomjs/lib/js/core/ui.js create mode 100755 web_widget_darkroom/static/lib/darkroomjs/lib/js/core/utils.js create mode 100755 web_widget_darkroom/static/lib/darkroomjs/lib/js/plugins/darkroom.crop.js create mode 100755 web_widget_darkroom/static/lib/darkroomjs/lib/js/plugins/darkroom.history.js create mode 100755 web_widget_darkroom/static/lib/darkroomjs/lib/js/plugins/darkroom.rotate.js create mode 100755 web_widget_darkroom/static/lib/darkroomjs/lib/js/plugins/darkroom.save.js create mode 100755 web_widget_darkroom/static/lib/darkroomjs/package.json create mode 100755 web_widget_darkroom/static/src/css/darkroom.css create mode 100644 web_widget_darkroom/static/src/js/darkroom_plugins.js create mode 100755 web_widget_darkroom/static/src/js/plugins/darkroom.crop.js create mode 100755 web_widget_darkroom/static/src/js/plugins/darkroom.history.js create mode 100755 web_widget_darkroom/static/src/js/plugins/darkroom.rotate.js create mode 100755 web_widget_darkroom/static/src/js/plugins/darkroom.zoom.js create mode 100644 web_widget_darkroom/static/src/js/widget_darkroom.js create mode 100644 web_widget_darkroom/static/src/js/widget_darkroom.js.orig create mode 100644 web_widget_darkroom/static/src/xml/field_templates.xml create mode 100644 web_widget_darkroom/views/assets.xml diff --git a/web_widget_darkroom/README.rst b/web_widget_darkroom/README.rst new file mode 100755 index 00000000..befb0c9f --- /dev/null +++ b/web_widget_darkroom/README.rst @@ -0,0 +1,95 @@ +.. image:: https://img.shields.io/badge/license-AGPL--3-blue.svg + :target: http://www.gnu.org/licenses/lgpl-3.0-standalone.html + :alt: License: LGPL-3 + +====================== +Odoo DarkroomJS Widget +====================== + +This module provides a `DarkroomJS`_ web widget for use with images fields. + +.. _DarkroomJS: https://github.com/MattKetmo/darkroomjs + +This widget will allow you to perform the following actions on images: + + * Zoom + * Rotate + * Crop + * Step back in history client-side (before save) + + +Usage +===== + +To use this module, you need to: + +* Install web_widget_darkroom +* Add the to any One2many image relation by using the `darkroom` widget. Options can be passed through to Darkroom using the `options` key:: + + + +The Odoo DarkroomJS widget passes options directly through to Darkroom, which are copied from the source below:: + + // 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 */ } + }, + + + +Known Issues/Roadmap +==================== + +* Plugins are not able to be added without inheriting, then redefining the widget in the registry due to JS inheritance. + ** This is not scalable because there would need to be an explicit dependency chain in order to avoid registry overwrite. + + + +Bug Tracker +=========== + +Bugs are tracked on `GitHub Issues +`_. In case of trouble, please +check there if your issue has already been reported. If you spotted it first, +help us smashing it by providing a detailed and welcomed feedback. + +Credits +======= + +Images +------ + +* Odoo Community Association: `Icon `_. + +Contributors +------------ + +* Dave Lasley + +Maintainer +---------- + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +This module is maintained by the OCA. + +OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use. + +To contribute to this module, please visit https://odoo-community.org. diff --git a/web_widget_darkroom/__init__.py b/web_widget_darkroom/__init__.py new file mode 100755 index 00000000..08d9d6b0 --- /dev/null +++ b/web_widget_darkroom/__init__.py @@ -0,0 +1,3 @@ +# -*- coding: utf-8 -*- +# Copyright 2016 LasLabs Inc. +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). diff --git a/web_widget_darkroom/__openerp__.py b/web_widget_darkroom/__openerp__.py new file mode 100755 index 00000000..9bc1c568 --- /dev/null +++ b/web_widget_darkroom/__openerp__.py @@ -0,0 +1,28 @@ +# -*- coding: utf-8 -*- +# Copyright 2016 LasLabs Inc. +# 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", + ], + "data": [ + 'views/assets.xml', + ], + 'qweb': [ + "static/src/xml/field_templates.xml", + ], + 'demo': [ + 'demo/res_users.xml', + ] +} diff --git a/web_widget_darkroom/demo/res_users.xml b/web_widget_darkroom/demo/res_users.xml new file mode 100644 index 00000000..43a17825 --- /dev/null +++ b/web_widget_darkroom/demo/res_users.xml @@ -0,0 +1,24 @@ + + + + + + + + res.users.form.darkroom + res.users + + + + + + + + + + + + diff --git a/web_widget_darkroom/static/description/icon.png b/web_widget_darkroom/static/description/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..3a0328b516c4980e8e44cdb63fd945757ddd132d GIT binary patch literal 9455 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 z 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 Xviia!U7SGha1wx#SCgwmn*{w2TRX*I literal 0 HcmV?d00001 diff --git a/web_widget_darkroom/static/lib/darkroomjs/.editorconfig b/web_widget_darkroom/static/lib/darkroomjs/.editorconfig new file mode 100755 index 00000000..8c98bb17 --- /dev/null +++ b/web_widget_darkroom/static/lib/darkroomjs/.editorconfig @@ -0,0 +1,9 @@ +root = true + +[*] +charset = utf-8 +end_of_line = LF +indent_style = space +trim_trailing_whitespace = true +insert_final_newline = true +indent_size = 2 diff --git a/web_widget_darkroom/static/lib/darkroomjs/.gitignore b/web_widget_darkroom/static/lib/darkroomjs/.gitignore new file mode 100755 index 00000000..abad6da3 --- /dev/null +++ b/web_widget_darkroom/static/lib/darkroomjs/.gitignore @@ -0,0 +1,5 @@ +/.sass-cache/ +/bower_components/ +/build/ +/node_modules/ +/docs/ diff --git a/web_widget_darkroom/static/lib/darkroomjs/CHANGELOG.md b/web_widget_darkroom/static/lib/darkroomjs/CHANGELOG.md new file mode 100755 index 00000000..17b13903 --- /dev/null +++ b/web_widget_darkroom/static/lib/darkroomjs/CHANGELOG.md @@ -0,0 +1,23 @@ +# 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 diff --git a/web_widget_darkroom/static/lib/darkroomjs/LICENSE b/web_widget_darkroom/static/lib/darkroomjs/LICENSE new file mode 100755 index 00000000..35541ed0 --- /dev/null +++ b/web_widget_darkroom/static/lib/darkroomjs/LICENSE @@ -0,0 +1,18 @@ +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. diff --git a/web_widget_darkroom/static/lib/darkroomjs/README.md b/web_widget_darkroom/static/lib/darkroomjs/README.md new file mode 100755 index 00000000..5ba31b0b --- /dev/null +++ b/web_widget_darkroom/static/lib/darkroomjs/README.md @@ -0,0 +1,88 @@ +# 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 + + +``` + +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. + diff --git a/web_widget_darkroom/static/lib/darkroomjs/bower.json b/web_widget_darkroom/static/lib/darkroomjs/bower.json new file mode 100755 index 00000000..aa38ffe7 --- /dev/null +++ b/web_widget_darkroom/static/lib/darkroomjs/bower.json @@ -0,0 +1,31 @@ +{ + "name": "darkroom", + "description": "Extensible image editing tool via HTML canvas", + "version": "2.0.0", + "homepage": "http://mattketmo.github.io/darkroomjs/", + "authors": [ + "Matthieu Moquet " + ], + "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" + ] +} diff --git a/web_widget_darkroom/static/lib/darkroomjs/gh-pages.sh b/web_widget_darkroom/static/lib/darkroomjs/gh-pages.sh new file mode 100755 index 00000000..2ae99a52 --- /dev/null +++ b/web_widget_darkroom/static/lib/darkroomjs/gh-pages.sh @@ -0,0 +1,21 @@ +#!/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 - diff --git a/web_widget_darkroom/static/lib/darkroomjs/gulpfile.js b/web_widget_darkroom/static/lib/darkroomjs/gulpfile.js new file mode 100755 index 00000000..dced1e88 --- /dev/null +++ b/web_widget_darkroom/static/lib/darkroomjs/gulpfile.js @@ -0,0 +1,112 @@ +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)) +}) diff --git a/web_widget_darkroom/static/lib/darkroomjs/lib/css/_layout.scss b/web_widget_darkroom/static/lib/darkroomjs/lib/css/_layout.scss new file mode 100755 index 00000000..693cd38a --- /dev/null +++ b/web_widget_darkroom/static/lib/darkroomjs/lib/css/_layout.scss @@ -0,0 +1,12 @@ +.darkroom-container { + position: relative; +} + +.darkroom-image-container { + top: 0; + left: 0; +} + +.darkroom-image-container img { + // display: none; +} diff --git a/web_widget_darkroom/static/lib/darkroomjs/lib/css/_toolbar.scss b/web_widget_darkroom/static/lib/darkroomjs/lib/css/_toolbar.scss new file mode 100755 index 00000000..f90aaac5 --- /dev/null +++ b/web_widget_darkroom/static/lib/darkroomjs/lib/css/_toolbar.scss @@ -0,0 +1,99 @@ +// +// 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; +} diff --git a/web_widget_darkroom/static/lib/darkroomjs/lib/css/darkroom.scss b/web_widget_darkroom/static/lib/darkroomjs/lib/css/darkroom.scss new file mode 100755 index 00000000..22d3411c --- /dev/null +++ b/web_widget_darkroom/static/lib/darkroomjs/lib/css/darkroom.scss @@ -0,0 +1,2 @@ +@import 'layout'; +@import 'toolbar'; diff --git a/web_widget_darkroom/static/lib/darkroomjs/lib/icons/close.svg b/web_widget_darkroom/static/lib/darkroomjs/lib/icons/close.svg new file mode 100755 index 00000000..6c375ca8 --- /dev/null +++ b/web_widget_darkroom/static/lib/darkroomjs/lib/icons/close.svg @@ -0,0 +1,4 @@ + + + + diff --git a/web_widget_darkroom/static/lib/darkroomjs/lib/icons/crop.svg b/web_widget_darkroom/static/lib/darkroomjs/lib/icons/crop.svg new file mode 100755 index 00000000..f3affcc4 --- /dev/null +++ b/web_widget_darkroom/static/lib/darkroomjs/lib/icons/crop.svg @@ -0,0 +1,4 @@ + + + + diff --git a/web_widget_darkroom/static/lib/darkroomjs/lib/icons/done.svg b/web_widget_darkroom/static/lib/darkroomjs/lib/icons/done.svg new file mode 100755 index 00000000..0af0fb9f --- /dev/null +++ b/web_widget_darkroom/static/lib/darkroomjs/lib/icons/done.svg @@ -0,0 +1,4 @@ + + + + diff --git a/web_widget_darkroom/static/lib/darkroomjs/lib/icons/redo.svg b/web_widget_darkroom/static/lib/darkroomjs/lib/icons/redo.svg new file mode 100755 index 00000000..fb1cc3a1 --- /dev/null +++ b/web_widget_darkroom/static/lib/darkroomjs/lib/icons/redo.svg @@ -0,0 +1,4 @@ + + + + diff --git a/web_widget_darkroom/static/lib/darkroomjs/lib/icons/rotate-left.svg b/web_widget_darkroom/static/lib/darkroomjs/lib/icons/rotate-left.svg new file mode 100755 index 00000000..dd87ec4f --- /dev/null +++ b/web_widget_darkroom/static/lib/darkroomjs/lib/icons/rotate-left.svg @@ -0,0 +1,4 @@ + + + + diff --git a/web_widget_darkroom/static/lib/darkroomjs/lib/icons/rotate-right.svg b/web_widget_darkroom/static/lib/darkroomjs/lib/icons/rotate-right.svg new file mode 100755 index 00000000..fdfae402 --- /dev/null +++ b/web_widget_darkroom/static/lib/darkroomjs/lib/icons/rotate-right.svg @@ -0,0 +1,4 @@ + + + + diff --git a/web_widget_darkroom/static/lib/darkroomjs/lib/icons/save.svg b/web_widget_darkroom/static/lib/darkroomjs/lib/icons/save.svg new file mode 100755 index 00000000..2a4e62bb --- /dev/null +++ b/web_widget_darkroom/static/lib/darkroomjs/lib/icons/save.svg @@ -0,0 +1,4 @@ + + + + diff --git a/web_widget_darkroom/static/lib/darkroomjs/lib/icons/undo.svg b/web_widget_darkroom/static/lib/darkroomjs/lib/icons/undo.svg new file mode 100755 index 00000000..273f4918 --- /dev/null +++ b/web_widget_darkroom/static/lib/darkroomjs/lib/icons/undo.svg @@ -0,0 +1,4 @@ + + + + diff --git a/web_widget_darkroom/static/lib/darkroomjs/lib/js/core/bootstrap.js b/web_widget_darkroom/static/lib/darkroomjs/lib/js/core/bootstrap.js new file mode 100755 index 00000000..34f32795 --- /dev/null +++ b/web_widget_darkroom/static/lib/darkroomjs/lib/js/core/bootstrap.js @@ -0,0 +1,14 @@ +(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 = ''; +document.body.appendChild(element); + +})(); diff --git a/web_widget_darkroom/static/lib/darkroomjs/lib/js/core/darkroom.js b/web_widget_darkroom/static/lib/darkroomjs/lib/js/core/darkroom.js new file mode 100755 index 00000000..7ea5b363 --- /dev/null +++ b/web_widget_darkroom/static/lib/darkroomjs/lib/js/core/darkroom.js @@ -0,0 +1,354 @@ +(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); + } + }, +} + +})(); diff --git a/web_widget_darkroom/static/lib/darkroomjs/lib/js/core/plugin.js b/web_widget_darkroom/static/lib/darkroomjs/lib/js/core/plugin.js new file mode 100755 index 00000000..07b35a05 --- /dev/null +++ b/web_widget_darkroom/static/lib/darkroomjs/lib/js/core/plugin.js @@ -0,0 +1,43 @@ +(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; +} + +})(); diff --git a/web_widget_darkroom/static/lib/darkroomjs/lib/js/core/transformation.js b/web_widget_darkroom/static/lib/darkroomjs/lib/js/core/transformation.js new file mode 100755 index 00000000..e0d27461 --- /dev/null +++ b/web_widget_darkroom/static/lib/darkroomjs/lib/js/core/transformation.js @@ -0,0 +1,38 @@ +(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; +} + +})(); diff --git a/web_widget_darkroom/static/lib/darkroomjs/lib/js/core/ui.js b/web_widget_darkroom/static/lib/darkroomjs/lib/js/core/ui.js new file mode 100755 index 00000000..b4d752a8 --- /dev/null +++ b/web_widget_darkroom/static/lib/darkroomjs/lib/js/core/ui.js @@ -0,0 +1,91 @@ +(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 = ''; + 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; + } +}; + +})(); diff --git a/web_widget_darkroom/static/lib/darkroomjs/lib/js/core/utils.js b/web_widget_darkroom/static/lib/darkroomjs/lib/js/core/utils.js new file mode 100755 index 00000000..f4de5f9a --- /dev/null +++ b/web_widget_darkroom/static/lib/darkroomjs/lib/js/core/utils.js @@ -0,0 +1,31 @@ +(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))), + } +} + +})(); diff --git a/web_widget_darkroom/static/lib/darkroomjs/lib/js/plugins/darkroom.crop.js b/web_widget_darkroom/static/lib/darkroomjs/lib/js/plugins/darkroom.crop.js new file mode 100755 index 00000000..9c441823 --- /dev/null +++ b/web_widget_darkroom/static/lib/darkroomjs/lib/js/plugins/darkroom.crop.js @@ -0,0 +1,669 @@ +(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'); + } +}); + +})(); diff --git a/web_widget_darkroom/static/lib/darkroomjs/lib/js/plugins/darkroom.history.js b/web_widget_darkroom/static/lib/darkroomjs/lib/js/plugins/darkroom.history.js new file mode 100755 index 00000000..81bea34a --- /dev/null +++ b/web_widget_darkroom/static/lib/darkroomjs/lib/js/plugins/darkroom.history.js @@ -0,0 +1,66 @@ +;(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); diff --git a/web_widget_darkroom/static/lib/darkroomjs/lib/js/plugins/darkroom.rotate.js b/web_widget_darkroom/static/lib/darkroomjs/lib/js/plugins/darkroom.rotate.js new file mode 100755 index 00000000..1f2fbe92 --- /dev/null +++ b/web_widget_darkroom/static/lib/darkroomjs/lib/js/plugins/darkroom.rotate.js @@ -0,0 +1,57 @@ +(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}) + ); + } + +}); + +})(); diff --git a/web_widget_darkroom/static/lib/darkroomjs/lib/js/plugins/darkroom.save.js b/web_widget_darkroom/static/lib/darkroomjs/lib/js/plugins/darkroom.save.js new file mode 100755 index 00000000..d12eeadf --- /dev/null +++ b/web_widget_darkroom/static/lib/darkroomjs/lib/js/plugins/darkroom.save.js @@ -0,0 +1,23 @@ +(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)); + }, +}); + +})(); diff --git a/web_widget_darkroom/static/lib/darkroomjs/package.json b/web_widget_darkroom/static/lib/darkroomjs/package.json new file mode 100755 index 00000000..4684f431 --- /dev/null +++ b/web_widget_darkroom/static/lib/darkroomjs/package.json @@ -0,0 +1,39 @@ +{ + "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 (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" + ] +} diff --git a/web_widget_darkroom/static/src/css/darkroom.css b/web_widget_darkroom/static/src/css/darkroom.css new file mode 100755 index 00000000..21d4668a --- /dev/null +++ b/web_widget_darkroom/static/src/css/darkroom.css @@ -0,0 +1,11 @@ +/*.darkroom-container{ + padding-top: 50px; +} +.darkroom-toolbar{ + top: 5px; +} +*/ + +.darkroom-button-group{ + display: inline; +} diff --git a/web_widget_darkroom/static/src/js/darkroom_plugins.js b/web_widget_darkroom/static/src/js/darkroom_plugins.js new file mode 100644 index 00000000..b7dd01c1 --- /dev/null +++ b/web_widget_darkroom/static/src/js/darkroom_plugins.js @@ -0,0 +1,20 @@ +/* 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 + +}); diff --git a/web_widget_darkroom/static/src/js/plugins/darkroom.crop.js b/web_widget_darkroom/static/src/js/plugins/darkroom.crop.js new file mode 100755 index 00000000..8819a57c --- /dev/null +++ b/web_widget_darkroom/static/src/js/plugins/darkroom.crop.js @@ -0,0 +1,683 @@ +/* Adapted from https://github.com/MattKetmo/darkroomjs/tree/master/lib/js/plugins + * License https://github.com/MattKetmo/darkroomjs/blob/master/LICENSE + */ + +odoo.define('web_widget_darkroom.darkroom_crop', function(require){ + + 'use strict'; + + var DarkroomPluginCrop = function(){ + + 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: 'fa fa-crop', + editOnly: true, + }); + this.okButton = buttonGroup.createButton({ + image: 'fa fa-check', + editOnly: true, + type: 'success', + hide: true + }); + this.cancelButton = buttonGroup.createButton({ + image: 'fa fa-times', + editOnly: true, + 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'); + } + }); + + } + + return {DarkroomPluginCrop: DarkroomPluginCrop}; + +}); diff --git a/web_widget_darkroom/static/src/js/plugins/darkroom.history.js b/web_widget_darkroom/static/src/js/plugins/darkroom.history.js new file mode 100755 index 00000000..99bc07f6 --- /dev/null +++ b/web_widget_darkroom/static/src/js/plugins/darkroom.history.js @@ -0,0 +1,80 @@ +/* Adapted from https://github.com/MattKetmo/darkroomjs/tree/master/lib/js/plugins + * License https://github.com/MattKetmo/darkroomjs/blob/master/LICENSE + */ + +odoo.define('web_widget_darkroom.darkroom_history', function(require){ + + 'use strict'; + + var DarkroomPluginHistory = function() { + + 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: 'fa fa-step-backward', + disabled: true, + editOnly: true, + }); + + this.forwardButton = buttonGroup.createButton({ + image: 'fa fa-step-forward', + disabled: true, + editOnly: 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(); + } + }); + + }; + + return {DarkroomPluginHistory: DarkroomPluginHistory}; + +}); diff --git a/web_widget_darkroom/static/src/js/plugins/darkroom.rotate.js b/web_widget_darkroom/static/src/js/plugins/darkroom.rotate.js new file mode 100755 index 00000000..e50a0d5f --- /dev/null +++ b/web_widget_darkroom/static/src/js/plugins/darkroom.rotate.js @@ -0,0 +1,70 @@ +/* Adapted from https://github.com/MattKetmo/darkroomjs/tree/master/lib/js/plugins + * License https://github.com/MattKetmo/darkroomjs/blob/master/LICENSE + */ + +odoo.define('web_widget_darkroom.darkroom_rotate', function(require){ + + 'use strict'; + + var DarkroomPluginRotate = function() { + + 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: 'fa fa-undo oe_edit_only', + editOnly: true, + }); + + var rightButton = buttonGroup.createButton({ + image: 'fa fa-repeat oe_edit_only', + editOnly: true, + }); + + 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}) + ); + } + + }); + + } + + return {DarkroomPluginRotate: DarkroomPluginRotate}; + +}); diff --git a/web_widget_darkroom/static/src/js/plugins/darkroom.zoom.js b/web_widget_darkroom/static/src/js/plugins/darkroom.zoom.js new file mode 100755 index 00000000..65085aee --- /dev/null +++ b/web_widget_darkroom/static/src/js/plugins/darkroom.zoom.js @@ -0,0 +1,198 @@ +/* Copyright 2016 LasLabs Inc. + * License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). + */ + +odoo.define('web_widget_darkroom.darkroom_zoom', function(require){ + + 'use strict'; + + var DarkroomPluginZoom = function(){ + + Darkroom.plugins['zoom'] = Darkroom.Plugin.extend({ + + inZoom: false, + zoomLevel: 0, + zoomFactor: .1, + + initialize: function() { + var self = this; + var buttonGroup = this.darkroom.toolbar.createButtonGroup(); + + this.zoomButton = buttonGroup.createButton({ + image: 'fa fa-search', + }) + this.zoomInButton = buttonGroup.createButton({ + image: 'fa fa-plus', + }) + this.zoomOutButton = buttonGroup.createButton({ + image: 'fa fa-minus', + }) + this.okButton = buttonGroup.createButton({ + image: 'fa fa-check', + type: 'success', + hide: true, + editOnly: true, + }); + this.cancelButton = buttonGroup.createButton({ + image: 'fa fa-times', + type: 'danger', + hide: true + }); + + // Buttons click + this.zoomButton.addEventListener('click', this.toggleZoom.bind(this)); + this.zoomInButton.addEventListener('click', this.zoomIn.bind(this)); + this.zoomOutButton.addEventListener('click', this.zoomOut.bind(this)); + //this.okButton.addEventListener('click', this.saveZoom.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)); + $(this.darkroom.canvas.wrapperEl).on('mousewheel', function(event){ + self.onMouseWheel(event); + }); + + //fabric.util.addListener(fabric.document, 'keydown', this.onKeyDown.bind(this)); + //fabric.util.addListener(fabric.document, 'keyup', this.onKeyUp.bind(this)); + this.toggleElements(false); + + }, + + toggleZoom: function() { + if (this.hasFocus()) { + this.releaseFocus(); + } else { + this.requireFocus(); + } + }, + + hasFocus: function() { + return this.inZoom; + }, + + releaseFocus: function() { + this.toggleElements(false); + }, + + requireFocus: function() { + this.toggleElements(true); + }, + + toggleElements: function(activate) { + if (activate === 'undefined') { + activate = !this.hasFocus(); + } + this.zoomButton.active(!activate); + this.inZoom = activate; + this.zoomInButton.hide(!activate); + this.zoomOutButton.hide(!activate); + this.okButton.hide(!activate); + this.cancelButton.hide(!activate); + this.darkroom.canvas.default_cursor = activate ? "move" : "default"; + }, + + // Return fabric.Point object for center of canvas + getCenterPoint: function() { + var center = this.darkroom.canvas.getCenter(); + return new fabric.Point(center.left, center.top); + }, + + // Set internal zoom + setZoomLevel: function(factor, point) { + var zoomLevel = this.zoomLevel + factor; + if (zoomLevel < 0) zoomLevel = 0; + if (zoomLevel == this.zoomLevel) return false; + console.log('Setting zoom factor'); + console.log(zoomLevel); + console.log(point); + if (point) { + var canvas = this.darkroom.canvas; + canvas.zoomToPoint(point, zoomLevel + 1); // Add one for zero index + this.zoomLevel = zoomLevel; + } + return true; + }, + + getObjectBounds: function() { + var canvas = this.darkroom.canvas; + var objects = canvas.getObjects(); + var top = 0, bottom = 0, left = 0, right = 0; + for (var idx in objects) { + var obj = objects[idx]; + var objRight = obj.left + obj.getWidth(); + var objBottom = obj.top + obj.getHeight(); + if (obj.left < left) left = obj.left; + if (objRight > right) right = objRight; + if (obj.top < top) top = obj.top; + if (objBottom > bottom) bottom = objBottom; + } + return { + top: top, + bottom: bottom, + left: left, + right: right, + height: (bottom - top), + width: (right - left), + } + }, + + zoomIn: function() { + return this.setZoomLevel(this.zoomFactor, this.getCenterPoint()); + }, + + zoomOut: function() { + return this.setZoomLevel(-this.zoomFactor, this.getCenterPoint()); + }, + + onMouseWheel: function(event) { + if (this.hasFocus() && event && event.originalEvent) { + var modifier = event.originalEvent.wheelDelta < 0 ? -1 : 1; + var pointer = this.darkroom.canvas.getPointer(event.originalEvent); + var mousePoint = new fabric.Point(pointer.x, pointer.y); + this.setZoomLevel(modifier * this.zoomFactor, mousePoint); + return event.preventDefault(); + } + }, + + onMouseDown: function(event) { + if (this.hasFocus()) { + this.panning = true; + } + }, + + onMouseUp: function(event) { + this.panning = false; + }, + + onMouseMove: function(event) { + if (this.panning && event && event.e) { + var delta = new fabric.Point(event.e.movementX, + event.e.movementY); + var canvas = this.darkroom.canvas; + var objBounds = this.getObjectBounds(); + var newPoint = new fabric.Point( + -delta.x - canvas.viewportTransform[4], + -delta.y - canvas.viewportTransform[5] + ) + if (newPoint.x < objBounds.left || newPoint.x > objBounds.right) { + return; + } + if (newPoint.y < objBounds.top || newPoint.y > objBounds.bottom) { + return; + } + canvas.absolutePan(newPoint); + //canvas.setCoords(); + } + }, + + }); + + } + + return {DarkroomPluginZoom: DarkroomPluginZoom}; + +}); diff --git a/web_widget_darkroom/static/src/js/widget_darkroom.js b/web_widget_darkroom/static/src/js/widget_darkroom.js new file mode 100644 index 00000000..582f8997 --- /dev/null +++ b/web_widget_darkroom/static/src/js/widget_darkroom.js @@ -0,0 +1,238 @@ +/* Copyright 2016 LasLabs Inc. + * License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.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, + + defaults: { + // Canvas initialization size + minWidth: 100, + minHeight: 100, + maxWidth: 700, + maxHeight: 500, + + // Plugins options + plugins: { + crop: { + minHeight: 50, + minWidth: 50, + ratio: 1 + }, + }, + + // Post initialization method + initialize: function() { + // Active crop selection + // this.plugins['crop'].requireFocus(); + // Add custom listener + // this.addEventListener('core:transformation', function() { /* ... */ }); + } + + }, + + init: function(field_manager, node) { + this._super(field_manager, node); + this.options = _.defaults(this.options, this.defaults); + }, + + _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 = ''; + 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(); + }, + + _init_darkroom: function() { + if (!this.darkroom) { + this._init_darkroom_icons(); + this._init_darkroom_ui(); + this._init_darkroom_plugins(); + } + }, + + _init_darkroom_ui: function() { + + 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 = ''; + if (options.editOnly) { + buttonElement.classList.add('oe_edit_only'); + } + if (options.addClass) { + buttonElement.classList.add(options.addClass); + } + // buttonElement.innerHTML = ''; + 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() { + if (this.darkroom && this.darkroom.containerElement) { + this.darkroom.containerElement.remove(); + this.darkroom = null; + } + }, + + set_value: function(value){ + this.destroy_content(); + return this._super(value); + }, + + render_value: function() { + this._init_darkroom(); + 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); + this.darkroom = new Darkroom($img.get(0), this.options); + this.darkroom.widget = this; + }, + + commit_value: function(callback) { + this.set_value( + this.darkroom.sourceImage.toDataURL().split(',')[1] + ); + }, + + }); + + core.form_widget_registry.add("darkroom", FieldDarkroomImage); + + return { + FieldDarkroomImage: FieldDarkroomImage, + } + +}); diff --git a/web_widget_darkroom/static/src/js/widget_darkroom.js.orig b/web_widget_darkroom/static/src/js/widget_darkroom.js.orig new file mode 100644 index 00000000..4b0f04f1 --- /dev/null +++ b/web_widget_darkroom/static/src/js/widget_darkroom.js.orig @@ -0,0 +1,246 @@ +/* © 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 = ''; + 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 = ''; + if (options.editOnly) { + buttonElement.classList.add('oe_edit_only'); + } +<<<<<<< Updated upstream + if (options.addClass) { + buttonElement.classList.add(options.addClass); + } + // buttonElement.innerHTML = ''; +======= +>>>>>>> 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, + } + +}); diff --git a/web_widget_darkroom/static/src/xml/field_templates.xml b/web_widget_darkroom/static/src/xml/field_templates.xml new file mode 100644 index 00000000..746426be --- /dev/null +++ b/web_widget_darkroom/static/src/xml/field_templates.xml @@ -0,0 +1,17 @@ + + + + + + + + +
+ + + + diff --git a/web_widget_darkroom/views/assets.xml b/web_widget_darkroom/views/assets.xml new file mode 100644 index 00000000..7b57c7a1 --- /dev/null +++ b/web_widget_darkroom/views/assets.xml @@ -0,0 +1,31 @@ + + + + + +