From 6614479976943ec1f65702be8500e3c2c84d9450 Mon Sep 17 00:00:00 2001 From: rulingcom Date: Mon, 2 Dec 2024 08:35:08 +0800 Subject: [PATCH] First version --- .gitignore | 8 + Gemfile | 14 + Gemfile.lock | 17 + MIT-LICENSE | 20 + README.rdoc | 3 + Rakefile | 32 + app/assets/images/line-height-line.svg | 2 + app/assets/javascripts/admin/greeting_card.js | 2 + .../javascripts/greeting_card/card_preview.js | 75 + .../javascripts/greeting_card/konva.min.js | 12 + .../greeting_card/konva_frontend.js | 287 ++ .../javascripts/greeting_card/konva_helper.js | 796 +++++ .../jquery.ui.datepicker.monthyearpicker.js | 2739 +++++++++++++++++ .../stylesheets/admin/greeting_card.css | 7 + app/assets/stylesheets/custom_field.css | 28 + .../greeting_card/card_preview.css | 3 + .../greeting_card/greeting_card.css | 71 + .../greeting_card/konva_frontend.css | 7 + .../greeting_card/konva_helper.css | 63 + .../jquery-ui-timepicker-addon.css | 39 + app/assets/stylesheets/yearpicker.css | 74 + ...eeting_card_acknowledgements_controller.rb | 20 + .../admin/greeting_card_admins_controller.rb | 45 + .../admin/greeting_cards_controller.rb | 659 ++++ app/controllers/greeting_cards_controller.rb | 703 +++++ app/helpers/admin/greeting_cards_helper.rb | 478 +++ app/mailers/.keep | 0 app/models/.keep | 0 .../concerns/greeting_card_setting_concern.rb | 99 + app/models/greeting_card_acknowledgement.rb | 6 + app/models/greeting_card_admin.rb | 7 + app/models/greeting_card_category_setting.rb | 197 ++ .../greeting_card_category_setting_index.rb | 6 + app/models/greeting_card_email.rb | 36 + app/models/greeting_card_file.rb | 7 + app/models/greeting_card_image.rb | 22 + app/models/greeting_card_layout_design.rb | 403 +++ app/models/greeting_card_list_setting.rb | 29 + app/models/greeting_card_record.rb | 183 ++ app/models/greeting_card_safe_email.rb | 5 + app/models/greeting_card_setting.rb | 295 ++ app/models/greeting_card_setting_index.rb | 5 + app/models/greeting_card_status_history.rb | 17 + app/models/greeting_card_ticket_status.rb | 46 + app/uploaders/greeting_card_image_uploader.rb | 71 + .../index.html.erb | 24 + .../_greeting_card_admin_form.html.erb | 42 + .../admin/greeting_card_admins/edit.html.erb | 32 + .../admin/greeting_card_admins/index.html.erb | 33 + .../_auto_send_setting.html.erb | 83 + .../_category_setting_field.html.erb | 7 + .../_category_setting_partial.html.erb | 11 + .../_default_greeting_card_setting.html.erb | 27 + .../_edit_box_for_index.html.erb | 8 + app/views/admin/greeting_cards/_form.html.erb | 231 ++ .../admin/greeting_cards/_form_image.html.erb | 48 + .../greeting_cards/_form_images.html.erb | 29 + .../_greeting_card_setting.html.erb | 499 +++ .../admin/greeting_cards/_index.html.erb | 256 ++ .../_print_format_explain.html.erb | 131 + ...how_default_greeting_card_setting.html.erb | 83 + .../backend_table_setting.html.erb | 47 + .../greeting_cards/category_setting.html.erb | 338 ++ .../admin/greeting_cards/do_export.xlsx.axlsx | 50 + app/views/admin/greeting_cards/edit.html.erb | 5 + app/views/admin/greeting_cards/email.html.erb | 21 + .../admin/greeting_cards/export.html.erb | 21 + app/views/admin/greeting_cards/index.html.erb | 6 + .../greeting_cards/layout_design.html.erb | 69 + .../list_table_setting.html.erb | 16 + .../greeting_cards/order_fields.html.erb | 209 ++ app/views/admin/greeting_cards/print.html.erb | 48 + .../admin/greeting_cards/setting.html.erb | 36 + .../greeting_cards/ticket_status.html.erb | 115 + app/views/greeting_cards/email.html.erb | 12 + .../email_verification.html.erb | 20 + app/views/greeting_cards/index.html.erb | 197 ++ app/views/greeting_cards/old_email.html.erb | 130 + app/views/greeting_cards/published_index.erb | 18 + app/views/greeting_cards/see_email.html.erb | 9 + app/views/greeting_cards/show.html.erb | 86 + app/views/greeting_cards/sorry.html.erb | 8 + app/views/greeting_cards/thank.html.erb | 14 + .../greeting_cards/verify_email.html.erb | 13 + .../back_end_with_jquery_first.html.erb | 4 + bin/rails | 4 + config/locales/en.yml | 183 ++ config/locales/zh_tw.yml | 190 ++ config/routes.rb | 43 + greeting_card.gemspec | 43 + lib/greeting_card.rb | 4 + lib/greeting_card/engine.rb | 184 ++ lib/greeting_card/version.rb | 3 + lib/tasks/greeting_card_task.rake | 4 + .../_greeting_card_widget_form.html.erb | 117 + .../_greeting_card_widget_list.html.erb | 18 + modules/greeting_card/info.json | 20 + modules/greeting_card/thumbs/thumb.png | Bin 0 -> 4075 bytes 98 files changed, 11487 insertions(+) create mode 100644 .gitignore create mode 100644 Gemfile create mode 100644 Gemfile.lock create mode 100644 MIT-LICENSE create mode 100644 README.rdoc create mode 100644 Rakefile create mode 100644 app/assets/images/line-height-line.svg create mode 100644 app/assets/javascripts/admin/greeting_card.js create mode 100644 app/assets/javascripts/greeting_card/card_preview.js create mode 100644 app/assets/javascripts/greeting_card/konva.min.js create mode 100644 app/assets/javascripts/greeting_card/konva_frontend.js create mode 100644 app/assets/javascripts/greeting_card/konva_helper.js create mode 100644 app/assets/javascripts/jquery.ui.datepicker.monthyearpicker.js create mode 100644 app/assets/stylesheets/admin/greeting_card.css create mode 100644 app/assets/stylesheets/custom_field.css create mode 100644 app/assets/stylesheets/greeting_card/card_preview.css create mode 100644 app/assets/stylesheets/greeting_card/greeting_card.css create mode 100644 app/assets/stylesheets/greeting_card/konva_frontend.css create mode 100644 app/assets/stylesheets/greeting_card/konva_helper.css create mode 100644 app/assets/stylesheets/jquery-ui-timepicker-addon.css create mode 100644 app/assets/stylesheets/yearpicker.css create mode 100644 app/controllers/admin/greeting_card_acknowledgements_controller.rb create mode 100644 app/controllers/admin/greeting_card_admins_controller.rb create mode 100644 app/controllers/admin/greeting_cards_controller.rb create mode 100644 app/controllers/greeting_cards_controller.rb create mode 100644 app/helpers/admin/greeting_cards_helper.rb create mode 100644 app/mailers/.keep create mode 100644 app/models/.keep create mode 100644 app/models/concerns/greeting_card_setting_concern.rb create mode 100644 app/models/greeting_card_acknowledgement.rb create mode 100644 app/models/greeting_card_admin.rb create mode 100644 app/models/greeting_card_category_setting.rb create mode 100644 app/models/greeting_card_category_setting_index.rb create mode 100644 app/models/greeting_card_email.rb create mode 100644 app/models/greeting_card_file.rb create mode 100644 app/models/greeting_card_image.rb create mode 100644 app/models/greeting_card_layout_design.rb create mode 100644 app/models/greeting_card_list_setting.rb create mode 100644 app/models/greeting_card_record.rb create mode 100644 app/models/greeting_card_safe_email.rb create mode 100644 app/models/greeting_card_setting.rb create mode 100644 app/models/greeting_card_setting_index.rb create mode 100644 app/models/greeting_card_status_history.rb create mode 100644 app/models/greeting_card_ticket_status.rb create mode 100644 app/uploaders/greeting_card_image_uploader.rb create mode 100644 app/views/admin/greeting_card_acknowledgements/index.html.erb create mode 100644 app/views/admin/greeting_card_admins/_greeting_card_admin_form.html.erb create mode 100644 app/views/admin/greeting_card_admins/edit.html.erb create mode 100644 app/views/admin/greeting_card_admins/index.html.erb create mode 100644 app/views/admin/greeting_cards/_auto_send_setting.html.erb create mode 100644 app/views/admin/greeting_cards/_category_setting_field.html.erb create mode 100644 app/views/admin/greeting_cards/_category_setting_partial.html.erb create mode 100644 app/views/admin/greeting_cards/_default_greeting_card_setting.html.erb create mode 100644 app/views/admin/greeting_cards/_edit_box_for_index.html.erb create mode 100644 app/views/admin/greeting_cards/_form.html.erb create mode 100644 app/views/admin/greeting_cards/_form_image.html.erb create mode 100644 app/views/admin/greeting_cards/_form_images.html.erb create mode 100644 app/views/admin/greeting_cards/_greeting_card_setting.html.erb create mode 100644 app/views/admin/greeting_cards/_index.html.erb create mode 100644 app/views/admin/greeting_cards/_print_format_explain.html.erb create mode 100644 app/views/admin/greeting_cards/_show_default_greeting_card_setting.html.erb create mode 100644 app/views/admin/greeting_cards/backend_table_setting.html.erb create mode 100644 app/views/admin/greeting_cards/category_setting.html.erb create mode 100644 app/views/admin/greeting_cards/do_export.xlsx.axlsx create mode 100644 app/views/admin/greeting_cards/edit.html.erb create mode 100644 app/views/admin/greeting_cards/email.html.erb create mode 100644 app/views/admin/greeting_cards/export.html.erb create mode 100644 app/views/admin/greeting_cards/index.html.erb create mode 100644 app/views/admin/greeting_cards/layout_design.html.erb create mode 100644 app/views/admin/greeting_cards/list_table_setting.html.erb create mode 100644 app/views/admin/greeting_cards/order_fields.html.erb create mode 100644 app/views/admin/greeting_cards/print.html.erb create mode 100644 app/views/admin/greeting_cards/setting.html.erb create mode 100644 app/views/admin/greeting_cards/ticket_status.html.erb create mode 100644 app/views/greeting_cards/email.html.erb create mode 100644 app/views/greeting_cards/email_verification.html.erb create mode 100644 app/views/greeting_cards/index.html.erb create mode 100644 app/views/greeting_cards/old_email.html.erb create mode 100644 app/views/greeting_cards/published_index.erb create mode 100644 app/views/greeting_cards/see_email.html.erb create mode 100644 app/views/greeting_cards/show.html.erb create mode 100644 app/views/greeting_cards/sorry.html.erb create mode 100644 app/views/greeting_cards/thank.html.erb create mode 100644 app/views/greeting_cards/verify_email.html.erb create mode 100644 app/views/layouts/back_end_with_jquery_first.html.erb create mode 100644 bin/rails create mode 100644 config/locales/en.yml create mode 100644 config/locales/zh_tw.yml create mode 100644 config/routes.rb create mode 100644 greeting_card.gemspec create mode 100644 lib/greeting_card.rb create mode 100644 lib/greeting_card/engine.rb create mode 100644 lib/greeting_card/version.rb create mode 100644 lib/tasks/greeting_card_task.rake create mode 100644 modules/greeting_card/_greeting_card_widget_form.html.erb create mode 100644 modules/greeting_card/_greeting_card_widget_list.html.erb create mode 100644 modules/greeting_card/info.json create mode 100644 modules/greeting_card/thumbs/thumb.png diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b09db87 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +.bundle/ +log/*.log +pkg/ +test/dummy/db/*.sqlite3 +test/dummy/db/*.sqlite3-journal +test/dummy/log/*.log +test/dummy/tmp/ +test/dummy/.sass-cache diff --git a/Gemfile b/Gemfile new file mode 100644 index 0000000..7a4ceb6 --- /dev/null +++ b/Gemfile @@ -0,0 +1,14 @@ +source "https://rubygems.org" + +# Declare your gem's dependencies in greeting_card.gemspec. +# Bundler will treat runtime dependencies like base dependencies, and +# development dependencies will be added by default to the :development group. +gemspec + +# Declare any dependencies that are still in development here instead of in +# your gemspec. These might include edge Rails or gems from your path or +# Git. Remember to move these dependencies to your gemspec before releasing +# your gem to rubygems.org. + +# To use debugger +# gem 'debugger' diff --git a/Gemfile.lock b/Gemfile.lock new file mode 100644 index 0000000..e8011dd --- /dev/null +++ b/Gemfile.lock @@ -0,0 +1,17 @@ +PATH + remote: . + specs: + greeting_card (0.0.1) + +GEM + remote: https://rubygems.org/ + specs: + +PLATFORMS + x86_64-linux + +DEPENDENCIES + greeting_card! + +BUNDLED WITH + 2.3.19 diff --git a/MIT-LICENSE b/MIT-LICENSE new file mode 100644 index 0000000..03d3c85 --- /dev/null +++ b/MIT-LICENSE @@ -0,0 +1,20 @@ +Copyright 2014 YOURNAME + +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/README.rdoc b/README.rdoc new file mode 100644 index 0000000..f079292 --- /dev/null +++ b/README.rdoc @@ -0,0 +1,3 @@ += GreetingCard + +This project rocks and uses MIT-LICENSE. \ No newline at end of file diff --git a/Rakefile b/Rakefile new file mode 100644 index 0000000..e976f45 --- /dev/null +++ b/Rakefile @@ -0,0 +1,32 @@ +begin + require 'bundler/setup' +rescue LoadError + puts 'You must `gem install bundler` and `bundle install` to run rake tasks' +end + +require 'rdoc/task' + +RDoc::Task.new(:rdoc) do |rdoc| + rdoc.rdoc_dir = 'rdoc' + rdoc.title = 'GreetingCard' + rdoc.options << '--line-numbers' + rdoc.rdoc_files.include('README.rdoc') + rdoc.rdoc_files.include('lib/**/*.rb') +end + + + + +Bundler::GemHelper.install_tasks + +require 'rake/testtask' + +Rake::TestTask.new(:test) do |t| + t.libs << 'lib' + t.libs << 'test' + t.pattern = 'test/**/*_test.rb' + t.verbose = false +end + + +task default: :test diff --git a/app/assets/images/line-height-line.svg b/app/assets/images/line-height-line.svg new file mode 100644 index 0000000..0a6a253 --- /dev/null +++ b/app/assets/images/line-height-line.svg @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/app/assets/javascripts/admin/greeting_card.js b/app/assets/javascripts/admin/greeting_card.js new file mode 100644 index 0000000..7576b10 --- /dev/null +++ b/app/assets/javascripts/admin/greeting_card.js @@ -0,0 +1,2 @@ +// Place all the behaviors and hooks related to the matching controller here. +// All this logic will automatically be available in application.js. diff --git a/app/assets/javascripts/greeting_card/card_preview.js b/app/assets/javascripts/greeting_card/card_preview.js new file mode 100644 index 0000000..4e54775 --- /dev/null +++ b/app/assets/javascripts/greeting_card/card_preview.js @@ -0,0 +1,75 @@ +$(document).ready(function(){ + var temp_layer; + $('.preview-card').on('click', function(ev, form) { + var active_form, selected_img, category_select, + category_idx = 0; + + if (!form) + form = $(this).parents('form'); + + category_select = form.find('select#greeting_card_record_category_id'); + + // form maybe not the same as active_form when multiple category exist + if (category_select.length != 0) + { + var selected_option = category_select.find('option:selected'); + active_form = $('form#' + selected_option.prop('value')); + category_idx = selected_option.index(); + } + else + { + active_form = $('.greeting_card_form'); + } + + selected_img = active_form.find('.card_images_wrapper input:checked').siblings('img'); + + if (selected_img.length == 0) + return false; + + var img_url = selected_img.attr('src'); + if (img_url) + { + var layer; + var data = {}; + var fields = collected_fields[category_idx]; + var objs = card_img_objs[category_idx]; + fields.forEach(function(f) { + var field_input = active_form.find("#greeting_card_record_" + f); + data[f] = field_input.val(); + }); + if (temp_layer) + { + destroy_layer(temp_layer); + } + layer = create_konva(active_form.find(".preview-card-section"), 'card-container', img_url, data); + window.layer = layer; + objs.forEach(function(obj) { + create_text_box(layer, + {x: obj.x, y: obj.y, w: obj.w, h: obj.h, l_h: obj.l_h}, + obj.text, + obj.fontSize, obj.fontFamily, obj.fontColor, + (obj.is_stroke == 'true'), obj.stroke, obj.strokeWidth, + (obj.is_fill == 'true')); + }); + fields.forEach(function(f) { + var field_input = active_form.find("#greeting_card_record_" + f); + field_input.on("input", function() { + layer.container.trigger("change", [f, $(this).val()]); + }) + }); + + active_form.find("input[name=\"greeting_card_record[card_id]\"]").on("change", function() { + layer.container_img.attr('src', $(this).siblings('img').attr('src')); + }) + + temp_layer = layer; + } + }); + $('select#greeting_card_record_category_id').on('change', function() { + if (temp_layer) + { + var form = $(this).parents('form'); + form.find('.preview-card').trigger('click', [form]); + } + }) +}) \ No newline at end of file diff --git a/app/assets/javascripts/greeting_card/konva.min.js b/app/assets/javascripts/greeting_card/konva.min.js new file mode 100644 index 0000000..cb918cf --- /dev/null +++ b/app/assets/javascripts/greeting_card/konva.min.js @@ -0,0 +1,12 @@ +!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(t="undefined"!=typeof globalThis?globalThis:t||self).Konva=e()}(this,(function(){"use strict"; +/* + * Konva JavaScript Framework v9.3.15 + * http://konvajs.org/ + * Licensed under the MIT + * Date: Mon Sep 09 2024 + * + * Original work Copyright (C) 2011 - 2013 by Eric Rowell (KineticJS) + * Modified work Copyright (C) 2014 - present by Anton Lavrenov (Konva) + * + * @license + */const t=Math.PI/180;const e="undefined"!=typeof global?global:"undefined"!=typeof window?window:"undefined"!=typeof WorkerGlobalScope?self:{},i={_global:e,version:"9.3.15",isBrowser:"undefined"!=typeof window&&("[object Window]"==={}.toString.call(window)||"[object global]"==={}.toString.call(window)),isUnminified:/param/.test(function(t){}.toString()),dblClickWindow:400,getAngle:e=>i.angleDeg?e*t:e,enableTrace:!1,pointerEventsEnabled:!0,autoDrawEnabled:!0,hitOnDragEnabled:!1,capturePointerEventsEnabled:!1,_mouseListenClick:!1,_touchListenClick:!1,_pointerListenClick:!1,_mouseInDblClickWindow:!1,_touchInDblClickWindow:!1,_pointerInDblClickWindow:!1,_mouseDblClickPointerId:null,_touchDblClickPointerId:null,_pointerDblClickPointerId:null,_fixTextRendering:!1,pixelRatio:"undefined"!=typeof window&&window.devicePixelRatio||1,dragDistance:3,angleDeg:!0,showWarnings:!0,dragButtons:[0,1],isDragging:()=>i.DD.isDragging,isTransforming(){var t;return null===(t=i.Transformer)||void 0===t?void 0:t.isTransforming()},isDragReady:()=>!!i.DD.node,releaseCanvasOnDestroy:!0,document:e.document,_injectGlobal(t){e.Konva=t}},r=t=>{i[t.prototype.getClassName()]=t};i._injectGlobal(i);class a{constructor(t=[1,0,0,1,0,0]){this.dirty=!1,this.m=t&&t.slice()||[1,0,0,1,0,0]}reset(){this.m[0]=1,this.m[1]=0,this.m[2]=0,this.m[3]=1,this.m[4]=0,this.m[5]=0}copy(){return new a(this.m)}copyInto(t){t.m[0]=this.m[0],t.m[1]=this.m[1],t.m[2]=this.m[2],t.m[3]=this.m[3],t.m[4]=this.m[4],t.m[5]=this.m[5]}point(t){var e=this.m;return{x:e[0]*t.x+e[2]*t.y+e[4],y:e[1]*t.x+e[3]*t.y+e[5]}}translate(t,e){return this.m[4]+=this.m[0]*t+this.m[2]*e,this.m[5]+=this.m[1]*t+this.m[3]*e,this}scale(t,e){return this.m[0]*=t,this.m[1]*=t,this.m[2]*=e,this.m[3]*=e,this}rotate(t){var e=Math.cos(t),i=Math.sin(t),r=this.m[0]*e+this.m[2]*i,a=this.m[1]*e+this.m[3]*i,n=this.m[0]*-i+this.m[2]*e,s=this.m[1]*-i+this.m[3]*e;return this.m[0]=r,this.m[1]=a,this.m[2]=n,this.m[3]=s,this}getTranslation(){return{x:this.m[4],y:this.m[5]}}skew(t,e){var i=this.m[0]+this.m[2]*e,r=this.m[1]+this.m[3]*e,a=this.m[2]+this.m[0]*t,n=this.m[3]+this.m[1]*t;return this.m[0]=i,this.m[1]=r,this.m[2]=a,this.m[3]=n,this}multiply(t){var e=this.m[0]*t.m[0]+this.m[2]*t.m[1],i=this.m[1]*t.m[0]+this.m[3]*t.m[1],r=this.m[0]*t.m[2]+this.m[2]*t.m[3],a=this.m[1]*t.m[2]+this.m[3]*t.m[3],n=this.m[0]*t.m[4]+this.m[2]*t.m[5]+this.m[4],s=this.m[1]*t.m[4]+this.m[3]*t.m[5]+this.m[5];return this.m[0]=e,this.m[1]=i,this.m[2]=r,this.m[3]=a,this.m[4]=n,this.m[5]=s,this}invert(){var t=1/(this.m[0]*this.m[3]-this.m[1]*this.m[2]),e=this.m[3]*t,i=-this.m[1]*t,r=-this.m[2]*t,a=this.m[0]*t,n=t*(this.m[2]*this.m[5]-this.m[3]*this.m[4]),s=t*(this.m[1]*this.m[4]-this.m[0]*this.m[5]);return this.m[0]=e,this.m[1]=i,this.m[2]=r,this.m[3]=a,this.m[4]=n,this.m[5]=s,this}getMatrix(){return this.m}decompose(){var t=this.m[0],e=this.m[1],i=this.m[2],r=this.m[3],a=t*r-e*i;let n={x:this.m[4],y:this.m[5],rotation:0,scaleX:0,scaleY:0,skewX:0,skewY:0};if(0!=t||0!=e){var s=Math.sqrt(t*t+e*e);n.rotation=e>0?Math.acos(t/s):-Math.acos(t/s),n.scaleX=s,n.scaleY=a/s,n.skewX=(t*i+e*r)/a,n.skewY=0}else if(0!=i||0!=r){var o=Math.sqrt(i*i+r*r);n.rotation=Math.PI/2-(r>0?Math.acos(-i/o):-Math.acos(i/o)),n.scaleX=a/o,n.scaleY=o,n.skewX=0,n.skewY=(t*i+e*r)/a}return n.rotation=g._getRotation(n.rotation),n}}var n=Math.PI/180,s=180/Math.PI,o="Konva error: ",h={aliceblue:[240,248,255],antiquewhite:[250,235,215],aqua:[0,255,255],aquamarine:[127,255,212],azure:[240,255,255],beige:[245,245,220],bisque:[255,228,196],black:[0,0,0],blanchedalmond:[255,235,205],blue:[0,0,255],blueviolet:[138,43,226],brown:[165,42,42],burlywood:[222,184,135],cadetblue:[95,158,160],chartreuse:[127,255,0],chocolate:[210,105,30],coral:[255,127,80],cornflowerblue:[100,149,237],cornsilk:[255,248,220],crimson:[220,20,60],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgoldenrod:[184,132,11],darkgray:[169,169,169],darkgreen:[0,100,0],darkgrey:[169,169,169],darkkhaki:[189,183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],darkseagreen:[143,188,143],darkslateblue:[72,61,139],darkslategray:[47,79,79],darkslategrey:[47,79,79],darkturquoise:[0,206,209],darkviolet:[148,0,211],deeppink:[255,20,147],deepskyblue:[0,191,255],dimgray:[105,105,105],dimgrey:[105,105,105],dodgerblue:[30,144,255],firebrick:[178,34,34],floralwhite:[255,255,240],forestgreen:[34,139,34],fuchsia:[255,0,255],gainsboro:[220,220,220],ghostwhite:[248,248,255],gold:[255,215,0],goldenrod:[218,165,32],gray:[128,128,128],green:[0,128,0],greenyellow:[173,255,47],grey:[128,128,128],honeydew:[240,255,240],hotpink:[255,105,180],indianred:[205,92,92],indigo:[75,0,130],ivory:[255,255,240],khaki:[240,230,140],lavender:[230,230,250],lavenderblush:[255,240,245],lawngreen:[124,252,0],lemonchiffon:[255,250,205],lightblue:[173,216,230],lightcoral:[240,128,128],lightcyan:[224,255,255],lightgoldenrodyellow:[250,250,210],lightgray:[211,211,211],lightgreen:[144,238,144],lightgrey:[211,211,211],lightpink:[255,182,193],lightsalmon:[255,160,122],lightseagreen:[32,178,170],lightskyblue:[135,206,250],lightslategray:[119,136,153],lightslategrey:[119,136,153],lightsteelblue:[176,196,222],lightyellow:[255,255,224],lime:[0,255,0],limegreen:[50,205,50],linen:[250,240,230],magenta:[255,0,255],maroon:[128,0,0],mediumaquamarine:[102,205,170],mediumblue:[0,0,205],mediumorchid:[186,85,211],mediumpurple:[147,112,219],mediumseagreen:[60,179,113],mediumslateblue:[123,104,238],mediumspringgreen:[0,250,154],mediumturquoise:[72,209,204],mediumvioletred:[199,21,133],midnightblue:[25,25,112],mintcream:[245,255,250],mistyrose:[255,228,225],moccasin:[255,228,181],navajowhite:[255,222,173],navy:[0,0,128],oldlace:[253,245,230],olive:[128,128,0],olivedrab:[107,142,35],orange:[255,165,0],orangered:[255,69,0],orchid:[218,112,214],palegoldenrod:[238,232,170],palegreen:[152,251,152],paleturquoise:[175,238,238],palevioletred:[219,112,147],papayawhip:[255,239,213],peachpuff:[255,218,185],peru:[205,133,63],pink:[255,192,203],plum:[221,160,203],powderblue:[176,224,230],purple:[128,0,128],rebeccapurple:[102,51,153],red:[255,0,0],rosybrown:[188,143,143],royalblue:[65,105,225],saddlebrown:[139,69,19],salmon:[250,128,114],sandybrown:[244,164,96],seagreen:[46,139,87],seashell:[255,245,238],sienna:[160,82,45],silver:[192,192,192],skyblue:[135,206,235],slateblue:[106,90,205],slategray:[119,128,144],slategrey:[119,128,144],snow:[255,255,250],springgreen:[0,255,127],steelblue:[70,130,180],tan:[210,180,140],teal:[0,128,128],thistle:[216,191,216],transparent:[255,255,255,0],tomato:[255,99,71],turquoise:[64,224,208],violet:[238,130,238],wheat:[245,222,179],white:[255,255,255],whitesmoke:[245,245,245],yellow:[255,255,0],yellowgreen:[154,205,5]},l=/rgb\((\d{1,3}),(\d{1,3}),(\d{1,3})\)/,d=[];const c="undefined"!=typeof requestAnimationFrame&&requestAnimationFrame||function(t){setTimeout(t,60)},g={_isElement:t=>!(!t||1!=t.nodeType),_isFunction:t=>!!(t&&t.constructor&&t.call&&t.apply),_isPlainObject:t=>!!t&&t.constructor===Object,_isArray:t=>"[object Array]"===Object.prototype.toString.call(t),_isNumber:t=>"[object Number]"===Object.prototype.toString.call(t)&&!isNaN(t)&&isFinite(t),_isString:t=>"[object String]"===Object.prototype.toString.call(t),_isBoolean:t=>"[object Boolean]"===Object.prototype.toString.call(t),isObject:t=>t instanceof Object,isValidSelector(t){if("string"!=typeof t)return!1;var e=t[0];return"#"===e||"."===e||e===e.toUpperCase()},_sign:t=>0===t||t>0?1:-1,requestAnimFrame(t){d.push(t),1===d.length&&c((function(){const t=d;d=[],t.forEach((function(t){t()}))}))},createCanvasElement(){var t=document.createElement("canvas");try{t.style=t.style||{}}catch(t){}return t},createImageElement:()=>document.createElement("img"),_isInDocument(t){for(;t=t.parentNode;)if(t==document)return!0;return!1},_urlToImage(t,e){var i=g.createImageElement();i.onload=function(){e(i)},i.src=t},_rgbToHex:(t,e,i)=>((1<<24)+(t<<16)+(e<<8)+i).toString(16).slice(1),_hexToRgb(t){t=t.replace("#","");var e=parseInt(t,16);return{r:e>>16&255,g:e>>8&255,b:255&e}},getRandomColor(){for(var t=(16777215*Math.random()|0).toString(16);t.length<6;)t="0"+t;return"#"+t},getRGB(t){var e;return t in h?{r:(e=h[t])[0],g:e[1],b:e[2]}:"#"===t[0]?this._hexToRgb(t.substring(1)):"rgb("===t.substr(0,4)?(e=l.exec(t.replace(/ /g,"")),{r:parseInt(e[1],10),g:parseInt(e[2],10),b:parseInt(e[3],10)}):{r:0,g:0,b:0}},colorToRGBA:t=>(t=t||"black",g._namedColorToRBA(t)||g._hex3ColorToRGBA(t)||g._hex4ColorToRGBA(t)||g._hex6ColorToRGBA(t)||g._hex8ColorToRGBA(t)||g._rgbColorToRGBA(t)||g._rgbaColorToRGBA(t)||g._hslColorToRGBA(t)),_namedColorToRBA(t){var e=h[t.toLowerCase()];return e?{r:e[0],g:e[1],b:e[2],a:1}:null},_rgbColorToRGBA(t){if(0===t.indexOf("rgb(")){var e=(t=t.match(/rgb\(([^)]+)\)/)[1]).split(/ *, */).map(Number);return{r:e[0],g:e[1],b:e[2],a:1}}},_rgbaColorToRGBA(t){if(0===t.indexOf("rgba(")){var e=(t=t.match(/rgba\(([^)]+)\)/)[1]).split(/ *, */).map(((t,e)=>"%"===t.slice(-1)?3===e?parseInt(t)/100:parseInt(t)/100*255:Number(t)));return{r:e[0],g:e[1],b:e[2],a:e[3]}}},_hex8ColorToRGBA(t){if("#"===t[0]&&9===t.length)return{r:parseInt(t.slice(1,3),16),g:parseInt(t.slice(3,5),16),b:parseInt(t.slice(5,7),16),a:parseInt(t.slice(7,9),16)/255}},_hex6ColorToRGBA(t){if("#"===t[0]&&7===t.length)return{r:parseInt(t.slice(1,3),16),g:parseInt(t.slice(3,5),16),b:parseInt(t.slice(5,7),16),a:1}},_hex4ColorToRGBA(t){if("#"===t[0]&&5===t.length)return{r:parseInt(t[1]+t[1],16),g:parseInt(t[2]+t[2],16),b:parseInt(t[3]+t[3],16),a:parseInt(t[4]+t[4],16)/255}},_hex3ColorToRGBA(t){if("#"===t[0]&&4===t.length)return{r:parseInt(t[1]+t[1],16),g:parseInt(t[2]+t[2],16),b:parseInt(t[3]+t[3],16),a:1}},_hslColorToRGBA(t){if(/hsl\((\d+),\s*([\d.]+)%,\s*([\d.]+)%\)/g.test(t)){const[e,...i]=/hsl\((\d+),\s*([\d.]+)%,\s*([\d.]+)%\)/g.exec(t),r=Number(i[0])/360,a=Number(i[1])/100,n=Number(i[2])/100;let s,o,h;if(0===a)return h=255*n,{r:Math.round(h),g:Math.round(h),b:Math.round(h),a:1};s=n<.5?n*(1+a):n+a-n*a;const l=2*n-s,d=[0,0,0];for(let t=0;t<3;t++)o=r+1/3*-(t-1),o<0&&o++,o>1&&o--,h=6*o<1?l+6*(s-l)*o:2*o<1?s:3*o<2?l+(s-l)*(2/3-o)*6:l,d[t]=255*h;return{r:Math.round(d[0]),g:Math.round(d[1]),b:Math.round(d[2]),a:1}}},haveIntersection:(t,e)=>!(e.x>t.x+t.width||e.x+e.widtht.y+t.height||e.y+e.heightt.slice(0),degToRad:t=>t*n,radToDeg:t=>t*s,_degToRad:t=>(g.warn("Util._degToRad is removed. Please use public Util.degToRad instead."),g.degToRad(t)),_radToDeg:t=>(g.warn("Util._radToDeg is removed. Please use public Util.radToDeg instead."),g.radToDeg(t)),_getRotation:t=>i.angleDeg?g.radToDeg(t):t,_capitalize:t=>t.charAt(0).toUpperCase()+t.slice(1),throw(t){throw new Error(o+t)},error(t){console.error(o+t)},warn(t){i.showWarnings&&console.warn("Konva warning: "+t)},each(t,e){for(var i in t)e(i,t[i])},_inRange:(t,e,i)=>e<=t&&t1?(s=i,o=r,h=(i-a)*(i-a)+(r-n)*(r-n)):h=((s=t+d*(i-t))-a)*(s-a)+((o=e+d*(r-e))-n)*(o-n)}return[s,o,h]},_getProjectionToLine(t,e,i){var r=g.cloneObject(t),a=Number.MAX_VALUE;return e.forEach((function(n,s){if(i||s!==e.length-1){var o=e[(s+1)%e.length],h=g._getProjectionToSegment(n.x,n.y,o.x,o.y,t.x,t.y),l=h[0],d=h[1],c=h[2];ce.length){var s=e;e=t,t=s}for(r=0;rt.touches?t.changedTouches[0].identifier:t.pointerId||999,releaseCanvas(...t){i.releaseCanvasOnDestroy&&t.forEach((t=>{t.width=0,t.height=0}))},drawRoundedRectPath(t,e,i,r){let a=0,n=0,s=0,o=0;"number"==typeof r?a=n=s=o=Math.min(r,e/2,i/2):(a=Math.min(r[0]||0,e/2,i/2),n=Math.min(r[1]||0,e/2,i/2),o=Math.min(r[2]||0,e/2,i/2),s=Math.min(r[3]||0,e/2,i/2)),t.moveTo(a,0),t.lineTo(e-n,0),t.arc(e-n,n,n,3*Math.PI/2,0,!1),t.lineTo(e,i-o),t.arc(e-o,i-o,o,0,Math.PI/2,!1),t.lineTo(s,i),t.arc(s,i-s,s,Math.PI/2,Math.PI,!1),t.lineTo(0,a),t.arc(a,a,a,Math.PI,3*Math.PI/2,!1)}};function u(t){return g._isString(t)?'"'+t+'"':"[object Number]"===Object.prototype.toString.call(t)||g._isBoolean(t)?t:Object.prototype.toString.call(t)}function f(t){return t>255?255:t<0?0:Math.round(t)}function p(){if(i.isUnminified)return function(t,e){return g._isNumber(t)||g.warn(u(t)+' is a not valid value for "'+e+'" attribute. The value should be a number.'),t}}function v(t){if(i.isUnminified)return function(e,i){let r=g._isNumber(e),a=g._isArray(e)&&e.length==t;return r||a||g.warn(u(e)+' is a not valid value for "'+i+'" attribute. The value should be a number or Array('+t+")"),e}}function m(){if(i.isUnminified)return function(t,e){return g._isNumber(t)||"auto"===t||g.warn(u(t)+' is a not valid value for "'+e+'" attribute. The value should be a number or "auto".'),t}}function _(){if(i.isUnminified)return function(t,e){return g._isString(t)||g.warn(u(t)+' is a not valid value for "'+e+'" attribute. The value should be a string.'),t}}function y(){if(i.isUnminified)return function(t,e){const i=g._isString(t),r="[object CanvasGradient]"===Object.prototype.toString.call(t)||t&&t.addColorStop;return i||r||g.warn(u(t)+' is a not valid value for "'+e+'" attribute. The value should be a string or a native gradient.'),t}}function x(){if(i.isUnminified)return function(t,e){return!0===t||!1===t||g.warn(u(t)+' is a not valid value for "'+e+'" attribute. The value should be a boolean.'),t}}var b="get",S="set";const w={addGetterSetter(t,e,i,r,a){w.addGetter(t,e,i),w.addSetter(t,e,r,a),w.addOverloadedGetterSetter(t,e)},addGetter(t,e,i){var r=b+g._capitalize(e);t.prototype[r]=t.prototype[r]||function(){var t=this.attrs[e];return void 0===t?i:t}},addSetter(t,e,i,r){var a=S+g._capitalize(e);t.prototype[a]||w.overWriteSetter(t,e,i,r)},overWriteSetter(t,e,i,r){var a=S+g._capitalize(e);t.prototype[a]=function(t){return i&&null!=t&&(t=i.call(this,t,e)),this._setAttr(e,t),r&&r.call(this),this}},addComponentsGetterSetter(t,e,r,a,n){var s,o,h=r.length,l=g._capitalize,d=b+l(e),c=S+l(e);t.prototype[d]=function(){var t={};for(s=0;s{this._setAttr(e+l(t),void 0)})),this._fireChangeEvent(e,s,t),n&&n.call(this),this},w.addOverloadedGetterSetter(t,e)},addOverloadedGetterSetter(t,e){var i=g._capitalize(e),r=S+i,a=b+i;t.prototype[e]=function(){return arguments.length?(this[r](arguments[0]),this):this[a]()}},addDeprecatedGetterSetter(t,e,i,r){g.error("Adding deprecated "+e);var a=b+g._capitalize(e),n=e+" property is deprecated and will be removed soon. Look at Konva change log for more information.";t.prototype[a]=function(){g.error(n);var t=this.attrs[e];return void 0===t?i:t},w.addSetter(t,e,r,(function(){g.error(n)})),w.addOverloadedGetterSetter(t,e)},backCompat(t,e){g.each(e,(function(e,i){var r=t.prototype[i],a=b+g._capitalize(e),n=S+g._capitalize(e);function s(){r.apply(this,arguments),g.error('"'+e+'" method is deprecated and will be removed soon. Use ""'+i+'" instead.')}t.prototype[e]=s,t.prototype[a]=s,t.prototype[n]=s}))},afterSetFilter(){this._filterUpToDate=!1}};var C=["arc","arcTo","beginPath","bezierCurveTo","clearRect","clip","closePath","createLinearGradient","createPattern","createRadialGradient","drawImage","ellipse","fill","fillText","getImageData","createImageData","lineTo","moveTo","putImageData","quadraticCurveTo","rect","roundRect","restore","rotate","save","scale","setLineDash","setTransform","stroke","strokeText","transform","translate"];class P{constructor(t){this.canvas=t,i.enableTrace&&(this.traceArr=[],this._enableTrace())}fillShape(t){t.fillEnabled()&&this._fill(t)}_fill(t){}strokeShape(t){t.hasStroke()&&this._stroke(t)}_stroke(t){}fillStrokeShape(t){t.attrs.fillAfterStrokeEnabled?(this.strokeShape(t),this.fillShape(t)):(this.fillShape(t),this.strokeShape(t))}getTrace(t,e){var i,r,a,n,s=this.traceArr,o=s.length,h="";for(i=0;i"number"==typeof t?Math.floor(t):t))),h+="("+n.join(",")+")")):(h+=r.property,t||(h+="="+r.val)),h+=";";return h}clearTrace(){this.traceArr=[]}_trace(t){var e=this.traceArr;e.push(t),e.length>=100&&e.shift()}reset(){var t=this.getCanvas().getPixelRatio();this.setTransform(1*t,0,0,1*t,0,0)}getCanvas(){return this.canvas}clear(t){var e=this.getCanvas();t?this.clearRect(t.x||0,t.y||0,t.width||0,t.height||0):this.clearRect(0,0,e.getWidth()/e.pixelRatio,e.getHeight()/e.pixelRatio)}_applyLineCap(t){const e=t.attrs.lineCap;e&&this.setAttr("lineCap",e)}_applyOpacity(t){var e=t.getAbsoluteOpacity();1!==e&&this.setAttr("globalAlpha",e)}_applyLineJoin(t){const e=t.attrs.lineJoin;e&&this.setAttr("lineJoin",e)}setAttr(t,e){this._context[t]=e}arc(t,e,i,r,a,n){this._context.arc(t,e,i,r,a,n)}arcTo(t,e,i,r,a){this._context.arcTo(t,e,i,r,a)}beginPath(){this._context.beginPath()}bezierCurveTo(t,e,i,r,a,n){this._context.bezierCurveTo(t,e,i,r,a,n)}clearRect(t,e,i,r){this._context.clearRect(t,e,i,r)}clip(...t){this._context.clip.apply(this._context,t)}closePath(){this._context.closePath()}createImageData(t,e){var i=arguments;return 2===i.length?this._context.createImageData(t,e):1===i.length?this._context.createImageData(t):void 0}createLinearGradient(t,e,i,r){return this._context.createLinearGradient(t,e,i,r)}createPattern(t,e){return this._context.createPattern(t,e)}createRadialGradient(t,e,i,r,a,n){return this._context.createRadialGradient(t,e,i,r,a,n)}drawImage(t,e,i,r,a,n,s,o,h){var l=arguments,d=this._context;3===l.length?d.drawImage(t,e,i):5===l.length?d.drawImage(t,e,i,r,a):9===l.length&&d.drawImage(t,e,i,r,a,n,s,o,h)}ellipse(t,e,i,r,a,n,s,o){this._context.ellipse(t,e,i,r,a,n,s,o)}isPointInPath(t,e,i,r){return i?this._context.isPointInPath(i,t,e,r):this._context.isPointInPath(t,e,r)}fill(...t){this._context.fill.apply(this._context,t)}fillRect(t,e,i,r){this._context.fillRect(t,e,i,r)}strokeRect(t,e,i,r){this._context.strokeRect(t,e,i,r)}fillText(t,e,i,r){r?this._context.fillText(t,e,i,r):this._context.fillText(t,e,i)}measureText(t){return this._context.measureText(t)}getImageData(t,e,i,r){return this._context.getImageData(t,e,i,r)}lineTo(t,e){this._context.lineTo(t,e)}moveTo(t,e){this._context.moveTo(t,e)}rect(t,e,i,r){this._context.rect(t,e,i,r)}roundRect(t,e,i,r,a){this._context.roundRect(t,e,i,r,a)}putImageData(t,e,i){this._context.putImageData(t,e,i)}quadraticCurveTo(t,e,i,r){this._context.quadraticCurveTo(t,e,i,r)}restore(){this._context.restore()}rotate(t){this._context.rotate(t)}save(){this._context.save()}scale(t,e){this._context.scale(t,e)}setLineDash(t){this._context.setLineDash?this._context.setLineDash(t):"mozDash"in this._context?this._context.mozDash=t:"webkitLineDash"in this._context&&(this._context.webkitLineDash=t)}getLineDash(){return this._context.getLineDash()}setTransform(t,e,i,r,a,n){this._context.setTransform(t,e,i,r,a,n)}stroke(t){t?this._context.stroke(t):this._context.stroke()}strokeText(t,e,i,r){this._context.strokeText(t,e,i,r)}transform(t,e,i,r,a,n){this._context.transform(t,e,i,r,a,n)}translate(t,e){this._context.translate(t,e)}_enableTrace(){var t,e,i=this,r=C.length,a=this.setAttr,n=function(t){var r,a=i[t];i[t]=function(){return e=function(t){var e,i,r=[],a=t.length,n=g;for(e=0;e{"dragging"===e.dragStatus&&(t=!0)})),t},justDragged:!1,get node(){var t;return E._dragElements.forEach((e=>{t=e.node})),t},_dragElements:new Map,_drag(t){const e=[];E._dragElements.forEach(((i,r)=>{const{node:a}=i,n=a.getStage();n.setPointersPositions(t),void 0===i.pointerId&&(i.pointerId=g._getFirstPointerId(t));const s=n._changedPointerPositions.find((t=>t.id===i.pointerId));if(s){if("dragging"!==i.dragStatus){var o=a.dragDistance();if(Math.max(Math.abs(s.x-i.startPointerPos.x),Math.abs(s.y-i.startPointerPos.y)){e.fire("dragmove",{type:"dragmove",target:e,evt:t},!0)}))},_endDragBefore(t){const e=[];E._dragElements.forEach((r=>{const{node:a}=r,n=a.getStage();t&&n.setPointersPositions(t);if(!n._changedPointerPositions.find((t=>t.id===r.pointerId)))return;"dragging"!==r.dragStatus&&"stopped"!==r.dragStatus||(E.justDragged=!0,i._mouseListenClick=!1,i._touchListenClick=!1,i._pointerListenClick=!1,r.dragStatus="stopped");const s=r.node.getLayer()||r.node instanceof i.Stage&&r.node;s&&-1===e.indexOf(s)&&e.push(s)})),e.forEach((t=>{t.draw()}))},_endDragAfter(t){E._dragElements.forEach(((e,i)=>{"stopped"===e.dragStatus&&e.node.fire("dragend",{type:"dragend",target:e.node,evt:t},!0),"dragging"!==e.dragStatus&&E._dragElements.delete(i)}))}};i.isBrowser&&(window.addEventListener("mouseup",E._endDragBefore,!0),window.addEventListener("touchend",E._endDragBefore,!0),window.addEventListener("mousemove",E._drag),window.addEventListener("touchmove",E._drag),window.addEventListener("mouseup",E._endDragAfter,!1),window.addEventListener("touchend",E._endDragAfter,!1));var D="absoluteOpacity",L="allEventListeners",I="absoluteTransform",O="absoluteScale",F="canvas",B="listening",N="mouseenter",H="mouseleave",W="Shape",z=" ",Y="stage",X="transform",j="visible",q=["xChange.konva","yChange.konva","scaleXChange.konva","scaleYChange.konva","skewXChange.konva","skewYChange.konva","rotationChange.konva","offsetXChange.konva","offsetYChange.konva","transformsEnabledChange.konva"].join(z);let U=1;class V{constructor(t){this._id=U++,this.eventListeners={},this.attrs={},this.index=0,this._allEventListeners=null,this.parent=null,this._cache=new Map,this._attachedDepsListeners=new Map,this._lastPos=null,this._batchingTransformChange=!1,this._needClearTransformCache=!1,this._filterUpToDate=!1,this._isUnderCache=!1,this._dragEventId=null,this._shouldFireChangeEvents=!1,this.setAttrs(t),this._shouldFireChangeEvents=!0}hasChildren(){return!1}_clearCache(t){t!==X&&t!==I||!this._cache.get(t)?t?this._cache.delete(t):this._cache.clear():this._cache.get(t).dirty=!0}_getCache(t,e){var i=this._cache.get(t);return(void 0===i||(t===X||t===I)&&!0===i.dirty)&&(i=e.call(this),this._cache.set(t,i)),i}_calculate(t,e,i){if(!this._attachedDepsListeners.get(t)){const i=e.map((t=>t+"Change.konva")).join(z);this.on(i,(()=>{this._clearCache(t)})),this._attachedDepsListeners.set(t,!0)}return this._getCache(t,i)}_getCanvasCache(){return this._cache.get(F)}_clearSelfAndDescendantCache(t){this._clearCache(t),t===I&&this.fire("absoluteTransformChange")}clearCache(){if(this._cache.has(F)){const{scene:t,filter:e,hit:i}=this._cache.get(F);g.releaseCanvas(t,e,i),this._cache.delete(F)}return this._clearSelfAndDescendantCache(),this._requestDraw(),this}cache(t){var e=t||{},i={};void 0!==e.x&&void 0!==e.y&&void 0!==e.width&&void 0!==e.height||(i=this.getClientRect({skipTransform:!0,relativeTo:this.getParent()||void 0}));var r=Math.ceil(e.width||i.width),a=Math.ceil(e.height||i.height),n=e.pixelRatio,s=void 0===e.x?Math.floor(i.x):e.x,o=void 0===e.y?Math.floor(i.y):e.y,h=e.offset||0,l=e.drawBorder||!1,d=e.hitCanvasPixelRatio||1;if(!r||!a)return void g.error("Can not cache the node. Width or height of the node equals 0. Caching is skipped.");const c=Math.abs(Math.round(i.x)-s)>.5?1:0,u=Math.abs(Math.round(i.y)-o)>.5?1:0;s-=h,o-=h;var f=new G({pixelRatio:n,width:r+=2*h+c,height:a+=2*h+u}),p=new G({pixelRatio:n,width:0,height:0,willReadFrequently:!0}),v=new R({pixelRatio:d,width:r,height:a}),m=f.getContext(),_=v.getContext();return v.isCache=!0,f.isCache=!0,this._cache.delete(F),this._filterUpToDate=!1,!1===e.imageSmoothingEnabled&&(f.getContext()._context.imageSmoothingEnabled=!1,p.getContext()._context.imageSmoothingEnabled=!1),m.save(),_.save(),m.translate(-s,-o),_.translate(-s,-o),this._isUnderCache=!0,this._clearSelfAndDescendantCache(D),this._clearSelfAndDescendantCache(O),this.drawScene(f,this),this.drawHit(v,this),this._isUnderCache=!1,m.restore(),_.restore(),l&&(m.save(),m.beginPath(),m.rect(0,0,r,a),m.closePath(),m.setAttr("strokeStyle","red"),m.setAttr("lineWidth",5),m.stroke(),m.restore()),this._cache.set(F,{scene:f,filter:p,hit:v,x:s,y:o}),this._requestDraw(),this}isCached(){return this._cache.has(F)}getClientRect(t){throw new Error('abstract "getClientRect" method call')}_transformedRect(t,e){var i=[{x:t.x,y:t.y},{x:t.x+t.width,y:t.y},{x:t.x+t.width,y:t.y+t.height},{x:t.x,y:t.y+t.height}],r=1/0,a=1/0,n=-1/0,s=-1/0,o=this.getAbsoluteTransform(e);return i.forEach((function(t){var e=o.point(t);void 0===r&&(r=n=e.x,a=s=e.y),r=Math.min(r,e.x),a=Math.min(a,e.y),n=Math.max(n,e.x),s=Math.max(s,e.y)})),{x:r,y:a,width:n-r,height:s-a}}_drawCachedSceneCanvas(t){t.save(),t._applyOpacity(this),t._applyGlobalCompositeOperation(this);const e=this._getCanvasCache();t.translate(e.x,e.y);var i=this._getCachedSceneCanvas(),r=i.pixelRatio;t.drawImage(i._canvas,0,0,i.width/r,i.height/r),t.restore()}_drawCachedHitCanvas(t){var e=this._getCanvasCache(),i=e.hit;t.save(),t.translate(e.x,e.y),t.drawImage(i._canvas,0,0,i.width/i.pixelRatio,i.height/i.pixelRatio),t.restore()}_getCachedSceneCanvas(){var t,e,i,r,a=this.filters(),n=this._getCanvasCache(),s=n.scene,o=n.filter,h=o.getContext();if(a){if(!this._filterUpToDate){var l=s.pixelRatio;o.setSize(s.width/s.pixelRatio,s.height/s.pixelRatio);try{for(t=a.length,h.clear(),h.drawImage(s._canvas,0,0,s.getWidth()/l,s.getHeight()/l),e=h.getImageData(0,0,o.getWidth(),o.getHeight()),i=0;i{var e,i;if(!t)return this;for(e in t)"children"!==e&&(i="set"+g._capitalize(e),g._isFunction(this[i])?this[i](t[e]):this._setAttr(e,t[e]))})),this}isListening(){return this._getCache(B,this._isListening)}_isListening(t){if(!this.listening())return!1;const e=this.getParent();return!e||e===t||this===t||e._isListening(t)}isVisible(){return this._getCache(j,this._isVisible)}_isVisible(t){if(!this.visible())return!1;const e=this.getParent();return!e||e===t||this===t||e._isVisible(t)}shouldDrawHit(t,e=!1){if(t)return this._isVisible(t)&&this._isListening(t);var r=this.getLayer(),a=!1;E._dragElements.forEach((t=>{"dragging"===t.dragStatus&&("Stage"===t.node.nodeType||t.node.getLayer()===r)&&(a=!0)}));var n=!e&&!i.hitOnDragEnabled&&(a||i.isTransforming());return this.isListening()&&this.isVisible()&&!n}show(){return this.visible(!0),this}hide(){return this.visible(!1),this}getZIndex(){return this.index||0}getAbsoluteZIndex(){var t,e,i,r,a=this.getDepth(),n=this,s=0;const o=this.getStage();return"Stage"!==n.nodeType&&o&&function o(h){for(t=[],e=h.length,i=0;i0&&t[0].getDepth()<=a&&o(t)}(o.getChildren()),s}getDepth(){for(var t=0,e=this.parent;e;)t++,e=e.parent;return t}_batchTransformChanges(t){this._batchingTransformChange=!0,t(),this._batchingTransformChange=!1,this._needClearTransformCache&&(this._clearCache(X),this._clearSelfAndDescendantCache(I)),this._needClearTransformCache=!1}setPosition(t){return this._batchTransformChanges((()=>{this.x(t.x),this.y(t.y)})),this}getPosition(){return{x:this.x(),y:this.y()}}getRelativePointerPosition(){const t=this.getStage();if(!t)return null;var e=t.getPointerPosition();if(!e)return null;var i=this.getAbsoluteTransform().copy();return i.invert(),i.point(e)}getAbsolutePosition(t){let e=!1,i=this.parent;for(;i;){if(i.isCached()){e=!0;break}i=i.parent}e&&!t&&(t=!0);var r=this.getAbsoluteTransform(t).getMatrix(),n=new a,s=this.offset();return n.m=r.slice(),n.translate(s.x,s.y),n.getTranslation()}setAbsolutePosition(t){const{x:e,y:i,...r}=this._clearTransform();this.attrs.x=e,this.attrs.y=i,this._clearCache(X);var a=this._getAbsoluteTransform().copy();return a.invert(),a.translate(t.x,t.y),t={x:this.attrs.x+a.getTranslation().x,y:this.attrs.y+a.getTranslation().y},this._setTransform(r),this.setPosition({x:t.x,y:t.y}),this._clearCache(X),this._clearSelfAndDescendantCache(I),this}_setTransform(t){var e;for(e in t)this.attrs[e]=t[e]}_clearTransform(){var t={x:this.x(),y:this.y(),rotation:this.rotation(),scaleX:this.scaleX(),scaleY:this.scaleY(),offsetX:this.offsetX(),offsetY:this.offsetY(),skewX:this.skewX(),skewY:this.skewY()};return this.attrs.x=0,this.attrs.y=0,this.attrs.rotation=0,this.attrs.scaleX=1,this.attrs.scaleY=1,this.attrs.offsetX=0,this.attrs.offsetY=0,this.attrs.skewX=0,this.attrs.skewY=0,t}move(t){var e=t.x,i=t.y,r=this.x(),a=this.y();return void 0!==e&&(r+=e),void 0!==i&&(a+=i),this.setPosition({x:r,y:a}),this}_eachAncestorReverse(t,e){var i,r,a=[],n=this.getParent();if(!e||e._id!==this._id){for(a.unshift(this);n&&(!e||n._id!==e._id);)a.unshift(n),n=n.parent;for(i=a.length,r=0;r0&&(this.parent.children.splice(t,1),this.parent.children.splice(t-1,0,this),this.parent._setChildrenIndices(),!0)}moveToBottom(){if(!this.parent)return g.warn("Node has no parent. moveToBottom function is ignored."),!1;var t=this.index;return t>0&&(this.parent.children.splice(t,1),this.parent.children.unshift(this),this.parent._setChildrenIndices(),!0)}setZIndex(t){if(!this.parent)return g.warn("Node has no parent. zIndex parameter is ignored."),this;(t<0||t>=this.parent.children.length)&&g.warn("Unexpected value "+t+" for zIndex property. zIndex is just index of a node in children of its parent. Expected value is from 0 to "+(this.parent.children.length-1)+".");var e=this.index;return this.parent.children.splice(e,1),this.parent.children.splice(t,0,this),this.parent._setChildrenIndices(),this}getAbsoluteOpacity(){return this._getCache(D,this._getAbsoluteOpacity)}_getAbsoluteOpacity(){var t=this.opacity(),e=this.getParent();return e&&!e._isUnderCache&&(t*=e.getAbsoluteOpacity()),t}moveTo(t){return this.getParent()!==t&&(this._remove(),t.add(this)),this}toObject(){var t,e,i,r,a=this.getAttrs();const n={attrs:{},className:this.getClassName()};for(t in a)e=a[t],g.isObject(e)&&!g._isPlainObject(e)&&!g._isArray(e)||(i="function"==typeof this[t]&&this[t],delete a[t],r=i?i.call(this):null,a[t]=e,r!==e&&(n.attrs[t]=e));return g._prepareToStringify(n)}toJSON(){return JSON.stringify(this.toObject())}getParent(){return this.parent}findAncestors(t,e,i){var r=[];e&&this._isMatch(t)&&r.push(this);for(var a=this.parent;a;){if(a===i)return r;a._isMatch(t)&&r.push(a),a=a.parent}return r}isAncestorOf(t){return!1}findAncestor(t,e,i){return this.findAncestors(t,e,i)[0]}_isMatch(t){if(!t)return!1;if("function"==typeof t)return t(this);var e,i,r=t.replace(/ /g,"").split(","),a=r.length;for(e=0;e{try{const i=null==t?void 0:t.callback;i&&delete t.callback,g._urlToImage(this.toDataURL(t),(function(t){e(t),null==i||i(t)}))}catch(t){i(t)}}))}toBlob(t){return new Promise(((e,i)=>{try{const i=null==t?void 0:t.callback;i&&delete t.callback,this.toCanvas(t).toBlob((t=>{e(t),null==i||i(t)}),null==t?void 0:t.mimeType,null==t?void 0:t.quality)}catch(t){i(t)}}))}setSize(t){return this.width(t.width),this.height(t.height),this}getSize(){return{width:this.width(),height:this.height()}}getClassName(){return this.className||this.nodeType}getType(){return this.nodeType}getDragDistance(){return void 0!==this.attrs.dragDistance?this.attrs.dragDistance:this.parent?this.parent.getDragDistance():i.dragDistance}_off(t,e,i){var r,a,n,s=this.eventListeners[t];for(r=0;r=0)&&!this.isDragging()){var e=!1;E._dragElements.forEach((t=>{this.isAncestorOf(t.node)&&(e=!0)})),e||this._createDragElement(t)}}))}_dragChange(){if(this.attrs.draggable)this._listenDrag();else{if(this._dragCleanup(),!this.getStage())return;const t=E._dragElements.get(this._id),e=t&&"dragging"===t.dragStatus,i=t&&"ready"===t.dragStatus;e?this.stopDrag():i&&E._dragElements.delete(this._id)}}_dragCleanup(){this.off("mousedown.konva"),this.off("touchstart.konva")}isClientRectOnScreen(t={x:0,y:0}){const e=this.getStage();if(!e)return!1;const i={x:-t.x,y:-t.y,width:e.width()+2*t.x,height:e.height()+2*t.y};return g.haveIntersection(i,this.getClientRect())}static create(t,e){return g._isString(t)&&(t=JSON.parse(t)),this._createNode(t,e)}static _createNode(t,e){var r,a,n,s=V.prototype.getClassName.call(t),o=t.children;e&&(t.attrs.container=e),i[s]||(g.warn('Can not find a node with class name "'+s+'". Fallback to "Shape".'),s="Shape");if(r=new(0,i[s])(t.attrs),o)for(a=o.length,n=0;n0}removeChildren(){return this.getChildren().forEach((t=>{t.parent=null,t.index=0,t.remove()})),this.children=[],this._requestDraw(),this}destroyChildren(){return this.getChildren().forEach((t=>{t.parent=null,t.index=0,t.destroy()})),this.children=[],this._requestDraw(),this}add(...t){if(0===t.length)return this;if(t.length>1){for(var e=0;e0?e[0]:void 0}_generalFind(t,e){var i=[];return this._descendants((r=>{const a=r._isMatch(t);return a&&i.push(r),!(!a||!e)})),i}_descendants(t){let e=!1;const i=this.getChildren();for(const r of i){if(e=t(r),e)return!0;if(r.hasChildren()&&(e=r._descendants(t),e))return!0}return!1}toObject(){var t=V.prototype.toObject.call(this);return t.children=[],this.getChildren().forEach((e=>{t.children.push(e.toObject())})),t}isAncestorOf(t){for(var e=t.getParent();e;){if(e._id===this._id)return!0;e=e.getParent()}return!1}clone(t){var e=V.prototype.clone.call(this,t);return this.getChildren().forEach((function(t){e.add(t.clone())})),e}getAllIntersections(t){var e=[];return this.find("Shape").forEach((i=>{i.isVisible()&&i.intersects(t)&&e.push(i)})),e}_clearSelfAndDescendantCache(t){var e;super._clearSelfAndDescendantCache(t),this.isCached()||null===(e=this.children)||void 0===e||e.forEach((function(e){e._clearSelfAndDescendantCache(t)}))}_setChildrenIndices(){var t;null===(t=this.children)||void 0===t||t.forEach((function(t,e){t.index=e})),this._requestDraw()}drawScene(t,e,i){var r=this.getLayer(),a=t||r&&r.getCanvas(),n=a&&a.getContext(),s=this._getCanvasCache(),o=s&&s.scene,h=a&&a.isCache;if(!this.isVisible()&&!h)return this;if(o){n.save();var l=this.getAbsoluteTransform(e).getMatrix();n.transform(l[0],l[1],l[2],l[3],l[4],l[5]),this._drawCachedSceneCanvas(n),n.restore()}else this._drawChildren("drawScene",a,e,i);return this}drawHit(t,e){if(!this.shouldDrawHit(e))return this;var i=this.getLayer(),r=t||i&&i.hitCanvas,a=r&&r.getContext(),n=this._getCanvasCache();if(n&&n.hit){a.save();var s=this.getAbsoluteTransform(e).getMatrix();a.transform(s[0],s[1],s[2],s[3],s[4],s[5]),this._drawCachedHitCanvas(a),a.restore()}else this._drawChildren("drawHit",r,e);return this}_drawChildren(t,e,i,r){var a,n=e&&e.getContext(),s=this.clipWidth(),o=this.clipHeight(),h=this.clipFunc(),l="number"==typeof s&&"number"==typeof o||h;const d=i===this;if(l){n.save();var c=this.getAbsoluteTransform(i),g=c.getMatrix();let t;if(n.transform(g[0],g[1],g[2],g[3],g[4],g[5]),n.beginPath(),h)t=h.call(this,n,this);else{var u=this.clipX(),f=this.clipY();n.rect(u||0,f||0,s,o)}n.clip.apply(n,t),g=c.copy().invert().getMatrix(),n.transform(g[0],g[1],g[2],g[3],g[4],g[5])}var p=!d&&"source-over"!==this.globalCompositeOperation()&&"drawScene"===t;p&&(n.save(),n._applyGlobalCompositeOperation(this)),null===(a=this.children)||void 0===a||a.forEach((function(a){a[t](e,i,r)})),p&&n.restore(),l&&n.restore()}getClientRect(t={}){var e,i,r,a,n,s=t.skipTransform,o=t.relativeTo,h={x:1/0,y:1/0,width:0,height:0},l=this;null===(e=this.children)||void 0===e||e.forEach((function(e){if(e.visible()){var s=e.getClientRect({relativeTo:l,skipShadow:t.skipShadow,skipStroke:t.skipStroke});0===s.width&&0===s.height||(void 0===i?(i=s.x,r=s.y,a=s.x+s.width,n=s.y+s.height):(i=Math.min(i,s.x),r=Math.min(r,s.y),a=Math.max(a,s.x+s.width),n=Math.max(n,s.y+s.height)))}}));for(var d=this.find("Shape"),c=!1,g=0;gt.indexOf("pointer")>=0?"pointer":t.indexOf("touch")>=0?"touch":"mouse",Tt=t=>{const e=kt(t);return"pointer"===e?i.pointerEventsEnabled&&Pt.pointer:"touch"===e?Pt.touch:"mouse"===e?Pt.mouse:void 0};function At(t={}){return(t.clipFunc||t.clipWidth||t.clipHeight)&&g.warn("Stage does not support clipping. Please use clip for Layers or Groups."),t}const Mt=[];class Gt extends Q{constructor(t){super(At(t)),this._pointerPositions=[],this._changedPointerPositions=[],this._buildDOM(),this._bindContentEvents(),Mt.push(this),this.on("widthChange.konva heightChange.konva",this._resizeDOM),this.on("visibleChange.konva",this._checkVisibility),this.on("clipWidthChange.konva clipHeightChange.konva clipFuncChange.konva",(()=>{At(this.attrs)})),this._checkVisibility()}_validateAdd(t){const e="Layer"===t.getType(),i="FastLayer"===t.getType();e||i||g.throw("You may only add layers to the stage.")}_checkVisibility(){if(!this.content)return;const t=this.visible()?"":"none";this.content.style.display=t}setContainer(t){if("string"==typeof t){if("."===t.charAt(0)){var e=t.slice(1);t=document.getElementsByClassName(e)[0]}else{var i;i="#"!==t.charAt(0)?t:t.slice(1),t=document.getElementById(i)}if(!t)throw"Can not find container in document with id "+i}return this._setAttr("container",t),this.content&&(this.content.parentElement&&this.content.parentElement.removeChild(this.content),t.appendChild(this.content)),this}shouldDrawHit(){return!0}clear(){var t,e=this.children,i=e.length;for(t=0;t-1&&Mt.splice(e,1),g.releaseCanvas(this.bufferCanvas._canvas,this.bufferHitCanvas._canvas),this}getPointerPosition(){const t=this._pointerPositions[0]||this._changedPointerPositions[0];return t?{x:t.x,y:t.y}:(g.warn("Pointer position is missing and not registered by the stage. Looks like it is outside of the stage container. You can set it manually from event: stage.setPointersPositions(event);"),null)}_getPointerById(t){return this._pointerPositions.find((e=>e.id===t))}getPointersPositions(){return this._pointerPositions}getStage(){return this}getContent(){return this.content}_toKonvaCanvas(t){(t=t||{}).x=t.x||0,t.y=t.y||0,t.width=t.width||this.width(),t.height=t.height||this.height();var e=new G({width:t.width,height:t.height,pixelRatio:t.pixelRatio||1}),i=e.getContext()._context,r=this.children;return(t.x||t.y)&&i.translate(-1*t.x,-1*t.y),r.forEach((function(e){if(e.isVisible()){var r=e._toKonvaCanvas(t);i.drawImage(r._canvas,t.x,t.y,r.getWidth()/r.getPixelRatio(),r.getHeight()/r.getPixelRatio())}})),e}getIntersection(t){if(!t)return null;var e,i=this.children;for(e=i.length-1;e>=0;e--){const r=i[e].getIntersection(t);if(r)return r}return null}_resizeDOM(){var t=this.width(),e=this.height();this.content&&(this.content.style.width=t+"px",this.content.style.height=e+"px"),this.bufferCanvas.setSize(t,e),this.bufferHitCanvas.setSize(t,e),this.children.forEach((i=>{i.setSize({width:t,height:e}),i.draw()}))}add(t,...e){if(arguments.length>1){for(var r=0;r5&&g.warn("The stage has "+a+" layers. Recommended maximum number of layers is 3-5. Adding more layers into the stage may drop the performance. Rethink your tree structure, you can use Konva.Group."),t.setSize({width:this.width(),height:this.height()}),t.draw(),i.isBrowser&&this.content.appendChild(t.canvas._canvas),this}getParent(){return null}getLayer(){return null}hasPointerCapture(t){return et(t,this)}setPointerCapture(t){it(t,this)}releaseCapture(t){rt(t)}getLayers(){return this.children}_bindContentEvents(){i.isBrowser&&Ct.forEach((([t,e])=>{this.content.addEventListener(t,(t=>{this[e](t)}),{passive:!1})}))}_pointerenter(t){this.setPointersPositions(t);const e=Tt(t.type);e&&this._fire(e.pointerenter,{evt:t,target:this,currentTarget:this})}_pointerover(t){this.setPointersPositions(t);const e=Tt(t.type);e&&this._fire(e.pointerover,{evt:t,target:this,currentTarget:this})}_getTargetShape(t){let e=this[t+"targetShape"];return e&&!e.getStage()&&(e=null),e}_pointerleave(t){const e=Tt(t.type),r=kt(t.type);if(e){this.setPointersPositions(t);var a=this._getTargetShape(r),n=!(i.isDragging()||i.isTransforming())||i.hitOnDragEnabled;a&&n?(a._fireAndBubble(e.pointerout,{evt:t}),a._fireAndBubble(e.pointerleave,{evt:t}),this._fire(e.pointerleave,{evt:t,target:this,currentTarget:this}),this[r+"targetShape"]=null):n&&(this._fire(e.pointerleave,{evt:t,target:this,currentTarget:this}),this._fire(e.pointerout,{evt:t,target:this,currentTarget:this})),this.pointerPos=null,this._pointerPositions=[]}}_pointerdown(t){const e=Tt(t.type),r=kt(t.type);if(e){this.setPointersPositions(t);var a=!1;this._changedPointerPositions.forEach((n=>{var s=this.getIntersection(n);if(E.justDragged=!1,i["_"+r+"ListenClick"]=!0,!s||!s.isListening())return void(this[r+"ClickStartShape"]=void 0);i.capturePointerEventsEnabled&&s.setPointerCapture(n.id),this[r+"ClickStartShape"]=s,s._fireAndBubble(e.pointerdown,{evt:t,pointerId:n.id}),a=!0;const o=t.type.indexOf("touch")>=0;s.preventDefault()&&t.cancelable&&o&&t.preventDefault()})),a||this._fire(e.pointerdown,{evt:t,target:this,currentTarget:this,pointerId:this._pointerPositions[0].id})}}_pointermove(t){const e=Tt(t.type),r=kt(t.type);if(!e)return;if(i.isDragging()&&E.node.preventDefault()&&t.cancelable&&t.preventDefault(),this.setPointersPositions(t),!(!(i.isDragging()||i.isTransforming())||i.hitOnDragEnabled))return;var a={};let n=!1;var s=this._getTargetShape(r);this._changedPointerPositions.forEach((i=>{const o=Z(i.id)||this.getIntersection(i),h=i.id,l={evt:t,pointerId:h};var d=s!==o;if(d&&s&&(s._fireAndBubble(e.pointerout,{...l},o),s._fireAndBubble(e.pointerleave,{...l},o)),o){if(a[o._id])return;a[o._id]=!0}o&&o.isListening()?(n=!0,d&&(o._fireAndBubble(e.pointerover,{...l},s),o._fireAndBubble(e.pointerenter,{...l},s),this[r+"targetShape"]=o),o._fireAndBubble(e.pointermove,{...l})):s&&(this._fire(e.pointerover,{evt:t,target:this,currentTarget:this,pointerId:h}),this[r+"targetShape"]=null)})),n||this._fire(e.pointermove,{evt:t,target:this,currentTarget:this,pointerId:this._changedPointerPositions[0].id})}_pointerup(t){const e=Tt(t.type),r=kt(t.type);if(!e)return;this.setPointersPositions(t);const a=this[r+"ClickStartShape"],n=this[r+"ClickEndShape"];var s={};let o=!1;this._changedPointerPositions.forEach((h=>{const l=Z(h.id)||this.getIntersection(h);if(l){if(l.releaseCapture(h.id),s[l._id])return;s[l._id]=!0}const d=h.id,c={evt:t,pointerId:d};let g=!1;i["_"+r+"InDblClickWindow"]?(g=!0,clearTimeout(this[r+"DblTimeout"])):E.justDragged||(i["_"+r+"InDblClickWindow"]=!0,clearTimeout(this[r+"DblTimeout"])),this[r+"DblTimeout"]=setTimeout((function(){i["_"+r+"InDblClickWindow"]=!1}),i.dblClickWindow),l&&l.isListening()?(o=!0,this[r+"ClickEndShape"]=l,l._fireAndBubble(e.pointerup,{...c}),i["_"+r+"ListenClick"]&&a&&a===l&&(l._fireAndBubble(e.pointerclick,{...c}),g&&n&&n===l&&l._fireAndBubble(e.pointerdblclick,{...c}))):(this[r+"ClickEndShape"]=null,i["_"+r+"ListenClick"]&&this._fire(e.pointerclick,{evt:t,target:this,currentTarget:this,pointerId:d}),g&&this._fire(e.pointerdblclick,{evt:t,target:this,currentTarget:this,pointerId:d}))})),o||this._fire(e.pointerup,{evt:t,target:this,currentTarget:this,pointerId:this._changedPointerPositions[0].id}),i["_"+r+"ListenClick"]=!1,t.cancelable&&"touch"!==r&&t.preventDefault()}_contextmenu(t){this.setPointersPositions(t);var e=this.getIntersection(this.getPointerPosition());e&&e.isListening()?e._fireAndBubble(_t,{evt:t}):this._fire(_t,{evt:t,target:this,currentTarget:this})}_wheel(t){this.setPointersPositions(t);var e=this.getIntersection(this.getPointerPosition());e&&e.isListening()?e._fireAndBubble(wt,{evt:t}):this._fire(wt,{evt:t,target:this,currentTarget:this})}_pointercancel(t){this.setPointersPositions(t);const e=Z(t.pointerId)||this.getIntersection(this.getPointerPosition());e&&e._fireAndBubble(gt,tt(t)),rt(t.pointerId)}_lostpointercapture(t){rt(t.pointerId)}setPointersPositions(t){var e=this._getContentPosition(),i=null,r=null;void 0!==(t=t||window.event).touches?(this._pointerPositions=[],this._changedPointerPositions=[],Array.prototype.forEach.call(t.touches,(t=>{this._pointerPositions.push({id:t.identifier,x:(t.clientX-e.left)/e.scaleX,y:(t.clientY-e.top)/e.scaleY})})),Array.prototype.forEach.call(t.changedTouches||t.touches,(t=>{this._changedPointerPositions.push({id:t.identifier,x:(t.clientX-e.left)/e.scaleX,y:(t.clientY-e.top)/e.scaleY})}))):(i=(t.clientX-e.left)/e.scaleX,r=(t.clientY-e.top)/e.scaleY,this.pointerPos={x:i,y:r},this._pointerPositions=[{x:i,y:r,id:g._getFirstPointerId(t)}],this._changedPointerPositions=[{x:i,y:r,id:g._getFirstPointerId(t)}])}_setPointerPosition(t){g.warn('Method _setPointerPosition is deprecated. Use "stage.setPointersPositions(event)" instead.'),this.setPointersPositions(t)}_getContentPosition(){if(!this.content||!this.content.getBoundingClientRect)return{top:0,left:0,scaleX:1,scaleY:1};var t=this.content.getBoundingClientRect();return{top:t.top,left:t.left,scaleX:t.width/this.content.clientWidth||1,scaleY:t.height/this.content.clientHeight||1}}_buildDOM(){if(this.bufferCanvas=new G({width:this.width(),height:this.height()}),this.bufferHitCanvas=new R({pixelRatio:1,width:this.width(),height:this.height()}),i.isBrowser){var t=this.container();if(!t)throw"Stage has no container. A container is required.";t.innerHTML="",this.content=document.createElement("div"),this.content.style.position="relative",this.content.style.userSelect="none",this.content.className="konvajs-content",this.content.setAttribute("role","presentation"),t.appendChild(this.content),this._resizeDOM()}}cache(){return g.warn("Cache function is not allowed for stage. You may use cache only for layers, groups and shapes."),this}clearCache(){return this}batchDraw(){return this.getChildren().forEach((function(t){t.batchDraw()})),this}}Gt.prototype.nodeType="Stage",r(Gt),w.addGetterSetter(Gt,"container"),i.isBrowser&&document.addEventListener("visibilitychange",(()=>{Mt.forEach((t=>{t.batchDraw()}))}));var Rt="hasShadow",Et="shadowRGBA",Dt="patternImage",Lt="linearGradient",It="radialGradient";let Ot;function Ft(){return Ot||(Ot=g.createCanvasElement().getContext("2d"),Ot)}const Bt={};class Nt extends V{constructor(t){let e;for(super(t);e=g.getRandomColor(),!e||e in Bt;);this.colorKey=e,Bt[e]=this}getContext(){return g.warn("shape.getContext() method is deprecated. Please do not use it."),this.getLayer().getContext()}getCanvas(){return g.warn("shape.getCanvas() method is deprecated. Please do not use it."),this.getLayer().getCanvas()}getSceneFunc(){return this.attrs.sceneFunc||this._sceneFunc}getHitFunc(){return this.attrs.hitFunc||this._hitFunc}hasShadow(){return this._getCache(Rt,this._hasShadow)}_hasShadow(){return this.shadowEnabled()&&0!==this.shadowOpacity()&&!!(this.shadowColor()||this.shadowBlur()||this.shadowOffsetX()||this.shadowOffsetY())}_getFillPattern(){return this._getCache(Dt,this.__getFillPattern)}__getFillPattern(){if(this.fillPatternImage()){const t=Ft().createPattern(this.fillPatternImage(),this.fillPatternRepeat()||"repeat");if(t&&t.setTransform){const e=new a;e.translate(this.fillPatternX(),this.fillPatternY()),e.rotate(i.getAngle(this.fillPatternRotation())),e.scale(this.fillPatternScaleX(),this.fillPatternScaleY()),e.translate(-1*this.fillPatternOffsetX(),-1*this.fillPatternOffsetY());const r=e.getMatrix(),n="undefined"==typeof DOMMatrix?{a:r[0],b:r[1],c:r[2],d:r[3],e:r[4],f:r[5]}:new DOMMatrix(r);t.setTransform(n)}return t}}_getLinearGradient(){return this._getCache(Lt,this.__getLinearGradient)}__getLinearGradient(){var t=this.fillLinearGradientColorStops();if(t){for(var e=Ft(),i=this.fillLinearGradientStartPoint(),r=this.fillLinearGradientEndPoint(),a=e.createLinearGradient(i.x,i.y,r.x,r.y),n=0;nthis.fillEnabled()&&!!(this.fill()||this.fillPatternImage()||this.fillLinearGradientColorStops()||this.fillRadialGradientColorStops())))}hasStroke(){return this._calculate("hasStroke",["strokeEnabled","strokeWidth","stroke","strokeLinearGradientColorStops"],(()=>this.strokeEnabled()&&this.strokeWidth()&&!(!this.stroke()&&!this.strokeLinearGradientColorStops())))}hasHitStroke(){const t=this.hitStrokeWidth();return"auto"===t?this.hasStroke():this.strokeEnabled()&&!!t}intersects(t){var e=this.getStage();if(!e)return!1;const i=e.bufferHitCanvas;i.getContext().clear(),this.drawHit(i,void 0,!0);return i.context.getImageData(Math.round(t.x),Math.round(t.y),1,1).data[3]>0}destroy(){return V.prototype.destroy.call(this),delete Bt[this.colorKey],delete this.colorKey,this}_useBufferCanvas(t){var e;if(!(null===(e=this.attrs.perfectDrawEnabled)||void 0===e||e))return!1;const i=t||this.hasFill(),r=this.hasStroke(),a=1!==this.getAbsoluteOpacity();if(i&&r&&a)return!0;const n=this.hasShadow(),s=this.shadowForStrokeEnabled();return!!(i&&r&&n&&s)}setStrokeHitEnabled(t){g.warn("strokeHitEnabled property is deprecated. Please use hitStrokeWidth instead."),t?this.hitStrokeWidth("auto"):this.hitStrokeWidth(0)}getStrokeHitEnabled(){return 0!==this.hitStrokeWidth()}getSelfRect(){var t=this.size();return{x:this._centroid?-t.width/2:0,y:this._centroid?-t.height/2:0,width:t.width,height:t.height}}getClientRect(t={}){let e=!1,i=this.getParent();for(;i;){if(i.isCached()){e=!0;break}i=i.getParent()}const r=t.skipTransform,a=t.relativeTo||e&&this.getStage()||void 0,n=this.getSelfRect(),s=!t.skipStroke&&this.hasStroke()&&this.strokeWidth()||0,o=n.width+s,h=n.height+s,l=!t.skipShadow&&this.hasShadow(),d=l?this.shadowOffsetX():0,c=l?this.shadowOffsetY():0,g=o+Math.abs(d),u=h+Math.abs(c),f=l&&this.shadowBlur()||0,p={width:g+2*f,height:u+2*f,x:-(s/2+f)+Math.min(d,0)+n.x,y:-(s/2+f)+Math.min(c,0)+n.y};return r?p:this._transformedRect(p,a)}drawScene(t,e,i){var r,a,n=this.getLayer(),s=t||n.getCanvas(),o=s.getContext(),h=this._getCanvasCache(),l=this.getSceneFunc(),d=this.hasShadow(),c=s.isCache,g=e===this;if(!this.isVisible()&&!g)return this;if(h){o.save();var u=this.getAbsoluteTransform(e).getMatrix();return o.transform(u[0],u[1],u[2],u[3],u[4],u[5]),this._drawCachedSceneCanvas(o),o.restore(),this}if(!l)return this;if(o.save(),this._useBufferCanvas()&&!c){r=this.getStage();const t=i||r.bufferCanvas;(a=t.getContext()).clear(),a.save(),a._applyLineJoin(this);var f=this.getAbsoluteTransform(e).getMatrix();a.transform(f[0],f[1],f[2],f[3],f[4],f[5]),l.call(this,a,this),a.restore();var p=t.pixelRatio;d&&o._applyShadow(this),o._applyOpacity(this),o._applyGlobalCompositeOperation(this),o.drawImage(t._canvas,0,0,t.width/p,t.height/p)}else{if(o._applyLineJoin(this),!g){f=this.getAbsoluteTransform(e).getMatrix();o.transform(f[0],f[1],f[2],f[3],f[4],f[5]),o._applyOpacity(this),o._applyGlobalCompositeOperation(this)}d&&o._applyShadow(this),l.call(this,o,this)}return o.restore(),this}drawHit(t,e,i=!1){if(!this.shouldDrawHit(e,i))return this;var r=this.getLayer(),a=t||r.hitCanvas,n=a&&a.getContext(),s=this.hitFunc()||this.sceneFunc(),o=this._getCanvasCache(),h=o&&o.hit;if(this.colorKey||g.warn("Looks like your canvas has a destroyed shape in it. Do not reuse shape after you destroyed it. If you want to reuse shape you should call remove() instead of destroy()"),h){n.save();var l=this.getAbsoluteTransform(e).getMatrix();return n.transform(l[0],l[1],l[2],l[3],l[4],l[5]),this._drawCachedHitCanvas(n),n.restore(),this}if(!s)return this;n.save(),n._applyLineJoin(this);if(!(this===e)){var d=this.getAbsoluteTransform(e).getMatrix();n.transform(d[0],d[1],d[2],d[3],d[4],d[5])}return s.call(this,n,this),n.restore(),this}drawHitFromCache(t=0){var e,i,r,a,n,s=this._getCanvasCache(),o=this._getCachedSceneCanvas(),h=s.hit,l=h.getContext(),d=h.getWidth(),c=h.getHeight();l.clear(),l.drawImage(o._canvas,0,0,d,c);try{for(r=(i=(e=l.getImageData(0,0,d,c)).data).length,a=g._hexToRgb(this.colorKey),n=0;nt?(i[n]=a.r,i[n+1]=a.g,i[n+2]=a.b,i[n+3]=255):i[n+3]=0;l.putImageData(e,0,0)}catch(t){g.error("Unable to draw hit graph from cached scene canvas. "+t.message)}return this}hasPointerCapture(t){return et(t,this)}setPointerCapture(t){it(t,this)}releaseCapture(t){rt(t)}}Nt.prototype._fillFunc=function(t){const e=this.attrs.fillRule;e?t.fill(e):t.fill()},Nt.prototype._strokeFunc=function(t){t.stroke()},Nt.prototype._fillFuncHit=function(t){const e=this.attrs.fillRule;e?t.fill(e):t.fill()},Nt.prototype._strokeFuncHit=function(t){t.stroke()},Nt.prototype._centroid=!1,Nt.prototype.nodeType="Shape",r(Nt),Nt.prototype.eventListeners={},Nt.prototype.on.call(Nt.prototype,"shadowColorChange.konva shadowBlurChange.konva shadowOffsetChange.konva shadowOpacityChange.konva shadowEnabledChange.konva",(function(){this._clearCache(Rt)})),Nt.prototype.on.call(Nt.prototype,"shadowColorChange.konva shadowOpacityChange.konva shadowEnabledChange.konva",(function(){this._clearCache(Et)})),Nt.prototype.on.call(Nt.prototype,"fillPriorityChange.konva fillPatternImageChange.konva fillPatternRepeatChange.konva fillPatternScaleXChange.konva fillPatternScaleYChange.konva fillPatternOffsetXChange.konva fillPatternOffsetYChange.konva fillPatternXChange.konva fillPatternYChange.konva fillPatternRotationChange.konva",(function(){this._clearCache(Dt)})),Nt.prototype.on.call(Nt.prototype,"fillPriorityChange.konva fillLinearGradientColorStopsChange.konva fillLinearGradientStartPointXChange.konva fillLinearGradientStartPointYChange.konva fillLinearGradientEndPointXChange.konva fillLinearGradientEndPointYChange.konva",(function(){this._clearCache(Lt)})),Nt.prototype.on.call(Nt.prototype,"fillPriorityChange.konva fillRadialGradientColorStopsChange.konva fillRadialGradientStartPointXChange.konva fillRadialGradientStartPointYChange.konva fillRadialGradientEndPointXChange.konva fillRadialGradientEndPointYChange.konva fillRadialGradientStartRadiusChange.konva fillRadialGradientEndRadiusChange.konva",(function(){this._clearCache(It)})),w.addGetterSetter(Nt,"stroke",void 0,y()),w.addGetterSetter(Nt,"strokeWidth",2,p()),w.addGetterSetter(Nt,"fillAfterStrokeEnabled",!1),w.addGetterSetter(Nt,"hitStrokeWidth","auto",m()),w.addGetterSetter(Nt,"strokeHitEnabled",!0,x()),w.addGetterSetter(Nt,"perfectDrawEnabled",!0,x()),w.addGetterSetter(Nt,"shadowForStrokeEnabled",!0,x()),w.addGetterSetter(Nt,"lineJoin"),w.addGetterSetter(Nt,"lineCap"),w.addGetterSetter(Nt,"sceneFunc"),w.addGetterSetter(Nt,"hitFunc"),w.addGetterSetter(Nt,"dash"),w.addGetterSetter(Nt,"dashOffset",0,p()),w.addGetterSetter(Nt,"shadowColor",void 0,_()),w.addGetterSetter(Nt,"shadowBlur",0,p()),w.addGetterSetter(Nt,"shadowOpacity",1,p()),w.addComponentsGetterSetter(Nt,"shadowOffset",["x","y"]),w.addGetterSetter(Nt,"shadowOffsetX",0,p()),w.addGetterSetter(Nt,"shadowOffsetY",0,p()),w.addGetterSetter(Nt,"fillPatternImage"),w.addGetterSetter(Nt,"fill",void 0,y()),w.addGetterSetter(Nt,"fillPatternX",0,p()),w.addGetterSetter(Nt,"fillPatternY",0,p()),w.addGetterSetter(Nt,"fillLinearGradientColorStops"),w.addGetterSetter(Nt,"strokeLinearGradientColorStops"),w.addGetterSetter(Nt,"fillRadialGradientStartRadius",0),w.addGetterSetter(Nt,"fillRadialGradientEndRadius",0),w.addGetterSetter(Nt,"fillRadialGradientColorStops"),w.addGetterSetter(Nt,"fillPatternRepeat","repeat"),w.addGetterSetter(Nt,"fillEnabled",!0),w.addGetterSetter(Nt,"strokeEnabled",!0),w.addGetterSetter(Nt,"shadowEnabled",!0),w.addGetterSetter(Nt,"dashEnabled",!0),w.addGetterSetter(Nt,"strokeScaleEnabled",!0),w.addGetterSetter(Nt,"fillPriority","color"),w.addComponentsGetterSetter(Nt,"fillPatternOffset",["x","y"]),w.addGetterSetter(Nt,"fillPatternOffsetX",0,p()),w.addGetterSetter(Nt,"fillPatternOffsetY",0,p()),w.addComponentsGetterSetter(Nt,"fillPatternScale",["x","y"]),w.addGetterSetter(Nt,"fillPatternScaleX",1,p()),w.addGetterSetter(Nt,"fillPatternScaleY",1,p()),w.addComponentsGetterSetter(Nt,"fillLinearGradientStartPoint",["x","y"]),w.addComponentsGetterSetter(Nt,"strokeLinearGradientStartPoint",["x","y"]),w.addGetterSetter(Nt,"fillLinearGradientStartPointX",0),w.addGetterSetter(Nt,"strokeLinearGradientStartPointX",0),w.addGetterSetter(Nt,"fillLinearGradientStartPointY",0),w.addGetterSetter(Nt,"strokeLinearGradientStartPointY",0),w.addComponentsGetterSetter(Nt,"fillLinearGradientEndPoint",["x","y"]),w.addComponentsGetterSetter(Nt,"strokeLinearGradientEndPoint",["x","y"]),w.addGetterSetter(Nt,"fillLinearGradientEndPointX",0),w.addGetterSetter(Nt,"strokeLinearGradientEndPointX",0),w.addGetterSetter(Nt,"fillLinearGradientEndPointY",0),w.addGetterSetter(Nt,"strokeLinearGradientEndPointY",0),w.addComponentsGetterSetter(Nt,"fillRadialGradientStartPoint",["x","y"]),w.addGetterSetter(Nt,"fillRadialGradientStartPointX",0),w.addGetterSetter(Nt,"fillRadialGradientStartPointY",0),w.addComponentsGetterSetter(Nt,"fillRadialGradientEndPoint",["x","y"]),w.addGetterSetter(Nt,"fillRadialGradientEndPointX",0),w.addGetterSetter(Nt,"fillRadialGradientEndPointY",0),w.addGetterSetter(Nt,"fillPatternRotation",0),w.addGetterSetter(Nt,"fillRule",void 0,_()),w.backCompat(Nt,{dashArray:"dash",getDashArray:"getDash",setDashArray:"getDash",drawFunc:"sceneFunc",getDrawFunc:"getSceneFunc",setDrawFunc:"setSceneFunc",drawHitFunc:"hitFunc",getDrawHitFunc:"getHitFunc",setDrawHitFunc:"setHitFunc"});var Ht=[{x:0,y:0},{x:-1,y:-1},{x:1,y:-1},{x:1,y:1},{x:-1,y:1}],Wt=Ht.length;class zt extends Q{constructor(t){super(t),this.canvas=new G,this.hitCanvas=new R({pixelRatio:1}),this._waitingForDraw=!1,this.on("visibleChange.konva",this._checkVisibility),this._checkVisibility(),this.on("imageSmoothingEnabledChange.konva",this._setSmoothEnabled),this._setSmoothEnabled()}createPNGStream(){return this.canvas._canvas.createPNGStream()}getCanvas(){return this.canvas}getNativeCanvasElement(){return this.canvas._canvas}getHitCanvas(){return this.hitCanvas}getContext(){return this.getCanvas().getContext()}clear(t){return this.getContext().clear(t),this.getHitCanvas().getContext().clear(t),this}setZIndex(t){super.setZIndex(t);var e=this.getStage();return e&&e.content&&(e.content.removeChild(this.getNativeCanvasElement()),t{this.draw(),this._waitingForDraw=!1}))),this}getIntersection(t){if(!this.isListening()||!this.isVisible())return null;for(var e=1,i=!1;;){for(let r=0;r0?{antialiased:!0}:{}}drawScene(t,e){var i=this.getLayer(),r=t||i&&i.getCanvas();return this._fire("beforeDraw",{node:this}),this.clearBeforeDraw()&&r.getContext().clear(),Q.prototype.drawScene.call(this,r,e),this._fire("draw",{node:this}),this}drawHit(t,e){var i=this.getLayer(),r=t||i&&i.hitCanvas;return i&&i.clearBeforeDraw()&&i.getHitCanvas().getContext().clear(),Q.prototype.drawHit.call(this,r,e),this}enableHitGraph(){return this.hitGraphEnabled(!0),this}disableHitGraph(){return this.hitGraphEnabled(!1),this}setHitGraphEnabled(t){g.warn("hitGraphEnabled method is deprecated. Please use layer.listening() instead."),this.listening(t)}getHitGraphEnabled(t){return g.warn("hitGraphEnabled method is deprecated. Please use layer.listening() instead."),this.listening()}toggleHitCanvas(){if(this.parent&&this.parent.content){var t=this.parent;!!this.hitCanvas._canvas.parentNode?t.content.removeChild(this.hitCanvas._canvas):t.content.appendChild(this.hitCanvas._canvas)}}destroy(){return g.releaseCanvas(this.getNativeCanvasElement(),this.getHitCanvas()._canvas),super.destroy()}}zt.prototype.nodeType="Layer",r(zt),w.addGetterSetter(zt,"imageSmoothingEnabled",!0),w.addGetterSetter(zt,"clearBeforeDraw",!0),w.addGetterSetter(zt,"hitGraphEnabled",!0,x());class Yt extends zt{constructor(t){super(t),this.listening(!1),g.warn('Konva.Fast layer is deprecated. Please use "new Konva.Layer({ listening: false })" instead.')}}Yt.prototype.nodeType="FastLayer",r(Yt);class Xt extends Q{_validateAdd(t){var e=t.getType();"Group"!==e&&"Shape"!==e&&g.throw("You may only add groups and shapes to groups.")}}Xt.prototype.nodeType="Group",r(Xt);const jt=e.performance&&e.performance.now?function(){return e.performance.now()}:function(){return(new Date).getTime()};class qt{constructor(t,e){this.id=qt.animIdCounter++,this.frame={time:0,timeDiff:0,lastTime:jt(),frameRate:0},this.func=t,this.setLayers(e)}setLayers(t){let e=[];return t&&(e=Array.isArray(t)?t:[t]),this.layers=e,this}getLayers(){return this.layers}addLayer(t){const e=this.layers,i=e.length;for(let r=0;rthis.duration?this.yoyo?(this._time=this.duration,this.reverse()):this.finish():t<0?this.yoyo?(this._time=0,this.play()):this.reset():(this._time=t,this.update())}getTime(){return this._time}setPosition(t){this.prevPos=this._pos,this.propFunc(t),this._pos=t}getPosition(t){return void 0===t&&(t=this._time),this.func(t,this.begin,this._change,this.duration)}play(){this.state=2,this._startTime=this.getTimer()-this._time,this.onEnterFrame(),this.fire("onPlay")}reverse(){this.state=3,this._time=this.duration-this._time,this._startTime=this.getTimer()-this._time,this.onEnterFrame(),this.fire("onReverse")}seek(t){this.pause(),this._time=t,this.update(),this.fire("onSeek")}reset(){this.pause(),this._time=0,this.update(),this.fire("onReset")}finish(){this.pause(),this._time=this.duration,this.update(),this.fire("onFinish")}update(){this.setPosition(this.getPosition(this._time)),this.fire("onUpdate")}onEnterFrame(){var t=this.getTimer()-this._startTime;2===this.state?this.setTime(t):3===this.state&&this.setTime(this.duration-t)}pause(){this.state=1,this.fire("onPause")}getTimer(){return(new Date).getTime()}}class Jt{constructor(t){var e,r,a=this,n=t.node,s=n._id,o=t.easing||$t.Linear,h=!!t.yoyo;e=void 0===t.duration?.3:0===t.duration?.001:t.duration,this.node=n,this._id=Vt++;var l=n.getLayer()||(n instanceof i.Stage?n.getLayers():null);for(r in l||g.error("Tween constructor have `node` that is not in a layer. Please add node into layer first."),this.anim=new qt((function(){a.tween.onEnterFrame()}),l),this.tween=new Qt(r,(function(t){a._tweenFunc(t)}),o,0,1,1e3*e,h),this._addListeners(),Jt.attrs[s]||(Jt.attrs[s]={}),Jt.attrs[s][this._id]||(Jt.attrs[s][this._id]={}),Jt.tweens[s]||(Jt.tweens[s]={}),t)void 0===Ut[r]&&this._addAttr(r,t[r]);this.reset(),this.onFinish=t.onFinish,this.onReset=t.onReset,this.onUpdate=t.onUpdate}_addAttr(t,e){var i,r,a,n,s,o,h,l,d=this.node,c=d._id;if((a=Jt.tweens[c][t])&&delete Jt.attrs[c][a][t],i=d.getAttr(t),g._isArray(e))if(r=[],s=Math.max(e.length,i.length),"points"===t&&e.length!==i.length&&(e.length>i.length?(h=i,i=g._prepareArrayForTween(i,e,d.closed())):(o=e,e=g._prepareArrayForTween(e,i,d.closed()))),0===t.indexOf("fill"))for(n=0;n{this.anim.start()},this.tween.onReverse=()=>{this.anim.start()},this.tween.onPause=()=>{this.anim.stop()},this.tween.onFinish=()=>{var t=this.node,e=Jt.attrs[t._id][this._id];e.points&&e.points.trueEnd&&t.setAttr("points",e.points.trueEnd),this.onFinish&&this.onFinish.call(this)},this.tween.onReset=()=>{var t=this.node,e=Jt.attrs[t._id][this._id];e.points&&e.points.trueStart&&t.points(e.points.trueStart),this.onReset&&this.onReset()},this.tween.onUpdate=()=>{this.onUpdate&&this.onUpdate.call(this)}}play(){return this.tween.play(),this}reverse(){return this.tween.reverse(),this}reset(){return this.tween.reset(),this}seek(t){return this.tween.seek(1e3*t),this}pause(){return this.tween.pause(),this}finish(){return this.tween.finish(),this}destroy(){var t,e=this.node._id,i=this._id,r=Jt.tweens[e];for(t in this.pause(),r)delete Jt.tweens[e][t];delete Jt.attrs[e][i]}}Jt.attrs={},Jt.tweens={},V.prototype.to=function(t){var e=t.onFinish;t.node=this,t.onFinish=function(){this.destroy(),e&&e()},new Jt(t).play()};const $t={BackEaseIn(t,e,i,r){var a=1.70158;return i*(t/=r)*t*((a+1)*t-a)+e},BackEaseOut(t,e,i,r){var a=1.70158;return i*((t=t/r-1)*t*((a+1)*t+a)+1)+e},BackEaseInOut(t,e,i,r){var a=1.70158;return(t/=r/2)<1?i/2*(t*t*((1+(a*=1.525))*t-a))+e:i/2*((t-=2)*t*((1+(a*=1.525))*t+a)+2)+e},ElasticEaseIn(t,e,i,r,a,n){var s=0;return 0===t?e:1==(t/=r)?e+i:(n||(n=.3*r),!a||a(t/=r)<1/2.75?i*(7.5625*t*t)+e:t<2/2.75?i*(7.5625*(t-=1.5/2.75)*t+.75)+e:t<2.5/2.75?i*(7.5625*(t-=2.25/2.75)*t+.9375)+e:i*(7.5625*(t-=2.625/2.75)*t+.984375)+e,BounceEaseIn:(t,e,i,r)=>i-$t.BounceEaseOut(r-t,0,i,r)+e,BounceEaseInOut:(t,e,i,r)=>ti*(t/=r)*t+e,EaseOut:(t,e,i,r)=>-i*(t/=r)*(t-2)+e,EaseInOut:(t,e,i,r)=>(t/=r/2)<1?i/2*t*t+e:-i/2*(--t*(t-2)-1)+e,StrongEaseIn:(t,e,i,r)=>i*(t/=r)*t*t*t*t+e,StrongEaseOut:(t,e,i,r)=>i*((t=t/r-1)*t*t*t*t+1)+e,StrongEaseInOut:(t,e,i,r)=>(t/=r/2)<1?i/2*t*t*t*t*t+e:i/2*((t-=2)*t*t*t*t+2)+e,Linear:(t,e,i,r)=>i*t/r+e},Zt=g._assign(i,{Util:g,Transform:a,Node:V,Container:Q,Stage:Gt,stages:Mt,Layer:zt,FastLayer:Yt,Group:Xt,DD:E,Shape:Nt,shapes:Bt,Animation:qt,Tween:Jt,Easings:$t,Context:P,Canvas:M});class te extends Nt{_sceneFunc(t){var e=i.getAngle(this.angle()),r=this.clockwise();t.beginPath(),t.arc(0,0,this.outerRadius(),0,e,r),t.arc(0,0,this.innerRadius(),e,0,!r),t.closePath(),t.fillStrokeShape(this)}getWidth(){return 2*this.outerRadius()}getHeight(){return 2*this.outerRadius()}setWidth(t){this.outerRadius(t/2)}setHeight(t){this.outerRadius(t/2)}getSelfRect(){const t=this.innerRadius(),e=this.outerRadius(),r=this.clockwise(),a=i.getAngle(r?360-this.angle():this.angle()),n=Math.cos(Math.min(a,Math.PI)),s=Math.sin(Math.min(Math.max(Math.PI,a),3*Math.PI/2)),o=Math.sin(Math.min(a,Math.PI/2)),h=n*(n>0?t:e),l=s*(s>0?t:e),d=o*(o>0?e:t);return{x:h,y:r?-1*d:l,width:1*e-h,height:d-l}}}function ee(t,e,i,r,a,n,s){var o=Math.sqrt(Math.pow(i-t,2)+Math.pow(r-e,2)),h=Math.sqrt(Math.pow(a-i,2)+Math.pow(n-r,2)),l=s*o/(o+h),d=s*h/(o+h);return[i-l*(a-t),r-l*(n-e),i+d*(a-t),r+d*(n-e)]}function ie(t,e){var i,r,a=t.length,n=[];for(i=2;i4){for(i=(e=this.getTensionPoints()).length,r=o?0:4,o||t.quadraticCurveTo(e[0],e[1],e[2],e[3]);r{let r,a,n;r=i/2,a=0;for(let i=0;i<20;i++)n=r*ae[20][i]+r,a+=ne[20][i]*le(t,e,n);return r*a},he=(t,e,i)=>{void 0===i&&(i=1);const r=t[0]-2*t[1]+t[2],a=e[0]-2*e[1]+e[2],n=2*t[1]-2*t[0],s=2*e[1]-2*e[0],o=4*(r*r+a*a),h=4*(r*n+a*s),l=n*n+s*s;if(0===o)return i*Math.sqrt(Math.pow(t[2]-t[0],2)+Math.pow(e[2]-e[0],2));const d=h/(2*o),c=i+d,g=l/o-d*d,u=c*c+g>0?Math.sqrt(c*c+g):0,f=d*d+g>0?Math.sqrt(d*d+g):0,p=d+Math.sqrt(d*d+g)!==0?g*Math.log(Math.abs((c+u)/(d+f))):0;return Math.sqrt(o)/2*(c*u-d*f+p)};function le(t,e,i){const r=de(1,i,t),a=de(1,i,e),n=r*r+a*a;return Math.sqrt(n)}const de=(t,e,i)=>{const r=i.length-1;let a,n;if(0===r)return 0;if(0===t){n=0;for(let t=0;t<=r;t++)n+=se[r][t]*Math.pow(1-e,r-t)*Math.pow(e,t)*i[t];return n}a=new Array(r);for(let t=0;t{let r=1,a=t/e,n=(t-i(a))/e,s=0;for(;r>.001;){const o=i(a+n),h=Math.abs(t-o)/e;if(h500)break}return a};class ge extends Nt{constructor(t){super(t),this.dataArray=[],this.pathLength=0,this._readDataAttribute(),this.on("dataChange.konva",(function(){this._readDataAttribute()}))}_readDataAttribute(){this.dataArray=ge.parsePathData(this.data()),this.pathLength=ge.getPathLength(this.dataArray)}_sceneFunc(t){var e=this.dataArray;t.beginPath();for(var i=!1,r=0;rl?h:l,p=h>l?1:h/l,v=h>l?l/h:1;t.translate(s,o),t.rotate(g),t.scale(p,v),t.arc(0,0,f,d,d+c,1-u),t.scale(1/p,1/v),t.rotate(-g),t.translate(-s,-o);break;case"z":i=!0,t.closePath()}}i||this.hasFill()?t.fillStrokeShape(this):t.strokeShape(this)}getSelfRect(){var t=[];this.dataArray.forEach((function(e){if("A"===e.command){var i=e.points[4],r=e.points[5],a=e.points[4]+r,n=Math.PI/180;if(Math.abs(i-a)a;r-=n){const i=ge.getPointOnEllipticalArc(e.points[0],e.points[1],e.points[2],e.points[3],r,0);t.push(i.x,i.y)}else for(let r=i+n;re[r].pathLength;)t-=e[r].pathLength,++r;if(r===a)return{x:(i=e[r-1].points.slice(-2))[0],y:i[1]};if(t<.01)return{x:(i=e[r].points.slice(0,2))[0],y:i[1]};var n=e[r],s=n.points;switch(n.command){case"L":return ge.getPointOnLine(t,n.start.x,n.start.y,s[0],s[1]);case"C":return ge.getPointOnCubicBezier(ce(t,ge.getPathLength(e),(t=>oe([n.start.x,s[0],s[2],s[4]],[n.start.y,s[1],s[3],s[5]],t))),n.start.x,n.start.y,s[0],s[1],s[2],s[3],s[4],s[5]);case"Q":return ge.getPointOnQuadraticBezier(ce(t,ge.getPathLength(e),(t=>he([n.start.x,s[0],s[2]],[n.start.y,s[1],s[3]],t))),n.start.x,n.start.y,s[0],s[1],s[2],s[3]);case"A":var o=s[0],h=s[1],l=s[2],d=s[3],c=s[4],g=s[5],u=s[6];return c+=g*t/n.pathLength,ge.getPointOnEllipticalArc(o,h,l,d,c,u)}return null}static getPointOnLine(t,e,i,r,a,n,s){n=null!=n?n:e,s=null!=s?s:i;const o=this.getLineLength(e,i,r,a);if(o<1e-10)return{x:e,y:i};if(r===e)return{x:n,y:s+(a>i?t:-t)};const h=(a-i)/(r-e),l=Math.sqrt(t*t/(1+h*h))*(r0&&!isNaN(u[0]);){var m,_,y,x,b,S,w,C,P,k,T="",A=[],M=h,G=l;switch(g){case"l":h+=u.shift(),l+=u.shift(),T="L",A.push(h,l);break;case"L":h=u.shift(),l=u.shift(),A.push(h,l);break;case"m":var R=u.shift(),E=u.shift();if(h+=R,l+=E,T="M",s.length>2&&"z"===s[s.length-1].command)for(var D=s.length-2;D>=0;D--)if("M"===s[D].command){h=s[D].points[0]+R,l=s[D].points[1]+E;break}A.push(h,l),g="l";break;case"M":h=u.shift(),l=u.shift(),T="M",A.push(h,l),g="L";break;case"h":h+=u.shift(),T="L",A.push(h,l);break;case"H":h=u.shift(),T="L",A.push(h,l);break;case"v":l+=u.shift(),T="L",A.push(h,l);break;case"V":l=u.shift(),T="L",A.push(h,l);break;case"C":A.push(u.shift(),u.shift(),u.shift(),u.shift()),h=u.shift(),l=u.shift(),A.push(h,l);break;case"c":A.push(h+u.shift(),l+u.shift(),h+u.shift(),l+u.shift()),h+=u.shift(),l+=u.shift(),T="C",A.push(h,l);break;case"S":_=h,y=l,"C"===(m=s[s.length-1]).command&&(_=h+(h-m.points[2]),y=l+(l-m.points[3])),A.push(_,y,u.shift(),u.shift()),h=u.shift(),l=u.shift(),T="C",A.push(h,l);break;case"s":_=h,y=l,"C"===(m=s[s.length-1]).command&&(_=h+(h-m.points[2]),y=l+(l-m.points[3])),A.push(_,y,h+u.shift(),l+u.shift()),h+=u.shift(),l+=u.shift(),T="C",A.push(h,l);break;case"Q":A.push(u.shift(),u.shift()),h=u.shift(),l=u.shift(),A.push(h,l);break;case"q":A.push(h+u.shift(),l+u.shift()),h+=u.shift(),l+=u.shift(),T="Q",A.push(h,l);break;case"T":_=h,y=l,"Q"===(m=s[s.length-1]).command&&(_=h+(h-m.points[0]),y=l+(l-m.points[1])),h=u.shift(),l=u.shift(),T="Q",A.push(_,y,h,l);break;case"t":_=h,y=l,"Q"===(m=s[s.length-1]).command&&(_=h+(h-m.points[0]),y=l+(l-m.points[1])),h+=u.shift(),l+=u.shift(),T="Q",A.push(_,y,h,l);break;case"A":x=u.shift(),b=u.shift(),S=u.shift(),w=u.shift(),C=u.shift(),P=h,k=l,h=u.shift(),l=u.shift(),T="A",A=this.convertEndpointToCenterParameterization(P,k,h,l,w,C,x,b,S);break;case"a":x=u.shift(),b=u.shift(),S=u.shift(),w=u.shift(),C=u.shift(),P=h,k=l,h+=u.shift(),l+=u.shift(),T="A",A=this.convertEndpointToCenterParameterization(P,k,h,l,w,C,x,b,S)}s.push({command:T||g,points:A,start:{x:M,y:G},pathLength:this.calcLength(M,G,T||g,A)})}"z"!==g&&"Z"!==g||s.push({command:"z",points:[],start:void 0,pathLength:0})}return s}static calcLength(t,e,i,r){var a,n,s,o,h=ge;switch(i){case"L":return h.getLineLength(t,e,r[0],r[1]);case"C":return oe([t,r[0],r[2],r[4]],[e,r[1],r[3],r[5]],1);case"Q":return he([t,r[0],r[2]],[e,r[1],r[3]],1);case"A":a=0;var l=r[4],d=r[5],c=r[4]+d,g=Math.PI/180;if(Math.abs(l-c)c;o-=g)s=h.getPointOnEllipticalArc(r[0],r[1],r[2],r[3],o,0),a+=h.getLineLength(n.x,n.y,s.x,s.y),n=s;else for(o=l+g;o1&&(s*=Math.sqrt(g),o*=Math.sqrt(g));var u=Math.sqrt((s*s*(o*o)-s*s*(c*c)-o*o*(d*d))/(s*s*(c*c)+o*o*(d*d)));a===n&&(u*=-1),isNaN(u)&&(u=0);var f=u*s*c/o,p=u*-o*d/s,v=(t+i)/2+Math.cos(l)*f-Math.sin(l)*p,m=(e+r)/2+Math.sin(l)*f+Math.cos(l)*p,_=function(t){return Math.sqrt(t[0]*t[0]+t[1]*t[1])},y=function(t,e){return(t[0]*e[0]+t[1]*e[1])/(_(t)*_(e))},x=function(t,e){return(t[0]*e[1]=1&&(C=0),0===n&&C>0&&(C-=2*Math.PI),1===n&&C<0&&(C+=2*Math.PI),[v,m,s,o,b,C,l,n]}}ge.prototype.className="Path",ge.prototype._attrsAffectingSize=["data"],r(ge),w.addGetterSetter(ge,"data");class ue extends re{_sceneFunc(t){super._sceneFunc(t);var e=2*Math.PI,i=this.points(),r=i,a=0!==this.tension()&&i.length>4;a&&(r=this.getTensionPoints());var n,s,o=this.pointerLength(),h=i.length;if(a){const t=[r[r.length-4],r[r.length-3],r[r.length-2],r[r.length-1],i[h-2],i[h-1]],e=ge.calcLength(r[r.length-4],r[r.length-3],"C",t),a=ge.getPointOnQuadraticBezier(Math.min(1,1-o/e),t[0],t[1],t[2],t[3],t[4],t[5]);n=i[h-2]-a.x,s=i[h-1]-a.y}else n=i[h-2]-i[h-4],s=i[h-1]-i[h-3];var l=(Math.atan2(s,n)+e)%e,d=this.pointerWidth();this.pointerAtEnding()&&(t.save(),t.beginPath(),t.translate(i[h-2],i[h-1]),t.rotate(l),t.moveTo(0,0),t.lineTo(-o,d/2),t.lineTo(-o,-d/2),t.closePath(),t.restore(),this.__fillStroke(t)),this.pointerAtBeginning()&&(t.save(),t.beginPath(),t.translate(i[0],i[1]),a?(n=(r[0]+r[2])/2-i[0],s=(r[1]+r[3])/2-i[1]):(n=i[2]-i[0],s=i[3]-i[1]),t.rotate((Math.atan2(-s,-n)+e)%e),t.moveTo(0,0),t.lineTo(-o,d/2),t.lineTo(-o,-d/2),t.closePath(),t.restore(),this.__fillStroke(t))}__fillStroke(t){var e=this.dashEnabled();e&&(this.attrs.dashEnabled=!1,t.setLineDash([])),t.fillStrokeShape(this),e&&(this.attrs.dashEnabled=!0)}getSelfRect(){const t=super.getSelfRect(),e=this.pointerWidth()/2;return{x:t.x-e,y:t.y-e,width:t.width+2*e,height:t.height+2*e}}}ue.prototype.className="Arrow",r(ue),w.addGetterSetter(ue,"pointerLength",10,p()),w.addGetterSetter(ue,"pointerWidth",10,p()),w.addGetterSetter(ue,"pointerAtBeginning",!1),w.addGetterSetter(ue,"pointerAtEnding",!0);class fe extends Nt{_sceneFunc(t){t.beginPath(),t.arc(0,0,this.attrs.radius||0,0,2*Math.PI,!1),t.closePath(),t.fillStrokeShape(this)}getWidth(){return 2*this.radius()}getHeight(){return 2*this.radius()}setWidth(t){this.radius()!==t/2&&this.radius(t/2)}setHeight(t){this.radius()!==t/2&&this.radius(t/2)}}fe.prototype._centroid=!0,fe.prototype.className="Circle",fe.prototype._attrsAffectingSize=["radius"],r(fe),w.addGetterSetter(fe,"radius",0,p());class pe extends Nt{_sceneFunc(t){var e=this.radiusX(),i=this.radiusY();t.beginPath(),t.save(),e!==i&&t.scale(1,i/e),t.arc(0,0,e,0,2*Math.PI,!1),t.restore(),t.closePath(),t.fillStrokeShape(this)}getWidth(){return 2*this.radiusX()}getHeight(){return 2*this.radiusY()}setWidth(t){this.radiusX(t/2)}setHeight(t){this.radiusY(t/2)}}pe.prototype.className="Ellipse",pe.prototype._centroid=!0,pe.prototype._attrsAffectingSize=["radiusX","radiusY"],r(pe),w.addComponentsGetterSetter(pe,"radius",["x","y"]),w.addGetterSetter(pe,"radiusX",0,p()),w.addGetterSetter(pe,"radiusY",0,p());class ve extends Nt{constructor(t){super(t),this.on("imageChange.konva",(()=>{this._setImageLoad()})),this._setImageLoad()}_setImageLoad(){const t=this.image();t&&t.complete||t&&4===t.readyState||t&&t.addEventListener&&t.addEventListener("load",(()=>{this._requestDraw()}))}_useBufferCanvas(){const t=!!this.cornerRadius(),e=this.hasShadow();return!(!t||!e)||super._useBufferCanvas(!0)}_sceneFunc(t){const e=this.getWidth(),i=this.getHeight(),r=this.cornerRadius(),a=this.attrs.image;let n;if(a){const t=this.attrs.cropWidth,r=this.attrs.cropHeight;n=t&&r?[a,this.cropX(),this.cropY(),t,r,0,0,e,i]:[a,0,0,e,i]}(this.hasFill()||this.hasStroke()||r)&&(t.beginPath(),r?g.drawRoundedRectPath(t,e,i,r):t.rect(0,0,e,i),t.closePath(),t.fillStrokeShape(this)),a&&(r&&t.clip(),t.drawImage.apply(t,n))}_hitFunc(t){var e=this.width(),i=this.height(),r=this.cornerRadius();t.beginPath(),r?g.drawRoundedRectPath(t,e,i,r):t.rect(0,0,e,i),t.closePath(),t.fillStrokeShape(this)}getWidth(){var t,e;return null!==(t=this.attrs.width)&&void 0!==t?t:null===(e=this.image())||void 0===e?void 0:e.width}getHeight(){var t,e;return null!==(t=this.attrs.height)&&void 0!==t?t:null===(e=this.image())||void 0===e?void 0:e.height}static fromURL(t,e,i=null){var r=g.createImageElement();r.onload=function(){var t=new ve({image:r});e(t)},r.onerror=i,r.crossOrigin="Anonymous",r.src=t}}ve.prototype.className="Image",r(ve),w.addGetterSetter(ve,"cornerRadius",0,v(4)),w.addGetterSetter(ve,"image"),w.addComponentsGetterSetter(ve,"crop",["x","y","width","height"]),w.addGetterSetter(ve,"cropX",0,p()),w.addGetterSetter(ve,"cropY",0,p()),w.addGetterSetter(ve,"cropWidth",0,p()),w.addGetterSetter(ve,"cropHeight",0,p());var me=["fontFamily","fontSize","fontStyle","padding","lineHeight","text","width","height","pointerDirection","pointerWidth","pointerHeight"],_e="up",ye="right",xe="down",be="left",Se=me.length;class we extends Xt{constructor(t){super(t),this.on("add.konva",(function(t){this._addListeners(t.child),this._sync()}))}getText(){return this.find("Text")[0]}getTag(){return this.find("Tag")[0]}_addListeners(t){var e,i=this,r=function(){i._sync()};for(e=0;e{e=Math.min(e,t.x),i=Math.max(i,t.x),r=Math.min(r,t.y),a=Math.max(a,t.y)})),{x:e,y:r,width:i-e,height:a-r}}getWidth(){return 2*this.radius()}getHeight(){return 2*this.radius()}setWidth(t){this.radius(t/2)}setHeight(t){this.radius(t/2)}}ke.prototype.className="RegularPolygon",ke.prototype._centroid=!0,ke.prototype._attrsAffectingSize=["radius"],r(ke),w.addGetterSetter(ke,"radius",0,p()),w.addGetterSetter(ke,"sides",0,p());var Te=2*Math.PI;class Ae extends Nt{_sceneFunc(t){t.beginPath(),t.arc(0,0,this.innerRadius(),0,Te,!1),t.moveTo(this.outerRadius(),0),t.arc(0,0,this.outerRadius(),Te,0,!0),t.closePath(),t.fillStrokeShape(this)}getWidth(){return 2*this.outerRadius()}getHeight(){return 2*this.outerRadius()}setWidth(t){this.outerRadius(t/2)}setHeight(t){this.outerRadius(t/2)}}Ae.prototype.className="Ring",Ae.prototype._centroid=!0,Ae.prototype._attrsAffectingSize=["innerRadius","outerRadius"],r(Ae),w.addGetterSetter(Ae,"innerRadius",0,p()),w.addGetterSetter(Ae,"outerRadius",0,p());class Me extends Nt{constructor(t){super(t),this._updated=!0,this.anim=new qt((()=>{var t=this._updated;return this._updated=!1,t})),this.on("animationChange.konva",(function(){this.frameIndex(0)})),this.on("frameIndexChange.konva",(function(){this._updated=!0})),this.on("frameRateChange.konva",(function(){this.anim.isRunning()&&(clearInterval(this.interval),this._setInterval())}))}_sceneFunc(t){var e=this.animation(),i=this.frameIndex(),r=4*i,a=this.animations()[e],n=this.frameOffsets(),s=a[r+0],o=a[r+1],h=a[r+2],l=a[r+3],d=this.image();if((this.hasFill()||this.hasStroke())&&(t.beginPath(),t.rect(0,0,h,l),t.closePath(),t.fillStrokeShape(this)),d)if(n){var c=n[e],g=2*i;t.drawImage(d,s,o,h,l,c[g+0],c[g+1],h,l)}else t.drawImage(d,s,o,h,l,0,0,h,l)}_hitFunc(t){var e=this.animation(),i=this.frameIndex(),r=4*i,a=this.animations()[e],n=this.frameOffsets(),s=a[r+2],o=a[r+3];if(t.beginPath(),n){var h=n[e],l=2*i;t.rect(h[l+0],h[l+1],s,o)}else t.rect(0,0,s,o);t.closePath(),t.fillShape(this)}_useBufferCanvas(){return super._useBufferCanvas(!0)}_setInterval(){var t=this;this.interval=setInterval((function(){t._updateIndex()}),1e3/this.frameRate())}start(){if(!this.isRunning()){var t=this.getLayer();this.anim.setLayers(t),this._setInterval(),this.anim.start()}}stop(){this.anim.stop(),clearInterval(this.interval)}isRunning(){return this.anim.isRunning()}_updateIndex(){var t=this.frameIndex(),e=this.animation();t(/\p{Emoji_Modifier_Base}\p{Emoji_Modifier}?(?:\u200D\p{Emoji_Presentation})+/u.test(e)?t.push(e):/\p{Regional_Indicator}{2}/u.test(e+(r[i+1]||""))?t.push(e+r[i+1]):i>0&&/\p{Mn}|\p{Me}|\p{Mc}/u.test(e)?t[t.length-1]+=e:t.push(e),t)),[])}Ge.prototype.className="Star",Ge.prototype._centroid=!0,Ge.prototype._attrsAffectingSize=["innerRadius","outerRadius"],r(Ge),w.addGetterSetter(Ge,"numPoints",5,p()),w.addGetterSetter(Ge,"innerRadius",0,p()),w.addGetterSetter(Ge,"outerRadius",0,p());var Ee,De="auto",Le="inherit",Ie="justify",Oe="left",Fe="middle",Be="normal",Ne=" ",He="none",We=["direction","fontFamily","fontSize","fontStyle","fontVariant","padding","align","verticalAlign","lineHeight","text","width","height","wrap","ellipsis","letterSpacing"],ze=We.length;function Ye(){return Ee||(Ee=g.createCanvasElement().getContext("2d"))}class Xe extends Nt{constructor(t){super(function(t){return(t=t||{}).fillLinearGradientColorStops||t.fillRadialGradientColorStops||t.fillPatternImage||(t.fill=t.fill||"black"),t}(t)),this._partialTextX=0,this._partialTextY=0;for(var e=0;e1&&(_+=o)}}}_hitFunc(t){var e=this.getWidth(),i=this.getHeight();t.beginPath(),t.rect(0,0,e,i),t.closePath(),t.fillStrokeShape(this)}setText(t){var e=g._isString(t)?t:null==t?"":t+"";return this._setAttr("text",e),this}getWidth(){return this.attrs.width===De||void 0===this.attrs.width?this.getTextWidth()+2*this.padding():this.attrs.width}getHeight(){return this.attrs.height===De||void 0===this.attrs.height?this.fontSize()*this.textArr.length*this.lineHeight()+2*this.padding():this.attrs.height}getTextWidth(){return this.textWidth}getTextHeight(){return g.warn("text.getTextHeight() method is deprecated. Use text.height() - for full height and text.fontSize() - for one line height."),this.textHeight}measureSize(t){var e,i,r,a,n,s,o,h,l,d,c,g,u=Ye(),f=this.fontSize();u.save(),u.font=this._getContextFont(),g=u.measureText(t),u.restore();const p=f/100;return{actualBoundingBoxAscent:null!==(e=g.actualBoundingBoxAscent)&&void 0!==e?e:71.58203125*p,actualBoundingBoxDescent:null!==(i=g.actualBoundingBoxDescent)&&void 0!==i?i:0,actualBoundingBoxLeft:null!==(r=g.actualBoundingBoxLeft)&&void 0!==r?r:-7.421875*p,actualBoundingBoxRight:null!==(a=g.actualBoundingBoxRight)&&void 0!==a?a:75.732421875*p,alphabeticBaseline:null!==(n=g.alphabeticBaseline)&&void 0!==n?n:0,emHeightAscent:null!==(s=g.emHeightAscent)&&void 0!==s?s:100*p,emHeightDescent:null!==(o=g.emHeightDescent)&&void 0!==o?o:-20*p,fontBoundingBoxAscent:null!==(h=g.fontBoundingBoxAscent)&&void 0!==h?h:91*p,fontBoundingBoxDescent:null!==(l=g.fontBoundingBoxDescent)&&void 0!==l?l:21*p,hangingBaseline:null!==(d=g.hangingBaseline)&&void 0!==d?d:72.80000305175781*p,ideographicBaseline:null!==(c=g.ideographicBaseline)&&void 0!==c?c:-21*p,width:g.width,height:f}}_getContextFont(){return this.fontStyle()+Ne+this.fontVariant()+Ne+(this.fontSize()+"px ")+this.fontFamily().split(",").map((t=>{const e=(t=t.trim()).indexOf(" ")>=0,i=t.indexOf('"')>=0||t.indexOf("'")>=0;return e&&!i&&(t=`"${t}"`),t})).join(", ")}_addTextLine(t){this.align()===Ie&&(t=t.trim());var e=this._getTextWidth(t);return this.textArr.push({text:t,width:e,lastInParagraph:!1})}_getTextWidth(t){var e=this.letterSpacing(),i=t.length;return Ye().measureText(t).width+(i?e*(i-1):0)}_setTextData(){var t=this.text().split("\n"),e=+this.fontSize(),i=0,r=this.lineHeight()*e,a=this.attrs.width,n=this.attrs.height,s=a!==De&&void 0!==a,o=n!==De&&void 0!==n,h=this.padding(),l=a-2*h,d=n-2*h,c=0,g=this.wrap(),u="char"!==g&&g!==He,f=this.ellipsis();this.textArr=[],Ye().font=this._getContextFont();for(var p=f?this._getTextWidth("…"):0,v=0,m=t.length;vl)for(;_.length>0;){for(var x=0,b=_.length,S="",w=0;x>>1,P=_.slice(0,C+1),k=this._getTextWidth(P)+p;k<=l?(x=C+1,S=P,w=k):b=C}if(!S)break;if(u){var T,A=_[S.length];(T=(A===Ne||"-"===A)&&w<=l?S.length:Math.max(S.lastIndexOf(Ne),S.lastIndexOf("-"))+1)>0&&(x=T,S=S.slice(0,x),w=this._getTextWidth(S))}if(S=S.trimRight(),this._addTextLine(S),i=Math.max(i,w),c+=r,this._shouldHandleEllipsis(c)){this._tryToAddEllipsisToLastLine();break}if((_=(_=_.slice(x)).trimLeft()).length>0&&(y=this._getTextWidth(_))<=l){this._addTextLine(_),c+=r,i=Math.max(i,y);break}}else this._addTextLine(_),c+=r,i=Math.max(i,y),this._shouldHandleEllipsis(c)&&vd)break}this.textHeight=e,this.textWidth=i}_shouldHandleEllipsis(t){var e=+this.fontSize(),i=this.lineHeight()*e,r=this.attrs.height,a=r!==De&&void 0!==r,n=r-2*this.padding();return!(this.wrap()!==He)||a&&t+i>n}_tryToAddEllipsisToLastLine(){var t=this.attrs.width,e=t!==De&&void 0!==t,i=t-2*this.padding(),r=this.ellipsis(),a=this.textArr[this.textArr.length-1];if(a&&r){if(e)this._getTextWidth(a.text+"…")this.pathLength?null:ge.getPointAtLengthOfDataArray(t,this.dataArray)}_readDataAttribute(){this.dataArray=ge.parsePathData(this.attrs.data),this.pathLength=this._getTextPathLength()}_sceneFunc(t){t.setAttr("font",this._getContextFont()),t.setAttr("textBaseline",this.textBaseline()),t.setAttr("textAlign","left"),t.save();var e=this.textDecoration(),i=this.fill(),r=this.fontSize(),a=this.glyphInfo;"underline"===e&&t.beginPath();for(var n=0;n=1){var i=e[0].p0;t.moveTo(i.x,i.y)}for(var r=0;rt+`.${Ke}`)).join(" "),Je="nodesRect",$e=["widthChange","heightChange","scaleXChange","scaleYChange","skewXChange","skewYChange","rotationChange","offsetXChange","offsetYChange","transformsEnabledChange","strokeWidthChange"],Ze={"top-left":-45,"top-center":0,"top-right":45,"middle-right":-90,"middle-left":90,"bottom-left":-135,"bottom-center":180,"bottom-right":135};const ti="ontouchstart"in i._global;var ei=["top-left","top-center","top-right","middle-right","middle-left","bottom-left","bottom-center","bottom-right"];function ii(t,e,i){const r=i.x+(t.x-i.x)*Math.cos(e)-(t.y-i.y)*Math.sin(e),a=i.y+(t.x-i.x)*Math.sin(e)+(t.y-i.y)*Math.cos(e);return{...t,rotation:t.rotation+e,x:r,y:a}}function ri(t,e){const i=function(t){return{x:t.x+t.width/2*Math.cos(t.rotation)+t.height/2*Math.sin(-t.rotation),y:t.y+t.height/2*Math.cos(t.rotation)+t.width/2*Math.sin(t.rotation)}}(t);return ii(t,e,i)}let ai=0;class ni extends Xt{constructor(t){super(t),this._movingAnchorName=null,this._transforming=!1,this._createElements(),this._handleMouseMove=this._handleMouseMove.bind(this),this._handleMouseUp=this._handleMouseUp.bind(this),this.update=this.update.bind(this),this.on(Qe,this.update),this.getNode()&&this.update()}attachTo(t){return this.setNode(t),this}setNode(t){return g.warn("tr.setNode(shape), tr.node(shape) and tr.attachTo(shape) methods are deprecated. Please use tr.nodes(nodesArray) instead."),this.setNodes([t])}getNode(){return this._nodes&&this._nodes[0]}_getEventNamespace(){return Ke+this._id}setNodes(t=[]){this._nodes&&this._nodes.length&&this.detach();const e=t.filter((t=>!t.isAncestorOf(this)||(g.error("Konva.Transformer cannot be an a child of the node you are trying to attach"),!1)));return this._nodes=t=e,1===t.length&&this.useSingleNodeRotation()?this.rotation(t[0].getAbsoluteRotation()):this.rotation(0),this._nodes.forEach((t=>{const e=()=>{1===this.nodes().length&&this.useSingleNodeRotation()&&this.rotation(this.nodes()[0].getAbsoluteRotation()),this._resetTransformCache(),this._transforming||this.isDragging()||this.update()},i=t._attrsAffectingSize.map((t=>t+"Change."+this._getEventNamespace())).join(" ");t.on(i,e),t.on($e.map((t=>t+`.${this._getEventNamespace()}`)).join(" "),e),t.on(`absoluteTransformChange.${this._getEventNamespace()}`,e),this._proxyDrag(t)})),this._resetTransformCache(),!!this.findOne(".top-left")&&this.update(),this}_proxyDrag(t){let e;t.on(`dragstart.${this._getEventNamespace()}`,(i=>{e=t.getAbsolutePosition(),this.isDragging()||t===this.findOne(".back")||this.startDrag(i,!1)})),t.on(`dragmove.${this._getEventNamespace()}`,(i=>{if(!e)return;const r=t.getAbsolutePosition(),a=r.x-e.x,n=r.y-e.y;this.nodes().forEach((e=>{if(e===t)return;if(e.isDragging())return;const r=e.getAbsolutePosition();e.setAbsolutePosition({x:r.x+a,y:r.y+n}),e.startDrag(i)})),e=null}))}getNodes(){return this._nodes||[]}getActiveAnchor(){return this._movingAnchorName}detach(){this._nodes&&this._nodes.forEach((t=>{t.off("."+this._getEventNamespace())})),this._nodes=[],this._resetTransformCache()}_resetTransformCache(){this._clearCache(Je),this._clearCache("transform"),this._clearSelfAndDescendantCache("absoluteTransform")}_getNodeRect(){return this._getCache(Je,this.__getNodeRect)}__getNodeShape(t,e=this.rotation(),r){var a=t.getClientRect({skipTransform:!0,skipShadow:!0,skipStroke:this.ignoreStroke()}),n=t.getAbsoluteScale(r),s=t.getAbsolutePosition(r),o=a.x*n.x-t.offsetX()*n.x,h=a.y*n.y-t.offsetY()*n.y;const l=(i.getAngle(t.getAbsoluteRotation())+2*Math.PI)%(2*Math.PI);return ii({x:s.x+o*Math.cos(l)+h*Math.sin(-l),y:s.y+h*Math.cos(l)+o*Math.sin(l),width:a.width*n.x,height:a.height*n.y,rotation:l},-i.getAngle(e),{x:0,y:0})}__getNodeRect(){if(!this.getNode())return{x:-1e8,y:-1e8,width:0,height:0,rotation:0};const t=[];this.nodes().map((e=>{const i=e.getClientRect({skipTransform:!0,skipShadow:!0,skipStroke:this.ignoreStroke()});var r=[{x:i.x,y:i.y},{x:i.x+i.width,y:i.y},{x:i.x+i.width,y:i.y+i.height},{x:i.x,y:i.y+i.height}],a=e.getAbsoluteTransform();r.forEach((function(e){var i=a.point(e);t.push(i)}))}));const e=new a;e.rotate(-i.getAngle(this.rotation()));var r=1/0,n=1/0,s=-1/0,o=-1/0;t.forEach((function(t){var i=e.point(t);void 0===r&&(r=s=i.x,n=o=i.y),r=Math.min(r,i.x),n=Math.min(n,i.y),s=Math.max(s,i.x),o=Math.max(o,i.y)})),e.invert();const h=e.point({x:r,y:n});return{x:h.x,y:h.y,width:s-r,height:o-n,rotation:i.getAngle(this.rotation())}}getX(){return this._getNodeRect().x}getY(){return this._getNodeRect().y}getWidth(){return this._getNodeRect().width}getHeight(){return this._getNodeRect().height}_createElements(){this._createBack(),ei.forEach((t=>{this._createAnchor(t)})),this._createAnchor("rotater")}_createAnchor(t){var e=new Pe({stroke:"rgb(0, 161, 255)",fill:"white",strokeWidth:1,name:t+" _anchor",dragDistance:0,draggable:!0,hitStrokeWidth:ti?10:"auto"}),r=this;e.on("mousedown touchstart",(function(t){r._handleMouseDown(t)})),e.on("dragstart",(t=>{e.stopDrag(),t.cancelBubble=!0})),e.on("dragend",(t=>{t.cancelBubble=!0})),e.on("mouseenter",(()=>{var r=i.getAngle(this.rotation()),a=this.rotateAnchorCursor(),n=function(t,e,i){if("rotater"===t)return i;e+=g.degToRad(Ze[t]||0);var r=(g.radToDeg(e)%360+360)%360;return g._inRange(r,337.5,360)||g._inRange(r,0,22.5)?"ns-resize":g._inRange(r,22.5,67.5)?"nesw-resize":g._inRange(r,67.5,112.5)?"ew-resize":g._inRange(r,112.5,157.5)?"nwse-resize":g._inRange(r,157.5,202.5)?"ns-resize":g._inRange(r,202.5,247.5)?"nesw-resize":g._inRange(r,247.5,292.5)?"ew-resize":g._inRange(r,292.5,337.5)?"nwse-resize":(g.error("Transformer has unknown angle for cursor detection: "+r),"pointer")}(t,r,a);e.getStage().content&&(e.getStage().content.style.cursor=n),this._cursorChange=!0})),e.on("mouseout",(()=>{e.getStage().content&&(e.getStage().content.style.cursor=""),this._cursorChange=!1})),this.add(e)}_createBack(){var t=new Nt({name:"back",width:0,height:0,draggable:!0,sceneFunc(t,e){var i=e.getParent(),r=i.padding();t.beginPath(),t.rect(-r,-r,e.width()+2*r,e.height()+2*r),t.moveTo(e.width()/2,-r),i.rotateEnabled()&&i.rotateLineVisible()&&t.lineTo(e.width()/2,-i.rotateAnchorOffset()*g._sign(e.height())-r),t.fillStrokeShape(e)},hitFunc:(t,e)=>{if(this.shouldOverdrawWholeArea()){var i=this.padding();t.beginPath(),t.rect(-i,-i,e.width()+2*i,e.height()+2*i),t.fillStrokeShape(e)}}});this.add(t),this._proxyDrag(t),t.on("dragstart",(t=>{t.cancelBubble=!0})),t.on("dragmove",(t=>{t.cancelBubble=!0})),t.on("dragend",(t=>{t.cancelBubble=!0})),this.on("dragmove",(t=>{this.update()}))}_handleMouseDown(t){if(!this._transforming){this._movingAnchorName=t.target.name().split(" ")[0];var e=this._getNodeRect(),i=e.width,r=e.height,a=Math.sqrt(Math.pow(i,2)+Math.pow(r,2));this.sin=Math.abs(r/a),this.cos=Math.abs(i/a),"undefined"!=typeof window&&(window.addEventListener("mousemove",this._handleMouseMove),window.addEventListener("touchmove",this._handleMouseMove),window.addEventListener("mouseup",this._handleMouseUp,!0),window.addEventListener("touchend",this._handleMouseUp,!0)),this._transforming=!0;var n=t.target.getAbsolutePosition(),s=t.target.getStage().getPointerPosition();this._anchorDragOffset={x:s.x-n.x,y:s.y-n.y},ai++,this._fire("transformstart",{evt:t.evt,target:this.getNode()}),this._nodes.forEach((e=>{e._fire("transformstart",{evt:t.evt,target:e})}))}}_handleMouseMove(t){var e,r,a,n=this.findOne("."+this._movingAnchorName),s=n.getStage();s.setPointersPositions(t);const o=s.getPointerPosition();let h={x:o.x-this._anchorDragOffset.x,y:o.y-this._anchorDragOffset.y};const l=n.getAbsolutePosition();this.anchorDragBoundFunc()&&(h=this.anchorDragBoundFunc()(l,h,t)),n.setAbsolutePosition(h);const d=n.getAbsolutePosition();if(l.x!==d.x||l.y!==d.y)if("rotater"!==this._movingAnchorName){var c,g=this.shiftBehavior();c="inverted"===g?this.keepRatio()&&!t.shiftKey:"none"===g?this.keepRatio():this.keepRatio()||t.shiftKey;var u=this.centeredScaling()||t.altKey;if("top-left"===this._movingAnchorName){if(c){var f=u?{x:this.width()/2,y:this.height()/2}:{x:this.findOne(".bottom-right").x(),y:this.findOne(".bottom-right").y()};a=Math.sqrt(Math.pow(f.x-n.x(),2)+Math.pow(f.y-n.y(),2));var p=this.findOne(".top-left").x()>f.x?-1:1,v=this.findOne(".top-left").y()>f.y?-1:1;e=a*this.cos*p,r=a*this.sin*v,this.findOne(".top-left").x(f.x-e),this.findOne(".top-left").y(f.y-r)}}else if("top-center"===this._movingAnchorName)this.findOne(".top-left").y(n.y());else if("top-right"===this._movingAnchorName){if(c){f=u?{x:this.width()/2,y:this.height()/2}:{x:this.findOne(".bottom-left").x(),y:this.findOne(".bottom-left").y()};a=Math.sqrt(Math.pow(n.x()-f.x,2)+Math.pow(f.y-n.y(),2));p=this.findOne(".top-right").x()f.y?-1:1;e=a*this.cos*p,r=a*this.sin*v,this.findOne(".top-right").x(f.x+e),this.findOne(".top-right").y(f.y-r)}var m=n.position();this.findOne(".top-left").y(m.y),this.findOne(".bottom-right").x(m.x)}else if("middle-left"===this._movingAnchorName)this.findOne(".top-left").x(n.x());else if("middle-right"===this._movingAnchorName)this.findOne(".bottom-right").x(n.x());else if("bottom-left"===this._movingAnchorName){if(c){f=u?{x:this.width()/2,y:this.height()/2}:{x:this.findOne(".top-right").x(),y:this.findOne(".top-right").y()};a=Math.sqrt(Math.pow(f.x-n.x(),2)+Math.pow(n.y()-f.y,2));p=f.x{var i;e._fire("transformend",{evt:t,target:e}),null===(i=e.getLayer())||void 0===i||i.batchDraw()})),this._movingAnchorName=null}}_fitNodesInto(t,e){var r=this._getNodeRect();if(g._inRange(t.width,2*-this.padding()-1,1))return void this.update();if(g._inRange(t.height,2*-this.padding()-1,1))return void this.update();var n=new a;if(n.rotate(i.getAngle(this.rotation())),this._movingAnchorName&&t.width<0&&this._movingAnchorName.indexOf("left")>=0){const e=n.point({x:2*-this.padding(),y:0});t.x+=e.x,t.y+=e.y,t.width+=2*this.padding(),this._movingAnchorName=this._movingAnchorName.replace("left","right"),this._anchorDragOffset.x-=e.x,this._anchorDragOffset.y-=e.y}else if(this._movingAnchorName&&t.width<0&&this._movingAnchorName.indexOf("right")>=0){const e=n.point({x:2*this.padding(),y:0});this._movingAnchorName=this._movingAnchorName.replace("right","left"),this._anchorDragOffset.x-=e.x,this._anchorDragOffset.y-=e.y,t.width+=2*this.padding()}if(this._movingAnchorName&&t.height<0&&this._movingAnchorName.indexOf("top")>=0){const e=n.point({x:0,y:2*-this.padding()});t.x+=e.x,t.y+=e.y,this._movingAnchorName=this._movingAnchorName.replace("top","bottom"),this._anchorDragOffset.x-=e.x,this._anchorDragOffset.y-=e.y,t.height+=2*this.padding()}else if(this._movingAnchorName&&t.height<0&&this._movingAnchorName.indexOf("bottom")>=0){const e=n.point({x:0,y:2*this.padding()});this._movingAnchorName=this._movingAnchorName.replace("bottom","top"),this._anchorDragOffset.x-=e.x,this._anchorDragOffset.y-=e.y,t.height+=2*this.padding()}if(this.boundBoxFunc()){const e=this.boundBoxFunc()(r,t);e?t=e:g.warn("boundBoxFunc returned falsy. You should return new bound rect from it!")}const s=1e7,o=new a;o.translate(r.x,r.y),o.rotate(r.rotation),o.scale(r.width/s,r.height/s);const h=new a,l=t.width/s,d=t.height/s;!1===this.flipEnabled()?(h.translate(t.x,t.y),h.rotate(t.rotation),h.translate(t.width<0?t.width:0,t.height<0?t.height:0),h.scale(Math.abs(l),Math.abs(d))):(h.translate(t.x,t.y),h.rotate(t.rotation),h.scale(l,d));const c=h.multiply(o.invert());this._nodes.forEach((t=>{var e;const i=t.getParent().getAbsoluteTransform(),r=t.getTransform().copy();r.translate(t.offsetX(),t.offsetY());const n=new a;n.multiply(i.copy().invert()).multiply(c).multiply(i).multiply(r);const s=n.decompose();t.setAttrs(s),null===(e=t.getLayer())||void 0===e||e.batchDraw()})),this.rotation(g._getRotation(t.rotation)),this._nodes.forEach((t=>{this._fire("transform",{evt:e,target:t}),t._fire("transform",{evt:e,target:t})})),this._resetTransformCache(),this.update(),this.getLayer().batchDraw()}forceUpdate(){this._resetTransformCache(),this.update()}_batchChangeChild(t,e){this.findOne(t).setAttrs(e)}update(){var t,e=this._getNodeRect();this.rotation(g._getRotation(e.rotation));var i=e.width,r=e.height,a=this.enabledAnchors(),n=this.resizeEnabled(),s=this.padding(),o=this.anchorSize();const h=this.find("._anchor");h.forEach((t=>{t.setAttrs({width:o,height:o,offsetX:o/2,offsetY:o/2,stroke:this.anchorStroke(),strokeWidth:this.anchorStrokeWidth(),fill:this.anchorFill(),cornerRadius:this.anchorCornerRadius()})})),this._batchChangeChild(".top-left",{x:0,y:0,offsetX:o/2+s,offsetY:o/2+s,visible:n&&a.indexOf("top-left")>=0}),this._batchChangeChild(".top-center",{x:i/2,y:0,offsetY:o/2+s,visible:n&&a.indexOf("top-center")>=0}),this._batchChangeChild(".top-right",{x:i,y:0,offsetX:o/2-s,offsetY:o/2+s,visible:n&&a.indexOf("top-right")>=0}),this._batchChangeChild(".middle-left",{x:0,y:r/2,offsetX:o/2+s,visible:n&&a.indexOf("middle-left")>=0}),this._batchChangeChild(".middle-right",{x:i,y:r/2,offsetX:o/2-s,visible:n&&a.indexOf("middle-right")>=0}),this._batchChangeChild(".bottom-left",{x:0,y:r,offsetX:o/2+s,offsetY:o/2-s,visible:n&&a.indexOf("bottom-left")>=0}),this._batchChangeChild(".bottom-center",{x:i/2,y:r,offsetY:o/2-s,visible:n&&a.indexOf("bottom-center")>=0}),this._batchChangeChild(".bottom-right",{x:i,y:r,offsetX:o/2-s,offsetY:o/2-s,visible:n&&a.indexOf("bottom-right")>=0}),this._batchChangeChild(".rotater",{x:i/2,y:-this.rotateAnchorOffset()*g._sign(r)-s,visible:this.rotateEnabled()}),this._batchChangeChild(".back",{width:i,height:r,visible:this.borderEnabled(),stroke:this.borderStroke(),strokeWidth:this.borderStrokeWidth(),dash:this.borderDash(),x:0,y:0});const l=this.anchorStyleFunc();l&&h.forEach((t=>{l(t)})),null===(t=this.getLayer())||void 0===t||t.batchDraw()}isTransforming(){return this._transforming}stopTransform(){if(this._transforming){this._removeEvents();var t=this.findOne("."+this._movingAnchorName);t&&t.stopDrag()}}destroy(){return this.getStage()&&this._cursorChange&&this.getStage().content&&(this.getStage().content.style.cursor=""),Xt.prototype.destroy.call(this),this.detach(),this._removeEvents(),this}toObject(){return V.prototype.toObject.call(this)}clone(t){return V.prototype.clone.call(this,t)}getClientRect(){return this.nodes().length>0?super.getClientRect():{x:0,y:0,width:0,height:0}}}ni.isTransforming=()=>ai>0,ni.prototype.className="Transformer",r(ni),w.addGetterSetter(ni,"enabledAnchors",ei,(function(t){return t instanceof Array||g.warn("enabledAnchors value should be an array"),t instanceof Array&&t.forEach((function(t){-1===ei.indexOf(t)&&g.warn("Unknown anchor name: "+t+". Available names are: "+ei.join(", "))})),t||[]})),w.addGetterSetter(ni,"flipEnabled",!0,x()),w.addGetterSetter(ni,"resizeEnabled",!0),w.addGetterSetter(ni,"anchorSize",10,p()),w.addGetterSetter(ni,"rotateEnabled",!0),w.addGetterSetter(ni,"rotateLineVisible",!0),w.addGetterSetter(ni,"rotationSnaps",[]),w.addGetterSetter(ni,"rotateAnchorOffset",50,p()),w.addGetterSetter(ni,"rotateAnchorCursor","crosshair"),w.addGetterSetter(ni,"rotationSnapTolerance",5,p()),w.addGetterSetter(ni,"borderEnabled",!0),w.addGetterSetter(ni,"anchorStroke","rgb(0, 161, 255)"),w.addGetterSetter(ni,"anchorStrokeWidth",1,p()),w.addGetterSetter(ni,"anchorFill","white"),w.addGetterSetter(ni,"anchorCornerRadius",0,p()),w.addGetterSetter(ni,"borderStroke","rgb(0, 161, 255)"),w.addGetterSetter(ni,"borderStrokeWidth",1,p()),w.addGetterSetter(ni,"borderDash"),w.addGetterSetter(ni,"keepRatio",!0),w.addGetterSetter(ni,"shiftBehavior","default"),w.addGetterSetter(ni,"centeredScaling",!1),w.addGetterSetter(ni,"ignoreStroke",!1),w.addGetterSetter(ni,"padding",0,p()),w.addGetterSetter(ni,"node"),w.addGetterSetter(ni,"nodes"),w.addGetterSetter(ni,"boundBoxFunc"),w.addGetterSetter(ni,"anchorDragBoundFunc"),w.addGetterSetter(ni,"anchorStyleFunc"),w.addGetterSetter(ni,"shouldOverdrawWholeArea",!1),w.addGetterSetter(ni,"useSingleNodeRotation",!0),w.backCompat(ni,{lineEnabled:"borderEnabled",rotateHandlerOffset:"rotateAnchorOffset",enabledHandlers:"enabledAnchors"});class si extends Nt{_sceneFunc(t){t.beginPath(),t.arc(0,0,this.radius(),0,i.getAngle(this.angle()),this.clockwise()),t.lineTo(0,0),t.closePath(),t.fillStrokeShape(this)}getWidth(){return 2*this.radius()}getHeight(){return 2*this.radius()}setWidth(t){this.radius(t/2)}setHeight(t){this.radius(t/2)}}function oi(){this.r=0,this.g=0,this.b=0,this.a=0,this.next=null}si.prototype.className="Wedge",si.prototype._centroid=!0,si.prototype._attrsAffectingSize=["radius"],r(si),w.addGetterSetter(si,"radius",0,p()),w.addGetterSetter(si,"angle",0,p()),w.addGetterSetter(si,"clockwise",!1),w.backCompat(si,{angleDeg:"angle",getAngleDeg:"getAngle",setAngleDeg:"setAngle"});var hi=[512,512,456,512,328,456,335,512,405,328,271,456,388,335,292,512,454,405,364,328,298,271,496,456,420,388,360,335,312,292,273,512,482,454,428,405,383,364,345,328,312,298,284,271,259,496,475,456,437,420,404,388,374,360,347,335,323,312,302,292,282,273,265,512,497,482,468,454,441,428,417,405,394,383,373,364,354,345,337,328,320,312,305,298,291,284,278,271,265,259,507,496,485,475,465,456,446,437,428,420,412,404,396,388,381,374,367,360,354,347,341,335,329,323,318,312,307,302,297,292,287,282,278,273,269,265,261,512,505,497,489,482,475,468,461,454,447,441,435,428,422,417,411,405,399,394,389,383,378,373,368,364,359,354,350,345,341,337,332,328,324,320,316,312,309,305,301,298,294,291,287,284,281,278,274,271,268,265,262,259,257,507,501,496,491,485,480,475,470,465,460,456,451,446,442,437,433,428,424,420,416,412,408,404,400,396,392,388,385,381,377,374,370,367,363,360,357,354,350,347,344,341,338,335,332,329,326,323,320,318,315,312,310,307,304,302,299,297,294,292,289,287,285,282,280,278,275,273,271,269,267,265,263,261,259],li=[9,11,12,13,13,14,14,15,15,15,15,16,16,16,16,17,17,17,17,17,17,17,18,18,18,18,18,18,18,18,18,19,19,19,19,19,19,19,19,19,19,19,19,19,19,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24];w.addGetterSetter(V,"blurRadius",0,p(),w.afterSetFilter);w.addGetterSetter(V,"brightness",0,p(),w.afterSetFilter);w.addGetterSetter(V,"contrast",0,p(),w.afterSetFilter);function di(t,e,i,r,a){var n=i-e,s=a-r;return 0===n?r+s/2:0===s?r:s*((t-e)/n)+r}w.addGetterSetter(V,"embossStrength",.5,p(),w.afterSetFilter),w.addGetterSetter(V,"embossWhiteLevel",.5,p(),w.afterSetFilter),w.addGetterSetter(V,"embossDirection","top-left",null,w.afterSetFilter),w.addGetterSetter(V,"embossBlend",!1,null,w.afterSetFilter);w.addGetterSetter(V,"enhance",0,p(),w.afterSetFilter);w.addGetterSetter(V,"hue",0,p(),w.afterSetFilter),w.addGetterSetter(V,"saturation",0,p(),w.afterSetFilter),w.addGetterSetter(V,"luminance",0,p(),w.afterSetFilter);w.addGetterSetter(V,"hue",0,p(),w.afterSetFilter),w.addGetterSetter(V,"saturation",0,p(),w.afterSetFilter),w.addGetterSetter(V,"value",0,p(),w.afterSetFilter);function ci(t,e,i){var r=4*(i*t.width+e),a=[];return a.push(t.data[r++],t.data[r++],t.data[r++],t.data[r++]),a}function gi(t,e){return Math.sqrt(Math.pow(t[0]-e[0],2)+Math.pow(t[1]-e[1],2)+Math.pow(t[2]-e[2],2))}w.addGetterSetter(V,"kaleidoscopePower",2,p(),w.afterSetFilter),w.addGetterSetter(V,"kaleidoscopeAngle",0,p(),w.afterSetFilter);w.addGetterSetter(V,"threshold",0,p(),w.afterSetFilter);w.addGetterSetter(V,"noise",.2,p(),w.afterSetFilter);w.addGetterSetter(V,"pixelSize",8,p(),w.afterSetFilter);w.addGetterSetter(V,"levels",.5,p(),w.afterSetFilter);w.addGetterSetter(V,"red",0,(function(t){return this._filterUpToDate=!1,t>255?255:t<0?0:Math.round(t)})),w.addGetterSetter(V,"green",0,(function(t){return this._filterUpToDate=!1,t>255?255:t<0?0:Math.round(t)})),w.addGetterSetter(V,"blue",0,f,w.afterSetFilter);w.addGetterSetter(V,"red",0,(function(t){return this._filterUpToDate=!1,t>255?255:t<0?0:Math.round(t)})),w.addGetterSetter(V,"green",0,(function(t){return this._filterUpToDate=!1,t>255?255:t<0?0:Math.round(t)})),w.addGetterSetter(V,"blue",0,f,w.afterSetFilter),w.addGetterSetter(V,"alpha",1,(function(t){return this._filterUpToDate=!1,t>1?1:t<0?0:t}));w.addGetterSetter(V,"threshold",.5,p(),w.afterSetFilter);return Zt.Util._assign(Zt,{Arc:te,Arrow:ue,Circle:fe,Ellipse:pe,Image:ve,Label:we,Tag:Ce,Line:re,Path:ge,Rect:Pe,RegularPolygon:ke,Ring:Ae,Sprite:Me,Star:Ge,Text:Xe,TextPath:Ve,Transformer:ni,Wedge:si,Filters:{Blur:function(t){var e=Math.round(this.blurRadius());e>0&&function(t,e){var i,r,a,n,s,o,h,l,d,c,g,u,f,p,v,m,_,y,x,b,S,w,C,P,k=t.data,T=t.width,A=t.height,M=e+e+1,G=T-1,R=A-1,E=e+1,D=E*(E+1)/2,L=new oi,I=null,O=L,F=null,B=null,N=hi[e],H=li[e];for(a=1;a>H,0!==C?(C=255/C,k[o]=(l*N>>H)*C,k[o+1]=(d*N>>H)*C,k[o+2]=(c*N>>H)*C):k[o]=k[o+1]=k[o+2]=0,l-=u,d-=f,c-=p,g-=v,u-=F.r,f-=F.g,p-=F.b,v-=F.a,n=h+((n=i+e+1)>H,C>0?(C=255/C,k[n]=(l*N>>H)*C,k[n+1]=(d*N>>H)*C,k[n+2]=(c*N>>H)*C):k[n]=k[n+1]=k[n+2]=0,l-=u,d-=f,c-=p,g-=v,u-=F.r,f-=F.g,p-=F.b,v-=F.a,n=i+((n=r+E)255?255:n,s=(s*=255)<0?0:s>255?255:s,o=(o*=255)<0?0:o>255?255:o,r[e]=n,r[e+1]=s,r[e+2]=o},Emboss:function(t){var e=10*this.embossStrength(),i=255*this.embossWhiteLevel(),r=this.embossDirection(),a=this.embossBlend(),n=0,s=0,o=t.data,h=t.width,l=t.height,d=4*h,c=l;switch(r){case"top-left":n=-1,s=-1;break;case"top":n=-1,s=0;break;case"top-right":n=-1,s=1;break;case"right":n=0,s=1;break;case"bottom-right":n=1,s=1;break;case"bottom":n=1,s=0;break;case"bottom-left":n=1,s=-1;break;case"left":n=0,s=-1;break;default:g.error("Unknown emboss direction: "+r)}do{var u=(c-1)*d,f=n;c+f<1&&(f=0),c+f>l&&(f=0);var p=(c-1+f)*h*4,v=h;do{var m=u+4*(v-1),_=s;v+_<1&&(_=0),v+_>h&&(_=0);var y=p+4*(v-1+_),x=o[m]-o[y],b=o[m+1]-o[y+1],S=o[m+2]-o[y+2],w=x,C=w>0?w:-w;if((b>0?b:-b)>C&&(w=b),(S>0?S:-S)>C&&(w=S),w*=e,a){var P=o[m]+w,k=o[m+1]+w,T=o[m+2]+w;o[m]=P>255?255:P<0?0:P,o[m+1]=k>255?255:k<0?0:k,o[m+2]=T>255?255:T<0?0:T}else{var A=i-w;A<0?A=0:A>255&&(A=255),o[m]=o[m+1]=o[m+2]=A}}while(--v)}while(--c)},Enhance:function(t){var e,i,r,a,n=t.data,s=n.length,o=n[0],h=o,l=n[1],d=l,c=n[2],g=c,u=this.enhance();if(0!==u){for(a=0;ah&&(h=e),(i=n[a+1])d&&(d=i),(r=n[a+2])g&&(g=r);var f,p,v,m,_,y,x,b,S;for(h===o&&(h=255,o=0),d===l&&(d=255,l=0),g===c&&(g=255,c=0),u>0?(p=h+u*(255-h),v=o-u*(o-0),_=d+u*(255-d),y=l-u*(l-0),b=g+u*(255-g),S=c-u*(c-0)):(p=h+u*(h-(f=.5*(h+o))),v=o+u*(o-f),_=d+u*(d-(m=.5*(d+l))),y=l+u*(l-m),b=g+u*(g-(x=.5*(g+c))),S=c+u*(c-x)),a=0;am?s:m;var _,y,x,b,S=d,w=l,C=360/w*Math.PI/180;for(y=0;yd&&(x=y,b=0,S=-1),i=0;iy?h:y;var x,b,S,w=g,C=c;for(a=0;a=0&&u=0&&f=0&&u=0&&f=1020?255:0}return s}(e=function(t,e,i){for(var r=[1,1,1,1,0,1,1,1,1],a=Math.round(Math.sqrt(r.length)),n=Math.floor(a/2),s=[],o=0;o=0&&u=0&&f=m))for(i=d;i=_||(a+=b[(r=4*(m*i+e))+0],n+=b[r+1],s+=b[r+2],o+=b[r+3],p+=1);for(a/=p,n/=p,s/=p,o/=p,e=h;e=m))for(i=d;i=_||(b[(r=4*(m*i+e))+0]=a,b[r+1]=n,b[r+2]=s,b[r+3]=o)}},Posterize:function(t){var e,i=Math.round(254*this.levels())+1,r=t.data,a=r.length,n=255/i;for(e=0;e127&&(h=255-h),l>127&&(l=255-l),d>127&&(d=255-d),e[o]=h,e[o+1]=l,e[o+2]=d}while(--s)}while(--a)},Threshold:function(t){var e,i=255*this.threshold(),r=t.data,a=r.length;for(e=0;e") + var container_img = $(""); + var container = $("
"); + var layer = new Konva.Layer({draggable: false}); + container_inner_wrapper.appendTo(container_wrapper).append(container_img).append(container); + + layer.font_scale = null; + layer.w_scale = null; + layer.h_scale = null; + + container_img.on("load", function(){ + var img_width = container_img.outerWidth(true), + img_height = container_img.outerHeight(true); + + var stage = new Konva.Stage({ + container: container_id, + width: img_width, + height: img_height + }); + // add canvas element + layer.font_scale = img_width / 500; + layer.img_width = img_width; + layer.img_height = img_height; + layer.w_scale = img_width / 100; + layer.h_scale = img_height / 100; + + + layer.text_boxes.forEach(function(box) { + if (box.scaled) + return; + else + { + apply_scale(layer, box); + } + }); + layer.pending_boxes.forEach(function(args) { + args[0] = layer; + create_text_box.apply(null, args); + }); + + layer.pending_boxes = []; + + stage.add(layer); + }); + + layer.container_inner_wrapper = container_inner_wrapper; + layer.container = container; + layer.container_img = container_img; + layer.data = data; + + layer.text_boxes = []; + layer.pending_boxes = []; + + layer.resize_func = function(container_img) { + var img_width, img_height; + var stage = this.getStage(); + if (this.resized) + { + window.setTimeout(function(){ + this.resize_func(container_img); + }.bind(this), 100); + return; + } + this.resized = true; + stage.setWidth(); + stage.setHeight(); + img_width = container_img.outerWidth(true); + img_height = container_img.outerHeight(true); + stage.setWidth(img_width); + stage.setHeight(img_height); + this.font_scale = img_width / 500; + this.w_scale = img_width / 100; + this.h_scale = img_height / 100; + this.img_width = img_width; + this.img_height = img_height; + + this.pending_boxes = [null]; + + this.text_boxes.forEach(function(box) { + apply_scale(this, box); + }.bind(this)); + + this.pending_boxes = []; + + this.resized = false; + }.bind(layer, container_img); + + $(window).on("resize", layer.resize_func); + + return layer; +} + +function create_text_box(layer, pos, text, fontSize, fontFamily, fontColor, is_stroke, stroke, strokeWidth, is_fill) +{ + if (!layer) + return; + + if (!layer.font_scale || !layer.w_scale || !layer.h_scale) + { + layer.pending_boxes.push(arguments); + return; + } + else if (layer.pending_boxes.length != 0) + { + var args = arguments; + window.setTimeout(function(args){ + create_text_box.apply(this, args); + }.bind(this, args), 100); + return; + } + + var scaled = false; + var org_fontSize = fontSize, + org_x = pos.x, + org_y = pos.y, + x, y, w, h, + org_w, org_h, + org_text = text, + _strokeWidth, + observe_fields = [], + observe_func; + if (!fontFamily) + fontFamily = 'Calibri'; + + if (!fontColor) + fontColor = '#333333'; + + if (!stroke) + stroke = '#333333'; + + if (!strokeWidth) + strokeWidth = 2; + + _strokeWidth = strokeWidth; + + org_w = (pos.w == 0 ? 'auto' : pos.w); + org_h = (pos.h == 0 ? 'auto' : pos.h); + x = pos.x; + y = pos.y; + w = org_w; + h = org_h; + + text = org_text.replace(/{([^{}]+)}/g, function(_, f) { + observe_fields.push(f); + return layer.data[f]; + }); + + if (layer.font_scale && layer.w_scale && layer.h_scale) + { + x = pos.x * layer.w_scale; + y = pos.y * layer.h_scale; + if (w != 'auto') + w *= layer.w_scale; + if (h != 'auto') + h *= layer.h_scale; + fontSize *= layer.font_scale; + strokeWidth *= layer.font_scale; + scaled = true; + } + + if (!is_stroke) + is_fill = true; + + // create shape + var textNode = new Konva.Text({ + text: text, + width: w, + height: h, + x: x, + y: y, + lineHeight: pos.l_h, + fontSize: fontSize, + draggable: true, + fontFamily: fontFamily, + stroke: (is_stroke ? stroke : null), + strokeWidth: (is_stroke ? strokeWidth : null), + fillAfterStrokeEnabled: true + }); + textNode.fill((is_fill ? fontColor : false)); + layer.add(textNode); + + textNode._fill = fontColor; + textNode.scaled = scaled; + textNode.org_fontSize = org_fontSize; + textNode.org_x = org_x; + textNode.org_y = org_y; + textNode.org_w = org_w; + textNode.org_h = org_h; + textNode.org_text = org_text; + textNode.is_stroke = is_stroke; + textNode.is_fill = is_fill; + textNode._stroke = stroke; + textNode._strokeWidth = _strokeWidth; + textNode.observe_fields = observe_fields; + + if (observe_fields.length != 0) { + observe_func = function(layer, event, f, content) { + if ($.inArray(f, this.observe_fields) != -1) + { + var new_text; + if (this.prev_change == f) + { + new_text = this.cached_text; + } + else + { + var other_fields = Array.from(this.observe_fields); + other_fields.removeItem(f); + new_text = this.org_text; + other_fields.forEach(function(ff) { + new_text = new_text.replaceAll('{' + ff + '}', layer.data[ff]); + }) + this.cached_text = new_text; + this.prev_change = f; + } + + layer.data[f] = content; + + new_text = new_text.replaceAll('{' + f + '}', content); + + this.text(new_text); + } + }.bind(textNode, layer); + textNode.observe_func = observe_func; + layer.container.on("change", observe_func); + } + + layer.text_boxes.push(textNode); + + return textNode; +} + +function destroy_text(textNode) +{ + if (textNode.observe_func) + layer.container.off("change", textNode.observe_func); + textNode.destroy(); +} + +function destroy_layer(layer) +{ + layer.text_boxes.forEach(function(textNode) { + destroy_text(textNode); + }); + $(window).off("resize", layer.resize_func); + layer.destroy(); + layer.container_inner_wrapper.remove(); +} \ No newline at end of file diff --git a/app/assets/javascripts/greeting_card/konva_helper.js b/app/assets/javascripts/greeting_card/konva_helper.js new file mode 100644 index 0000000..26e1e53 --- /dev/null +++ b/app/assets/javascripts/greeting_card/konva_helper.js @@ -0,0 +1,796 @@ +function _round(num, i) +{ + if (i == 0 || i == undefined) + { + return Math.round(num); + } + else + { + var scale = 10 ** i; + return Math.round(num * scale) / scale; + } +} + +function apply_scale(layer, box) +{ + box.x( box.org_x * layer.w_scale ); + box.y( box.org_y * layer.h_scale ); + if (box.org_w != 'auto') + box.width( box.org_w * layer.w_scale ); + if (box.org_h != 'auto') + box.height( box.org_h * layer.h_scale ); + box.fontSize( box.org_fontSize * layer.font_scale); + box.strokeWidth( box._strokeWidth * layer.font_scale); + box.scaled = true; +} + +function create_konva(container_wrapper, container_hidden_field, container_id, img_url, fonts, trans) +{ + var lineheight_trans = "Line Height", + stroke_trans = "Stroke", + fill_trans = "Fill"; + if (trans && trans['lineheight']) + lineheight_trans = trans['lineheight']; + if (trans && trans['stroke']) + stroke_trans = trans['stroke']; + else + stroke_trans = [stroke_trans, stroke_trans]; + var fill_trans = "Fill"; + if (trans && trans['fill']) + fill_trans = trans['fill']; + else + fill_trans = [fill_trans, fill_trans]; + var toolbar_wrapper = $("
"); + var container_inner_wrapper = $("
") + var color_picker = $(""); + var font_picker = $(""); + var fontsize_picker = $(""); + var lineheight_picker = $("\""") + var stroke_checker = $("" + stroke_trans[0] + ""); + var stroke_picker = $(""); + var strokeWidth_picker = $(""); + var fill_checker = $("" + fill_trans[0] + ""); + var add_btn = $(""); + var remove_btn = $(""); + var container_img = $(""); + var container = $("
"); + var layer = new Konva.Layer({draggable: false}); + var hidden_field_name = container_hidden_field.data('name'); + toolbar_wrapper.append(color_picker); + toolbar_wrapper.append(font_picker); + toolbar_wrapper.append(fontsize_picker); + toolbar_wrapper.append(lineheight_picker); + toolbar_wrapper.append(stroke_checker); + toolbar_wrapper.append(stroke_picker); + toolbar_wrapper.append(strokeWidth_picker); + toolbar_wrapper.append(fill_checker); + toolbar_wrapper.append(add_btn); + toolbar_wrapper.append(remove_btn); + toolbar_wrapper.appendTo(container_wrapper); + container_inner_wrapper.appendTo(container_wrapper).append(container_img).append(container); + + lineheight_picker = lineheight_picker.find('>input'); + stroke_checker = stroke_checker.find('>input'); + fill_checker = fill_checker.find('>input'); + + $.each(fonts, function(i, font){ + font_picker.append(new Option(font, i.toString())); + }); + + font_picker.select2({ + // templateResult: function(el){ + // var $element = $(el.element), + // image = $element.data("image"); + // return $("" + el.text + ""); + // }, + // minimumResultsForSearch: -1, + // width : 250 + }); + + layer.font_scale = null; + layer.w_scale = null; + layer.h_scale = null; + + container_img.on("load", function(){ + var img_width = container_img.outerWidth(true), + img_height = container_img.outerHeight(true); + + var stage = new Konva.Stage({ + container: container_id, + width: img_width, + height: img_height + }); + // add canvas element + layer.font_scale = img_width / 500; + layer.img_width = img_width; + layer.img_height = img_height; + layer.w_scale = img_width / 100; + layer.h_scale = img_height / 100; + + + layer.text_boxes.forEach(function(box) { + if (box.scaled) + return; + else + { + apply_scale(layer, box); + } + }); + layer.pending_boxes.forEach(function(args) { + args[0] = layer; + create_text_box.apply(null, args); + }); + + layer.pending_boxes = []; + + add_btn.removeAttr('disabled'); + remove_btn.removeAttr('disabled'); + + stage.add(layer); + }); + + color_picker.on('input', function(layer){ + var target_node = layer.target_node; + if (target_node) + { + var _fill = this.val(); + target_node._fill = _fill; + if (target_node.is_fill) + target_node.fill(_fill); + target_node.hidden_field_dict.fontColor.val(_fill); + layer.container_hidden_field.trigger("change"); + } + }.bind(color_picker, layer)); + + stroke_picker.on('input', function(layer){ + var target_node = layer.target_node; + if (target_node) + { + var _stroke = this.val(); + target_node._stroke = _stroke; + if (target_node.is_stroke) + target_node.stroke(_stroke); + target_node.hidden_field_dict.stroke.val(_stroke); + layer.container_hidden_field.trigger("change"); + } + }.bind(stroke_picker, layer)); + + layer.container = container; + layer.container_hidden_field = container_hidden_field; + layer.hidden_field_name = hidden_field_name; + layer.box_idx = -1; + layer.fonts = fonts; + + layer.color_picker = color_picker; + layer.font_picker = font_picker; + layer.fontsize_picker = fontsize_picker; + layer.lineheight_picker = lineheight_picker; + layer.stroke_checker = stroke_checker; + layer.stroke_picker = stroke_picker; + layer.strokeWidth_picker = strokeWidth_picker; + layer.fill_checker = fill_checker; + layer.max_y = null; + layer.default_fontsize = 20; + layer.text_boxes = []; + layer.pending_boxes = []; + + font_picker.on('change', function(fonts, layer){ + var target_node = layer.target_node; + if (target_node) + { + var fontFamily = fonts[Number(this.val())]; + target_node.fontFamily(fontFamily); + target_node.hidden_field_dict.fontFamily.val(fontFamily); + layer.container_hidden_field.trigger("change"); + } + }.bind(font_picker, fonts, layer)); + + fontsize_picker.on('change', function(layer){ + var target_node = layer.target_node; + if (target_node) + { + var org_fontSize = Number(this.val()); + var new_fontSize = layer.font_scale * org_fontSize; + var org_height = target_node.height(); + var newlines = target_node.getText().match(/\n/g); + var height_diff = new_fontSize * ((newlines ? newlines.length : 0) + 1) - org_height - 2 * target_node.padding(); + target_node.fontSize(new_fontSize); + target_node.org_fontSize = org_fontSize; + target_node.hidden_field_dict.fontSize.val(org_fontSize.toString()); + layer.container_hidden_field.trigger("change"); + if (height_diff > 0) + { + target_node.height(org_height + height_diff); + } + } + }.bind(fontsize_picker, layer)); + + lineheight_picker.on('change', function(layer){ + var target_node = layer.target_node; + if (target_node) + { + var lineHeight = Number(this.val()); + var fontSize = target_node.fontSize(); + var org_height = target_node.height(); + var newlines = target_node.getText().match(/\n/g); + var height_diff = fontSize * ((newlines ? newlines.length : 0) + 1) * lineHeight - org_height - 2 * target_node.padding(); + target_node.setLineHeight(lineHeight); + target_node.hidden_field_dict.l_h.val(lineHeight.toString()); + layer.container_hidden_field.trigger("change"); + if (height_diff > 0) + { + target_node.height(org_height + height_diff); + } + } + }.bind(lineheight_picker, layer)); + + strokeWidth_picker.on('input', function(layer){ + var target_node = layer.target_node; + if (target_node) + { + var org_strokeWidth = Number(this.val()); + if (isNaN(org_strokeWidth)) + return; + var new_strokeWidth = layer.font_scale * org_strokeWidth; + target_node.strokeWidth(new_strokeWidth); + target_node._strokeWidth = org_strokeWidth; + target_node.hidden_field_dict.strokeWidth.val(org_strokeWidth.toString()); + layer.container_hidden_field.trigger("change"); + } + }.bind(strokeWidth_picker, layer)); + + stroke_checker.on('change', function(layer){ + var target_node = layer.target_node; + if (target_node) + { + var is_stroke = this.prop("checked"); + if (is_stroke) + { + layer.stroke_checker.attr("checked", ""); + layer.stroke_picker.removeClass("hide"); + layer.strokeWidth_picker.removeClass("hide"); + layer.fill_checker.parent().removeClass("hide"); + target_node.stroke(target_node._stroke); + } + else + { + layer.stroke_checker.removeAttr("checked"); + layer.stroke_picker.addClass("hide"); + layer.strokeWidth_picker.addClass("hide"); + layer.fill_checker.parent().addClass("hide"); + target_node.stroke(null); + } + target_node.is_stroke = is_stroke; + target_node.hidden_field_dict.is_stroke.val(is_stroke.toString()); + layer.container_hidden_field.trigger("change"); + } + }.bind(stroke_checker, layer)); + + fill_checker.on('change', function(layer){ + var target_node = layer.target_node; + if (target_node) + { + var is_fill = this.prop("checked"); + if (is_fill) + { + target_node.fill(target_node._fill); + } + else + { + target_node.fill(null); + } + target_node.is_fill = is_fill; + target_node.hidden_field_dict.is_fill.val(is_fill.toString()); + layer.container_hidden_field.trigger("change"); + } + }.bind(fill_checker, layer)); + + add_btn.on('click', function(layer) { + create_text_box(layer, {x: 50, y: (layer.max_y || 50) + layer.default_fontsize * 100 / layer.img_width, w: 'auto', h: 'auto'}, "Some text here", layer.default_fontsize); + layer.container_hidden_field.trigger("change"); + }.bind(add_btn, layer)); + + remove_btn.on('click', function(layer) { + var target_node = layer.target_node; + if (target_node) + { + destroy_text(target_node); + } + }.bind(remove_btn, layer)); + + container.prop('tabIndex', 1); + container.focus(); + + container.on('keydown', function(layer, event) { + const key = event.key; + if (key === "Backspace" || key === "Delete") { + var target_node = layer.target_node; + if (target_node) + { + destroy_text(target_node); + } + } + }.bind(container, layer)); + + layer.resize_func = function(container_img) { + var img_width, img_height; + var stage = this.getStage(); + if (this.resized) + { + window.setTimeout(function(){ + this.resize_func(container_img); + }.bind(this), 100); + return; + } + this.resized = true; + stage.setWidth(); + stage.setHeight(); + img_width = container_img.outerWidth(true); + img_height = container_img.outerHeight(true); + stage.setWidth(img_width); + stage.setHeight(img_height); + this.font_scale = img_width / 500; + this.w_scale = img_width / 100; + this.h_scale = img_height / 100; + this.img_width = img_width; + this.img_height = img_height; + + this.pending_boxes = [null]; + + this.text_boxes.forEach(function(box) { + apply_scale(this, box); + }.bind(this)); + + this.pending_boxes = []; + + this.resized = false; + }.bind(layer, container_img); + + $(window).on("resize", layer.resize_func); + + return layer; +} + +function text_box_to_hidden_field(hidden_field_name, idx) +{ + var x = this.org_x.toString(); + var y = this.org_y.toString(); + var w = this.org_w.toString(); + var h = this.org_h.toString(); + var l_h = this.lineHeight().toString(); + var text = this.text(); + var fontSize = this.org_fontSize.toString(); + var fontFamily = this.fontFamily(); + var fontColor = this.fill(); + var is_fill = this.is_fill ? 'true' : 'false'; + var is_stroke = this.is_stroke ? 'true' : 'false'; + var stroke = this._stroke ? this._stroke : ''; + var strokeWidth = this._strokeWidth ? this._strokeWidth : ''; + var hidden_field_dict = { + "x": $(""), + "y": $(""), + "w": $(""), + "h": $(""), + "l_h": $(""), + "text": $(""), + "fontSize": $(""), + "fontFamily": $(""), + "fontColor": $(""), + "is_fill": $(""), + "is_stroke": $(""), + "stroke": $(""), + "strokeWidth": $(""), + }; + + Object.keys(hidden_field_dict).forEach(function(dict, k) + { + dict[k].appendTo(this.hidden_field_block); + }.bind(this, hidden_field_dict)); + + return hidden_field_dict; +} + +function create_text_box(layer, pos, text, fontSize, fontFamily, fontColor, is_stroke, stroke, strokeWidth, is_fill) +{ + if (!layer) + return; + + if (!layer.font_scale || !layer.w_scale || !layer.h_scale) + { + layer.pending_boxes.push(arguments); + return; + } + else if (layer.pending_boxes.length != 0) + { + var args = arguments; + window.setTimeout(function(args){ + create_text_box.apply(this, args); + }.bind(this, args), 100); + return; + } + + var scaled = false; + var org_fontSize = fontSize, + org_x = pos.x, + org_y = pos.y, + x, y, w, h, + line_height, + org_w, org_h, + _strokeWidth; + var hidden_field_block = $("
"); + var hidden_field_dict; + if (!fontFamily) + fontFamily = 'Calibri'; + + if (!fontColor) + fontColor = '#333333'; + + if (!stroke) + stroke = '#333333'; + + if (!strokeWidth) + strokeWidth = 2; + + _strokeWidth = strokeWidth; + + org_w = (pos.w == 0 ? 'auto' : pos.w); + org_h = (pos.h == 0 ? 'auto' : pos.h); + x = pos.x; + y = pos.y; + w = org_w; + h = org_h; + + if (layer.font_scale && layer.w_scale && layer.h_scale) + { + x = pos.x * layer.w_scale; + y = pos.y * layer.h_scale; + if (w != 'auto') + w *= layer.w_scale; + if (h != 'auto') + h *= layer.h_scale; + fontSize *= layer.font_scale; + strokeWidth *= layer.font_scale; + scaled = true; + } + + if (!is_stroke) + is_fill = true; + + // create shape + var textNode = new Konva.Text({ + text: text, + width: w, + height: h, + x: x, + y: y, + lineHeight: pos.l_h, + fontSize: fontSize, + draggable: true, + fontFamily: fontFamily, + stroke: (is_stroke ? stroke : null), + strokeWidth: (is_stroke ? strokeWidth : null), + fillAfterStrokeEnabled: true + }); + textNode.fill((is_fill ? fontColor : false)); + layer.add(textNode); + + textNode._fill = fontColor; + textNode.scaled = scaled; + textNode.org_fontSize = org_fontSize; + textNode.org_x = org_x; + textNode.org_y = org_y; + textNode.org_w = org_w; + textNode.org_h = org_h; + textNode.is_stroke = is_stroke; + textNode.is_fill = is_fill; + textNode._stroke = stroke; + textNode._strokeWidth = _strokeWidth; + textNode.hidden_field_block = hidden_field_block; + + if (layer.max_y) + layer.max_y = Math.max(layer.max_y, pos.y); + else + layer.max_y = pos.y; + + layer.box_idx += 1; + hidden_field_dict = text_box_to_hidden_field.call(textNode, layer.hidden_field_name, layer.box_idx); + layer.container_hidden_field.append(hidden_field_block); + + textNode.hidden_field_dict = hidden_field_dict; + + // textNode.on('transformend', function () { + // console.log('transform end - before reset font size = ' + this.fontSize() + ' at scale ' + this.scaleX()); + // this.fontSize(this.fontSize() * this.scaleX()); + // this.scale({x: 1, y: 1}); + // layer.batchDraw(); + // console.log('transform end - after reset font size = ' + this.fontSize() + ' at scale ' + this.scaleX()); + // }); + textNode.on('transformend', function (layer) { + this.org_w = _round(this.width() / layer.w_scale, 5); + this.org_h = _round(this.height() / layer.h_scale, 5); + this.hidden_field_dict.w.val(this.org_w.toString()); + this.hidden_field_dict.h.val(this.org_h.toString()); + layer.container_hidden_field.trigger("change"); + }.bind(textNode, layer)); + + textNode.on('dragend', function () { + this.org_x = _round(this.x() / layer.w_scale, 5); + this.org_y = _round(this.y() / layer.h_scale, 5); + this.hidden_field_dict.x.val(this.org_x.toString()); + this.hidden_field_dict.y.val(this.org_y.toString()); + layer.container_hidden_field.trigger("change"); + }.bind(textNode)); + + textNode.on('transform', function() { + this.setAttrs({ + width: Math.max(this.width() * this.scaleX(), 5), + height: Math.max(this.height() * this.scaleY(), 5), + scaleX: 1, + scaleY: 1, + }); + }.bind(textNode)); + + var tr_border = new Konva.Transformer({ + // node: textNode, + keepRatio: false, + borderEnabled: true, + borderStrokeWidth: 3, + rotateEnabled: false, + enabledAnchors: [] + }); + + var tr = new Konva.Transformer({ + // node: textNode, + ignoreStroke: true, + rotateEnabled: false, + // flipEnabled: false, + keepRatio: false, + borderEnabled: true, + // enabledAnchors: ['top-left', 'top-right', 'bottom-left', 'bottom-right'], + // set minimum width of text + boundBoxFunc: function (oldBox, newBox) { + newBox.width = Math.max(30, newBox.width); + return newBox; + }, + }); + + textNode.children = [tr, tr_border]; + + // window.tr_border = tr_border; + // window.tr = tr; + // window.layer = layer; + + layer.add(tr_border); + layer.add(tr); + + // add cursor styling + textNode.on('mouseover', function(tr_border) { + document.body.style.cursor = 'pointer'; + if (tr_border.nodes().length == 0) + { + tr_border.nodes([this]); + tr_border.forceUpdate(); + } + }.bind(textNode, tr_border)); + textNode.on('mouseout', function(tr_border) { + document.body.style.cursor = 'default'; + if (tr_border.nodes().length != 0) + { + tr_border.nodes([]); + tr_border.forceUpdate(); + } + }.bind(textNode, tr_border)); + textNode.on('click', function(tr, layer, _, e) { + if (tr.nodes().length == 0) + { + tr.nodes([this]); + tr.forceUpdate(); + layer.target_node = this; + layer.color_picker.removeAttr('disabled'); + layer.font_picker.removeAttr('disabled'); + layer.fontsize_picker.removeAttr('disabled'); + layer.lineheight_picker.removeAttr('disabled'); + layer.stroke_checker.removeAttr('disabled'); + layer.stroke_picker.removeAttr('disabled'); + layer.color_picker.val(this._fill); + layer.font_picker.val(layer.fonts.indexOf(this.fontFamily()).toString()); + layer.fontsize_picker.val(this.org_fontSize.toString()); + layer.lineheight_picker.val(this.lineHeight().toString()); + layer.stroke_checker.prop("checked", this.is_stroke); + layer.fill_checker.prop("checked", this.is_fill); + layer.stroke_picker.val(this._stroke); + layer.strokeWidth_picker.val(this._strokeWidth); + if (this.is_stroke) + { + layer.stroke_checker.attr("checked", ""); + layer.stroke_picker.removeClass("hide"); + layer.strokeWidth_picker.removeClass("hide"); + layer.fill_checker.parent().removeClass("hide"); + } + else + { + layer.stroke_checker.removeAttr("checked"); + layer.stroke_picker.addClass("hide"); + layer.strokeWidth_picker.addClass("hide"); + layer.fill_checker.parent().addClass("hide"); + } + layer.font_picker.trigger("change"); + } + this.click_triggered = true; + layer.click_triggered = true; + }.bind(textNode, tr, layer)); + + textNode.global_click = function(tr, layer, e) { + if (this.click_triggered) { + this.click_triggered = false; + return; + } + if (tr.nodes().length != 0) + { + tr.nodes([]); + tr.forceUpdate(); + if (!layer.click_triggered) + { + layer.target_node = null; + layer.color_picker.attr('disabled', ''); + layer.font_picker.attr('disabled', ''); + layer.fontsize_picker.attr('disabled', ''); + layer.lineheight_picker.attr('disabled', ''); + layer.stroke_checker.prop('checked', false); + layer.stroke_checker.attr('disabled', ''); + layer.stroke_picker.attr('disabled', ''); + layer.stroke_picker.addClass("hide"); + layer.strokeWidth_picker.addClass("hide"); + layer.fill_checker.parent().addClass("hide"); + } + } + if (layer.click_triggered) + { + layer.click_triggered = false; + } + }.bind(textNode, tr, layer); + + layer.container.on('click', textNode.global_click); + + textNode.on('dblclick dbltap', function(tr, layer) { + var textNode = this; + + // hide text node and transformer: + this.hide(); + // tr.hide(); + + // create textarea over canvas with absolute position + // first we need to find position for textarea + // how to find it? + + // at first lets find position of text node relative to the stage: + var textPosition = this.absolutePosition(); + + // so position of textarea will be the sum of positions above: + var areaPosition = { + x: layer.container.prop('offsetLeft') + textPosition.x, + y: layer.container.prop('offsetTop') + textPosition.y, + }; + + // create textarea and style it + var textarea = document.createElement('textarea'); + layer.container.parent().append(textarea); + + // apply many styles to match text on canvas as close as possible + // remember that text rendering on canvas and on the textarea can be different + // and sometimes it is hard to make it 100% the same. But we will try... + textarea.value = this.text(); + textarea.style.top = areaPosition.y + 'px'; + textarea.style.left = areaPosition.x + 'px'; + textarea.style.width = this.width() - this.padding() * 2 + 'px'; + textarea.style.height = + this.height() - this.padding() * 2 + 'px'; + textarea.style.fontSize = this.fontSize() + 'px'; + textarea.style.lineHeight = this.lineHeight(); + textarea.style.fontFamily = this.fontFamily(); + textarea.style.textAlign = this.align(); + textarea.style.color = this._fill; + rotation = this.rotation(); + var transform = ''; + if (rotation) { + transform += 'rotateZ(' + rotation + 'deg)'; + } + + var px = 0; + // also we need to slightly move textarea on firefox + // because it jumps a bit + var isFirefox = + navigator.userAgent.toLowerCase().indexOf('firefox') > -1; + if (isFirefox) { + px += 2 + Math.round(this.fontSize() / 20); + } + transform += 'translateY(-' + px + 'px)'; + + textarea.style.transform = transform; + + // reset height + // textarea.style.height = 'auto'; + // // after browsers resized it we can set actual value + // textarea.style.height = textarea.scrollHeight + 3 + 'px'; + + textarea.focus(); + + function removeTextarea() { + textNode.hidden_field_dict.text.val(textNode.text()); + layer.container_hidden_field.trigger("change"); + textarea.parentNode.removeChild(textarea); + window.removeEventListener('click', handleOutsideClick); + // textNode.width('auto'); + textNode.show(); + // tr.show(); + tr.forceUpdate(); + } + + function setTextareaWidth(newWidth) { + if (!newWidth) { + // set width for placeholder + newWidth = this.placeholder.length * this.fontSize(); + } + // some extra fixes on different browsers + var isSafari = /^((?!chrome|android).)*safari/i.test( + navigator.userAgent + ); + var isFirefox = + navigator.userAgent.toLowerCase().indexOf('firefox') > -1; + if (isSafari || isFirefox) { + newWidth = Math.ceil(newWidth); + } + + var isEdge = + document.documentMode || /Edge/.test(navigator.userAgent); + if (isEdge) { + newWidth += 1; + } + textarea.style.width = newWidth + 'px'; + } + + textarea.addEventListener('keydown', function (e) { + // hide on enter + // but don't hide on shift + enter + if (e.keyCode === 13 && !e.shiftKey) { + textNode.text(textarea.value); + removeTextarea(); + } + // on esc do not set value back to node + if (e.keyCode === 27) { + removeTextarea(); + } + }); + + textarea.addEventListener('keydown', function (e) { + scale = textNode.getAbsoluteScale().x; + setTextareaWidth(textNode.width() * scale); + // textarea.style.height = 'auto'; + // textarea.style.height = + // textarea.scrollHeight + textNode.fontSize() + 'px'; + }); + + function handleOutsideClick(e) { + if (e.target !== textarea) { + textNode.text(textarea.value); + removeTextarea(); + } + } + setTimeout(function(){ + window.addEventListener('click', handleOutsideClick); + }); + }.bind(textNode, tr, layer)); + + layer.text_boxes.push(textNode); + + return textNode; +} + +function destroy_text(target_node) +{ + target_node.children.forEach(function(child){ + child.destroy(); + }) + target_node.hidden_field_block.remove(); + target_node.destroy(); +} \ No newline at end of file diff --git a/app/assets/javascripts/jquery.ui.datepicker.monthyearpicker.js b/app/assets/javascripts/jquery.ui.datepicker.monthyearpicker.js new file mode 100644 index 0000000..f0f844b --- /dev/null +++ b/app/assets/javascripts/jquery.ui.datepicker.monthyearpicker.js @@ -0,0 +1,2739 @@ +/* +Month and Year picker for jQuery UI Datepicker 1.8.21 + +Written by Anton Ludescher (silverskater{at}gmail.com). +Dual licensed under the GPL (http://dev.jquery.com/browser/trunk/jquery/GPL-LICENSE.txt) and +MIT (http://dev.jquery.com/browser/trunk/jquery/MIT-LICENSE.txt) licenses. */ + +if (datepicker == undefined){ + var datepicker_fn = $.fn.datepicker + var datepicker = $.datepicker +} +(function($, undefined ) { + + //overriding functions meant to be private (starting with an underscore) + $.ui_datepicker = datepicker + $.fn.ui_datepicker = datepicker_fn + $.ui_datepicker._updateDatepicker_MonthYearPicker = $.ui_datepicker._updateDatepicker; + $.ui_datepicker._showDatepicker_MonthYearPicker = $.ui_datepicker._showDatepicker; + $.ui_datepicker._doKeyDown_MonthYearPicker = $.ui_datepicker._doKeyDown; + + $.extend($.ui_datepicker, { + _doKeyDown: function(event) { + var inst = $.ui_datepicker._getInst(event.target); + var handled = true; + //var isRTL = inst.dpDiv.is('.ui-datepicker-rtl'); + inst._keyEvent = true; + if ($.ui_datepicker._datepickerShowing) { + switch (event.keyCode) { + case 27: + if($('.ui-datepicker-select-month').is(':visible')) { + $.ui_datepicker._updateDatepicker(inst); + } + else if($('.ui-datepicker-select-year').is(':visible')) { + $.ui_datepicker._toggleDisplay_MonthYearPicker(inst, 2, this); + } + else { + $.ui_datepicker._hideDatepicker(); + } + break; // hide on escape + //TODO prev/next month/year on month/year picker screens + default: + //call the original function + $.ui_datepicker._doKeyDown_MonthYearPicker(event); + } + } + else { + //call the original function + $.ui_datepicker._doKeyDown_MonthYearPicker(event); + } + }, + + _updateDatepicker: function(inst) { + //call the original function + if (this._get(inst, 'show_view') == 'year' && !$('.ui-datepicker-select-year').is(':visible')){ + var current_format = this._get(inst, 'dateFormat') + var regex = /\W/, indice = 0; + var result = current_format.split(/\W/) + for (var i=0;i').append(this.dpDiv.clone()).html()); + + var uidptitle = inst.dpDiv.find('.ui-datepicker-title'); + var uidptitle_link = uidptitle.wrapInner(''); + uidptitle_link.click(function(){$.ui_datepicker.select_month = false;$.ui_datepicker._toggleDisplay_MonthYearPicker('#' + inst.id, 2); return false;}); + inst.dpDiv.children('table.ui-datepicker-calendar').after(this._generateExtraHTML_MonthYearPicker(inst)); + if (!$('.ui-datepicker-select-year').is(':visible') && !$('.ui-datepicker-select-month').is(':visible')){ + if (this._get(inst, 'show_view') == 'month') + { + this._toggleDisplay_MonthYearPicker('#' + inst.id,2) + }else if (this._get(inst, 'show_view') == 'year'){ + this._toggleDisplay_MonthYearPicker('#' + inst.id,3) + } + } + this._reposition_MonthYearPicker(inst); + }, + + //focus the date input field + _instInputFocus_MYP: function(inst) { + //code copied from datePicker's _updateDatepicker() + if (inst == $.ui_datepicker._curInst && $.ui_datepicker._datepickerShowing && inst.input && + // #6694 - don't focus the input if it's already focused + // this breaks the change event in IE + inst.input.is(':visible') && !inst.input.is(':disabled') && inst.input[0] != document.activeElement) + inst.input.focus(); + + }, + + _generateMonthPickerHTML_MonthYearPicker: function(inst, minDate, maxDate, drawMonth, inMinYear, inMaxYear) { + //TODO RTL? + var monthNamesShort = this._get(inst, 'monthNamesShort'); + + var monthPicker = ''; + + var unselectable = false; + for (var month = 0; month < 12; ) { + unselectable = (inMinYear && month < minDate.getMonth()) || + (inMaxYear && month > maxDate.getMonth()); + monthPicker += ''; // display selectable date + + if(++month % 4 === 0) { + monthPicker += ''; + if(month != 12) { + monthPicker += ''; + } + } + } + monthPicker += '
' + // actions + ((unselectable ? '' + monthNamesShort[month] + '' : '' + monthNamesShort[month] + '')) + '
'; + + return monthPicker; + }, + + _generateExtraHTML_MonthYearPicker: function(inst,show_view) { + if (show_view != 'year'){ + var minDate = this._getMinMaxDate(inst, 'min'); + var maxDate = this._getMinMaxDate(inst, 'max'); + var drawYear = inst.drawYear; + var drawMonth = inst.drawMonth; + var inMinYear = (minDate && minDate.getFullYear() == drawYear); + var inMaxYear = (maxDate && maxDate.getFullYear() == drawYear); + var monthPicker = this._generateMonthPickerHTML_MonthYearPicker(inst, minDate, maxDate, drawMonth, inMinYear, inMaxYear); + if (show_view != 'month'){ + return '' + + ''; //yearPicker gets filled dinamically + } + else{ + return '
' + monthPicker + '
' + + ''; + } + } + else{ + return '' + + '
'; + } + }, + + _pickMonthYear_MonthYearPicker: function(id, valueMY, period) { + var dummySelect = $(''; + + // Create the markup + for (i = 0, l = this.units.length; i < l; i++) { + litem = this.units[i]; + uitem = litem.substr(0, 1).toUpperCase() + litem.substr(1); + show = o['show' + uitem] !== null ? o['show' + uitem] : this.support[litem]; + + // Added by Peter Medeiros: + // - Figure out what the hour/minute/second max should be based on the step values. + // - Example: if stepMinute is 15, then minMax is 45. + max[litem] = parseInt((o[litem + 'Max'] - ((o[litem + 'Max'] - o[litem + 'Min']) % o['step' + uitem])), 10); + gridSize[litem] = 0; + + html += '
' + o[litem + 'Text'] + '
' + + '
'; + + if (show && o[litem + 'Grid'] > 0) { + html += '
'; + + if (litem === 'hour') { + for (var h = o[litem + 'Min']; h <= max[litem]; h += parseInt(o[litem + 'Grid'], 10)) { + gridSize[litem]++; + var tmph = $.ui_datepicker.formatTime(this.support.ampm ? 'hht' : 'HH', {hour: h}, o); + html += ''; + } + } + else { + for (var m = o[litem + 'Min']; m <= max[litem]; m += parseInt(o[litem + 'Grid'], 10)) { + gridSize[litem]++; + html += ''; + } + } + + html += '
' + tmph + '' + ((m < 10) ? '0' : '') + m + '
'; + } + html += '
'; + } + + // Timezone + var showTz = o.showTimezone !== null ? o.showTimezone : this.support.timezone; + html += '
' + o.timezoneText + '
'; + html += '
'; + + // Create the elements from string + html += ''; + var $tp = $(html); + + // if we only want time picker... + if (o.timeOnly === true) { + $tp.prepend('
' + '
' + o.timeOnlyTitle + '
' + '
'); + $dp.find('.ui-datepicker-header, .ui-datepicker-calendar').hide(); + } + + // add sliders, adjust grids, add events + for (i = 0, l = tp_inst.units.length; i < l; i++) { + litem = tp_inst.units[i]; + uitem = litem.substr(0, 1).toUpperCase() + litem.substr(1); + show = o['show' + uitem] !== null ? o['show' + uitem] : this.support[litem]; + + // add the slider + tp_inst[litem + '_slider'] = tp_inst.control.create(tp_inst, $tp.find('.ui_tpicker_' + litem + '_slider'), litem, tp_inst[litem], o[litem + 'Min'], max[litem], o['step' + uitem]); + + // adjust the grid and add click event + if (show && o[litem + 'Grid'] > 0) { + size = 100 * gridSize[litem] * o[litem + 'Grid'] / (max[litem] - o[litem + 'Min']); + $tp.find('.ui_tpicker_' + litem + ' table').css({ + width: size + "%", + marginLeft: o.isRTL ? '0' : ((size / (-2 * gridSize[litem])) + "%"), + marginRight: o.isRTL ? ((size / (-2 * gridSize[litem])) + "%") : '0', + borderCollapse: 'collapse' + }).find("td").click(function (e) { + var $t = $(this), + h = $t.html(), + n = parseInt(h.replace(/[^0-9]/g), 10), + ap = h.replace(/[^apm]/ig), + f = $t.data('for'); // loses scope, so we use data-for + + if (f === 'hour') { + if (ap.indexOf('p') !== -1 && n < 12) { + n += 12; + } + else { + if (ap.indexOf('a') !== -1 && n === 12) { + n = 0; + } + } + } + + tp_inst.control.value(tp_inst, tp_inst[f + '_slider'], litem, n); + + tp_inst._onTimeChange(); + tp_inst._onSelectHandler(); + }).css({ + cursor: 'pointer', + width: (100 / gridSize[litem]) + '%', + textAlign: 'center', + overflow: 'hidden' + }); + } // end if grid > 0 + } // end for loop + + // Add timezone options + this.timezone_select = $tp.find('.ui_tpicker_timezone').append('').find("select"); + $.fn.append.apply(this.timezone_select, + $.map(o.timezoneList, function (val, idx) { + return $("
#{trans} #{active ? arrow : ""}" + + "#{th_data}".html_safe + end + +end diff --git a/app/controllers/greeting_cards_controller.rb b/app/controllers/greeting_cards_controller.rb new file mode 100644 index 0000000..43dc883 --- /dev/null +++ b/app/controllers/greeting_cards_controller.rb @@ -0,0 +1,703 @@ +class GreetingCardsController < ApplicationController + helper Admin::GreetingCardsHelper + include Admin::GreetingCardsHelper + include ActionView::Helpers::FormHelper + include ActionView::Helpers::FormOptionsHelper + helper ActionView::Helpers::UrlHelper + include ActionController::RequestForgeryProtection + include RuCaptcha::ViewHelpers + FrontendMethods = ["thank", "sorry", "see_email"] + def widget + subpart = OrbitHelper.get_current_widget + case subpart.widget_type + when /.*_form/ + read_more_page = Page.where(id: subpart.read_more_page_id).first || Page.where(:module => 'greeting_card').first + referer_url = read_more_page ? "/#{I18n.locale}#{read_more_page.url}" : "/#{I18n.locale}/greeting_cards" + module_app = ModuleApp.where(:key => "greeting_card").first + if OrbitHelper.widget_categories.include?('all') + categories = module_app.categories.enabled + else + categories = Category.where(:id.in=>OrbitHelper.widget_categories).enabled + end + unless Category.respond_to?(:sorted) + if (module_app.asc rescue true) + categories = categories.sort_by{|category| (category.sort_number.to_i rescue category.id)} + else + categories = categories.sort_by{|category| (-category.sort_number.to_i rescue category.id)} + end + end + tags = module_app.tags + greeting_card_record = GreetingCardRecord.new + locale = I18n.locale + category_ids = categories.collect{|v| v.id.to_s} + default_setting = GreetingCardSetting.first + default_setting = GreetingCardSetting.create() if default_setting.nil? + greeting_card_settings_map = GreetingCardCategorySetting.where(:category_id.in=> category_ids).map{|s| [s.category_id.to_s, s]}.to_h + greeting_card_settings = category_ids.map do |category_id| + if greeting_card_settings_map.has_key?(category_id) + greeting_card_settings_map[category_id] + else + default_setting + end + end + tmp_ + first_category_id = category_ids.first + greeting_card_setting = greeting_card_settings[0] + @default_greeting_card_setting = default_setting + all_fields = cal_form_from_setting(greeting_card_setting,categories) + request = OrbitHelper.request + csrf_value = OrbitHelper.request.session[:_csrf_token] || SecureRandom.base64(32) + token_tag = hidden_field_tag('authenticity_token',csrf_value) + switch_form = cal_switch_form(greeting_card_settings,categories,token_tag,referer_url, OrbitHelper.params[:id]) + email_regex = @default_greeting_card_setting.get_email_regex(true) + { + 'fields' => all_fields, + 'extras'=>{ + 'switch_form' => switch_form, + 'email_regex' => email_regex + } + } + else + data_count = OrbitHelper.widget_data_count.to_i + if data_count != 0 + greeting_card_records = GreetingCardRecord.where(situation: 'is_published').page(1).per(data_count) + else + greeting_card_records = GreetingCardRecord.where(situation: 'is_published') + end + greeting_card_index_page = OrbitHelper.widget_more_url rescue nil + greeting_card_records = greeting_card_records.collect do |v| + url = ((greeting_card_index_page+"?item=#{v.id}") rescue "javascript:alert('#{t('greeting_card.no_index_page')}')") + {'td'=> [ + {'content' =>v.category.title}, + {'content' => link_to(v.title,"#{url}",title: v.title)} + ] + } + end + { + 'greeting_card_records' => greeting_card_records, + 'th' => [{'td'=>t('categories')},{'td'=>t('title')}], + 'extras' => {'more_title'=>I18n.t('greeting_card.more_title'), + 'more_href'=>greeting_card_index_page} + } + end + end + def get_layout_type + @params = OrbitHelper.params + page = Page.where(url:@params['url']).first + @layout_type = 'index' + if page.methods.include? 'select_option_items'.to_sym + ModuleApp.all.select{|tmp| tmp.key.to_s=='greeting_card'}.each do |modile_app| + @show_option_items = modile_app.show_option_items rescue nil + end + page.select_option_items.each do |select_option_item| + if !(@show_option_items.nil?) && select_option_item.field_name == @show_option_items.keys.first.to_s + value = YAML.load(select_option_item.value) + I18n.with_locale(:en) do + if value[locale] == t('greeting_card.widget.index') + @layout_type = 'index' + elsif value[locale] == t('greeting_card.is_published') + @layout_type = 'published_index' + end + end + end + end + end + end + def initialize + super + @app_title = 'greeting_card' + self.request = OrbitHelper.request + end + def show + greeting_card_record = GreetingCardRecord.where(id: @params['item']).first + greeting_card_setting = GreetingCardCategorySetting.enabled.where(category_id: greeting_card_record.category_id.to_s).first + greeting_card_setting = GreetingCardSetting.first if greeting_card_setting.nil? + {'greeting_card_record' => greeting_card_record,'layout_type'=>'show','greeting_card_setting'=>greeting_card_setting} + end + def index + @params = OrbitHelper.params + referer_url = OrbitHelper.request.path + if @params['item'].to_s.empty? + get_layout_type + else + @layout_type = 'show' + end + if @layout_type == 'index' + module_app = ModuleApp.where(:key => "greeting_card").first + if OrbitHelper.page_categories.include? 'all' + categories = module_app.categories.enabled + else + categories = Category.where(:id.in=>OrbitHelper.page_categories).enabled + end + unless Category.respond_to?(:sorted) + if (module_app.asc rescue true) + categories = categories.sort_by{|category| (category.sort_number.to_i rescue category.id)} + else + categories = categories.sort_by{|category| (-category.sort_number.to_i rescue category.id)} + end + end + category_ids = categories.collect{|v| v.id.to_s} + default_setting = GreetingCardSetting.first + default_setting = GreetingCardSetting.create() if default_setting.nil? + greeting_card_settings_map = GreetingCardCategorySetting.where(:category_id.in=> category_ids).map{|s| [s.category_id.to_s, s]}.to_h + greeting_card_settings = category_ids.map do |category_id| + if greeting_card_settings_map.has_key?(category_id) + greeting_card_settings_map[category_id] + else + default_setting + end + end + first_category_id = category_ids.first + greeting_card_setting = greeting_card_settings[0] + @default_greeting_card_setting = default_setting + csrf_value = OrbitHelper.request.session[:_csrf_token] || SecureRandom.base64(32) + token_tag = hidden_field_tag('authenticity_token',csrf_value) + switch_form = cal_switch_form(greeting_card_settings,categories,token_tag,referer_url, @params[:id]) + tags = module_app.tags + greeting_card_record = GreetingCardRecord.new(id: nil) + email_regex = @default_greeting_card_setting.get_email_regex(true) + { + 'layout_type' => 'index', + 'greeting_card_record' => greeting_card_record, + 'categories' => categories, + 'tags' => tags, + 'module_app' => module_app, + 'switch_form' => switch_form, + 'greeting_card_setting' => greeting_card_setting, + 'default_greeting_card_setting' => @default_greeting_card_setting, + 'email_regex' => email_regex + } + elsif @layout_type == 'published_index' + page_number = OrbitHelper.page_number.to_i + page_number = 1 if page_number == 0 + page_data_count = OrbitHelper.page_data_count.to_i + if page_data_count != 0 + greeting_card_records = GreetingCardRecord.where(situation: 'is_published').page(page_number).per(page_data_count) + else + greeting_card_records = GreetingCardRecord.where(situation: 'is_published') + end + { + 'layout_type' => 'published_index', + 'greeting_card_records' => greeting_card_records, + 'url' => @params['url'] + } + else + show + end + end + + def create + if !params[:referer_url].blank? && !params[:referer_url].to_s.start_with?("/") + render :file => "#{Rails.root}/app/views/errors/403.html", :layout => false, :status => 403 and return + end + temp_params = create_params + if temp_params[:id].present? + @greeting_card_record = GreetingCardRecord.where(id: temp_params[:id]).first + if @greeting_card_record.nil? + render_404 and return + end + else + @greeting_card_record = GreetingCardRecord.new(:ip=>request.remote_ip) + end + new_record = @greeting_card_record.new_record? + all_to_save = [] + greeting_card_setting = GreetingCardCategorySetting.enabled.where(category_id: params['greeting_card_record']['category_id']).first + override_sort_number = nil + if greeting_card_setting && greeting_card_setting.use_default + override_sort_number = greeting_card_setting.default_sort_number + greeting_card_setting = nil + end + if greeting_card_setting.nil? + greeting_card_setting = GreetingCardSetting.first + greeting_card_setting = GreetingCardSetting.create() if greeting_card_setting.nil? + end + @greeting_card_setting = greeting_card_setting + @must_verify_email = greeting_card_setting.must_verify_email && new_record + if @must_verify_email + only_email = true + @greeting_card_record.is_hidden = true + else + only_email = false + check_fields = greeting_card_setting.default_setting.select{|k,v| v}.keys & greeting_card_setting.default_setting_required.select{|k,v| v}.keys - GreetingCardSetting::No_required + unless new_record + @greeting_card_record.is_hidden = false + check_fields.delete('mail') + end + flag = true + check_fields.each do |f| + next if f == 'greeting_card_category_id' || f == 'recaptcha' + if temp_params[f].blank? + puts "field = #{f} is empty" + flag = false + break + end + end + end + custom_values = temp_params[:custom_values] + custom_values = {} if custom_values.nil? + if flag + check_custom_fields = greeting_card_setting.custom_fields.select{|k,v| v["required"] == 'true' } + if check_custom_fields.count != 0 + check_custom_fields.each do |k,v| + next if v['type'] == 'instructions' + tmp = custom_values[k] + v_type = v['type'] + is_date = false + if v_type == "checkbox" + tmp = tmp.values rescue [] + elsif v_type == 'date' + is_date = true + tmp = tmp["datetime"].values[0].values rescue [] + end + if tmp.present? + if is_date && v["range_flag"] == 'true' + if (tmp.class != Array) || (tmp.select{|v| v.blank?}.count != 0) + flag = false + break + end + end + else + flag = false + break + end + end + end + end + if !flag && !only_email + redirect_to "#{params[:referer_url]}?method=sorry" and return + end + email_regex = greeting_card_setting.get_email_regex + unless only_email + greeting_card_setting.custom_fields.select{|k,v| v['type']=='file' || v['type']=='image'}.each do |k,v| + file = custom_values[k] + if !file.blank? + if v['type']=='image' + all_to_save += [[k,GreetingCardImage.new(file: file,greeting_card_record_id: @greeting_card_record.id)]] + else + all_to_save += [[k,GreetingCardFile.new(file: file,greeting_card_record_id: @greeting_card_record.id)]] + end + end + end + end + flag = !greeting_card_setting.default_setting['recaptcha'] || gotcha_valid? + if flag && !only_email + all_to_save.each do |to_save| + flag = flag && to_save[1].save + custom_values[to_save[0]] = [custom_values[to_save[0]].original_filename ,to_save[1].file.url] + end + end + if (email_regex ? ::Regexp.new(email_regex).match(temp_params["mail"].to_s) : true) && flag + @disp_fields_infos = GreetingCardSetting.get_disp_fields_infos(true, greeting_card_setting, true, override_sort_number, true) + @usage_rule = greeting_card_setting.usage_rule + if temp_params['mail'].blank? + temp_params = temp_params.merge({ + situation: GreetingCardTicketStatus::DefaultKeys[1] + }) + end + @greeting_card_record.update_attributes(temp_params) + if @must_verify_email + referer_url = "#{params[:referer_url]}?id=#{@greeting_card_record.id}" + else + referer_url = nil + end + if (greeting_card_setting.validate_enable || @must_verify_email) && GreetingCardSafeEmail.where(:email=> @greeting_card_record.sender_mail).count == 0 + build_verification_email(@greeting_card_record, referer_url) + elsif @must_verify_email + redirect_to referer_url and return + else + @greeting_card_record.update(:situation => GreetingCardTicketStatus::DefaultKeys[1]) + build_email(@greeting_card_record) + end + if @must_verify_email + redirect_to "#{params[:referer_url]}?method=see_email" + else + redirect_to "#{params[:referer_url]}?method=thank" + end + else + redirect_to "#{params[:referer_url]}?method=sorry" + end + end + + def thank + acknowledgement = GreetingCardAcknowledgement.last + { + "acknowledgement" => acknowledgement + } + end + + def sorry + {} + end + + def see_email + end + + def render_404 + render :file => "#{Rails.root}/app/views/errors/404", :layout => false, :status => :not_found, :formats => [:html] + end + + def verify_email + @greeting_card_record = GreetingCardRecord.where(:id=>params[:id]).first + if @greeting_card_record.nil? + render_404 and return + elsif @greeting_card_record.situation != GreetingCardTicketStatus::DefaultKeys[0] + @already_verify = true + else + @already_verify = false + @greeting_card_record.update(:situation => GreetingCardTicketStatus::DefaultKeys[1]) + GreetingCardSafeEmail.create(:email=> @greeting_card_record.sender_mail) + build_email(@greeting_card_record) + end + end + + def build_verification_email(email_er, referer_url) + email = Email.new + email.save + email_er.email_id = email.id + email_er.save + + group_mail = email_er.sender_mail + manager_emails = email_er.reviewer_emails + mail_sentdate = DateTime.now + + site = current_site rescue Site.first + mail_from = site.title_translations[site.default_locale] + host_url = Site.first.root_url rescue "http://" + if host_url == "http://" + host_url = request.protocol + request.host_with_port + end + verify_url = "#{host_url}/#{I18n.locale}/xhr/greeting_cards/verify_email/#{email_er.id}" + verify_link = "#{verify_url}" + if referer_url + referer_url = host_url + referer_url + referer_link = "#{referer_url}" + else + referer_link = nil + end + mail_subject = mail_from+": #{t('greeting_card.email_verification_notification')}" + email_er.email.update_attributes( + :mail_lang=> site.default_locale, + :create_user=>(current_user rescue nil), + :mail_sentdate=>mail_sentdate, + :module_app=>@module_app, + :mail_to=>group_mail, + :mail_subject=>mail_subject, + :template=>'greeting_cards/email_verification', + :template_data=>{ + "validation_email_content" => @greeting_card_setting.validation_email_content, + "verify_link" => verify_link, + "referer_link" => referer_link, + "site_host" => host_url + }, + :mail_reply_to => (manager_emails.empty? ? nil : manager_emails) + ) + # email_er.email.deliver + end + + def build_email(email_er) + site = current_site rescue Site.first + host_url = Site.first.root_url rescue "http://" + if host_url == "http://" + host_url = request.protocol + request.host_with_port + end + locale = site.default_locale + email_er.build_email(site, host_url, current_user, @module_app, locale) + end + + def create_params + params.require(:greeting_card_record).permit! + end + private + def cal_card_html(greeting_card_images) + field_name = "greeting_card_record[card]" + greeting_card_images_html = "
" + greeting_card_images.each do |image| + img_url = image.file.url + next if img_url.blank? + greeting_card_images_html += "
" + end + greeting_card_images_html += "
" + greeting_card_images_html + end + def cal_form_from_setting(greeting_card_setting,categories,show_categories=false,filter_fields=nil) + is_cat_record = (greeting_card_setting.class == GreetingCardCategorySetting) + override_sort_number = nil + greeting_card_images = [] + if is_cat_record + category_id = greeting_card_setting.category_id + greeting_card_images = @default_greeting_card_setting.greeting_card_images.to_a + greeting_card_setting.greeting_card_images.to_a + if greeting_card_setting.use_default + override_sort_number = greeting_card_setting.default_sort_number + if override_sort_number.blank? + override_sort_number = nil + elsif @default_fields + all_fields = @default_fields.sort_by{|h| override_sort_number[h['field']].to_i} + all_fields.each do |f1| + if f1['field'] == 'card' + f1['content'] = cal_card_html(greeting_card_images) + end + end + return all_fields + end + greeting_card_setting = @default_greeting_card_setting + is_cat_record = false + elsif greeting_card_setting.default_sort_number.blank? + greeting_card_setting = @default_greeting_card_setting + is_cat_record = false + end + else + category_id = (categories[0].id.to_s rescue '') + greeting_card_images = greeting_card_setting.greeting_card_images.to_a + end + tmp_greeting_card_cat_id = (is_cat_record ? greeting_card_setting.id.to_s : '') + disp_fields_infos = GreetingCardSetting.get_disp_fields_infos(true, greeting_card_setting, true, override_sort_number, true) + if filter_fields + disp_fields_infos = disp_fields_infos.select{|field, info| filter_fields.include?(field)} + end + # tmp = 'mongoid.attributes.greeting_card_record' + all_fields = [] + set_input_name_for_greeting_card("greeting_card_record") + has_greeting_card_category_id = false + if greeting_card_setting.must_verify_email && @greeting_card_record.nil? + field = "mail" + field_name = "greeting_card_record[#{field}]" + id = field_name + required = true + placeholder = @default_greeting_card_setting.prompt_word(field) + f1 = {'style_html'=>''} + f1['content'] = text_field_tag(field_name,nil,data: (required ? {"fv-validation" => "required;check_email;", "fv-messages" => "必填欄位;Email不正確;"} : {}),required: required,placeholder: placeholder) + f1['field'] = field + f1['label'] = greeting_card_label(id, @default_greeting_card_setting.field_name("#{field}"),required.to_s) + all_fields << f1 + + field = "recaptcha" + id = "greeting_card_rucaptcha" + f1 = {'style_html'=>''} + f1['content'] = "#{gotcha_error(:espeak=>true)}#{gotcha(id: id)}" + f1['field'] = field + f1['label'] = greeting_card_label(id, @default_greeting_card_setting.field_name("#{field}"),required.to_s) + all_fields << f1 + + all_fields << {'field'=>'must_verify_email','style_html'=>'display: block; text-align: center;','label'=>'','content'=> I18n.t('greeting_card.must_verify_email')} + else + disp_fields_infos.each do |field, info| + if field.start_with?("default@") + k = is_cat_record ? field : field[8..-1] + v = greeting_card_setting.custom_fields[k] + if v + field_name = info['trans'] + if field_name.present? + id = "#{get_input_name_for_greeting_card}[custom_values][#{k}]" + all_fields << {'field'=>field,'label'=>greeting_card_label(id, field_name, v['required']),'content'=>show_on_front(k,v)} + end + end + elsif field.start_with?("custom@") && is_cat_record + if field.include?(tmp_greeting_card_cat_id) + k = field[8+tmp_greeting_card_cat_id.length..-1] + v = greeting_card_setting.custom_fields[k] + if v + field_name = info['trans'] + if field_name.present? + id = "#{get_input_name_for_greeting_card}[custom_values][#{k}]" + all_fields << {'field'=>field,'label'=>greeting_card_label(id, v['field'][I18n.locale],v['required']),'content'=>show_on_front(k,v)} + end + end + end + else + if field == 'greeting_card_category_id' + has_greeting_card_category_id = true + if show_categories || greeting_card_setting.default_setting[field] + if categories.count > 1 + id = "greeting_card_record[category_id]" + all_fields << {'field'=>field,'style_html'=>'','label'=>greeting_card_label(id, @default_greeting_card_setting.field_name("greeting_card_category_id"),'true'),'content'=>select_tag('greeting_card_record[category_id]', options_for_select(categories.collect{|t| [ t.title, t.id ]}))} + else + all_fields << {'field'=>field,'style_html'=>'','label'=>greeting_card_label(nil, @default_greeting_card_setting.field_name("greeting_card_category_id"),'true'),'content'=>"#{(categories[0].title rescue '')}"+hidden_field_tag('greeting_card_record[category_id]', (categories[0].id.to_s rescue ''))} + end + else + all_fields << {'field'=>field,'style_html'=>'display: none;','label'=>'','content'=>hidden_field_tag('greeting_card_record[category_id]', (categories[0].id.to_s rescue ''))} + end + else + if greeting_card_setting.default_setting[field] || field == "usage_rule" + required = greeting_card_setting.is_required(field) + f1 = {'style_html'=>''} + field_name = "greeting_card_record[#{field}]" + placeholder = @default_greeting_card_setting.prompt_word(field) + id = field_name + case field + when 'title' + f1['content'] = text_field_tag(field_name,nil,placeholder: placeholder,required: required) + when 'name' + f1['content'] = text_field_tag(field_name,nil,data: (required ? {"fv-validation" => "required;", "fv-messages" => "必填欄位;"} : {}),required: required,placeholder: placeholder) + when 'sender_mail', 'recipients' + if @greeting_card_record + f1['content'] = @greeting_card_record.send(field) + else + f1['content'] = text_field_tag(field_name,nil,data: (required ? {"fv-validation" => "required;check_email;", "fv-messages" => "必填欄位;Email不正確;"} : {}),required: required,placeholder: placeholder) + end + when 'greetings' + f1['content'] = text_field_tag(field_name,nil,placeholder: placeholder,required: required) + when 'card' + f1['content'] = cal_card_html(greeting_card_images) + f1['inner_style_html'] = "max-width: unset; width: 76%;" + when 'sending_time' + f1['content'] = "
#{text_field_tag(field_name,nil,placeholder: placeholder,data: {format: 'yyyy/MM/dd hh:mm'},required: required)}
" + when 'recaptcha' + id = "greeting_card_rucaptcha" + f1['content'] = "#{gotcha_error(:espeak=>true)}#{gotcha(id: id)}" + when 'usage_rule' + next if greeting_card_setting.usage_rule.blank? + f1['content'] = greeting_card_setting.usage_rule + when 'agree_show' + f1['content'] = check_box_tag(field_name, "1", false, {required: required}) + when 'agree_usage' + f1['content'] = check_box_tag(field_name, "1", false, {required: required}) + end + f1['field'] = field + f1['label'] = greeting_card_label(id, @default_greeting_card_setting.field_name("#{field}"),required.to_s) + all_fields << f1 + end + end + end + end + if @greeting_card_record + all_fields << {'field'=>'greeting_card_record_id','style_html'=>'display: none;','label'=>'','content'=>hidden_field_tag('greeting_card_record[id]', @greeting_card_record.id)} + end + end + unless has_greeting_card_category_id + all_fields << {'field'=>'greeting_card_category_id','style_html'=>'display: none;','label'=>'','content'=>hidden_field_tag('greeting_card_record[category_id]', category_id)} + end + unless is_cat_record + @default_fields = all_fields + end + all_fields + end + def cal_html(fields,token_tag,form_id,referer_url,greeting_card_setting,is_hidden=true) + form_url = "/#{I18n.locale.to_s}/greeting_cards" + submit_tag = submit_tag(t('submit'), :class=> 'btn btn-primary', :id => 'button-mail') + close_tag = button_tag(t('cancel'), type: 'reset', :class=> 'btn') + multi_col_class = (greeting_card_setting.title_layout>0 rescue false) ? ' multi-col' : ' single-col' + col_class = (greeting_card_setting.title_layout==1 rescue false) ? ' col-sm-6' : '' + tmp = fields.collect do |field| + style_html = field['style_html'] + inner_style_html = field['inner_style_html'] + label = field['label'] + content = field['content'] + "
+ #{label} +
+ #{content} +
+
" + end.join + preview_btn = "" + preview_section = "
" + "
+ #{token_tag} +
+ #{tmp} + #{preview_btn} + #{preview_section} +
+
+ + #{submit_tag} + #{close_tag} +
+
" + end + def script_text + "" + end + def cal_switch_form(greeting_card_settings,categories,token_tag,referer_url, greeting_card_record_id) + switch_form = '' + default_idx = 0 + if greeting_card_record_id.present? + @greeting_card_record = GreetingCardRecord.where(:id=> greeting_card_record_id).first + if @greeting_card_record && !(@greeting_card_record.is_hidden) + @greeting_card_record = nil + end + if @greeting_card_record + GreetingCardSafeEmail.create(:email=> @greeting_card_record.sender_mail) if GreetingCardSafeEmail.where(:email=> @greeting_card_record.sender_mail).count == 0 + greeting_card_settings = GreetingCardCategorySetting.enabled.where(:category_id=> @greeting_card_record.category_id).to_a + if greeting_card_settings.blank? + return switch_form + end + end + else + @greeting_card_record = nil + end + if categories.count != 0 + default_idx = greeting_card_settings[1..-1].to_a.map{|a| a.category_id.to_s}.index(categories[0].id.to_s) + default_idx = default_idx.nil? ? 0 : default_idx + 1 + end + @default_greeting_card_setting ||= GreetingCardSetting.first + collected_fields_var = "var collected_fields = " + collected_fields = [] + img_objs_var = "var card_img_objs = " + img_objs = [] + default_layout_design = GreetingCardLayoutDesign.where(:category_id=> nil).first + switch_form = script_text + greeting_card_settings.collect.with_index do |greeting_card_setting,i| + category_id = categories[i].id.to_s + layout_design = GreetingCardLayoutDesign.where(:category_id=> category_id).first + if layout_design.nil? + if default_layout_design.nil? + next + else + layout_design = default_layout_design + end + end + collected_fields << layout_design.preserved_keys + img_objs << layout_design.img_objs.values + fields = cal_form_from_setting(greeting_card_setting,categories,true) + is_hidden = (i != default_idx) + cal_html(fields,token_tag,category_id,referer_url,greeting_card_setting,is_hidden) + end.join + collected_fields_var += collected_fields.to_json + img_objs_var += img_objs.to_json + img_objs_var = "" + switch_form + img_objs_var + end +end \ No newline at end of file diff --git a/app/helpers/admin/greeting_cards_helper.rb b/app/helpers/admin/greeting_cards_helper.rb new file mode 100644 index 0000000..e2fa7f1 --- /dev/null +++ b/app/helpers/admin/greeting_cards_helper.rb @@ -0,0 +1,478 @@ +module Admin::GreetingCardsHelper + extend self + extend ActionView::Helpers::FormTagHelper + extend ActionView::Helpers::FormOptionsHelper + extend ActionView::Helpers::DateHelper + extend ActionView::Helpers::TagHelper + extend ActionView::Helpers::RenderingHelper + extend ActionView::Context + extend OrbitBasis::RenderAnywhere + extend ActionView::Helpers::UrlHelper + extend OrbitFormHelper + extend Ckeditor::Helpers::FormHelper + def get_categories_info_for_greeting_card + current_user = OrbitHelper.current_user + OrbitHelper.set_params(params,current_user) + OrbitHelper.set_this_module_app("greeting_card") + cats_relations = [] + access_level = OrbitHelper.user_access_level? + include_all = false + cats = [] + if access_level == "sub_manager" + cats = current_user.approved_categories_for_module(@module_app) + elsif access_level && access_level != "user" + cats = @module_app.categories.enabled + include_all = true + end + if (@module_app.asc rescue true) + cats = cats.sort_by{|category| (category.sort_number.to_i rescue category.id) } + else + cats = cats.sort_by{|category| (-category.sort_number.to_i rescue category.id)} + end + cats_relations = cats.map{|c| [c.id.to_s, c.title]} + if include_all && cats_relations.count != 1 + cats_relations.insert(0, ["",t(:all)]) + end + cats_relations + end + def set_input_name_for_greeting_card(input_name) + @input_name = input_name + end + def get_input_name_for_greeting_card + @input_name + end + def create_lang_panel_for_greeting_card(field) + tmp2 = content_tag(:div,:class => 'btn-group', :data=>{:toggle=>"buttons-radio"}) do + I18n.available_locales.collect do |key| + link_entry_ary = ["##{field}", "_#{key}", @field_postfix] + link_entry = link_entry_ary.join + link_to(I18n.t(key),link_entry,:data=>{:toggle=>"tab"},:class=>"btn #{(key == I18n.locale ? "active" : nil)}",:for=>key) + end.join.html_safe + end + end + def multiple_lang_show_tag_for_greeting_card(field_name, index1,field,value=nil,combine_element='',exteral_options={},sortable=false, extra_tr_class="",only_contents=false) + if !index1.nil? + all_field = (get_input_name_for_greeting_card + "[#{index1}][#{field}][parant]").gsub(/[\[@]/,'_').gsub(/\]/,'') + else + all_field = (get_input_name_for_greeting_card + "[#{field}][parant]").gsub(/[\[@]/,'_').gsub(/\]/,'') + end + if @field_postfix.nil? + @field_postfix = 1 + else + @field_postfix += 1 + end + tmp = (I18n.available_locales.collect do |locale| + active_flag = ((locale == I18n.locale) ? ' active' : '') + content_tag(:div,:class => "tab-content#{active_flag}",:id=>"#{all_field}_#{locale}#{@field_postfix}") do + value_locale = value[locale.to_s] rescue nil + "
#{value_locale}
".html_safe + end + end.join + create_lang_panel_for_greeting_card(all_field)).html_safe + combine_element + + if sortable + if exteral_options['style'].nil? + exteral_options['style'] = 'display: flex;align-items: center;flex-wrap: nowrap;' + else + exteral_options['style'] = exteral_options['style'] + 'display: flex;align-items: center;flex-wrap: nowrap;' + end + tmp = content_tag(:div,{:class => "tab-panel border"}.merge(exteral_options)) do + ("" +content_tag(:div) do + tmp + end).html_safe + end + else + tmp = content_tag(:div,{:class => "tab-panel"}.merge(exteral_options)) do + tmp + end + end + if only_contents + tmp + else + tmp = "#{field_name}".html_safe + ":".html_safe + tmp + "".html_safe + end + end + def multiple_lang_tag_for_greeting_card(index1,type_of_tag,field,value=nil,custom_options={},combine_element='',exteral_options={},sortable=false) + if !index1.nil? + all_field = (get_input_name_for_greeting_card + "[#{index1}][#{field}][parant]").gsub(/[\[@]/,'_').gsub(/\]/,'') + else + all_field = (get_input_name_for_greeting_card + "[#{field}][parant]").gsub(/[\[@]/,'_').gsub(/\]/,'') + end + if @field_postfix.nil? + @field_postfix = 1 + else + @field_postfix += 1 + end + tmp = (I18n.available_locales.collect do |locale| + active_flag = ((locale == I18n.locale) ? ' active' : '') + content_tag(:div,:class => "tab-content#{active_flag}",:id=>"#{all_field}_#{locale}#{@field_postfix}") do + value_locale = value[locale.to_s] rescue nil + if !index1.nil? + self.__send__("#{type_of_tag}_tag","#{get_input_name_for_greeting_card}[#{index1}][#{field}][#{locale}]",value_locale,custom_options) + else + self.__send__("#{type_of_tag}_tag","#{get_input_name_for_greeting_card}[#{field}][#{locale}]",value_locale,custom_options) + end + end + end.join + create_lang_panel_for_greeting_card(all_field)).html_safe + combine_element + + if sortable + if exteral_options['style'].nil? + exteral_options['style'] = 'display: flex;align-items: center;flex-wrap: nowrap;' + else + exteral_options['style'] = exteral_options['style'] + 'display: flex;align-items: center;flex-wrap: nowrap;' + end + content_tag(:div,{:class => "tab-panel border"}.merge(exteral_options)) do + ("" +content_tag(:div) do + tmp + end).html_safe + end + else + content_tag(:div,{:class => "tab-panel"}.merge(exteral_options)) do + tmp + end + end + end + def time_setting_block(key,value={}) + class_block = (value['type'] != 'date') ? "time_setting_block" : "time_setting_block active" + format_selected = (value['type'] != 'date') ? nil : value['format'] + range_selected = (value['type'] != 'date') ? nil : value['range_flag'] + options1 = [['YYYY / MM / DD HH:mm','format1'],['YYYY / MM / DD','format2'],['YYYY / MM','format3'],['YYYY','format4']] + options2 = [[t('yes'),'true'],[t('no'),'false']] + format_setting_tag = field_select_tag_for_greeting_card(key,'format',options1,format_selected) + range_setting_tag = field_select_tag_for_greeting_card(key,'range_flag',options2,range_selected) + "
+ + + + + + + + + +
#{t('greeting_card.format')}:#{format_setting_tag}
#{t('greeting_card.enable_range_setting')}:#{range_setting_tag}
+
" + end + def field_select_tag_for_greeting_card(index1,field,options,selected=nil,custom_options={}) + select_tag("#{get_input_name_for_greeting_card}[#{index1}][#{field}]",options_for_select(options,selected: selected),custom_options) + end + def field_radio_button_tag(index1,field,show_value,checked,custom_options={}) + radio_button_tag("#{get_input_name_for_greeting_card}[#{index1}][#{field}]",show_value,checked,custom_options) + end + def render_date_block(field_name,v,i,value, prefix_contents="") + case v['format'] + when 'format1' + t1 = text_field_tag("#{field_name}[datetime][date][#{i}]",(value['datetime']['date']["#{i}"] rescue nil),{:required => v['required']=='true',autocomplete: 'off',placeholder: t('greeting_card.datepicker'),title: t('greeting_card.datepicker')}) + t1 = prefix_contents + t1 + #t2_value = value['datetime']['h']["#{i}"] rescue nil + #t3_value = value['datetime']['m']["#{i}"] rescue nil + #t2 = select_tag("#{field_name}[datetime][h][#{i}]",options_for_select((1..24).collect{|v1| v1.to_s.rjust(2, "0")},selected: t2_value)) + #t3 = select_tag("#{field_name}[datetime][m][#{i}]",options_for_select((0..59).collect{|v1| v1.to_s.rjust(2, "0")},selected: t3_value)) + #t4 = "#{t2}:#{t3}" + "
#{t1}
+ ".html_safe + when 'format2' + t1 = text_field_tag("#{field_name}[datetime][date][#{i}]",(value['datetime']['date']["#{i}"] rescue nil),{:required => v['required']=='true',autocomplete: 'off',placeholder: t('greeting_card.datepicker'),title: t('greeting_card.datepicker')}) + t1 = prefix_contents + t1 + "
#{t1}
+ ".html_safe + when 'format3' + t1 = text_field_tag("#{field_name}[datetime][date][#{i}]",(value['datetime']['date']["#{i}"] rescue nil),{:required => v['required']=='true',autocomplete: 'off',placeholder: t('greeting_card.datepicker'),title: t('greeting_card.datepicker'),:class => 'yearpicker'}) + t1 = prefix_contents + t1 + "
#{t1}
+ + " + when 'format4' + t1 = text_field_tag("#{field_name}[datetime][date][#{i}]",(value['datetime']['date']["#{i}"] rescue nil),{:required => v['required']=='true',autocomplete: 'off',placeholder: t('greeting_card.datepicker'),title: t('greeting_card.datepicker'),:class => 'yearpicker'}) + t1 = prefix_contents + t1 + "
#{t1}
+ + " + end + end + def greeting_card_label(id, value, required='false') + label_tag(id,value,{:class=>"control-label#{required=='true' ? ' required' : ''}"}) + end + def format_checkbox(options,value,multple_choose=false) + options.select{|index1,option| option['disabled'] != 'true'}.collect do |index1,option| + if multple_choose + if value && value[index1]==index1 + "●#{option[I18n.locale]}" + else + "○#{option[I18n.locale]}" + end + else + if index1 == value + "●#{option[I18n.locale]}" + else + "○#{option[I18n.locale]}" + end + end + end.join('  ') + end + def show_on_front(k,v,object=nil,readonly=false,lock=false) + value = nil + if !object.nil? + value = object.custom_values[k] + if value.nil? + if k.start_with?("default@") + value = object.custom_values[k.sub(/.*@/,'')] + elsif k.start_with?("custom@") + category_id, k = k.scan(/custom@((?:(?!@).)*)@\w+/)[0][0] + if category_id==object.category_id.to_s + value = object.custom_values[k] + end + end + end + end + field_name = "#{get_input_name_for_greeting_card}[custom_values][#{k}]" + begin + case v['type'] + when 'text_field' + readonly ? value : text_field_tag(field_name,value,{:required => v['required']=='true',placeholder: v['prompt_word'][I18n.locale],title: v['prompt_word'][I18n.locale]}) + when 'instructions' + (v['instructions'][I18n.locale].html_safe rescue "") + when 'select' + prompt_hash = v['prompt_word'][I18n.locale].blank? ? {} : {prompt: v['prompt_word'][I18n.locale]} + prompt_hash.merge!(required: v['required']=='true') + options_hash = Hash(v['options']) + if lock + format_checkbox(options_hash,value) + else + if v['default_option'].present? + default_idx = v['default_option'].to_i + default_key = options_hash.keys[default_idx] + value ||= default_key + end + readonly ? (v['options'][value.to_s][I18n.locale] rescue '') : select_tag(field_name,options_for_select(options_hash.select{|index1,option| option['disabled'] != 'true'}.collect{|index1,option| [option[I18n.locale],index1]},selected: value),prompt_hash) + end + when 'date' + if value.nil? + value = {} + end + if v['range_flag']=='true' + if readonly + format_str = "%Y/%m/%d %H:%M" + case v['format'] + when 'format1' + format_str = "%Y/%m/%d %H:%M" + when 'format2' + format_str = "%Y/%m/%d" + when 'format3' + format_str = "%Y/%m" + when 'format4' + format_str = "%Y" + end + tmp = value['datetime']['date'] rescue {} + "#{DateTime.parse(tmp['0']).strftime(format_str) rescue ''}~#{DateTime.parse(tmp['1']).strftime(format_str) rescue ''}" + else + "
+ #{render_date_block(field_name,v,0,value, t('greeting_card.start') + ': ')} +
~
+ #{render_date_block(field_name,v,1,value, t('greeting_card.end') + ': ')} +
".html_safe + end + else + if readonly + format_str = "%Y/%m/%d %H:%M" + case v['format'] + when 'format1' + format_str = "%Y/%m/%d %H:%M" + when 'format2' + format_str = "%Y/%m/%d" + when 'format3' + format_str = "%Y/%m" + when 'format4' + format_str = "%Y" + end + tmp = value['datetime']['date'] rescue {} + (DateTime.parse(tmp['0']).strftime(format_str) rescue '') + else + render_date_block(field_name,v,0,value) + end + end + when 'text_area' + readonly ? value : text_area_tag(field_name,value,{:required => v['required']=='true',:placeholder=> v['prompt_word'][I18n.locale],:title=> v['prompt_word'][I18n.locale],:class=>'ckeditor'}) + when 'radio_button' + options_hash = Hash(v['options']) + if lock + format_checkbox(options_hash,value) + else + field_name_underscre = field_name.gsub(/(\[|\])/,'_').gsub('__','_').chomp('_') + default_idx = v['default_option'].to_i + value ||= options_hash.keys[default_idx] + readonly ? (options_hash[value.to_s][I18n.locale] rescue '') : options_hash.collect do |index1,option| + next if option['disabled'] == 'true' + "" + end.compact.join + end + when 'checkbox' + options_hash = Hash(v['options']) + if lock + format_checkbox(options_hash,value,true) + else + default_idx = v['default_option'].to_i + default_key = options_hash.keys[default_idx] + options_values = options_hash.values + value ||= {default_key=>default_key} + field_name_underscre = field_name.gsub(/(\[|\])/,'_').gsub('__','_').chomp('_') + readonly ? value.collect{|k1,v1| options_values[v1.to_i][I18n.locale]}.join(', ') : ("" + options_hash.collect do |index1,option| + next if option['disabled'] == 'true' + checkbox_option = option[I18n.locale] + "" + end.compact.join + "") + end + when 'file' + file_value = value[0] rescue nil + file_path = value[1] rescue nil + file_required = v['required']=='true' + readonly ? (file_path ? "#{file_value}" : "") : "
" + when 'image' + file_value = value[0] rescue nil + file_path = value[1] rescue nil + file_required = v['required']=='true' + readonly ? (file_value ? "\"#{file_value}\"" : "") : "
" + end + rescue => e + "
#{e.inspect}
#{e.backtrace.to_yaml}
" + end + end + def custom_field_block_for_greeting_card(k,v={},show_only=false, other_first_rows="",extra_class="") + # markups = LIST[:markups].select{|k,v| k != 'member_relationship' && k != 'address'}.collect{|key,val| [t("lists.markups."+key),key]} +[[t('greeting_card.file_field'),'file'],[t('greeting_card.image_field'),'image'],[t('greeting_card.instructions'),'instructions']] + allow_types = ['text_field'] + markups = LIST[:markups].select{|k,v| allow_types.include?(k)} + if show_only + multi_lang_tag = multiple_lang_show_tag_for_greeting_card(t('greeting_card.field_name'),k,'field',v['field']) + require_greeting_card_tag = "#{t('greeting_card.required')}:#{v['required']=='true' ? t('greeting_card.yes') : t('greeting_card.no')}" + markups = markups.map{|k,v| [v,k]}.to_h + tmp = markups[v['type']] + tmp = markups.values[0] if tmp.nil? + options_hash = Hash(v['options']) + tmp_field_select_tag_for_greeting_card = "#{t('greeting_card.setting_type')}:
#{tmp}
" + active_prompt_class = (v['type'] != 'instructions' ? '' : ' hide') + active_class = (['select','radio_button','checkbox'].include?(v['type']) ? '' : ' hide') + multi_lang_prompt_tag = multiple_lang_show_tag_for_greeting_card(t('greeting_card.prompt_word'),k,'prompt_word',v['prompt_word'],'',{},false, active_prompt_class) + multi_lang_default_option = multiple_lang_show_tag_for_greeting_card(t('greeting_card.default_option'),k,'default_option',v['default_option'].present? ? options_hash.values[v['default_option'].to_i] : I18n.t('greeting_card.please_select'),'',{},false, active_class) + options_area = options_hash.collect do |key,value| + "#{multiple_lang_show_tag_for_greeting_card("",k,"options][#{key}",value,'',{},false,"",true)}" + end.join + options_area = "#{I18n.t('greeting_card.options')}#{options_area}" + field_html = " + + + #{other_first_rows} + #{multi_lang_tag} + #{require_greeting_card_tag} + #{tmp_field_select_tag_for_greeting_card} + #{multi_lang_prompt_tag} + #{multi_lang_default_option} + #{options_area} + " + else + not_editable = k.to_s.include?('default@') + hint_text = "" + if not_editable + hint_text = "
" + t('greeting_card.this_field_is_default_custom_fields') + "
" + end + disabled_attr = (not_editable ? 'disabled' : nil) + multi_lang_tag = multiple_lang_tag_for_greeting_card(k,'text_field','field',v['field'],{placeholder: t('greeting_card.field_name'), title: t('greeting_card.field_name'), disabled: disabled_attr}) + tmp_field_select_tag_for_greeting_card = field_select_tag_for_greeting_card(k,'type',markups,v['type'],{:onchange=>'check_available_setting_block(this)', :disabled=>disabled_attr}) + key = hidden_field_tag "#{get_input_name_for_greeting_card}[#{k}][key]",k,{'class' => 'key'} + options_hash = Hash(v['options']) + all_new_options = options_hash.collect do |key,value| + tmp = create_delete_button_for_greeting_card('delete_added_select_option').html_safe+hidden_field_tag("#{get_input_name_for_greeting_card}[#{k}][options][#{key}][disabled]",value['disabled'],{'class' => 'disabled_field'}) + hidden_style = value['disabled'] == 'true' ? {style: 'display:none;'} : {} + "#{multiple_lang_tag_for_greeting_card(k,'text_field',"options][#{key}",value,{ placeholder: t('greeting_card.option_name'), class: 'option_name_field', title: t('greeting_card.option_name'), disabled: disabled_attr},tmp,hidden_style,true)}" + end.join + tmp_count = {} + options_hash.each{|k,v| v.each{|l,vv| tmp_count[l] = tmp_count[l].to_i + 1 if vv.present?}} + current_locale = I18n.locale.to_s + maximum_locale = tmp_count.sort_by{|k,v| [v, k == current_locale]}[0][0] rescue nil + maximum_locale = I18n.locale.to_s if maximum_locale.nil? + locale_options_for_field = options_hash.map.with_index{|(k, v), i| [v[maximum_locale], i] rescue ""} + is_selection_field = ['select','radio_button','checkbox'].include?(v['type']) + active_class = is_selection_field ? ' active' : '' + active_prompt_class = (v['type'] != 'instructions' ? ' active' : '') + multi_lang_prompt_tag = "
#{multiple_lang_tag_for_greeting_card(k,'text_field','prompt_word',v['prompt_word'],{ placeholder: t('greeting_card.prompt_word'), title: t('greeting_card.prompt_word'), disabled: disabled_attr})}
" + multi_lang_default_option = "
#{field_select_tag_for_greeting_card(k,'default_option',locale_options_for_field,v['default_option'],{ title: t('greeting_card.default_option'), class: 'default_option_select', disabled: disabled_attr, data: {locale: maximum_locale}, prompt: I18n.t('greeting_card.please_select')})}
" + instructions_text_area = "
#{multiple_lang_tag_for_greeting_card(k,'text_area','instructions',v['instructions'],{ placeholder: t('greeting_card.prompt_word'), title: t('greeting_card.prompt_word'), class: 'ckeditor', disabled: disabled_attr})}
" + require_greeting_card_tag = "
#{t('greeting_card.required')}:  #{field_radio_button_tag(k,'required','true',v['required']=='true',{disabled: disabled_attr})}#{t('greeting_card.yes')}#{field_radio_button_tag(k,'required','false',v['required']!='true',{disabled: disabled_attr})}#{t('greeting_card.no')}
" + tmp = "
#{all_new_options}
" + field_html = " + + #{other_first_rows} + #{hint_text} + #{multi_lang_tag} + #{require_greeting_card_tag} + #{tmp_field_select_tag_for_greeting_card}
+ #{multi_lang_prompt_tag} + #{multi_lang_default_option} + #{instructions_text_area} + #{time_setting_block(k,v)} + #{tmp} + #{key} + + + #{create_delete_button_for_greeting_card('delete_field_func')} + + " + end + end + def create_delete_button_for_greeting_card(func_name) + "" + end + def page_for_greeting_cardquestion(greeting_cardquestion) + ann_page = nil + pages = Page.where(:module=>'greeting_card') + + pages.each do |page| + if page.categories.count ==1 + if page.categories.include?(greeting_cardquestion.category.id.to_s) + ann_page = page + end + end + break if !ann_page.nil? + end + + if ann_page.nil? + pages.each do |page| + if page.categories.include?(greeting_cardquestion.category.id.to_s) + ann_page = page + end + break if !ann_page.nil? + end + end + + ann_page = pages.first if ann_page.nil? + request.protocol+(request.host_with_port+ann_page.url+'/'+greeting_cardquestion.to_param).gsub('//','/') rescue "/" + end +end diff --git a/app/mailers/.keep b/app/mailers/.keep new file mode 100644 index 0000000..e69de29 diff --git a/app/models/.keep b/app/models/.keep new file mode 100644 index 0000000..e69de29 diff --git a/app/models/concerns/greeting_card_setting_concern.rb b/app/models/concerns/greeting_card_setting_concern.rb new file mode 100644 index 0000000..1f60340 --- /dev/null +++ b/app/models/concerns/greeting_card_setting_concern.rb @@ -0,0 +1,99 @@ +module GreetingCardSettingConcern + extend ActiveSupport::Concern + + included do + self.field :email_regex_enable, type: Boolean, default: false + self.field :email_regex, type: String, default: '\A[^@\s]+@([^@.\s]+\.)+[^@.\s]+\z' + self.field :only_logged_in_users, type: Boolean, default: false + self.field :validate_enable, type: Boolean, default: false + self.field :must_verify_email, type: Boolean, default: false + self.field :validation_email_content, type: String, localize: true + + self.field :tmp_sort_number, type: Hash, default: {} # For Frontend + self.field :sort_number, type: Hash, default: {} + self.field :default_setting, type: Hash,default: {title:true,greeting_card_category_id: true,name: true,sender_mail: true,recipients: true,greetings: true,card: true,sending_time: true,recaptcha: true,agree_show: true,agree_usage: true} + self.field :default_setting_required, type: Hash,default: {title:true,greeting_card_category_id: true,name: true,sender_mail: true,recipients: true,greetings: true,card: true,sending_time: true,recaptcha: true,agree_show: false,agree_usage: true} + self.field :default_setting_field_name, type: Hash,default: {} + self.field :default_setting_prompt_word, type: Hash,default: {} + + self.field :custom_fields, type: Hash,default: {} + self.field :usage_rule, type: String, default: '' + self.field :title_layout, type: Integer, default: 0 + + self.has_many :greeting_card_images, :autosave => true, :dependent => :destroy + self.accepts_nested_attributes_for :greeting_card_images, :allow_destroy => true + + self.before_save do + unless @skip_callback + if self.sort_number_changed? + self.sort_number = self.sort_number.map{|k,v| [k,v.to_i]}.sort_by{|k,v| v}.to_h + end + @custom_fields_changed = self.custom_fields_changed? + if @custom_fields_changed + @delete_custom_fields = self.custom_fields.select{|k,v| v['delete'] == true}.keys + @delete_custom_fields.each{|f| self.sort_number.delete(f)} + self.custom_fields = self.custom_fields.select{|k,v| v['delete'] != true} + end + self.recalc_sort(false) + end + true + end + end + + def recalc_sort(save_flag=true) + self.tmp_sort_number = GreetingCardSetting.get_sort_number_only(true, self, true).map.with_index{|(k,v), i| [k,i]}.to_h + self.save if save_flag + end + + def get_cache_sort_number #For Frontend + if self.tmp_sort_number.blank? + self.recalc_sort + end + self.tmp_sort_number + end + + def default_field_name(k, locale=I18n.locale.to_s, use_checkbox_trans=false) + I18n.with_locale(locale) do + if use_checkbox_trans && ['agree_show','agree_usage'].include?(k.to_s) + I18n.t("greeting_card.#{k}_checkbox") + else + if ['serial_number','situation','ip'].include?(k) + I18n.t("greeting_card.#{k}") + else + I18n.t("mongoid.attributes.greeting_card_record.#{k}") + end + end + end + end + + def field_name(k, locale=I18n.locale.to_s, use_checkbox_trans=false) + tmp = self.default_setting_field_name[k] + (tmp && tmp[locale]) ? tmp[locale] : self.default_field_name(k, locale, use_checkbox_trans) + end + + def prompt_word(k,locale=I18n.locale.to_s,use_checkbox_trans=false) + tmp = self.default_setting_prompt_word[k] + (tmp&&tmp[locale]) ? tmp[locale] : self.field_name(k,locale) + end + + def field_name_translations(k) + I18n.available_locales.map{|v| [v.to_s,self.field_name(k,v.to_s)]}.to_h + end + + def prompt_word_translations(k) + I18n.available_locales.map{|v| [v.to_s,self.prompt_word(k,v.to_s)]}.to_h + end + + def is_required(k) + self.default_setting_required[k] + end + + def get_email_regex(frontend=false) + tmp = self.email_regex + if tmp && frontend + tmp = tmp.gsub("\\A","^").gsub("\\z","$").gsub("\\","\\\\\\\\") + end + tmp = nil if !(self.email_regex_enable) || ((self.default_setting[:mail] == false) rescue false) + tmp + end +end \ No newline at end of file diff --git a/app/models/greeting_card_acknowledgement.rb b/app/models/greeting_card_acknowledgement.rb new file mode 100644 index 0000000..fe0a68b --- /dev/null +++ b/app/models/greeting_card_acknowledgement.rb @@ -0,0 +1,6 @@ +class GreetingCardAcknowledgement + include Mongoid::Document + include Mongoid::Timestamps + + field :content, type: Hash,default:{} +end \ No newline at end of file diff --git a/app/models/greeting_card_admin.rb b/app/models/greeting_card_admin.rb new file mode 100644 index 0000000..5f66a0e --- /dev/null +++ b/app/models/greeting_card_admin.rb @@ -0,0 +1,7 @@ +class GreetingCardAdmin + include Mongoid::Document + include Mongoid::Timestamps + + field :email, type: String + has_and_belongs_to_many :categories +end \ No newline at end of file diff --git a/app/models/greeting_card_category_setting.rb b/app/models/greeting_card_category_setting.rb new file mode 100644 index 0000000..3ab5ba0 --- /dev/null +++ b/app/models/greeting_card_category_setting.rb @@ -0,0 +1,197 @@ +class GreetingCardCategorySetting + include Mongoid::Document + include Mongoid::Timestamps + include ::GreetingCardSettingConcern + + DateTypes = ['regular_date', 'birthday'] + + field :default_sort_number, type: Hash, default: {} + + field :automatically_send, type: Boolean, default: false + field :auto_send_image_id, type: BSON::ObjectId + field :auto_send_date_type, type: Integer, default: -1 + field :sender_mail, type: String + field :regular_date, type: String + field :recipient_ids, type: Array, default: [] + + field :need_check_customs, type: Array, default: [] #From GreetingCardSetting + field :reject_customs, type: Array, default: [] #From GreetingCardSetting + field :agree_customs, type: Array, default: [] #From GreetingCardSetting + + field :category_id, type: String + field :use_default, type: Boolean, default: false + + field :host_url + field :update_user_id + + scope :enabled, ->{any_of([{:use_default.ne=>true}, {:use_default=>true, :default_sort_number.nin=>[nil, {}]}])} + scope :custom_enabled, ->{where({:use_default.ne=>true})} + + before_save do + unless @skip_callback + if @custom_fields_changed + @delete_custom_fields = @delete_custom_fields.select{|k| k.include?("default@")} + self.delete_customs_func(@delete_custom_fields, true) + end + if self.automatically_send + if !self.automatically_send_was || self.auto_send_image_id_changed? || self.auto_send_date_type_changed? || self.sender_mail_changed? || self.regular_date_changed? + self.generate_records + end + end + true # prevent return false in before_save + end + end + def self.get_images(category_id) + greeting_card_setting = GreetingCardSetting.first + images = [] + if greeting_card_setting + images += greeting_card_setting.greeting_card_images.map{|img| img.file.url} + end + if category_id + greeting_card_setting2 = self.where(:category_id=> category_id).first + if greeting_card_setting2 + images += greeting_card_setting2.greeting_card_images.map{|img| img.file.url} + end + end + images + end + + def agree_customs_func(apply_custom_fields, check_custom_fields=nil) + if apply_custom_fields.count != 0 + self.agree_customs += apply_custom_fields + self.agree_customs.uniq! + if check_custom_fields.nil? + check_custom_fields = GreetingCardSetting.first.custom_fields + end + apply_custom_fields.each{|k| self.custom_fields["default@#{k}"] = check_custom_fields[k]} + self.need_check_customs -= apply_custom_fields + self.reject_customs -= apply_custom_fields + self.recalc_sort(false) + @skip_callback = true + self.save + @skip_callback = false + end + true + end + def delete_customs_func(delete_custom_fields, add_to_reject=false) + if delete_custom_fields.count != 0 + delete_custom_fields = delete_custom_fields.map{|f| f.sub(/^default@/, '')} + self.need_check_customs -= delete_custom_fields + self.agree_customs -= delete_custom_fields + if add_to_reject + self.reject_customs += delete_custom_fields + self.reject_customs.uniq! + else + self.reject_customs -= delete_custom_fields + delete_custom_fields.each do |f| + self.custom_fields.delete("default@#{f}") + end + end + self.recalc_sort(false) + @skip_callback = true + self.save + @skip_callback = false + end + true + end + def update_need_check_customs(check_custom_fields, is_locale=false, need_check=false) + if need_check + check_custom_fields = check_custom_fields.map{|k,v| [k, v["field"][I18n.default_locale.to_s]]}.to_h unless is_locale + category_custom_fields = self.custom_fields.map{|k,v| v["field"][I18n.default_locale.to_s]} + self.need_check_customs += check_custom_fields.select{|k,v| v.blank? || !(category_custom_fields.include?(v))}.keys + else + self.need_check_customs += check_custom_fields.keys + end + self.need_check_customs = self.need_check_customs.uniq + self.need_check_customs -= self.reject_customs + self.need_check_customs -= self.agree_customs + unless is_locale + self.agree_customs.each{|k| self.custom_fields["default@#{k}"] = check_custom_fields[k]} + end + self.recalc_sort(false) + @skip_callback = true + self.save + @skip_callback = false + end + + def recipients + MemberProfile.where(:id.in=> self.recipient_ids) + end + + def enabled + if use_default + default_sort_number.present? + else + true + end + end + + def generate_record(site, module_app, member_profile, locale, sending_time, today) + update_user = User.where(id: self.update_user_id).first + if sending_time.nil? + sending_time = member_profile.birthday + sending_time = Date.parse(sending_time.strftime("#{today.year}-%m-%d")) + end + if sending_time < today + sending_time += 1.year + end + # only update future email + email_record = GreetingCardEmail.where(category_id: self.category_id, member_profile: member_profile, year: today.year, sending_time: {"$gt"=> today.to_datetime}, is_sent: false).first + if email_record.nil? + I18n.with_locale(locale) do + email = Email.new + email.save + r = GreetingCardRecord.new(category_id: self.category_id, is_hidden: true, title: member_profile.name, name: member_profile.name, sender_mail: self.sender_mail, recipients: member_profile.email, sending_time: sending_time, card: self.auto_send_image_id) + r.email_id = email.id + r.save + email_record = GreetingCardEmail.create(category_id: self.category_id, year: today.year, sending_time: sending_time, member_profile: member_profile, email: email, greeting_card_record: r) + r.build_email(site, self.host_url, update_user, module_app, locale, email, {"model_class"=> "GreetingCardEmail", "model_id"=> email_record.id}) + end + else + r = email_record.greeting_card_record + r.instance_variable_set(:@skip_callback, true) + if r.card != self.auto_send_image_id # no need to generate image + r.generate_image + end + r.update(sending_time: sending_time, card: self.auto_send_image_id) + # I18n.with_locale(locale) do + # r.build_email(site, self.host_url, update_user, module_app, locale, r.email, {"model_class"=> "GreetingCardEmail", "model_id"=> email_record.id}) + # end + email_record.update(sending_time: sending_time) + r.email.update(mail_sentdate: sending_time) + end + end + + def generate_records + layout_design = GreetingCardLayoutDesign.where(:category_id=> category_id).first + if layout_design.nil? + default_layout_design = GreetingCardLayoutDesign.where(:category_id=> nil).first + if default_layout_design.nil? + return false + else + layout_design = default_layout_design + end + end + if @generating_records + return false + else + @generating_records = true + Thread.new do + site = Site.first + module_app = ModuleApp.where(key: 'greeting_card').first + today = Date.today + if self.auto_send_date_type == 0 + sending_time = DateTime.parse("#{today.year}/#{self.regular_date}") + else + sending_time = nil # use birthday + end + self.recipients.to_a.each do |member_profile| + self.generate_record(site, module_app, member_profile, I18n.locale, sending_time, today) + end + @generating_records = false + end + return true + end + end + +end diff --git a/app/models/greeting_card_category_setting_index.rb b/app/models/greeting_card_category_setting_index.rb new file mode 100644 index 0000000..c498dc9 --- /dev/null +++ b/app/models/greeting_card_category_setting_index.rb @@ -0,0 +1,6 @@ +class GreetingCardCategorySettingIndex + include Mongoid::Document + include Mongoid::Timestamps + field :key,type: Integer,default: 0 + field :category_id +end diff --git a/app/models/greeting_card_email.rb b/app/models/greeting_card_email.rb new file mode 100644 index 0000000..4c28454 --- /dev/null +++ b/app/models/greeting_card_email.rb @@ -0,0 +1,36 @@ +class GreetingCardEmail + include Mongoid::Document + include Mongoid::Timestamps + + field :category_id + field :year, type: Integer + field :sending_time, type: DateTime + field :is_sent, :type => Boolean, :default => false + + belongs_to :member_profile + belongs_to :email, dependent: :destroy + belongs_to :greeting_card_record, dependent: :destroy, index: true + + index({category_id: 1, year: -1, sending_time: -1, member_profile_id: 1}, { unique: false, background: true }) + + def email_sent(mails, flag) + if flag # Generate next record when email is sent + new_sending_time = self.sending_time + 1.year + new_email_record = self.dup + new_record = self.greeting_card_record.dup + new_email_record.year += 1 + new_email_record.sending_time = new_sending_time + new_record.sending_time = new_sending_time + new_email = self.email.dup + new_email.mail_sentdate = new_sending_time + new_email.template_data["model_id"] = new_email_record.id + new_email.save + new_record.email_id = new_email.id + new_email_record.email_id = new_email.id + new_email_record.greeting_card_record = new_record + new_record.save + new_email_record.save + self.update(is_sent: true) + end + end +end \ No newline at end of file diff --git a/app/models/greeting_card_file.rb b/app/models/greeting_card_file.rb new file mode 100644 index 0000000..0b01a9f --- /dev/null +++ b/app/models/greeting_card_file.rb @@ -0,0 +1,7 @@ +class GreetingCardFile + include Mongoid::Document + include Mongoid::Timestamps + + mount_uploader :file, AssetUploader + field :greeting_card_record_id +end \ No newline at end of file diff --git a/app/models/greeting_card_image.rb b/app/models/greeting_card_image.rb new file mode 100644 index 0000000..d0f7231 --- /dev/null +++ b/app/models/greeting_card_image.rb @@ -0,0 +1,22 @@ +# encoding: utf-8 +class GreetingCardImage + + include Mongoid::Document + include Mongoid::Timestamps + + mount_uploader :file, ImageUploader + + field :description, localize: true + + belongs_to :greeting_card_setting + + belongs_to :greeting_card_category_setting + + def description_text + if self.description.blank? + "" + else + Nokogiri::HTML::fragment(self.description.to_s).text + end + end +end \ No newline at end of file diff --git a/app/models/greeting_card_layout_design.rb b/app/models/greeting_card_layout_design.rb new file mode 100644 index 0000000..852230f --- /dev/null +++ b/app/models/greeting_card_layout_design.rb @@ -0,0 +1,403 @@ +class GreetingCardLayoutDesign + require 'freetype/api' + include Mongoid::Document + include Mongoid::Timestamps + field :print_format, type: String,default: '' + field :save_name, type: String,default: 'greeting_card_{title}' + field :img_objs, type: Hash, default: {} + field :preserved_keys, type: Array + field :category_id + + before_save do + self.preserved_keys = img_objs.map do |i, obj| + get_preserved_keys(obj[:text]) + end.flatten + self.preserved_keys += get_preserved_keys(self.save_name) + + self.preserved_keys = self.preserved_keys.uniq.map(&:to_sym) + end + + def get_preserved_keys(text) + if text.present? + text.scan(/(?<={)[^{}]+(?=})/) + else + [] + end + end + + # 微軟正黑體 + # need to call f.close manually to prevent memory leak + def get_font(font_name) + font_file = `fc-match '#{font_name}' -f "%{file}" 2>/dev/null` + if font_file.blank? + puts "#{font_name} font not found!" + nil + else + f = FreeType::API::Font.open(font_file) + font_info = { + m_w: 1.0 * f.face[:max_advance_width] / f.face[:units_per_EM], + m_h: 1.0 * f.face[:max_advance_height] / f.face[:units_per_EM], + l_g: 1.0 * (f.face[:height] - f.face[:max_advance_height]) / f.face[:units_per_EM], # line gap + f: f, + file: font_file + } + font_info + end + end + + def set_char_size(f, font_size) + f.set_char_size(0, font_size * 64, 0, 0) + end + + def get_char_real_width(c, font_size, f=nil, prev_c=nil) + if f.nil? + c_w = get_char_estimate_width(c, font_size) + else + c_w = (f.glyph(c).char_width >> 6) # this size is 64 times larger + if prev_c + c_w += (f.kerning_unscaled(prev_c, c).x >> 6) + end + end + c_w + end + + def get_char_estimate_width(c, font_size) + chinese_ratio = 1 + sixth_chars = ('0'..'9').to_a + seventh_chars = ('A'..'Z').to_a + ['g', 'h', 'n', 'o', 'p', 'q', 'w'] + half_chars = ['-', '_', '?'] + (('a'..'z').to_a - ['m', 't']) + third_chars = [' ', '[', ']', '【', '】', '《', '》', '(', ')', 'i', 'l', 'r', 't', 'f', '!', '.', '-', ':', ';'] + point_chars = [] + if point_chars.include?(c) + w = 0.1 + elsif third_chars.include?(c) + w = 0.3 + elsif half_chars.include?(c) + w = 0.5 + elsif sixth_chars.include?(c) + w = 0.6 + elsif seventh_chars.include?(c) + w = 0.7 + elsif c.match(/\p{Han}/) + w = chinese_ratio + else + w = 1 + end + return w * font_size + end + + def wrap_text_accurate(text, font_info, font_size, width, height, line_height=1, ellipsis=true) + if font_info.nil? + wrap_text_estimate(text, font_size, width, height, line_height, ellipsis) + else + if width == 0.0 && height == 0.0 # auto width and auto height + return text + end + if height == 0.0 + max_lines = 0 + else + max_lines = [height / (line_height * font_size * font_info[:m_h]).to_i, 1].max + end + f = font_info[:f] + set_char_size(f, font_size) + new_text = "" + last_line = "" + w = 0 + lines = 1 + prev_c = nil + check_ellipsis = false + text.chars.each do |c| + if c == "\n" + lines += 1 + if max_lines != 0 && lines > max_lines + check_ellipsis = true + break + end + new_text += last_line + new_text += c + last_line = "" + prev_c = nil + w = 0 + elsif width != 0.0 + c_w = get_char_real_width(c, font_size, f, prev_c) + prev_c = c + tmp_w = w + c_w + if tmp_w > width + lines += 1 + if max_lines != 0 && lines > max_lines + check_ellipsis = true + break + end + # Wrap the word + last_word = nil + last_line.sub!(/ ([^ ]+)$/){|s| last_word = $1; ' '} + new_text += last_line + new_text += "\n" + if last_word + # last line starts with word + last_line = last_word + w = get_text_real_width(last_word, font_size, f) + c_w + else + w = c_w + last_line = "" + end + else + w = tmp_w + end + last_line += c + end + end + if check_ellipsis && ellipsis # Add ... + last_w = w + ellipsis_text = "..." + ellipsis_width = get_text_real_width(ellipsis_text, font_size, f) + remain_width = width - last_w + if width == 0.0 || remain_width >= ellipsis_width + new_text += ellipsis_text + else + last_line_arr = last_line.split(/ /) + if last_line_arr.count > 1 + space_width = get_char_real_width(' ', font_size, f) + while remain_width < ellipsis_width do + if last_line_arr.count > 1 + remain_width += space_width + prev_c = ' '.freeze + else + prev_c = nil + end + s = last_line_arr.pop + s_width = get_text_real_width(s, font_size, f, prev_c) + remain_width += s_width + break if last_line_arr.empty? + end + last_line = last_line_arr.join(' ') + else + while remain_width < ellipsis_width do + c = last_line[-1] + last_line.chop! + if last_line.empty? + prev_c = nil + else + prev_c = last_line[-1] + end + remain_width += get_char_real_width(c, font_size, f, prev_c) + if prev_c.nil? + break + end + end + end + new_text += last_line + new_text += ellipsis_text + end + else + new_text += last_line + end + new_text + end + end + + def wrap_text_estimate(text, font_size, width, height, line_height=1, ellipsis=true) + if width == 0.0 && height == 0.0 # auto width and auto height + return text + end + if height == 0.0 + max_lines = 0 + else + max_lines = [height / (line_height * font_size).to_i, 1].max + end + new_text = "" + last_line = "" + w = 0 + lines = 1 + check_ellipsis = false + text.chars.each do |c| + if c == "\n" + lines += 1 + if max_lines != 0 && lines > max_lines + check_ellipsis = true + break + end + new_text += last_line + new_text += c + last_line = "" + prev_c = nil + w = 0 + elsif width != 0.0 + c_w = get_char_estimate_width(c, font_size) + tmp_w = w + c_w + if tmp_w > width + lines += 1 + if max_lines != 0 && lines > max_lines + check_ellipsis = true + break + end + # Wrap the word + last_word = nil + last_line.sub!(/ ([^ ]+)$/){|s| last_word = $1; ' '} + new_text += last_line + new_text += "\n" + if last_word + # last line starts with word + last_line = last_word + w = get_text_estimate_width(last_word, font_size) + c_w + else + w = c_w + last_line = "" + end + else + w = tmp_w + end + last_line += c + end + end + if check_ellipsis && ellipsis + last_w = w + ellipsis_text = "..." + ellipsis_width = get_text_estimate_width(ellipsis_text, font_size) + remain_width = width - last_w + if width == 0.0 || remain_width >= ellipsis_width + new_text += "..." + else + last_line_arr = last_line.split(/ /) + if last_line_arr.count > 1 + space_width = get_char_estimate_width(' ', font_size) + while remain_width < ellipsis_width do + if last_line_arr.count > 1 + remain_width += space_width + end + s = last_line_arr.pop + s_width = get_text_estimate_width(s, font_size) + remain_width += s_width + break if last_line_arr.empty? + end + last_line = last_line_arr.join(' ') + else + while remain_width < ellipsis_width do + c = last_line[-1] + last_line.chop! + remain_width += get_char_estimate_width(c, font_size) + if last_line.empty? + break + end + end + end + new_text += last_line + new_text += '...' + end + else + new_text += last_line + end + new_text + end + + def get_text_real_width(text, font_size, f=nil, prev_c=nil) + if f.nil? + get_text_estimate_width(text, font_size) + else + set_char_size(f, font_size) + w = 0 + text.chars.each do |c| + w += get_char_real_width(c, font_size, f, prev_c) + prev_c = c + end + w + end + end + + def get_text_estimate_width(text, font_size) + w = 0 + text.chars.each do |c| + w += get_char_estimate_width(c, font_size) + end + return w + end + + def generate_image(record, image_source, data, saved=true) + # change locale to chinese + ENV['LANG'] = "zh_TW.UTF-8" + begin + img = MiniMagick::Image.open(image_source) + rescue => e + puts "image open: #{image_source} failed!" + return nil + end + data = data.select{|k, v| self.preserved_keys.include?(k)} + img_objs_arr = self.img_objs.map do |i, _obj| + obj = _obj.dup + obj[:text].gsub!(/{([^{}]+)}/){|k| data[$1.to_sym]} + obj[:text].gsub!("\r\n", "\n") + obj[:text].strip! + obj + end + img_width = img[:width] + img_height = img[:height] + font_scale = img_width / 500.0 + width_scale = img_width / 100.0 + height_scale = img_height / 100.0 + image_filename = save_name.gsub!(/{([^{}]+)}/){|k| data[$1.to_sym]}.gsub(CarrierWave::SanitizedFile.sanitize_regexp, '_') + File.extname(image_source) + record.image_will_change! + record[:image] = image_filename # must called + record.image.retrieve_from_store!(image_filename) # must called + dest_image_path = record.image.file.path + image_dir = File.dirname(dest_image_path) + FileUtils.rm_rf image_dir + FileUtils.mkdir_p image_dir + FileUtils.copy_file(image_source, dest_image_path) + + record.image.manipulate! do |image| + image.combine_options do |c| + # c << "thumb_榮獲設計競賽-transformed.png" + img_objs_arr.each do |obj| + is_stroke = (obj[:is_stroke] == 'true') + if is_stroke + is_fill = obj[:is_fill] != 'false' + else + is_fill = true + end + x = width_scale * obj[:x].to_f + y = height_scale * obj[:y].to_f + w = width_scale * obj[:w].to_f + h = height_scale * obj[:h].to_f + fontSize = font_scale * obj[:fontSize].to_f + fontFamily = obj[:fontFamily] + font_info = get_font(fontFamily) + if font_info + fontFamily = font_info[:file] + end + line_height = (obj[:l_h] ? obj[:l_h].to_f : 1.0) + wrapped_text = wrap_text_accurate(obj[:text], font_info, fontSize, w, h, line_height) + y = y + fontSize + # y = img_height - y + # x = img_width - x + # c.interline_spacing caption_interline_spacing + line_gap = 0 + if font_info + line_gap = -(font_info[:l_g] * fontSize) + end + if obj[:l_h] + line_gap = line_gap + (line_height - 1.0) * fontSize + end + c.interline_spacing line_gap.round(2) + c.font fontFamily + c.pointsize fontSize + c.fill (is_fill ? obj[:fontColor] : 'none') + if is_stroke + c.stroke obj[:stroke] + c.strokewidth (font_scale * obj[:strokeWidth].to_f) + else + c.stroke 'none' + end + # c.kerning -2 + c.kerning 1.5 + c.annotate "+#{x}+#{y}" + c << wrapped_text #.force_encoding("UTF-8") + # c << "123" + if font_info + font_info[:f].close + end + end + end + end + record.save if saved + end +end diff --git a/app/models/greeting_card_list_setting.rb b/app/models/greeting_card_list_setting.rb new file mode 100644 index 0000000..ec74fe5 --- /dev/null +++ b/app/models/greeting_card_list_setting.rb @@ -0,0 +1,29 @@ +class GreetingCardListSetting + include Mongoid::Document + include Mongoid::Timestamps + DefaultFields = ['serial_number','image','greeting_card_category_id','title','name','sender_mail','recipients','created_at','ip'] + DefaultEnabled = ['serial_number','image','greeting_card_category_id','title','name','created_at','ip'] + field :default_fields, type: Array, default: [] + field :custom_fields, type: Array, default: [] + field :category_id + def default_fields + tmp = super() + if tmp.empty? + tmp = self.class::DefaultEnabled + end + return tmp + end + def custom_fields_list + locale = I18n.locale + default_setting = GreetingCardSetting.first + use_default = self.category_id.blank? || GreetingCardCategorySetting.where(:category_id=>self.category_id, :use_default.ne=>true).first.nil? + if use_default + fields = default_setting ? {I18n.t(:default) => default_setting.custom_fields.map{|k,v| ["default@#{k}",v['field'][locale]]}.to_h} : {} + cat_ids_map = Category.where(:id.in=> GreetingCardCategorySetting.all.pluck(:category_id),:disabled.ne=>true).map{|c| [c.id.to_s, c.title]}.to_h + fields = fields.merge(GreetingCardCategorySetting.where(:category_id.in => cat_ids_map.keys()).map{|c| [cat_ids_map[c.category_id] , c.custom_fields.select{|k,v| !(k.start_with?('default@'))}.map{|k,v| ["custom@#{c.id}@#{k}",v['field'][locale]]}]}.to_h) + else + fields = GreetingCardCategorySetting.where(:category_id=>self.category_id).to_a.map{|c| [(Category.find(c.category_id).title rescue ''), c.custom_fields.map{|k,v| [k,v['field'][locale]]}]}.to_h + end + fields + end +end diff --git a/app/models/greeting_card_record.rb b/app/models/greeting_card_record.rb new file mode 100644 index 0000000..f236caa --- /dev/null +++ b/app/models/greeting_card_record.rb @@ -0,0 +1,183 @@ +class GreetingCardRecord + include Mongoid::Document + include Mongoid::Timestamps + # include ActiveModel::Validations + include OrbitCategory::Categorizable + include OrbitTag::Taggable + + #https://stackoverflow.com/questions/378887/how-do-i-calculate-a-strings-width-in-ruby + mount_uploader :image, ImageUploader + + # 欄位定義 + field :serial_number, type: Integer + field :ip, type: String + field :title, type: String + field :name, type: String + field :sender_mail, type: String + field :recipients, type: String + field :greetings, type: String + field :sending_time, type: DateTime + + field :situation, type: String, default: "is_email_not_confirmed" # 預設email未驗證 + field :send_email, type: Integer, default: 0 + field :email_id + field :verify_email_id + field :custom_values, type: Hash, default: {} + field :agree_show, type: Boolean, default: false + field :agree_usage, type: Boolean, default: false + field :is_hidden, type: Boolean + + field :review_time, type: DateTime + belongs_to :reviewer, class_name: "MemberProfile", foreign_key: :reviewer_id + belongs_to :card_image, class_name: "GreetingCardImage", foreign_key: :card + has_many :greeting_card_status_histories + + index({situation: 1, is_hidden: -1}, { unique: false, background: true }) + + attr_accessor :release_comment, :release_file, :user + + before_create do + unless self.is_hidden + last_serial_number = GreetingCardSetting.update_last_serial_number + self.serial_number = last_serial_number + end + self.generate_image(false) + end + + before_save :create_greeting_card_status_history + + def self.get_fonts + ["Arial", "Calibri", "Times New Roman", "微軟正黑體", "標楷體", "新細明體"] + end + + def release_comment=(v) + @changed = true + @release_comment = v + end + + def release_file=(v) + @changed = true + @release_file = v + end + + def email + mail = Email.where(:id=>self.email_id).first + end + + def verify_email + verify_email = Email.where(:id=>self.verify_email_id).first + end + + def reviewer_emails + email_address = GreetingCardAdmin.or(:category_ids.in => [self.category_id, [], nil]).pluck(:email).select { |s| s.present? }.uniq rescue [] + authorizes = Authorization.where(module_app_id: ModuleApp.where(key: 'greeting_card').first.id).to_a rescue [] + authorizes.each do |a| + if a.category_id + next if a.category_id != self.category_id + end + if a.user_id + u = a.user + email_address << u.email if u && u.email + elsif a.role_id + email_address = email_address + MemberProfile.where(role_ids: a.role_id).pluck(:email).select { |s| s.present? }.uniq + else + a.destroy + end + end + email_address = email_address.flatten + email_address.uniq + end + + def get_serial_number(last_serial_number=nil, display_length=nil) + if display_length.nil? && last_serial_number.nil? + can_update_shared_hash = (defined?(OrbitHelper::SharedHash) && OrbitHelper::SharedHash) + last_serial_number = (can_update_shared_hash ? OrbitHelper::SharedHash["greeting_card"][:last_serial_number] : GreetingCardSetting.pluck(:last_serial_number)[0].to_i) + end + display_length = [last_serial_number.to_s.length + 1, 4].max if display_length.nil? + display_format_string(self.serial_number, display_length) if self.serial_number + end + + def display_format_string(num, str_length) + format("%0#{str_length}d", num) + end + + def send_email? + self.send_email == 1 + end + + def new_history + @new_history + end + + def generate_image(saved=true) + if self.card_image + layout_design = GreetingCardLayoutDesign.where(:category_id=> self.category_id.to_s).first + if layout_design.nil? + layout_design = GreetingCardLayoutDesign.where(:category_id=> nil).first + if layout_design.nil? + puts "Layout Design not exist!" + return false + end + end + data = { + title: self.title, + name: self.name, + sender_mail: self.sender_mail, + recipients: self.recipients, + greetings: self.greetings + } + + layout_design.generate_image(self, self.card_image.file.path, data, saved) + end + end + + def build_email(site, host_url, current_user, module_app, locale, email=nil, extra_data={}) + if email.nil? + email = Email.new + email.save + self.email_id = email.id + self.save + end + + group_mail = self.recipients + manager_emails = self.sender_mail + mail_sentdate = self.sending_time + + mail_from = site.title_translations[locale] + mail_subject = mail_from + " #{I18n.t('greeting_card.new_card')}:" + template_data = { + "greeting_card_record_id" => self.id.to_s, + "site_host" => host_url + } + template_data.merge!(extra_data) if extra_data.present? + email.update_attributes( + :mail_lang=> locale, + :create_user=> current_user, + :mail_sentdate=> mail_sentdate, + :module_app=> module_app, + :mail_to=>group_mail, + :mail_subject=>mail_subject, + :template=>'greeting_cards/email', + :template_data=> template_data, + :mail_reply_to => (manager_emails.empty? ? nil : manager_emails) + ) + + email + end + + private + + def create_greeting_card_status_history + if !@skip_callback && (changed? || @changed) + @new_history = GreetingCardStatusHistory.create( + greeting_card_record: self, + status: self.situation, + comment: @release_comment, + file: @release_file, + user: @user + ) + else + @new_history = nil + end + end +end diff --git a/app/models/greeting_card_safe_email.rb b/app/models/greeting_card_safe_email.rb new file mode 100644 index 0000000..8059074 --- /dev/null +++ b/app/models/greeting_card_safe_email.rb @@ -0,0 +1,5 @@ +class GreetingCardSafeEmail + include Mongoid::Document + field :email, type: String + index({email: 1}, { unique: false, background: true }) +end \ No newline at end of file diff --git a/app/models/greeting_card_setting.rb b/app/models/greeting_card_setting.rb new file mode 100644 index 0000000..0c707ab --- /dev/null +++ b/app/models/greeting_card_setting.rb @@ -0,0 +1,295 @@ +class GreetingCardSetting + include Mongoid::Document + include Mongoid::Timestamps + + require_relative'concerns/greeting_card_setting_concern' + include ::GreetingCardSettingConcern + + All_default_fields = ['greeting_card_category_id','name','sender_mail','recipients','greetings','card','sending_time','recaptcha','usage_rule','agree_show','agree_usage'] + No_prompt_word = ['greeting_card_category_id', 'recaptcha'] + No_required = ['agree_show'] + Required = ['sender_mail', 'recipients','card'] + + field :last_ticket_key, type: Integer, default: 0 + field :last_serial_number, type: Integer, default: 0 + + before_save do + @email_regex_enable_changed = self.email_regex_enable_changed? + @email_regex_changed = self.email_regex_changed? + @default_setting_prompt_word_changed = self.default_setting_prompt_word_changed? + @default_setting_field_name_changed = self.default_setting_field_name_changed? + true + end + after_save do + update_all_hash = {} + if @email_regex_enable_changed || @email_regex_changed + update_all_hash[:email_regex_enable] = self.email_regex_enable + update_all_hash[:email_regex] = self.email_regex + end + if @default_setting_prompt_word_changed + update_all_hash[:default_setting_prompt_word] = self.default_setting_prompt_word + end + if @default_setting_field_name_changed + update_all_hash[:default_setting_field_name] = self.default_setting_field_name + end + if update_all_hash.present? + GreetingCardCategorySetting.all.update_all(update_all_hash) + end + if @custom_fields_changed + GreetingCardCategorySetting.all.each do |a| + a.delete_customs_func(@delete_custom_fields) + a.update_need_check_customs(self.custom_fields) + end + end + end + def use_default + false + end + + def category_id + nil + end + + def get_attrs + attrs = self.attributes.clone + self.fields.each do |k, v| + if (v.options[:localize] rescue false) + attrs["#{k}_translations"] = attrs[k] + attrs.delete(k) + end + end + attrs.except("_type","_id","uid","updated_at", "created_at", "email_regex", "last_ticket_key", "default_setting_required", "last_serial_number") + end + def self.update_last_serial_number(last_serial_number=nil) + can_update_shared_hash = (defined?(OrbitHelper::SharedHash) && OrbitHelper::SharedHash) + if last_serial_number.nil? + last_serial_number = (can_update_shared_hash ? OrbitHelper::SharedHash["greeting_card"][:last_serial_number] : self.pluck(:last_serial_number)[0].to_i) + self.all.inc({'last_serial_number'=>1}) + last_serial_number += 1 + else + self.all.update_all(:last_serial_number=>last_serial_number) + end + if can_update_shared_hash + OrbitHelper::SharedHash["greeting_card"][:last_serial_number] = last_serial_number + end + last_serial_number + end + def self.get_sort_number_only(only_display=true, cat=nil, cat_is_record=false, override_sort_number=nil, use_cache=false) + greeting_card_setting = nil + if cat_is_record + greeting_card_setting = cat + is_cat_record = (cat.class == GreetingCardCategorySetting) + is_cat_record2 = is_cat_record || !(override_sort_number.nil?) + else + if cat.present? + greeting_card_setting = GreetingCardCategorySetting.enabled.where(:category_id=>cat).first + is_cat_record = true + end + greeting_card_setting = self.first if greeting_card_setting.nil? + greeting_card_setting = self.create() if greeting_card_setting.nil? + end + if only_display && use_cache + if override_sort_number + sort_number = override_sort_number + else + sort_number = greeting_card_setting.get_cache_sort_number + end + return sort_number + end + is_default_setting = (greeting_card_setting.class == self) + if override_sort_number.present? + sort_number = override_sort_number + else + sort_number = greeting_card_setting.sort_number + if greeting_card_setting.use_default + if greeting_card_setting.default_sort_number.blank? + sort_number = (is_default_setting ? sort_number : self.first.sort_number) + else + sort_number = greeting_card_setting.default_sort_number + end + override_sort_number = sort_number + greeting_card_setting = self.first + end + end + default_greeting_card_setting = is_default_setting ? greeting_card_setting : self.first + disp_fields = self::All_default_fields + disp_fields_infos = {} + locale = I18n.locale.to_s + if !only_display || greeting_card_setting.default_setting['title'] != false + disp_fields_infos['title'] = sort_number['title'].to_i + end + tmp = disp_fields[0...-4] + if only_display + tmp = tmp.select{|f| greeting_card_setting.default_setting[f]} + end + tmp.each do |f| + disp_fields_infos[f] = sort_number[f] + end + tmp_locales = I18n.available_locales.map{|l| l.to_s} + tmp_locales = [locale] + (tmp_locales - [locale]) + greeting_card_setting.custom_fields.each do |k,v| + tmp_k = k + if !k.start_with?('default@') + prefix = is_cat_record ? "custom@#{cat.id}@" : 'default@' + tmp_k = "#{prefix}#{k}" + end + disp_fields_infos[tmp_k] = sort_number[tmp_k] + end + greeting_card_category_settings = [] + if is_cat_record2 + greeting_card_category_settings = [] + else + greeting_card_category_settings = GreetingCardCategorySetting.custom_enabled.to_a + end + greeting_card_category_settings.each do |c| + category = Category.find(c.category_id) rescue nil + if category.nil? + c.destroy + next + end + c.custom_fields.each do |k,v| + next if k.start_with?('default@') + tmp_k = "custom@#{c.id}@#{k}" + cat_title = category.title rescue "" + disp_fields_infos[tmp_k] = sort_number[tmp_k] + end + end + tmp = disp_fields[-4..-1] + if only_display + tmp = tmp.select{|f| greeting_card_setting.default_setting[f] || f == 'usage_rule'} + end + tmp.each do |f| + disp_fields_infos[f] = sort_number[f] + end + max_sort_number = disp_fields_infos.values.map{|v| v}.compact.max + if max_sort_number + if !use_cache || (is_default_setting && override_sort_number.nil?) + max_sort_number = max_sort_number + 1 + disp_fields_infos.each_with_index do |(k,v), i| + if v.blank? + disp_fields_infos[k] = max_sort_number + i + end + end + end + disp_fields_infos = disp_fields_infos.sort_by {|_key, value| value}.to_h + end + disp_fields_infos + end + def self.get_disp_fields_infos(only_display=true, cat=nil, cat_is_record=false, override_sort_number=nil, use_cache=false) + greeting_card_setting = nil + if cat_is_record + greeting_card_setting = cat + is_cat_record = (cat.class == GreetingCardCategorySetting) + is_cat_record2 = is_cat_record || !(override_sort_number.nil?) + else + if cat.present? + greeting_card_setting = GreetingCardCategorySetting.enabled.where(:category_id=>cat).first + is_cat_record = true + end + greeting_card_setting = self.first if greeting_card_setting.nil? + greeting_card_setting = self.create() if greeting_card_setting.nil? + end + is_default_setting = (greeting_card_setting.class == self) + sort_number = nil + if only_display && use_cache + if override_sort_number + sort_number = override_sort_number + else + sort_number = greeting_card_setting.get_cache_sort_number + end + else + use_cache = false + sort_number = greeting_card_setting.sort_number + if override_sort_number.present? + sort_number = override_sort_number + elsif greeting_card_setting.use_default + if greeting_card_setting.default_sort_number.blank? + sort_number = (is_default_setting ? sort_number : self.first.sort_number) + else + sort_number = greeting_card_setting.default_sort_number + end + override_sort_number = sort_number + greeting_card_setting = self.first + end + end + default_greeting_card_setting = is_default_setting ? greeting_card_setting : self.first + disp_fields = self::All_default_fields + disp_fields_infos = {} + locale = I18n.locale.to_s + if !only_display || greeting_card_setting.default_setting['title'] != false + disp_fields_infos['title'] = {"trans"=>greeting_card_setting.field_name('title'), "sort_number"=>sort_number['title'].to_i} + end + tmp = disp_fields[0...-4] + if only_display + tmp = tmp.select{|f| greeting_card_setting.default_setting[f]} + end + tmp.each do |f| + disp_fields_infos[f] = {"trans"=>default_greeting_card_setting.field_name(f,locale),"sort_number"=>sort_number[f]} + end + tmp_locales = I18n.available_locales.map{|l| l.to_s} + tmp_locales = [locale] + (tmp_locales - [locale]) + greeting_card_setting.custom_fields.each do |k,v| + trans = v["field"] + tmp_locales.each do |l| + if trans[l].present? + trans = trans[l] + break + end + end + next if trans.class != String + tmp_k = k + if !k.start_with?('default@') + prefix = is_cat_record ? "custom@#{cat.id}@" : 'default@' + tmp_k = "#{prefix}#{k}" + end + disp_fields_infos[tmp_k] = {"trans"=>trans,"sort_number"=>sort_number[tmp_k],"key"=>k,"type"=>v["type"],"options"=>v["options"],"instructions"=>v["instructions"]} + end + greeting_card_category_settings = [] + if is_cat_record2 + greeting_card_category_settings = [] + else + greeting_card_category_settings = GreetingCardCategorySetting.custom_enabled.to_a + end + greeting_card_category_settings.each do |c| + category = Category.find(c.category_id) rescue nil + if category.nil? + c.destroy + next + end + c.custom_fields.each do |k,v| + trans = v["field"] + tmp_locales.each do |l| + if trans[l].present? + trans = trans[l] + break + end + end + next if trans.class != String + next if k.start_with?('default@') + tmp_k = "custom@#{c.id}@#{k}" + cat_title = category.title rescue "" + disp_fields_infos[tmp_k] = {"trans"=>"#{cat_title}-#{trans}","sort_number"=>sort_number[tmp_k],"key"=>k,"type"=>v["type"],"options"=>v["options"],"instructions"=>v["instructions"]} + end + end + tmp = disp_fields[-4..-1] + if only_display + tmp = tmp.select{|f| greeting_card_setting.default_setting[f] || f == 'usage_rule'} + end + tmp.each do |f| + disp_fields_infos[f] = {"trans"=>default_greeting_card_setting.field_name(f,locale),"sort_number"=>sort_number[f]} + end + max_sort_number = disp_fields_infos.values.map{|h| h["sort_number"]}.compact.max + if max_sort_number + if !use_cache || (is_default_setting && override_sort_number.nil?) + max_sort_number = max_sort_number + 1 + disp_fields_infos.each_with_index do |(k,h), i| + if h["sort_number"].blank? + h["sort_number"] = max_sort_number + i + end + end + end + disp_fields_infos = disp_fields_infos.sort_by {|_key, value| value["sort_number"]}.to_h + end + disp_fields_infos + end +end diff --git a/app/models/greeting_card_setting_index.rb b/app/models/greeting_card_setting_index.rb new file mode 100644 index 0000000..d221727 --- /dev/null +++ b/app/models/greeting_card_setting_index.rb @@ -0,0 +1,5 @@ +class GreetingCardSettingIndex + include Mongoid::Document + include Mongoid::Timestamps + field :key,type: Integer,default: 0 +end diff --git a/app/models/greeting_card_status_history.rb b/app/models/greeting_card_status_history.rb new file mode 100644 index 0000000..ef0d679 --- /dev/null +++ b/app/models/greeting_card_status_history.rb @@ -0,0 +1,17 @@ +class GreetingCardStatusHistory + include Mongoid::Document + include Mongoid::Timestamps + + field :status, type: String + field :comment, type: String + belongs_to :user + belongs_to :greeting_card_record + + mount_uploader :file, AssetUploader + + def modified_by_name + if user.present? && user.member_profile.present? + user.member_profile.name + end + end +end \ No newline at end of file diff --git a/app/models/greeting_card_ticket_status.rb b/app/models/greeting_card_ticket_status.rb new file mode 100644 index 0000000..e2e1b57 --- /dev/null +++ b/app/models/greeting_card_ticket_status.rb @@ -0,0 +1,46 @@ +class GreetingCardTicketStatus + include Mongoid::Document + include Mongoid::Timestamps + DefaultKeys = ["is_email_not_confirmed" , "is_waiting", "is_processed", "is_referral", "is_published"] + field :title, type: String, localize: true + field :is_default, type: Boolean, default: false # if true => cannot delete + field :key, type: String + before_create do + if self.key.blank? + can_update_shared_hash = (defined?(OrbitHelper::SharedHash) && OrbitHelper::SharedHash) + last_ticket_key = can_update_shared_hash ? OrbitHelper::SharedHash["greeting_card"][:last_ticket_key] : GreetingCardSetting.pluck(:last_ticket_key)[0].to_i + self.key = last_ticket_key.to_s + GreetingCardSetting.all.inc({'last_ticket_key'=>1}) + if can_update_shared_hash + OrbitHelper::SharedHash["greeting_card"][:last_ticket_key] = last_ticket_key + 1 + end + end + true + end + after_destroy do + # can_update_shared_hash = (defined?(OrbitHelper::SharedHash) && OrbitHelper::SharedHash) + # GreetingCardSetting.all.inc({'last_ticket_key'=>-1}) + # if can_update_shared_hash + # OrbitHelper::SharedHash["greeting_card"][:last_ticket_key] = OrbitHelper::SharedHash["greeting_card"][:last_ticket_key] - 1 + # end + end + def title + tmp = super + if self.is_default + tmp = I18n.t("greeting_card.#{self.key}") if tmp.blank? + end + tmp + end + def get_title_translations(locales=nil) + locales = I18n.available_locales if locales.nil? + trans = {} + locales.each do |locale| + trans[locale] = I18n.with_locale(locale){self.title} + end + trans + end + + def self.default_sorting + self.all.sort_by{|a| [a.is_default ? 0 : 1, DefaultKeys.index(a.key)] } + end +end \ No newline at end of file diff --git a/app/uploaders/greeting_card_image_uploader.rb b/app/uploaders/greeting_card_image_uploader.rb new file mode 100644 index 0000000..53b0abd --- /dev/null +++ b/app/uploaders/greeting_card_image_uploader.rb @@ -0,0 +1,71 @@ +# encoding: utf-8 +module CarrierWave + module Uploader + module Versions + def store_dir + "uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}" + end + end + end +end + +class GreetingCardImageUploader < CarrierWave::Uploader::Base + # Include RMagick or ImageScience support: + # include CarrierWave::RMagick + # include CarrierWave::ImageScience + include CarrierWave::MiniMagick + # Choose what kind of storage to use for this uploader: + # storage :file + # storage :s3 + + # Override the directory where uploaded files will be stored. + # This is a sensible default for uploaders that are meant to be mounted: + def store_dir + "uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}" + end + def get_org_url + if have_crop? + model.file.resized.url + else + model.file.url + end + end + def fix_exif_rotation + manipulate! do |img| + img.tap(&:auto_orient) + end + end + process :optimize + + version :thumb do + process :pad_process => [200,200] + end + + version :theater do + process :limit_process => [1920, 1080] + end + version :mobile do + process :limit_process => [1152, 768] + end + + def optimize (*arg) + manipulate! do |img| + return img unless img.mime_type.match /image\/jpeg/ + img.strip + img.combine_options do |c| + c.quality "90" + c.depth "24" + c.interlace "plane" + end + img + end + end + + def limit_process(w,h) + resize_to_limit(w,h) + end + def pad_process (w,h) + resize_and_pad(w, h, :transparent, 'Center') + end +end + diff --git a/app/views/admin/greeting_card_acknowledgements/index.html.erb b/app/views/admin/greeting_card_acknowledgements/index.html.erb new file mode 100644 index 0000000..612b95b --- /dev/null +++ b/app/views/admin/greeting_card_acknowledgements/index.html.erb @@ -0,0 +1,24 @@ +<% + set_input_name_for_greeting_card('greeting_card_acknowledgement') +%> + +
+ <%= form_for @greeting_card_acknowledgements, url: @url, html: { class: 'form-horizontal' } do |f| %> +
+ <%= f.label :content, t('greeting_card.acknowledgements'), class: 'control-label' %> + <%= multiple_lang_tag_for_greeting_card(nil,'text_area','content',@greeting_card_acknowledgements.content,{:class=>'ckeditor',placeholder: t('greeting_card.acknowledgements'),rows:10},nil,{'class' => 'controls','style'=>'display: flex;flex-direction: column-reverse;'}) + %> +
+
+ <%= f.submit t('submit'), class: 'btn btn-primary' %> + <%= f.button t('cancel'), type: 'reset', class: 'btn' %> +
+ <% end %> +
\ No newline at end of file diff --git a/app/views/admin/greeting_card_admins/_greeting_card_admin_form.html.erb b/app/views/admin/greeting_card_admins/_greeting_card_admin_form.html.erb new file mode 100644 index 0000000..ba2a6ec --- /dev/null +++ b/app/views/admin/greeting_card_admins/_greeting_card_admin_form.html.erb @@ -0,0 +1,42 @@ +

<%= @greeting_card_admin.new_record? ? t(:add) : t(:edit) %>

+ +
+
+ <%= f.label :email, class: 'control-label' %> +
+ <%= f.text_field :email, class: 'input-xxlarge', data: {"fv-validation" => "required;check_email;", "fv-messages" => "必填欄位;Email不正確;"} %> +
+
+
+ <%= f.label :category, class: 'control-label' %> +
+ + <% @module_app.categories.each do |category| %> + + <% end %> +
+
+
+
+ <%= f.submit t(:submit), class: 'btn btn-primary' %> +
+ + \ No newline at end of file diff --git a/app/views/admin/greeting_card_admins/edit.html.erb b/app/views/admin/greeting_card_admins/edit.html.erb new file mode 100644 index 0000000..8273a0a --- /dev/null +++ b/app/views/admin/greeting_card_admins/edit.html.erb @@ -0,0 +1,32 @@ +<%= render 'layouts/delete_modal', delete_options: @delete_options %> + + + + <% @table_fields.each do |f| %> + <%= thead(f) %> + <% end %> + + + + <% @greeting_card_admins.each do |a| %> + + + + <% end %> + +
+ <%= a.email %> +
+ +
+
+ +<%= form_for @greeting_card_admin, url: @url, html: { class: 'form-horizontal' } do |f| %> + <%= render :partial => 'greeting_card_admin_form', locals: {f: f} %> +<% end %> + diff --git a/app/views/admin/greeting_card_admins/index.html.erb b/app/views/admin/greeting_card_admins/index.html.erb new file mode 100644 index 0000000..ccf4294 --- /dev/null +++ b/app/views/admin/greeting_card_admins/index.html.erb @@ -0,0 +1,33 @@ +<%= render 'layouts/delete_modal', delete_options: @delete_options %> + + + + <% @table_fields.each do |f| %> + <%= thead(f) %> + <% end %> + + + + <% @greeting_card_admins.each do |a| %> + + + + <% end %> + +
+ <%= a.email %> +
+ +
+
+ +<%= javascript_include_tag 'validator' %> +<%= form_for @greeting_card_admin, url: admin_greeting_card_admins_path, html: {class: "form-horizontal main-forms previewable"} do |f| %> + + <%= render :partial => 'greeting_card_admin_form', locals: {f: f} %> +<% end %> \ No newline at end of file diff --git a/app/views/admin/greeting_cards/_auto_send_setting.html.erb b/app/views/admin/greeting_cards/_auto_send_setting.html.erb new file mode 100644 index 0000000..79c69ac --- /dev/null +++ b/app/views/admin/greeting_cards/_auto_send_setting.html.erb @@ -0,0 +1,83 @@ +<%#= javascript_include_tag "lib/bootstrap-datetimepicker" %> +<%#= javascript_include_tag "lib/datetimepicker/datetimepicker.js" %> +
+ <% id_postfix = f.object.category_id %> + <% + if @_idx.nil? + @_idx = 0 + else + @_idx += 1 + end + id_postfix += @_idx.to_s + %> +
+
+ +
+
+ <%= f.check_box :automatically_send, {class: "automatically_send_enable toggle-check", title: t("greeting_card.enable"), "data-disabled": !(f.object.automatically_send), id: "automatically_send_#{id_postfix}"} %> +
+ <%= f.hidden_field_tag "#{f.object_name}[auto_send_date_type]", "-1" %> +
<%= t('mongoid.attributes.greeting_card_record.image') %>
+
+ <% + greeting_card_images = @default_greeting_card_setting.greeting_card_images.to_a + if f.object.class == GreetingCardCategorySetting + greeting_card_images += f.object.greeting_card_images.to_a + end + %> + <% greeting_card_images.each do |image| %> + <% + img_url = image.file.url + next if img_url.blank? + %> +
+ + <%= f.radio_button :auto_send_image_id, image.id %> +
+ <% end %> +
+ <% f.object.class::DateTypes.each_with_index do |date_type, i| %> + + <% end %> + <%#= f.datetime_picker(:regular_date, no_label: true, new_record: f.object.new_record?, :class => "regular_date #{'hide' unless f.object.auto_send_date_type == 0}", 'id' => "regular_date_#{id_postfix}") %> +
+ <%= f.text_field(:regular_date, :class => "regular_date #{'hide' unless f.object.auto_send_date_type == 0}", 'id' => "regular_date_#{id_postfix}") %> +
+
+ <%= f.text_field :sender_mail, {placeholder: t('greeting_card.sender_mail')} %> +

<%= t('greeting_card._recipients') %>

+
+ <%= render partial: 'admin/member_selects/email_selection_box', locals: {field: "#{f.object_name}[recipient_ids][]", email_members: f.object.recipients, index: id_postfix, select_name: id_postfix} %> +
+
+
+
+
+ +
\ No newline at end of file diff --git a/app/views/admin/greeting_cards/_category_setting_field.html.erb b/app/views/admin/greeting_cards/_category_setting_field.html.erb new file mode 100644 index 0000000..bb5bfa3 --- /dev/null +++ b/app/views/admin/greeting_cards/_category_setting_field.html.erb @@ -0,0 +1,7 @@ +<% + set_input_name_for_greeting_card('greeting_card_category_setting[custom_fields]') +%> +<%= form_for greeting_card_setting,method: 'post',url: category_setting_save_admin_greeting_cards_path ,html: { class: "form-horizontal fade-in active detail",id: greeting_card_setting.id} do |f| %> + <%= render partial: 'greeting_card_setting',locals:{greeting_card_setting: greeting_card_setting, f: f, submit_text: t('greeting_card.modify'), is_default: is_default} %> + <%= hidden_field_tag "category_id",greeting_card_setting.category_id %> +<% end %> \ No newline at end of file diff --git a/app/views/admin/greeting_cards/_category_setting_partial.html.erb b/app/views/admin/greeting_cards/_category_setting_partial.html.erb new file mode 100644 index 0000000..58f4d62 --- /dev/null +++ b/app/views/admin/greeting_cards/_category_setting_partial.html.erb @@ -0,0 +1,11 @@ +<% + if f.nil? + form_for greeting_card_setting, method: 'post', url: category_setting_save_admin_greeting_cards_path do |_f| + f = _f + end + end +%> +
+ <%= render :partial=> "form_images", locals: {f: f, greeting_card_setting: greeting_card_setting, is_default: is_default} %> + <%= render :partial=> "auto_send_setting", locals: {f: f} %> +
\ No newline at end of file diff --git a/app/views/admin/greeting_cards/_default_greeting_card_setting.html.erb b/app/views/admin/greeting_cards/_default_greeting_card_setting.html.erb new file mode 100644 index 0000000..ea1778d --- /dev/null +++ b/app/views/admin/greeting_cards/_default_greeting_card_setting.html.erb @@ -0,0 +1,27 @@ +
+
+ <%= render :partial => 'show_default_greeting_card_setting' %> +
+
+ <%= form_for @default_greeting_card_setting, method: 'post',url: @url,html: { class: 'form-horizontal main-forms previewable fix_diabled_form' } do |f| %> + <% f.object_name = 'greeting_card_category_setting' %> + <%= hidden_field_tag "not_clone_attrs", true , :class=> 'not_clone_attrs' %> + <%= hidden_field_tag "category_id", @first_category_id , :class=> 'hidden_category_id' %> + <% + @has_email_regex = false + @has_switch_button = true + @default_custom_fields = true + %> + <%= render partial: 'greeting_card_setting',locals:{greeting_card_setting: @default_greeting_card_setting,f: f,submit_text: t('submit'), is_default: true } %> + <% + @has_switch_button = false + @default_custom_fields = false + %> + <% end %> + +
+
\ No newline at end of file diff --git a/app/views/admin/greeting_cards/_edit_box_for_index.html.erb b/app/views/admin/greeting_cards/_edit_box_for_index.html.erb new file mode 100644 index 0000000..44561e3 --- /dev/null +++ b/app/views/admin/greeting_cards/_edit_box_for_index.html.erb @@ -0,0 +1,8 @@ +
+ +
\ No newline at end of file diff --git a/app/views/admin/greeting_cards/_form.html.erb b/app/views/admin/greeting_cards/_form.html.erb new file mode 100644 index 0000000..98076eb --- /dev/null +++ b/app/views/admin/greeting_cards/_form.html.erb @@ -0,0 +1,231 @@ +<% + greeting_card_setting = @greeting_card_setting + set_input_name_for_greeting_card('greeting_card_record') +%> +<% content_for :page_specific_css do %> + <%= stylesheet_link_tag "lib/main-forms" %> + <%= stylesheet_link_tag "lib/main-list" %> + <%= stylesheet_link_tag "custom_field" %> +<% end %> +<%# javascript_include_tag "lib/bootstrap-datetimepicker.js" %> +<%# javascript_include_tag "//cdnjs.cloudflare.com/ajax/libs/jquery/1.11.0/jquery.min.js"%> +<%# javascript_include_tag "/assets/lib/jquery-ui-1.12.1/jquery-ui.min" %> +<%# javascript_include_tag "lib/module-area" %> +<%# javascript_include_tag "jquery.ui.datepicker.monthyearpicker" %> +<%# javascript_include_tag "lib/bootstrap-datetimepicker" %> + + + +
+
+
+
+
<%= GreetingCardRecord.human_attribute_name(:name) %>:<%= @greeting_card_record.name %>
+
<%= GreetingCardRecord.human_attribute_name(:mail) %>:<%= @greeting_card_record.mail %>
+
<%= GreetingCardRecord.human_attribute_name(:phone) %>:<%= @greeting_card_record.phone %>
+
+ <% greeting_card_setting.custom_fields.each do |k,v| %> + <% + required_pattern = v['required']=='true' ? '*' : '' + %> +
+ +
+ <%= show_on_front(k,v,@greeting_card_record,true) %> +
+
+ <% end %> + +
+
+ +
+ <%= @greeting_card_record.get_serial_number %> +
+
+ +
+ +
+ <%= @greeting_card_record.ip %> +
+
+ +
+ +
+ <%= @greeting_card_record.title %> +
+
+ + <% if greeting_card_setting.default_setting['appointment'] %> +
+ +
+ <%= @greeting_card_record.appointment.strftime("%Y-%m-%d %H:%M") if @greeting_card_record.appointment %> +
+
+ <% end %> + + <% if !@greeting_card_record.comment.blank? %> +
+ <%= f.label :comment, class: "control-label muted" %> +
+ <%= @greeting_card_record.comment %> +
+
+ <% end %> +
+ <%= f.label :agree_show, class: "control-label muted" %> +
+ <%= @greeting_card_record.agree_show ? t('greeting_card.yes') : t('greeting_card.no') %> +
+
+
+ <%= f.label t('situation'), class: "control-label muted" %> +
+ <%= f.select :situation, GreetingCardTicketStatus.default_sorting.map{|a| [a.title, a.key] } %> +
+
+
+ + <%= select_tags(f, @module_app) %> +
+
+ <%= f.label :send_email, class: "control-label muted" %> + +
+ + + + + + + + + +
+
+
+ <%= f.label :reply, class: "control-label muted" %> +
+ <%= f.text_area :reply, rows: 10, style: 'max-width: 500px; width: 100%;' %> +
+
+
+ +
+ <%= f.text_area :release_comment, :id => "#{f.object_name}_comment" %> +
+
+
+ +
+ <%= f.file_field :release_file %> +
+
+
+
+
+ +
+

<%= t('history') %>

+ <% if f.object.greeting_card_status_histories.present? %> + <% status_mapping = GreetingCardTicketStatus.default_sorting.map{|a| [a.key, a.title]}.to_h %> + + + + + + + + + + + + <% f.object.greeting_card_status_histories.each do |greeting_card_status_history| %> + + + + + + + + <% end %> + +
<%= t('status') %><%= t('greeting_card.modified_by') %><%= t('greeting_card.remark') %><%= t('greeting_card.attachment') %><%= t('greeting_card.updated_at') %>
+ <%= status_mapping[greeting_card_status_history.status] %> + + <%= greeting_card_status_history.modified_by_name %> + + <%= greeting_card_status_history.comment %> + + <%= link_to(greeting_card_status_history[:file], greeting_card_status_history.file.url) if greeting_card_status_history.file.present? %> + + <%= greeting_card_status_history.created_at.strftime("%Y-%m-%d %H:%M:%S") %> +
+ <% else %> +

<%= t('no_history_records') %>

+ <% end %> +
+
+
+
+<% + cancel_href = url_for(:back) + if cancel_href == 'javascript:history.back()' + now_greeting_card_page = GreetingCardRecord.order_by(:id).map(&:id).map.with_index.select{|v,i| v==@greeting_card_record.id}[0][1] rescue nil + now_greeting_card_page = now_greeting_card_page.nil? ? 1 : ((now_greeting_card_page+1).to_f/10).ceil + cancel_href = "/#{I18n.locale}/admin/greeting_cards?page=#{now_greeting_card_page}" + end +%> +
+ <%= f.submit t('submit'), class: 'btn btn-primary' %> + <%= link_to t('cancel'),cancel_href,title: t('cancel'),:class=> 'btn' %> +
diff --git a/app/views/admin/greeting_cards/_form_image.html.erb b/app/views/admin/greeting_cards/_form_image.html.erb new file mode 100644 index 0000000..bdcfac0 --- /dev/null +++ b/app/views/admin/greeting_cards/_form_image.html.erb @@ -0,0 +1,48 @@ + +
+ + <% form_image = f.object if form_image.nil? %> +
+ +
+
+
+ <% if form_image.file.file %> + <%= image_tag form_image.file %> + <% else %> + + <% end %> +
+
+ + <%= t(:select_image) %> + <%= t(:change) %> + <%= f.file_field :file %> + + <%= t(:cancel) %> +
+
+
+ <% @site_in_use_locales.each do |locale| %> + <%= f.fields_for :description_translations do |f| %> +
+ +
+ <%= f.text_field locale, value: (form_image.description_translations[locale.to_s] rescue nil) %> +
+
+ <% end %> + <% end %> +
\ No newline at end of file diff --git a/app/views/admin/greeting_cards/_form_images.html.erb b/app/views/admin/greeting_cards/_form_images.html.erb new file mode 100644 index 0000000..45c06a4 --- /dev/null +++ b/app/views/admin/greeting_cards/_form_images.html.erb @@ -0,0 +1,29 @@ +
+
+ +
+
+
+ <% greeting_card_images = greeting_card_setting.greeting_card_images %> + <% if greeting_card_images.present? && !is_default %> +
+ <% greeting_card_images.each_with_index do |greeting_card_image, i| %> + <%= f.fields_for :greeting_card_images_attributes, greeting_card_image do |f| %> + <%= f.fields_for i.to_s, greeting_card_image do |f| %> + <%= render :partial => 'form_image', :object => greeting_card_image, :locals => {:f => f, :i => i} %> + <% end %> + <% end %> + <% end %> +
+
+ <% end %> + +
+
+

+ <%= hidden_field_tag 'greeting_card_image_count', f.object.greeting_card_images.count %> + <%= t(:add) %> +

+
+
+
\ No newline at end of file diff --git a/app/views/admin/greeting_cards/_greeting_card_setting.html.erb b/app/views/admin/greeting_cards/_greeting_card_setting.html.erb new file mode 100644 index 0000000..af17645 --- /dev/null +++ b/app/views/admin/greeting_cards/_greeting_card_setting.html.erb @@ -0,0 +1,499 @@ +<% if !@include_css_js %> + <% @include_css_js = true %> + <% content_for :page_specific_css do %> + <%= stylesheet_link_tag "lib/main-forms" %> + <%= stylesheet_link_tag "lib/fileupload" %> + <%= stylesheet_link_tag "lib/main-list" %> + <% end %> + <% content_for :page_specific_javascript do %> + <%= javascript_include_tag "lib/bootstrap-fileupload" %> + <%= javascript_include_tag "lib/bootstrap-datetimepicker" %> + <%= javascript_include_tag "lib/datetimepicker/datetimepicker.js" %> + <%= javascript_include_tag "lib/file-type" %> + <%= javascript_include_tag "lib/module-area" %> + <%= javascript_include_tag "form" %> + <% end %> + +<% end %> + +<% tmp_get_input_name = get_input_name_for_greeting_card + form_type = tmp_get_input_name.split('[')[0] + set_input_name_for_greeting_card("#{form_type}") +%> +
+ <% if @has_switch_button %> + + <% elsif form_type!= 'greeting_card_setting' %> +
+
+ <%= t("greeting_card.use_default") %> + <%= hidden_field_tag "#{f.object_name}[use_default]", false %> +
+
+
+
+ <% end %> + + <%= render :partial=> "form_images", locals: {f: f, greeting_card_setting: greeting_card_setting, is_default: is_default} %> + <% if !is_default %> + + <%= render :partial=> "auto_send_setting", locals: {f: f} %> + <% end %> +
+
+ +
+
+ <%= f.check_box :only_logged_in_users, {:id=>"only_logged_in_users"} %><%= t("greeting_card.enable") %> +
+
+ <% if @has_email_regex %> +
+
+ +
+
+ <%= f.check_box :email_regex_enable, {:id=>"email_regex_enable"} %><%= t("greeting_card.enable") %> +
+ <%= f.text_field :email_regex, {:id=>"greeting_card_email_regex", :style => "width: 30em;"}.merge(f.object.email_regex_enable ? {} : {:disabled=>"disabled"}) %> +
+
+ +
+
+ +
+
+ + +
+ <%= multiple_lang_tag_for_greeting_card( + nil, + 'text_area', + 'validation_email_content_translations', + f.object.validation_email_content_translations, + { + class: 'ckeditor' + }) %> +
+
+ <% else %> +
+
+ <%= t("greeting_card.email_regex") %>: +
+
+ <%= check_box_tag :email_regex_enable, @default_greeting_card_setting.email_regex_enable , @default_greeting_card_setting.email_regex_enable , {:id=>"",:disabled=>'disabled'} %><%= t("greeting_card.enable") %> +
+
+ <%= @default_greeting_card_setting.email_regex %> +
+
+
+
+
+ +
+
+ + + <%= multiple_lang_tag_for_greeting_card( + nil, + 'text_area', + 'validation_email_content_translations', + f.object.validation_email_content_translations, + { + class: 'ckeditor' + }) %> +
+
+ <% end %> + + + + + + + <% if form_type=='greeting_card_setting' %> + + + <% end %> + + + + + <% locale = I18n.locale.to_s %> + <% + default_setting = greeting_card_setting.default_setting + if default_setting.keys[0].to_s != 'title' + default_setting = {title: true}.merge(default_setting) + end + %> + <% default_setting.each do |k,v| %> + + + + <% if form_type=='greeting_card_setting' %> + + + <% end %> + <% v = greeting_card_setting.default_setting_required[k] %> + + + <% end %> + <% set_input_name_for_greeting_card(tmp_get_input_name) %> + + + + + <% if @default_custom_fields %> + <% greeting_card_setting.custom_fields.each do |k,v| %> + <%= custom_field_block_for_greeting_card("default@#{k}",v, false, (hidden_field_tag("#{f.object_name}[agree_customs][]", k))).html_safe %> + <% end %> + <%= f.hidden_field :use_default, :value => false %> + <% else %> + <% greeting_card_setting.custom_fields.each do |k,v| %> + <%= custom_field_block_for_greeting_card(k,v).html_safe %> + <% end %> + <% end %> + + + + + + + <% has_need_check_customs = (greeting_card_setting.class == GreetingCardCategorySetting && (greeting_card_setting.need_check_customs.count + greeting_card_setting.reject_customs.count) != 0) + org_hide = greeting_card_setting.class == GreetingCardCategorySetting ? (greeting_card_setting.need_check_customs.count == 0) : true; + %> + <% if has_need_check_customs %> + + + + + <% end %> + + <% if has_need_check_customs %> + <% (greeting_card_setting.need_check_customs + greeting_card_setting.reject_customs).sort_by{|k| k.to_i}.each do |k| %> + <% if greeting_card_setting.reject_customs.include?(k) %> + <% other_first_rows = "" %> + <%= custom_field_block_for_greeting_card(k, @default_greeting_card_setting.custom_fields[k], true, other_first_rows, "discard_fields hide").html_safe %> + <% else %> + <% other_first_rows = "" %> + <%= custom_field_block_for_greeting_card(k, @default_greeting_card_setting.custom_fields[k], true, other_first_rows).html_safe %> + <% end %> + <% end %> + + <% end %> +
+ <%= t('greeting_card.field') %> + + <%= t('greeting_card.whether_open') %> + + <%= t('greeting_card.field_name') %> + + <%= t('greeting_card.prompt_word') %> + + <%= t('greeting_card.required') %> +
+ <%= tmp = (form_type=='greeting_card_setting' ? greeting_card_setting.default_field_name(k,locale, true) : @default_greeting_card_setting.field_name(k,locale, true)) %> + + <%= select_tag "#{get_input_name_for_greeting_card}[default_setting][#{k}]",options_for_select([[t('yes'),'true'],[t('no'),false]],v) %> + + <%= multiple_lang_tag_for_greeting_card(nil,'text_field',"default_setting_field_name][#{k}",greeting_card_setting.field_name_translations(k),{placeholder: tmp}) %> + + <% if GreetingCardSetting::No_prompt_word.include?(k) %> + <%= t('greeting_card.the_same_as_field_name') %> + <% else %> + <%= multiple_lang_tag_for_greeting_card(nil,'text_field',"default_setting_prompt_word][#{k}",greeting_card_setting.prompt_word_translations(k),{placeholder: tmp}) %> + <% end %> + + <% if GreetingCardSetting::Required.include?(k) %> +
<%= t('yes') %>
+ <% else %> + <%= select_tag "#{get_input_name_for_greeting_card}[default_setting_required][#{k}]",options_for_select([[t('yes'),'true'],[t('no'),false]],v) %> + <% end %> +
+ <%= t("greeting_card.usage_rule") %> + + <%= f.text_area "usage_rule",class: 'ckeditor' %> +
+ +
+ <%= hidden_field_tag :delete_field, nil,{'class'=> 'delete_field' } %> + <% if !greeting_card_setting.new_record? %> + <%= f.hidden_field :id, value: greeting_card_setting.id %> + <% end %> + +
+ <%= t('greeting_card.default_custom_fields') %> + + + + + + <%=t('greeting_card.apply_all')%> + <%=t('greeting_card.discard_changes')%> +
#{t('greeting_card.apply')}
#{t('greeting_card.apply')}#{t('greeting_card.discard')}
+ +
\ No newline at end of file diff --git a/app/views/admin/greeting_cards/_index.html.erb b/app/views/admin/greeting_cards/_index.html.erb new file mode 100644 index 0000000..aa7ffc9 --- /dev/null +++ b/app/views/admin/greeting_cards/_index.html.erb @@ -0,0 +1,256 @@ + +
+
+

<%= t(:category) %>

+ +
+
+ + + + + <% @table_fields.each do |f| %> + <%= greeting_card_thead(f, @greeting_card_setting.field_name(f)) %> + <% end %> + <% @table_ext_fields.each do |f, v| %> + <%= greeting_card_thead(f, v['field'][I18n.locale.to_s]) %> + <% end %> + + + + <% + situation_class_relation = { + "is_waiting"=>"label-important", + "is_processed"=>"label-warning", + "is_published"=>"label-success" + } + situation_trans = GreetingCardTicketStatus.all.map{|a| [a.key, a.title]}.to_h + last_serial_number = @greeting_card_setting.last_serial_number + display_length = [last_serial_number.to_s.length + 1, 4].max + edit_box_display_index = @table_fields.count > 3 ? 2 : @table_fields.count + %> + <% @greeting_cards.each do |b| %> + + <% is_editable = can_edit_or_delete?(b) + greeting_card_show_url = Page.where(:module=>'greeting_card').first.url rescue nil + edit_button_can_show = true + %> + + <% @table_fields.each_with_index do |f,i| %> + + <% end %> + <% @table_ext_fields.each do |f, v| %> + + <% end %> + + <% end %> + +
<% if is_editable %><% end %> + <% case f %> + <% when 'serial_number' %> + <%= b.get_serial_number(last_serial_number, display_length) %> + <% when 'greeting_card_category_id' %> + <%= b.category.title rescue 'category not set' %> + <% when 'image' %> + + <% when 'sender_mail' %> + <%= b.sender_mail %> + <% when 'recipients' %> + <%= b.recipients %> + <% when 'title' %> + <% if b.situation == 'is_published' %> + <%= link_to b.title, (greeting_card_show_url ? (greeting_card_show_url+"?item=#{b.id}") : "javascript:alert('#{t('greeting_card.no_index_page')}')") %> + <% else %> + <%= b.title %> + <% end %> + <% when 'name' %> + <%= b.name %> + <% when 'appointment' %> + <%= b.appointment.strftime("%Y-%m-%d %H:%M") rescue nil %> + <% when 'created_at' %> + <%= b.created_at.strftime("%Y-%m-%d %H:%M") rescue nil %> + <% when 'ip' %> + <%= b.ip %> + <% end %> + <% if edit_box_display_index == i %> + <%= render partial: 'edit_box_for_index', :locals => {is_editable: is_editable, b: b} %> + <% end %> + + <%= show_on_front(f,v,b,true) %> +
+ +<%= content_tag :div, class: "bottomnav clearfix" do %> + <%= content_tag :div, class: "pagination pagination-centered" do + paginate(@greeting_cards) + end %> + + <%= content_tag :div, link_to(I18n.t('greeting_card.display_fields_setting'), backend_table_setting_admin_greeting_cards_path + (@category_id.present? ? "?category_id=#{@category_id}" : ''), :class=>'btn btn-primary' ) + link_to(t("greeting_card.delete_selected"), "javascript:void(0)", :class=>"btn btn-danger delete_greeting_cards_btn hide", :style=>'margin-left: 0.5em;') + link_to(t("greeting_card.batch_modify_status"), "javascript:void(0)", :class=>"btn btn-success batch_modify_status_btn hide", :style=>'margin-left: 0.5em;'), class: 'pull-right' %> + +<% end %> + diff --git a/app/views/admin/greeting_cards/_print_format_explain.html.erb b/app/views/admin/greeting_cards/_print_format_explain.html.erb new file mode 100644 index 0000000..7844957 --- /dev/null +++ b/app/views/admin/greeting_cards/_print_format_explain.html.erb @@ -0,0 +1,131 @@ +<% trans = 'mongoid.attributes.greeting_card_record' %> +
+ <%= t('greeting_card.explanation') %>: +
+ {title} -> <%= t('title') %> +
+ {created_at} -> <%= t('greeting_card.created_at') %> +
+ {category} -> <%= t("#{trans}.greeting_card_category_id") %> +
+ {name} -> <%= t("#{trans}.name") %> +
+ {greetings} -> <%= t("#{trans}.greetings") %> +
+ {sender_mail} -> <%= t("#{trans}.sender_mail") %> +
+ {recipients} -> <%= t("#{trans}.recipients") %> +
+ {custom1} -> <%= t("greeting_card.custom1") %> +
+ {custom2} -> <%= t("greeting_card.custom2") %> +
+ {custom3} -> <%= t("greeting_card.custom3") %> +
+ <%= t("greeting_card.and_so_on") %> +
+ {custom} -><%= t("greeting_card.custom_explain") %> +
+ {custom|format} -><%= t("greeting_card.custom_format_explain") %> +
+ {?_title} -> ?<%= t("greeting_card.can_be_filled_with") %>title、created_at、category、name、greetings、sender_mail、recipients、custom、custom1、custom2、custom3 ...,<%= t("greeting_card.show_title_trans") %>, <%= t("greeting_card.like") %>{created_at_title}-><%= t("#{trans}.created_at") %> +
+
+ <%= t("greeting_card.example") %>: +
+ <%= t("greeting_card.example_explain") %> +
+ <%= t('greeting_card.print_format') %>: +
+ + + + + + +
<%= t("greeting_card.example") %>
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
{title_title}{title}
{category_title}{category}
{name_title}{name}
{greetings_title}{greetings}
{sender_mail_title}{sender_mail}
{recipients_title}{recipients}
{custom1_title}{custom1}
{custom2_title}{custom2}
+
+
+ <%= t('greeting_card.output_result') %>: +
+ + + + + + +
<%= t("greeting_card.example") %>
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
<%= t('title') %>example
<%= t("#{trans}.greeting_card_category_id") %>測試
<%= t("#{trans}.name") %>工程師1號
<%= t("#{trans}.greetings") %>新的一年,祝您事事順利
<%= t("#{trans}.sender_mail") %>test@rulingcom.com
<%= t("#{trans}.recipients") %>test2@rulingcom.com
test1a
test2b
+
\ No newline at end of file diff --git a/app/views/admin/greeting_cards/_show_default_greeting_card_setting.html.erb b/app/views/admin/greeting_cards/_show_default_greeting_card_setting.html.erb new file mode 100644 index 0000000..1c65cb7 --- /dev/null +++ b/app/views/admin/greeting_cards/_show_default_greeting_card_setting.html.erb @@ -0,0 +1,83 @@ +
+ + + + + + + + + + + + + + + + + + + + <% locale = I18n.locale.to_s %> + <% true_false_trans = {'true'=> t('yes'), 'false'=>t('no')} %> + <% + default_setting = @default_greeting_card_setting.default_setting + if default_setting.keys[0].to_s != 'title' + default_setting = {title: true}.merge(default_setting) + end + %> + <% default_setting.each do |k,v| %> + + + + + <% end %> + + + + + + <% @default_greeting_card_setting.custom_fields.each do |k,v| %> + <%= custom_field_block_for_greeting_card(k,v,true).html_safe %> + <% end %> +
+ <%= t('greeting_card.clone_default_setting') %> + <%= t('greeting_card.recover_data') %> +
<%= t("greeting_card.email_regex") %>: + <%= check_box_tag :email_regex_enable, @default_greeting_card_setting.email_regex_enable , nil, {:id=>"",:disabled=>'disabled'} %><%= t("greeting_card.enable") %> +
+
+ <%= @default_greeting_card_setting.email_regex %> +
+
<%= t("greeting_card.validation_email_content") %>: + <%= check_box_tag :validate_enable, @default_greeting_card_setting.validate_enable , nil, {:id=>"",:disabled=>'disabled'} %><%= t("greeting_card.enable") %> +
+
+ <%= @default_greeting_card_setting.validation_email_content %> +
+
+ <%= t('greeting_card.field') %> + + <%= t('greeting_card.whether_open') %> +
+ <%= tmp = @default_greeting_card_setting.field_name(k,locale, true) %> + + <%= + tmp2 = true_false_trans[v] + tmp2 = true_false_trans.values[0] if tmp2.nil? + tmp2 + %> +
+ <%= t("greeting_card.usage_rule") %> + + <%= @default_greeting_card_setting.usage_rule.to_s.html_safe %> +
+
\ No newline at end of file diff --git a/app/views/admin/greeting_cards/backend_table_setting.html.erb b/app/views/admin/greeting_cards/backend_table_setting.html.erb new file mode 100644 index 0000000..cbcdbf2 --- /dev/null +++ b/app/views/admin/greeting_cards/backend_table_setting.html.erb @@ -0,0 +1,47 @@ +<% cat = Category.where(:id=>params[:category_id]).first %> +<%= form_for @greeting_card_list_setting,method: 'post',url: @url,html: { class: 'form-horizontal main-forms prekiewable' } do |f| %> +
+

<%= t('greeting_card.backend_table_setting') %> - <%= cat ? cat.title : t(:all) %>

+

+ <%= I18n.t("greeting_card.default_head_for_table") %> +

+ <% tmp = GreetingCardListSetting::DefaultFields + %> + <% tmp.each do |k| %> + + + <%= f.label "default_fields_#{k}", I18n.t("greeting_card.#{k}",default: I18n.t("mongoid.attributes.greeting_card_record.#{k}")) %> + + + <%= f.check_box :default_fields, {:checked=> @greeting_card_list_setting.default_fields.include?(k), :multiple=> true}, k, nil %> + <%= f.label "default_fields_#{k}", I18n.t(:enable), {:style=> 'display: inline-block'} %> + + + <% end %> + <% @greeting_card_list_setting.custom_fields_list.each do |title, data| %> +

+ <%= title %> +

+ <% data.each do |k,name| %> + + + <%= f.label "custom_fields#{k}", name %> + + + <%= f.check_box :custom_fields, {:checked=> @greeting_card_list_setting.custom_fields.include?(k), :multiple=> true}, k, nil %> + <%= f.label "custom_fields_#{k}", I18n.t(:enable), {:style=> 'display: inline-block'} %> + + + <% end %> + <% end %> + <%= hidden_field_tag :id, @greeting_card_list_setting.id %> + + <%= f.submit t(:submit), class: 'btn btn-primary' %> + +
+ +<% end %> \ No newline at end of file diff --git a/app/views/admin/greeting_cards/category_setting.html.erb b/app/views/admin/greeting_cards/category_setting.html.erb new file mode 100644 index 0000000..68ef202 --- /dev/null +++ b/app/views/admin/greeting_cards/category_setting.html.erb @@ -0,0 +1,338 @@ +<%= stylesheet_link_tag "lib/togglebox", media: "all" %> + +<% if current_user.is_admin? || current_user.is_manager?(module_app) %> + +<% end %> + + + + + +
+ + <% + set_input_name_for_greeting_card('greeting_card_category_setting[custom_fields]') + %> + + <% + first_category_html = "" + @first_category_id = nil + @first_setting = nil + %> +
+ <% @categories.each_with_index do |category, index1| %> + <% + category_id = category.id.to_s + has_category_setting = @category_added_ids.include?(category_id) + name1 = category.title + if name1.blank? + name1 = "No Name" + end + is_default = false + if has_category_setting + category_added = @category_added[category_id] + is_default = category_added.use_default + else + category_added = @default_greeting_card_setting + is_default = true + end + if index1 == 0 + @first_category_id = category_id + @first_setting = category_added + if has_category_setting && !is_default + first_category_html = render(partial: 'category_setting_field',locals: {greeting_card_setting: category_added, is_default: is_default}) + end + end + %> + <%= name1 %> (<%=t('greeting_card.default')%>) + <% end %> +
+ <% if @first_setting %> + <%= form_for @first_setting, method: 'post', url: category_setting_save_admin_greeting_cards_path , html: { class: "form-horizontal fade-in detail #{(first_category_html.blank? ? 'active' : '')}", id: "background_setting_form_wrapper"} do |f| %> + <%= render :partial=> "category_setting_partial", locals: {f: f, greeting_card_setting: @first_setting, is_default: (@first_setting == @default_greeting_card_setting)} %> + <%= f.hidden_field :use_default, :value => true %> + <%= hidden_field_tag "category_id", @first_category_id, class: "card_category_id" %> +
+ +
+ <% end %> + <% end %> +
+ <%= first_category_html %> +
+ <% + @is_hidden = (first_category_html.present? || @categories.length == 0) + %> + <%= render(:partial=>"default_greeting_card_setting") %> +
+ \ No newline at end of file diff --git a/app/views/admin/greeting_cards/do_export.xlsx.axlsx b/app/views/admin/greeting_cards/do_export.xlsx.axlsx new file mode 100644 index 0000000..c345c26 --- /dev/null +++ b/app/views/admin/greeting_cards/do_export.xlsx.axlsx @@ -0,0 +1,50 @@ +# encoding: utf-8 + +wb = xlsx_package.workbook + +wb.add_worksheet(name: "GreetingCard Question") do |sheet| + + heading = sheet.styles.add_style(:b => true, :locked => true) + heads = [] + fields_with_key_group.each do |cat_id, fields_with_key| + fields_with_key.each do |type, fs| + fs.each do |key, field| + heads << field if !heads.include?(field) + end + end + end + sheet.add_row heads, :style => heading + + greeting_card_records.each do |greeting_card_record| + row = [] + row_group = {} + s = greeting_card_category_settings[greeting_card_record.category_id] + fields_with_key_group[greeting_card_record.category_id]['default_values'].each do |key, field| + text = '' + case key + when 'created_at', 'sending_time' + text = greeting_card_record.send(key).strftime('%Y/%m/%d %H:%M') + when 'title','name','sender_mail',recipients','greetings' + text = greeting_card_record.send(key).to_s + when 'greeting_card_category_id' + text = categories[greeting_card_record.category_id].title rescue '' + when 'agree_show' + if greeting_card_record.agree_show + text = I18n.t('greeting_card.yes') + else + text = I18n.t('greeting_card.no') + end + when 'situation' + text = situations[greeting_card_record.situation].collect{|k,v| v}.join('/') rescue '' + end + row_group[field] = text + end + fields_with_key_group[greeting_card_record.category_id]['custom_values'].each do |key, field| + row_group[field] = Admin::GreetingCardsHelper.show_on_front(key, s.custom_fields[key], greeting_card_record, true) + end + row = heads.collect do |head| + row_group[head] + end + sheet.add_row row + end +end diff --git a/app/views/admin/greeting_cards/edit.html.erb b/app/views/admin/greeting_cards/edit.html.erb new file mode 100644 index 0000000..03983a0 --- /dev/null +++ b/app/views/admin/greeting_cards/edit.html.erb @@ -0,0 +1,5 @@ +<%= form_for @greeting_card_record, url: @url, html: { class: 'form-horizontal main-forms previewable',multipart: true } do |f| %> +
+ <%= render :partial => 'form', locals: {f: f} %> +
+<% end %> \ No newline at end of file diff --git a/app/views/admin/greeting_cards/email.html.erb b/app/views/admin/greeting_cards/email.html.erb new file mode 100644 index 0000000..d73aefb --- /dev/null +++ b/app/views/admin/greeting_cards/email.html.erb @@ -0,0 +1,21 @@ + + + + + + +

+ <%= @data['reply'].to_s.gsub(/[(\n)(\r)]/, "\n" => "
", "\r" => "" ).html_safe %> +

+ <% attachment = @data['attachment'] + if attachment.present? %> + <% filename = File.basename(attachment) %> +

+ <%= t('greeting_card.attachment') %>: + <%= filename %> +

+ <% end %> +
+

<%= t('greeting_card.email_automation_hint') %>

+ + \ No newline at end of file diff --git a/app/views/admin/greeting_cards/export.html.erb b/app/views/admin/greeting_cards/export.html.erb new file mode 100644 index 0000000..19ef6c8 --- /dev/null +++ b/app/views/admin/greeting_cards/export.html.erb @@ -0,0 +1,21 @@ +
+ <%= form_tag export_admin_greeting_cards_path, method: 'post' do |f| %> + + + + + + + + + + + + +
<%= t('date_') %><%= date_select 'export', :start %> <%= t('greeting_card.to') %>
<%= date_select 'export', :end %>
+
+ <%= submit_tag t('submit'), class: 'btn btn-primary' %> +
+
+ <% end %> +
\ No newline at end of file diff --git a/app/views/admin/greeting_cards/index.html.erb b/app/views/admin/greeting_cards/index.html.erb new file mode 100644 index 0000000..bc30c02 --- /dev/null +++ b/app/views/admin/greeting_cards/index.html.erb @@ -0,0 +1,6 @@ +<%= render_filter @filter_fields, "index_table" %> + + <%= render 'index'%> + + +<%= render 'layouts/delete_modal', delete_options: @delete_options %> \ No newline at end of file diff --git a/app/views/admin/greeting_cards/layout_design.html.erb b/app/views/admin/greeting_cards/layout_design.html.erb new file mode 100644 index 0000000..d9f944d --- /dev/null +++ b/app/views/admin/greeting_cards/layout_design.html.erb @@ -0,0 +1,69 @@ +<%= stylesheet_link_tag "greeting_card/konva_helper", media: "all" %> +<%= stylesheet_link_tag "select2-v4/select2", media: "all" %> +<%= javascript_include_tag "greeting_card/konva.min" %> +<%= javascript_include_tag "greeting_card/konva_helper" %> +<%= javascript_include_tag "select2-v4/select2.min" %> + +