From 7e172b05726ebbbafccdf4571dca54054839656c Mon Sep 17 00:00:00 2001 From: choibk Date: Mon, 5 Jan 2026 12:28:46 +0900 Subject: [PATCH] Initial --- .gitignore | 68 + easy_baseline/Gemfile | 0 easy_baseline/README.md | 1 + easy_baseline/after_init.rb | 22 + .../easy_baseline_gantt_controller.rb | 44 + .../controllers/easy_baselines_controller.rb | 79 + easy_baseline/app/helpers/.gitkeep | 0 .../app/models/easy_baseline_source.rb | 22 + easy_baseline/app/views/.gitkeep | 0 .../views/easy_baseline_gantt/show.api.rsb | 23 + .../app/views/easy_baselines/index.api.rsb | 8 + .../app/views/easy_baselines/index.html.erb | 32 + .../app/views/easy_baselines/new.html.erb | 2 + easy_baseline/assets/images/.gitkeep | 0 easy_baseline/assets/javascripts/.gitkeep | 0 easy_baseline/config/locales/ar.yml | 2 + easy_baseline/config/locales/cs.yml | 11 + easy_baseline/config/locales/da.yml | 2 + easy_baseline/config/locales/de.yml | 13 + easy_baseline/config/locales/en-AU.yml | 2 + easy_baseline/config/locales/en-GB.yml | 2 + easy_baseline/config/locales/en-US.yml | 2 + easy_baseline/config/locales/en.yml | 13 + easy_baseline/config/locales/es.yml | 13 + easy_baseline/config/locales/fi.yml | 2 + easy_baseline/config/locales/fr.yml | 13 + easy_baseline/config/locales/he.yml | 2 + easy_baseline/config/locales/hr.yml | 2 + easy_baseline/config/locales/hu.yml | 12 + easy_baseline/config/locales/it.yml | 13 + easy_baseline/config/locales/ja.yml | 13 + easy_baseline/config/locales/ko.yml | 12 + easy_baseline/config/locales/mk.yml | 2 + easy_baseline/config/locales/nl.yml | 12 + easy_baseline/config/locales/no.yml | 2 + easy_baseline/config/locales/pl.yml | 12 + easy_baseline/config/locales/pt-BR.yml | 13 + easy_baseline/config/locales/pt.yml | 2 + easy_baseline/config/locales/ro.yml | 2 + easy_baseline/config/locales/ru.yml | 12 + easy_baseline/config/locales/sk.yml | 2 + easy_baseline/config/locales/sl.yml | 6 + easy_baseline/config/locales/sq.yml | 2 + easy_baseline/config/locales/sr-YU.yml | 2 + easy_baseline/config/locales/sr.yml | 2 + easy_baseline/config/locales/sv.yml | 2 + easy_baseline/config/locales/th.yml | 2 + easy_baseline/config/locales/tr.yml | 2 + easy_baseline/config/locales/zh-TW.yml | 2 + easy_baseline/config/locales/zh.yml | 12 + easy_baseline/config/routes.rb | 6 + ...233049_add_easy_baseline_for_to_project.rb | 5 + ...0812122322_create_easy_baseline_sources.rb | 10 + easy_baseline/init.rb | 11 + easy_baseline/lib/easy_baseline.rb | 23 + easy_baseline/lib/easy_baseline/hooks.rb | 9 + .../lib/easy_baseline/issue_patch.rb | 24 + .../lib/easy_baseline/project_patch.rb | 83 + .../lib/easy_baseline/version_patch.rb | 28 + .../easy_baselines_controller_spec.rb | 35 + easy_gantt/Gemfile | 1 + easy_gantt/LICENSE | 60 + easy_gantt/README.md | 3 + easy_gantt/after_init.rb | 97 + .../app/controllers/easy_gantt_controller.rb | 261 + easy_gantt/app/helpers/easy_gantt_helper.rb | 219 + .../easy_gantt/easy_gantt_issue_query.rb | 82 + .../easy_gantt/easy_gantt_project_query.rb | 92 + .../easy_gantt/_already_active_error.html.erb | 3 + .../app/views/easy_gantt/_includes.html.erb | 49 + .../app/views/easy_gantt/_js_prepare.html.erb | 151 + .../app/views/easy_gantt/_menu_graph.html.erb | 71 + .../easy_gantt/easy_queries/_show.html.erb | 72 + .../app/views/easy_gantt/index.html.erb | 176 + .../app/views/easy_gantt/issues.api.rsb | 12 + .../views/easy_gantt/project_issues.api.rsb | 5 + .../app/views/easy_gantt/projects.api.rsb | 7 + .../_filters_custom_formatting.html.erb | 5 + .../app/views/settings/_easy_gantt.html.erb | 44 + easy_gantt/assets/data/gantt.json | 84 + easy_gantt/assets/data/sample_1.json | 565 + easy_gantt/assets/data/sample_global.json | 9663 ++++++++++++ .../fonts/EasyMaterialIcons-Regular.woff2 | Bin 0 -> 80288 bytes easy_gantt/assets/images/.gitkeep | 0 easy_gantt/assets/images/yt.png | Bin 0 -> 763 bytes .../javascripts/easy_gantt/background.js | 304 + .../assets/javascripts/easy_gantt/bars.js | 278 + .../javascripts/easy_gantt/collapsor.js | 148 + .../assets/javascripts/easy_gantt/data.js | 1046 ++ .../javascripts/easy_gantt/dhtml_addons.js | 478 + .../javascripts/easy_gantt/dhtml_modif.js | 696 + .../javascripts/easy_gantt/dhtml_relations.js | 623 + .../javascripts/easy_gantt/dhtml_rewrite.js | 703 + .../javascripts/easy_gantt/dhtmlxgantt.js | 9439 +++++++++++ .../easy_gantt/dhtmlxgantt_marker.js | 127 + .../javascripts/easy_gantt/easy_gantt.js | 9 + .../javascripts/easy_gantt/flash_message.js | 37 + .../javascripts/easy_gantt/gantt_widget.js | 589 + .../assets/javascripts/easy_gantt/history.js | 130 + .../javascripts/easy_gantt/left_grid.js | 331 + .../javascripts/easy_gantt/libs/moment.js | 12915 ++++++++++++++++ .../javascripts/easy_gantt/libs/mustache.js | 740 + .../assets/javascripts/easy_gantt/libs/raf.js | 9 + .../assets/javascripts/easy_gantt/libs/svg.js | 5472 +++++++ .../assets/javascripts/easy_gantt/loader.js | 278 + .../assets/javascripts/easy_gantt/logger.js | 92 + .../assets/javascripts/easy_gantt/main.js | 15 + .../javascripts/easy_gantt/panel_widget.js | 470 + .../assets/javascripts/easy_gantt/print.js | 277 + .../javascripts/easy_gantt/pro_manager.js | 72 + .../javascripts/easy_gantt/problem_finder.js | 172 + .../assets/javascripts/easy_gantt/sample.js | 141 + .../assets/javascripts/easy_gantt/saver.js | 529 + .../assets/javascripts/easy_gantt/storage.js | 38 + .../assets/javascripts/easy_gantt/sumrow.js | 201 + .../javascripts/easy_gantt/toolpanel.js | 91 + .../assets/javascripts/easy_gantt/tooltip.js | 217 + .../assets/javascripts/easy_gantt/utils.js | 119 + .../assets/javascripts/easy_gantt/view.js | 48 + .../assets/javascripts/easy_gantt/widget.js | 266 + .../stylesheets/easy_gantt/easy_gantt.css | 3295 ++++ .../stylesheets/easy_gantt/modal_editor.css | 63 + easy_gantt/config/locales/ar.yml | 2 + easy_gantt/config/locales/cs.yml | 183 + easy_gantt/config/locales/da.yml | 2 + easy_gantt/config/locales/de.yml | 194 + easy_gantt/config/locales/en-AU.yml | 2 + easy_gantt/config/locales/en-GB.yml | 2 + easy_gantt/config/locales/en-US.yml | 2 + easy_gantt/config/locales/en.yml | 162 + easy_gantt/config/locales/es.yml | 163 + easy_gantt/config/locales/fi.yml | 2 + easy_gantt/config/locales/fr.yml | 163 + easy_gantt/config/locales/he.yml | 2 + easy_gantt/config/locales/hr.yml | 8 + easy_gantt/config/locales/hu.yml | 159 + easy_gantt/config/locales/it.yml | 164 + easy_gantt/config/locales/ja.yml | 146 + easy_gantt/config/locales/ko.yml | 143 + easy_gantt/config/locales/mk.yml | 2 + easy_gantt/config/locales/nl.yml | 159 + easy_gantt/config/locales/no.yml | 2 + easy_gantt/config/locales/pl.yml | 160 + easy_gantt/config/locales/pt-BR.yml | 161 + easy_gantt/config/locales/pt.yml | 2 + easy_gantt/config/locales/ro.yml | 19 + easy_gantt/config/locales/ru.yml | 185 + easy_gantt/config/locales/sk.yml | 2 + easy_gantt/config/locales/sl.yml | 2 + easy_gantt/config/locales/sq.yml | 2 + easy_gantt/config/locales/sr-YU.yml | 2 + easy_gantt/config/locales/sr.yml | 2 + easy_gantt/config/locales/sv.yml | 2 + easy_gantt/config/locales/th.yml | 2 + easy_gantt/config/locales/tr.yml | 2 + easy_gantt/config/locales/zh-TW.yml | 2 + easy_gantt/config/locales/zh.yml | 138 + easy_gantt/config/routes.rb | 13 + ...13152215_add_default_printable_template.rb | 10 + ...20170224134615_update_rest_api_settings.rb | 10 + easy_gantt/init.rb | 21 + easy_gantt/lib/easy_gantt.rb | 34 + .../easy_gantt/application_helper_patch.rb | 17 + easy_gantt/lib/easy_gantt/hooks.rb | 7 + easy_gantt/lib/easy_gantt/issue_patch.rb | 50 + .../lib/easy_gantt/issue_relation_patch.rb | 70 + easy_gantt/lib/easy_gantt/project_patch.rb | 157 + .../easy_gantt/queries_controller_patch.rb | 27 + easy_gantt/lib/easy_gantt/version_patch.rb | 23 + .../controllers/easy_gantt_controller_spec.rb | 28 + easy_gantt/spec/features/add_task_spec.rb | 24 + easy_gantt/spec/features/correct_tree_spec.rb | 73 + easy_gantt/spec/features/easy_gantt_spec.rb | 25 + easy_gantt/spec/features/gantt_save_spec.rb | 52 + easy_gantt/spec/features/global_gantt_spec.rb | 30 + easy_gantt/spec/requests/permissions_spec.rb | 71 + easy_gantt_pro/.run-linter.sh | 18 + easy_gantt_pro/.run-tests.sh | 169 + easy_gantt_pro/LICENSE | 60 + easy_gantt_pro/README.md | 3 + easy_gantt_pro/after_init.rb | 72 + .../controllers/easy_gantt_pro_controller.rb | 64 + easy_gantt_pro/app/helpers/.gitkeep | 0 .../easy_gantt_suppress_notification.rb | 3 + .../easy_gantt_pro/_gantt_tools.html.erb | 0 .../views/easy_gantt_pro/_includes.html.erb | 16 + .../views/easy_gantt_pro/_js_prepare.html.erb | 148 + .../lowest_progress_tasks.api.rsb | 15 + .../projects/_easy_global_gantt_edit.html.erb | 0 .../projects/_easy_global_gantt_show.html.erb | 8 + .../_easy_project_gantt_edit.html.erb | 0 .../_easy_project_gantt_show.html.erb | 12 + .../easy_settings/_easy_gantt_pro.html.erb | 38 + .../_view_easy_gantt_index_bottom.html.erb | 4 + ..._view_easy_gantts_issues_toolbars.html.erb | 42 + .../javascripts/easy_gantt_pro/add_task.js | 500 + .../javascripts/easy_gantt_pro/baseline.js | 427 + .../javascripts/easy_gantt_pro/common.js | 80 + .../javascripts/easy_gantt_pro/critical.js | 337 + .../easy_gantt_pro/delayed_issues.js | 66 + .../easy_gantt_pro/delayed_projects.js | 53 + .../easy_gantt_pro/easy_gantt_pro.js | 12 + .../easy_gantt_pro/easy_gantt_pro_global.js | 13 + .../easy_gantt_pro/email_silencer.js | 32 + .../javascripts/easy_gantt_pro/gg_resource.js | 158 + .../easy_gantt_pro/lowest_progress.js | 150 + .../easy_gantt_pro/project_move.js | 51 + .../javascripts/easy_gantt_pro/sorting.js | 80 + .../easy_gantt_pro/easy_gantt_pro.css | 93 + easy_gantt_pro/config/locales/ar.yml | 2 + easy_gantt_pro/config/locales/cs.yml | 76 + easy_gantt_pro/config/locales/da.yml | 2 + easy_gantt_pro/config/locales/de.yml | 78 + easy_gantt_pro/config/locales/en-AU.yml | 2 + easy_gantt_pro/config/locales/en-GB.yml | 2 + easy_gantt_pro/config/locales/en-US.yml | 2 + easy_gantt_pro/config/locales/en.yml | 72 + easy_gantt_pro/config/locales/es.yml | 75 + easy_gantt_pro/config/locales/fi.yml | 2 + easy_gantt_pro/config/locales/fr.yml | 77 + easy_gantt_pro/config/locales/he.yml | 2 + easy_gantt_pro/config/locales/hr.yml | 2 + easy_gantt_pro/config/locales/hu.yml | 73 + easy_gantt_pro/config/locales/it.yml | 73 + easy_gantt_pro/config/locales/ja.yml | 62 + easy_gantt_pro/config/locales/ko.yml | 65 + easy_gantt_pro/config/locales/mk.yml | 2 + easy_gantt_pro/config/locales/nl.yml | 74 + easy_gantt_pro/config/locales/no.yml | 2 + easy_gantt_pro/config/locales/pl.yml | 74 + easy_gantt_pro/config/locales/pt-BR.yml | 73 + easy_gantt_pro/config/locales/pt.yml | 2 + easy_gantt_pro/config/locales/ro.yml | 2 + easy_gantt_pro/config/locales/ru.yml | 72 + easy_gantt_pro/config/locales/sk.yml | 2 + easy_gantt_pro/config/locales/sl.yml | 2 + easy_gantt_pro/config/locales/sq.yml | 2 + easy_gantt_pro/config/locales/sr-YU.yml | 2 + easy_gantt_pro/config/locales/sr.yml | 2 + easy_gantt_pro/config/locales/sv.yml | 2 + easy_gantt_pro/config/locales/th.yml | 2 + easy_gantt_pro/config/locales/tr.yml | 2 + easy_gantt_pro/config/locales/zh-TW.yml | 2 + easy_gantt_pro/config/locales/zh.yml | 63 + easy_gantt_pro/config/routes.rb | 11 + easy_gantt_pro/init.rb | 13 + easy_gantt_pro/lib/easy_gantt_pro.rb | 2 + easy_gantt_pro/lib/easy_gantt_pro/hooks.rb | 7 + .../easy_gantt_pro/issues_controller_patch.rb | 24 + .../easy_gantt_pro/suppress_notification.rb | 25 + easy_gantt_pro/spec/features/add_task_spec.rb | 91 + easy_gantt_pro/spec/features/baseline_spec.rb | 68 + .../spec/features/correct_tree_spec.rb | 57 + easy_gantt_pro/spec/features/critical_spec.rb | 53 + .../spec/features/easy_gantt_spec.rb | 21 + .../spec/features/global_gantt_spec.rb | 65 + easy_gantt_pro/test/test_helper.rb | 2 + easy_gantt_pro/version | 1 + 258 files changed, 60235 insertions(+) create mode 100644 .gitignore create mode 100644 easy_baseline/Gemfile create mode 100644 easy_baseline/README.md create mode 100644 easy_baseline/after_init.rb create mode 100644 easy_baseline/app/controllers/easy_baseline_gantt_controller.rb create mode 100644 easy_baseline/app/controllers/easy_baselines_controller.rb create mode 100644 easy_baseline/app/helpers/.gitkeep create mode 100644 easy_baseline/app/models/easy_baseline_source.rb create mode 100644 easy_baseline/app/views/.gitkeep create mode 100644 easy_baseline/app/views/easy_baseline_gantt/show.api.rsb create mode 100644 easy_baseline/app/views/easy_baselines/index.api.rsb create mode 100644 easy_baseline/app/views/easy_baselines/index.html.erb create mode 100644 easy_baseline/app/views/easy_baselines/new.html.erb create mode 100644 easy_baseline/assets/images/.gitkeep create mode 100644 easy_baseline/assets/javascripts/.gitkeep create mode 100644 easy_baseline/config/locales/ar.yml create mode 100644 easy_baseline/config/locales/cs.yml create mode 100644 easy_baseline/config/locales/da.yml create mode 100644 easy_baseline/config/locales/de.yml create mode 100644 easy_baseline/config/locales/en-AU.yml create mode 100644 easy_baseline/config/locales/en-GB.yml create mode 100644 easy_baseline/config/locales/en-US.yml create mode 100644 easy_baseline/config/locales/en.yml create mode 100644 easy_baseline/config/locales/es.yml create mode 100644 easy_baseline/config/locales/fi.yml create mode 100644 easy_baseline/config/locales/fr.yml create mode 100644 easy_baseline/config/locales/he.yml create mode 100644 easy_baseline/config/locales/hr.yml create mode 100644 easy_baseline/config/locales/hu.yml create mode 100644 easy_baseline/config/locales/it.yml create mode 100644 easy_baseline/config/locales/ja.yml create mode 100644 easy_baseline/config/locales/ko.yml create mode 100644 easy_baseline/config/locales/mk.yml create mode 100644 easy_baseline/config/locales/nl.yml create mode 100644 easy_baseline/config/locales/no.yml create mode 100644 easy_baseline/config/locales/pl.yml create mode 100644 easy_baseline/config/locales/pt-BR.yml create mode 100644 easy_baseline/config/locales/pt.yml create mode 100644 easy_baseline/config/locales/ro.yml create mode 100644 easy_baseline/config/locales/ru.yml create mode 100644 easy_baseline/config/locales/sk.yml create mode 100644 easy_baseline/config/locales/sl.yml create mode 100644 easy_baseline/config/locales/sq.yml create mode 100644 easy_baseline/config/locales/sr-YU.yml create mode 100644 easy_baseline/config/locales/sr.yml create mode 100644 easy_baseline/config/locales/sv.yml create mode 100644 easy_baseline/config/locales/th.yml create mode 100644 easy_baseline/config/locales/tr.yml create mode 100644 easy_baseline/config/locales/zh-TW.yml create mode 100644 easy_baseline/config/locales/zh.yml create mode 100644 easy_baseline/config/routes.rb create mode 100644 easy_baseline/db/migrate/20150810233049_add_easy_baseline_for_to_project.rb create mode 100644 easy_baseline/db/migrate/20150812122322_create_easy_baseline_sources.rb create mode 100644 easy_baseline/init.rb create mode 100644 easy_baseline/lib/easy_baseline.rb create mode 100644 easy_baseline/lib/easy_baseline/hooks.rb create mode 100644 easy_baseline/lib/easy_baseline/issue_patch.rb create mode 100644 easy_baseline/lib/easy_baseline/project_patch.rb create mode 100644 easy_baseline/lib/easy_baseline/version_patch.rb create mode 100644 easy_baseline/spec/controllers/easy_baselines_controller_spec.rb create mode 100644 easy_gantt/Gemfile create mode 100644 easy_gantt/LICENSE create mode 100644 easy_gantt/README.md create mode 100644 easy_gantt/after_init.rb create mode 100644 easy_gantt/app/controllers/easy_gantt_controller.rb create mode 100644 easy_gantt/app/helpers/easy_gantt_helper.rb create mode 100644 easy_gantt/app/models/easy_gantt/easy_gantt_issue_query.rb create mode 100644 easy_gantt/app/models/easy_gantt/easy_gantt_project_query.rb create mode 100644 easy_gantt/app/views/easy_gantt/_already_active_error.html.erb create mode 100644 easy_gantt/app/views/easy_gantt/_includes.html.erb create mode 100644 easy_gantt/app/views/easy_gantt/_js_prepare.html.erb create mode 100644 easy_gantt/app/views/easy_gantt/_menu_graph.html.erb create mode 100644 easy_gantt/app/views/easy_gantt/easy_queries/_show.html.erb create mode 100644 easy_gantt/app/views/easy_gantt/index.html.erb create mode 100644 easy_gantt/app/views/easy_gantt/issues.api.rsb create mode 100644 easy_gantt/app/views/easy_gantt/project_issues.api.rsb create mode 100644 easy_gantt/app/views/easy_gantt/projects.api.rsb create mode 100644 easy_gantt/app/views/easy_gantt_easy_issue_queries/_filters_custom_formatting.html.erb create mode 100644 easy_gantt/app/views/settings/_easy_gantt.html.erb create mode 100644 easy_gantt/assets/data/gantt.json create mode 100644 easy_gantt/assets/data/sample_1.json create mode 100644 easy_gantt/assets/data/sample_global.json create mode 100644 easy_gantt/assets/fonts/EasyMaterialIcons-Regular.woff2 create mode 100644 easy_gantt/assets/images/.gitkeep create mode 100644 easy_gantt/assets/images/yt.png create mode 100644 easy_gantt/assets/javascripts/easy_gantt/background.js create mode 100644 easy_gantt/assets/javascripts/easy_gantt/bars.js create mode 100644 easy_gantt/assets/javascripts/easy_gantt/collapsor.js create mode 100644 easy_gantt/assets/javascripts/easy_gantt/data.js create mode 100644 easy_gantt/assets/javascripts/easy_gantt/dhtml_addons.js create mode 100644 easy_gantt/assets/javascripts/easy_gantt/dhtml_modif.js create mode 100644 easy_gantt/assets/javascripts/easy_gantt/dhtml_relations.js create mode 100644 easy_gantt/assets/javascripts/easy_gantt/dhtml_rewrite.js create mode 100644 easy_gantt/assets/javascripts/easy_gantt/dhtmlxgantt.js create mode 100644 easy_gantt/assets/javascripts/easy_gantt/dhtmlxgantt_marker.js create mode 100644 easy_gantt/assets/javascripts/easy_gantt/easy_gantt.js create mode 100644 easy_gantt/assets/javascripts/easy_gantt/flash_message.js create mode 100644 easy_gantt/assets/javascripts/easy_gantt/gantt_widget.js create mode 100644 easy_gantt/assets/javascripts/easy_gantt/history.js create mode 100644 easy_gantt/assets/javascripts/easy_gantt/left_grid.js create mode 100644 easy_gantt/assets/javascripts/easy_gantt/libs/moment.js create mode 100644 easy_gantt/assets/javascripts/easy_gantt/libs/mustache.js create mode 100644 easy_gantt/assets/javascripts/easy_gantt/libs/raf.js create mode 100644 easy_gantt/assets/javascripts/easy_gantt/libs/svg.js create mode 100644 easy_gantt/assets/javascripts/easy_gantt/loader.js create mode 100644 easy_gantt/assets/javascripts/easy_gantt/logger.js create mode 100644 easy_gantt/assets/javascripts/easy_gantt/main.js create mode 100644 easy_gantt/assets/javascripts/easy_gantt/panel_widget.js create mode 100644 easy_gantt/assets/javascripts/easy_gantt/print.js create mode 100644 easy_gantt/assets/javascripts/easy_gantt/pro_manager.js create mode 100644 easy_gantt/assets/javascripts/easy_gantt/problem_finder.js create mode 100644 easy_gantt/assets/javascripts/easy_gantt/sample.js create mode 100644 easy_gantt/assets/javascripts/easy_gantt/saver.js create mode 100644 easy_gantt/assets/javascripts/easy_gantt/storage.js create mode 100644 easy_gantt/assets/javascripts/easy_gantt/sumrow.js create mode 100644 easy_gantt/assets/javascripts/easy_gantt/toolpanel.js create mode 100644 easy_gantt/assets/javascripts/easy_gantt/tooltip.js create mode 100644 easy_gantt/assets/javascripts/easy_gantt/utils.js create mode 100644 easy_gantt/assets/javascripts/easy_gantt/view.js create mode 100644 easy_gantt/assets/javascripts/easy_gantt/widget.js create mode 100644 easy_gantt/assets/stylesheets/easy_gantt/easy_gantt.css create mode 100644 easy_gantt/assets/stylesheets/easy_gantt/modal_editor.css create mode 100644 easy_gantt/config/locales/ar.yml create mode 100644 easy_gantt/config/locales/cs.yml create mode 100644 easy_gantt/config/locales/da.yml create mode 100644 easy_gantt/config/locales/de.yml create mode 100644 easy_gantt/config/locales/en-AU.yml create mode 100644 easy_gantt/config/locales/en-GB.yml create mode 100644 easy_gantt/config/locales/en-US.yml create mode 100644 easy_gantt/config/locales/en.yml create mode 100644 easy_gantt/config/locales/es.yml create mode 100644 easy_gantt/config/locales/fi.yml create mode 100644 easy_gantt/config/locales/fr.yml create mode 100644 easy_gantt/config/locales/he.yml create mode 100644 easy_gantt/config/locales/hr.yml create mode 100644 easy_gantt/config/locales/hu.yml create mode 100644 easy_gantt/config/locales/it.yml create mode 100644 easy_gantt/config/locales/ja.yml create mode 100644 easy_gantt/config/locales/ko.yml create mode 100644 easy_gantt/config/locales/mk.yml create mode 100644 easy_gantt/config/locales/nl.yml create mode 100644 easy_gantt/config/locales/no.yml create mode 100644 easy_gantt/config/locales/pl.yml create mode 100644 easy_gantt/config/locales/pt-BR.yml create mode 100644 easy_gantt/config/locales/pt.yml create mode 100644 easy_gantt/config/locales/ro.yml create mode 100644 easy_gantt/config/locales/ru.yml create mode 100644 easy_gantt/config/locales/sk.yml create mode 100644 easy_gantt/config/locales/sl.yml create mode 100644 easy_gantt/config/locales/sq.yml create mode 100644 easy_gantt/config/locales/sr-YU.yml create mode 100644 easy_gantt/config/locales/sr.yml create mode 100644 easy_gantt/config/locales/sv.yml create mode 100644 easy_gantt/config/locales/th.yml create mode 100644 easy_gantt/config/locales/tr.yml create mode 100644 easy_gantt/config/locales/zh-TW.yml create mode 100644 easy_gantt/config/locales/zh.yml create mode 100644 easy_gantt/config/routes.rb create mode 100644 easy_gantt/db/migrate/20170213152215_add_default_printable_template.rb create mode 100644 easy_gantt/db/migrate/20170224134615_update_rest_api_settings.rb create mode 100644 easy_gantt/init.rb create mode 100644 easy_gantt/lib/easy_gantt.rb create mode 100644 easy_gantt/lib/easy_gantt/application_helper_patch.rb create mode 100644 easy_gantt/lib/easy_gantt/hooks.rb create mode 100644 easy_gantt/lib/easy_gantt/issue_patch.rb create mode 100644 easy_gantt/lib/easy_gantt/issue_relation_patch.rb create mode 100644 easy_gantt/lib/easy_gantt/project_patch.rb create mode 100644 easy_gantt/lib/easy_gantt/queries_controller_patch.rb create mode 100644 easy_gantt/lib/easy_gantt/version_patch.rb create mode 100644 easy_gantt/spec/controllers/easy_gantt_controller_spec.rb create mode 100644 easy_gantt/spec/features/add_task_spec.rb create mode 100644 easy_gantt/spec/features/correct_tree_spec.rb create mode 100644 easy_gantt/spec/features/easy_gantt_spec.rb create mode 100644 easy_gantt/spec/features/gantt_save_spec.rb create mode 100644 easy_gantt/spec/features/global_gantt_spec.rb create mode 100644 easy_gantt/spec/requests/permissions_spec.rb create mode 100644 easy_gantt_pro/.run-linter.sh create mode 100644 easy_gantt_pro/.run-tests.sh create mode 100644 easy_gantt_pro/LICENSE create mode 100644 easy_gantt_pro/README.md create mode 100644 easy_gantt_pro/after_init.rb create mode 100644 easy_gantt_pro/app/controllers/easy_gantt_pro_controller.rb create mode 100644 easy_gantt_pro/app/helpers/.gitkeep create mode 100644 easy_gantt_pro/app/helpers/easy_gantt_suppress_notification.rb create mode 100644 easy_gantt_pro/app/views/easy_gantt_pro/_gantt_tools.html.erb create mode 100644 easy_gantt_pro/app/views/easy_gantt_pro/_includes.html.erb create mode 100644 easy_gantt_pro/app/views/easy_gantt_pro/_js_prepare.html.erb create mode 100644 easy_gantt_pro/app/views/easy_gantt_pro/lowest_progress_tasks.api.rsb create mode 100644 easy_gantt_pro/app/views/easy_page_modules/projects/_easy_global_gantt_edit.html.erb create mode 100644 easy_gantt_pro/app/views/easy_page_modules/projects/_easy_global_gantt_show.html.erb create mode 100644 easy_gantt_pro/app/views/easy_page_modules/projects/_easy_project_gantt_edit.html.erb create mode 100644 easy_gantt_pro/app/views/easy_page_modules/projects/_easy_project_gantt_show.html.erb create mode 100644 easy_gantt_pro/app/views/easy_settings/_easy_gantt_pro.html.erb create mode 100644 easy_gantt_pro/app/views/hooks/easy_gantt_pro/_view_easy_gantt_index_bottom.html.erb create mode 100644 easy_gantt_pro/app/views/hooks/easy_gantt_pro/_view_easy_gantts_issues_toolbars.html.erb create mode 100644 easy_gantt_pro/assets/javascripts/easy_gantt_pro/add_task.js create mode 100644 easy_gantt_pro/assets/javascripts/easy_gantt_pro/baseline.js create mode 100644 easy_gantt_pro/assets/javascripts/easy_gantt_pro/common.js create mode 100644 easy_gantt_pro/assets/javascripts/easy_gantt_pro/critical.js create mode 100644 easy_gantt_pro/assets/javascripts/easy_gantt_pro/delayed_issues.js create mode 100644 easy_gantt_pro/assets/javascripts/easy_gantt_pro/delayed_projects.js create mode 100644 easy_gantt_pro/assets/javascripts/easy_gantt_pro/easy_gantt_pro.js create mode 100644 easy_gantt_pro/assets/javascripts/easy_gantt_pro/easy_gantt_pro_global.js create mode 100644 easy_gantt_pro/assets/javascripts/easy_gantt_pro/email_silencer.js create mode 100644 easy_gantt_pro/assets/javascripts/easy_gantt_pro/gg_resource.js create mode 100644 easy_gantt_pro/assets/javascripts/easy_gantt_pro/lowest_progress.js create mode 100644 easy_gantt_pro/assets/javascripts/easy_gantt_pro/project_move.js create mode 100644 easy_gantt_pro/assets/javascripts/easy_gantt_pro/sorting.js create mode 100644 easy_gantt_pro/assets/stylesheets/easy_gantt_pro/easy_gantt_pro.css create mode 100644 easy_gantt_pro/config/locales/ar.yml create mode 100644 easy_gantt_pro/config/locales/cs.yml create mode 100644 easy_gantt_pro/config/locales/da.yml create mode 100644 easy_gantt_pro/config/locales/de.yml create mode 100644 easy_gantt_pro/config/locales/en-AU.yml create mode 100644 easy_gantt_pro/config/locales/en-GB.yml create mode 100644 easy_gantt_pro/config/locales/en-US.yml create mode 100644 easy_gantt_pro/config/locales/en.yml create mode 100644 easy_gantt_pro/config/locales/es.yml create mode 100644 easy_gantt_pro/config/locales/fi.yml create mode 100644 easy_gantt_pro/config/locales/fr.yml create mode 100644 easy_gantt_pro/config/locales/he.yml create mode 100644 easy_gantt_pro/config/locales/hr.yml create mode 100644 easy_gantt_pro/config/locales/hu.yml create mode 100644 easy_gantt_pro/config/locales/it.yml create mode 100644 easy_gantt_pro/config/locales/ja.yml create mode 100644 easy_gantt_pro/config/locales/ko.yml create mode 100644 easy_gantt_pro/config/locales/mk.yml create mode 100644 easy_gantt_pro/config/locales/nl.yml create mode 100644 easy_gantt_pro/config/locales/no.yml create mode 100644 easy_gantt_pro/config/locales/pl.yml create mode 100644 easy_gantt_pro/config/locales/pt-BR.yml create mode 100644 easy_gantt_pro/config/locales/pt.yml create mode 100644 easy_gantt_pro/config/locales/ro.yml create mode 100644 easy_gantt_pro/config/locales/ru.yml create mode 100644 easy_gantt_pro/config/locales/sk.yml create mode 100644 easy_gantt_pro/config/locales/sl.yml create mode 100644 easy_gantt_pro/config/locales/sq.yml create mode 100644 easy_gantt_pro/config/locales/sr-YU.yml create mode 100644 easy_gantt_pro/config/locales/sr.yml create mode 100644 easy_gantt_pro/config/locales/sv.yml create mode 100644 easy_gantt_pro/config/locales/th.yml create mode 100644 easy_gantt_pro/config/locales/tr.yml create mode 100644 easy_gantt_pro/config/locales/zh-TW.yml create mode 100644 easy_gantt_pro/config/locales/zh.yml create mode 100644 easy_gantt_pro/config/routes.rb create mode 100644 easy_gantt_pro/init.rb create mode 100644 easy_gantt_pro/lib/easy_gantt_pro.rb create mode 100644 easy_gantt_pro/lib/easy_gantt_pro/hooks.rb create mode 100644 easy_gantt_pro/lib/easy_gantt_pro/issues_controller_patch.rb create mode 100644 easy_gantt_pro/lib/easy_gantt_pro/suppress_notification.rb create mode 100644 easy_gantt_pro/spec/features/add_task_spec.rb create mode 100644 easy_gantt_pro/spec/features/baseline_spec.rb create mode 100644 easy_gantt_pro/spec/features/correct_tree_spec.rb create mode 100644 easy_gantt_pro/spec/features/critical_spec.rb create mode 100644 easy_gantt_pro/spec/features/easy_gantt_spec.rb create mode 100644 easy_gantt_pro/spec/features/global_gantt_spec.rb create mode 100644 easy_gantt_pro/test/test_helper.rb create mode 100644 easy_gantt_pro/version diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e988f5f --- /dev/null +++ b/.gitignore @@ -0,0 +1,68 @@ +# ========================= +# OS / Editor +# ========================= +.DS_Store +Thumbs.db +.vscode/ +.idea/ +*.swp +*.swo + +# ========================= +# Ruby / Rails +# ========================= +.bundle/ +vendor/bundle/ +coverage/ +tmp/ +log/ +*.log + +# ========================= +# Redmine / Rails cache +# ========================= +public/assets/ +public/packs/ +node_modules/ +.yarn/ +.yarn-cache/ + +# ========================= +# Database (local test only) +# ========================= +*.sqlite3 +*.sqlite3-journal + +# ========================= +# Test / CI artifacts +# ========================= +spec/examples.txt +rerun.txt + +# ========================= +# Plugin build / generated files +# ========================= +*.gem +pkg/ +*.rbc + +# ========================= +# Security / Local override +# ========================= +.env +.env.* +config/database.yml +config/secrets.yml +config/initializers/secret_token.rb + +# ========================= +# Easy plugins specific +# ========================= +easy_*/tmp/ +easy_*/log/ +easy_*/public/assets/ + +# ========================= +# Linter / Test scripts output +# ========================= +.run-*.log diff --git a/easy_baseline/Gemfile b/easy_baseline/Gemfile new file mode 100644 index 0000000..e69de29 diff --git a/easy_baseline/README.md b/easy_baseline/README.md new file mode 100644 index 0000000..5ea3505 --- /dev/null +++ b/easy_baseline/README.md @@ -0,0 +1 @@ +# Easy Baseline diff --git a/easy_baseline/after_init.rb b/easy_baseline/after_init.rb new file mode 100644 index 0000000..21cd196 --- /dev/null +++ b/easy_baseline/after_init.rb @@ -0,0 +1,22 @@ +lib_dir = File.join(File.dirname(__FILE__), 'lib', 'easy_baseline') + +# Redmine patches +patch_path = File.join(lib_dir, '*_patch.rb') +Dir.glob(patch_path).each do |file| + require file +end + +require lib_dir +require File.join(lib_dir, 'hooks') + +Redmine::AccessControl.map do |map| + map.project_module :easy_baselines do |pmap| + pmap.permission :view_baselines, { + easy_baselines: [:index, :show], + easy_baseline_gantt: [:show] + } + pmap.permission :edit_baselines, { + easy_baselines: [:create, :destroy, :new] + } + end +end diff --git a/easy_baseline/app/controllers/easy_baseline_gantt_controller.rb b/easy_baseline/app/controllers/easy_baseline_gantt_controller.rb new file mode 100644 index 0000000..36d6272 --- /dev/null +++ b/easy_baseline/app/controllers/easy_baseline_gantt_controller.rb @@ -0,0 +1,44 @@ +class EasyBaselineGanttController < ApplicationController + accept_api_auth :show + + before_action :find_baseline + before_action :authorize + + def show + # Wait for gantt modifications + issue_ids = @project.issues.pluck(:id) + version_ids = @project.versions.pluck(:id) + + b_table = EasyBaselineSource.table_name + i_table = Issue.table_name + v_table = Version.table_name + + @issues = @baseline.issues. + joins(:easy_baseline_source). + where(easy_baseline_sources: { source_id: issue_ids }). + pluck("#{i_table}.start_date, #{i_table}.due_date, #{i_table}.done_ratio, #{b_table}.source_id") + + @versions = @baseline.easy_baseline_sources. + versions. + joins_baseline_versions. + where(easy_baseline_sources: { source_id: version_ids }). + pluck("#{v_table}.effective_date, #{b_table}.source_id") + + respond_to do |format| + format.api + end + end + + private + + def find_baseline + @baseline = Project.includes(:easy_baseline_for). + where(id: params[:id]). + where.not(easy_baseline_for_id: nil). + first! + @project = @baseline.easy_baseline_for + rescue ActiveRecord::RecordNotFound + render_404 + end + +end diff --git a/easy_baseline/app/controllers/easy_baselines_controller.rb b/easy_baseline/app/controllers/easy_baselines_controller.rb new file mode 100644 index 0000000..f5606b7 --- /dev/null +++ b/easy_baseline/app/controllers/easy_baselines_controller.rb @@ -0,0 +1,79 @@ +class EasyBaselinesController < ApplicationController + accept_api_auth :index, :create, :destroy + + before_action :find_project_by_project_id + before_action :authorize + before_action :authorize_baseline_source + + include Redmine::I18n + include SortHelper + + def index + @baselines = Project.where(easy_baseline_for: @project) + end + + def new + prepare_baseline + end + + def create + prepare_baseline + + Mailer.with_deliveries(false) do + if @baseline.save(validate: false) && @baseline.copy(@project, only: ['versions', 'issues'], with_time_entries: false) + # Easyredmine copies time on {copy_issues} + @baseline.time_entries.destroy_all + + respond_to do |format| + format.html { + flash[:notice] = l(:notice_easy_baseline_created, project_name: @project.name) + redirect_back_or_default project_easy_baselines_path(@project) + } + format.api { render_api_ok } + end + else + respond_to do |format| + format.html { render :new } + format.api { render_validation_errors(@baseline) } + end + end + end + end + + def destroy + @baseline = Project.find(params[:id]) + @baseline.destroy + + respond_to do |format| + format.html { + flash[:notice] = l(:notice_successful_delete) + redirect_back_or_default project_easy_baselines_path(@project) + } + format.api { head :no_content } + end + end + + private + + def prepare_baseline(options={}) + options[:name] = params[:easy_baseline][:name] if params[:easy_baseline] + + @baseline = Project.copy_from(@project) + @baseline.status = Project::STATUS_ARCHIVED + # Without this hack it disables a modules on original project see http://www.redmine.org/issues/20512 for details + @baseline.enabled_modules = [] + @baseline.enabled_module_names = @project.enabled_module_names + @baseline.name = options[:name] || (format_time(Time.now) + ' ' + @project.name) + @baseline.identifier = options[:name].present? ? options[:name].parameterize : @project.identifier + '_' + Time.now.strftime('%Y%m%d%H%M%S') + @baseline.easy_baseline_for_id = @project.id + @baseline.parent = EasyBaseline.baseline_root_project + # Project.copy_from change customized so CV are not copyied but moved + # Already done in easyredmine + @baseline.custom_values = @project.custom_values.map{|v| cloned_v = v.dup; cloned_v.customized = @baseline; cloned_v} + end + + def authorize_baseline_source + render_404 unless @project.easy_baseline_for.nil? + end + +end diff --git a/easy_baseline/app/helpers/.gitkeep b/easy_baseline/app/helpers/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/easy_baseline/app/models/easy_baseline_source.rb b/easy_baseline/app/models/easy_baseline_source.rb new file mode 100644 index 0000000..05edda2 --- /dev/null +++ b/easy_baseline/app/models/easy_baseline_source.rb @@ -0,0 +1,22 @@ +class EasyBaselineSource < ActiveRecord::Base + + belongs_to :baseline, class_name: 'Project' + + scope :issues, -> { where(:relation_type => 'Issue') } + scope :versions, -> { where(:relation_type => 'Version') } + + scope :with_source, -> { where.not(:source_id => nil) } + scope :without_source, -> { where(:source_id => nil) } + + scope :joins_source_versions, -> { joins("INNER JOIN #{Version.table_name} ON #{self.table_name}.source_id = #{Version.table_name}.id") } + scope :joins_baseline_versions, -> { joins("INNER JOIN #{Version.table_name} ON #{self.table_name}.destination_id = #{Version.table_name}.id") } + + def source + @source ||= self.relation_type.constantize.find(source_id) + end + + def destination + @destination ||= self.relation_type.constantize.find(destination_id) + end + +end diff --git a/easy_baseline/app/views/.gitkeep b/easy_baseline/app/views/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/easy_baseline/app/views/easy_baseline_gantt/show.api.rsb b/easy_baseline/app/views/easy_baseline_gantt/show.api.rsb new file mode 100644 index 0000000..fd55540 --- /dev/null +++ b/easy_baseline/app/views/easy_baseline_gantt/show.api.rsb @@ -0,0 +1,23 @@ +api.easy_baseline_gantt do + + api.array :issues do + @issues.each do |start_date, due_date, done_ratio, source_id| + api.issue do + api.connected_to_issue_id source_id + api.start_date start_date + api.due_date due_date + api.done_ratio done_ratio + end + end + end + + api.array :versions do + @versions.each do |effective_date, source_id| + api.version do + api.connected_to_version_id source_id + api.start_date effective_date + end + end + end + +end diff --git a/easy_baseline/app/views/easy_baselines/index.api.rsb b/easy_baseline/app/views/easy_baselines/index.api.rsb new file mode 100644 index 0000000..d2d020b --- /dev/null +++ b/easy_baseline/app/views/easy_baselines/index.api.rsb @@ -0,0 +1,8 @@ +api.array :easy_baselines do + @baselines.each do |baseline| + api.easy_baseline do + api.id baseline.id + api.name baseline.name + end + end +end diff --git a/easy_baseline/app/views/easy_baselines/index.html.erb b/easy_baseline/app/views/easy_baselines/index.html.erb new file mode 100644 index 0000000..4e1aacf --- /dev/null +++ b/easy_baseline/app/views/easy_baselines/index.html.erb @@ -0,0 +1,32 @@ +
+ <% if User.current.allowed_to?(:edit_baselines, @project) %> + <%# todo: show modal to pick baseline's name %> + <%#= link_to_function(l(:label_create_easy_baseline), "ysy.pro.baseline.openCreateModal()", :class => 'icon icon-add') %> + <%= link_to(l(:label_create_easy_baseline), project_easy_baselines_path(@project), :method => :post, :class => 'icon icon-add') %> + <% end %> +
+ +

<%= l(:label_easy_baselines) %>

+ + + + + + + + +<% @baselines.each do |baseline| %> + + + + + +<% end %> + +
<%= l(:field_name) %><%= l(:field_project) %>
<%= baseline.name %><%= link_to baseline.easy_baseline_for.name, project_path(baseline.easy_baseline_for_id) %> + <% if User.current.allowed_to?(:edit_baselines, @project) %> + <%= link_to l("button_delete"), project_easy_baseline_path(@project, baseline), + :method => 'delete', :class => "icon icon-del", :data => { :confirm => l(:text_are_you_sure) } %> + <% end %> +
+ diff --git a/easy_baseline/app/views/easy_baselines/new.html.erb b/easy_baseline/app/views/easy_baselines/new.html.erb new file mode 100644 index 0000000..e7dbf44 --- /dev/null +++ b/easy_baseline/app/views/easy_baselines/new.html.erb @@ -0,0 +1,2 @@ +<%= error_messages_for(@baseline) %> +<%= button_to(l(:label_try_again), project_easy_baselines_path(@project, back_url: params[:back_url])) %> diff --git a/easy_baseline/assets/images/.gitkeep b/easy_baseline/assets/images/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/easy_baseline/assets/javascripts/.gitkeep b/easy_baseline/assets/javascripts/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/easy_baseline/config/locales/ar.yml b/easy_baseline/config/locales/ar.yml new file mode 100644 index 0000000..b93c21a --- /dev/null +++ b/easy_baseline/config/locales/ar.yml @@ -0,0 +1,2 @@ +--- +ar: diff --git a/easy_baseline/config/locales/cs.yml b/easy_baseline/config/locales/cs.yml new file mode 100644 index 0000000..e76d3c5 --- /dev/null +++ b/easy_baseline/config/locales/cs.yml @@ -0,0 +1,11 @@ +--- +cs: + easy_baseline_plugin_author: Easy Software + easy_baseline_plugin_author_url: http://www.easyproject.cz/ + easy_baseline_plugin_description: '' + easy_baseline_plugin_name: Easy Baseline + easy_baseline_project_prefix: Baseline pro + label_create_easy_baseline: Vytvořit baseline + label_easy_baselines: Easy Baselines + label_try_again: Zkusit znovu + notice_easy_baseline_created: Baseline projektu %{project_name} úspěšně vytvořena diff --git a/easy_baseline/config/locales/da.yml b/easy_baseline/config/locales/da.yml new file mode 100644 index 0000000..d3dcd59 --- /dev/null +++ b/easy_baseline/config/locales/da.yml @@ -0,0 +1,2 @@ +--- +da: diff --git a/easy_baseline/config/locales/de.yml b/easy_baseline/config/locales/de.yml new file mode 100644 index 0000000..8a42dd2 --- /dev/null +++ b/easy_baseline/config/locales/de.yml @@ -0,0 +1,13 @@ +--- +de: + easy_baseline_plugin_author: Easy Software + easy_baseline_plugin_author_url: http://www.easyproject.cz/de + easy_baseline_plugin_description: '' + easy_baseline_plugin_name: Easy Basisplan + easy_baseline_project_prefix: Basisplan für + field_easy_baseline_for: Baseline + label_create_easy_baseline: Basisplan erstellen + label_easy_baselines: Easy Basispläne + label_try_again: Wiederholen + notice_easy_baseline_created: Basisplan für den Projekt %{project_name} wurde + erfolgreich erstellt diff --git a/easy_baseline/config/locales/en-AU.yml b/easy_baseline/config/locales/en-AU.yml new file mode 100644 index 0000000..56b80b3 --- /dev/null +++ b/easy_baseline/config/locales/en-AU.yml @@ -0,0 +1,2 @@ +--- +en-AU: diff --git a/easy_baseline/config/locales/en-GB.yml b/easy_baseline/config/locales/en-GB.yml new file mode 100644 index 0000000..a32c228 --- /dev/null +++ b/easy_baseline/config/locales/en-GB.yml @@ -0,0 +1,2 @@ +--- +en-GB: diff --git a/easy_baseline/config/locales/en-US.yml b/easy_baseline/config/locales/en-US.yml new file mode 100644 index 0000000..54b3be8 --- /dev/null +++ b/easy_baseline/config/locales/en-US.yml @@ -0,0 +1,2 @@ +--- +en-US: diff --git a/easy_baseline/config/locales/en.yml b/easy_baseline/config/locales/en.yml new file mode 100644 index 0000000..b10093e --- /dev/null +++ b/easy_baseline/config/locales/en.yml @@ -0,0 +1,13 @@ +--- +en: + easy_baseline_plugin_author: Easy Software + easy_baseline_plugin_author_url: http://www.easyproject.cz/en + easy_baseline_plugin_description: '' + easy_baseline_plugin_name: Easy Baseline + easy_baseline_project_prefix: Baseline for + field_easy_baseline_for: Baseline + label_create_easy_baseline: Create baseline + label_easy_baselines: Easy Baselines + label_try_again: Try again + notice_easy_baseline_created: Baseline for project %{project_name} succesfully created + project_module_easy_baselines: Easy Baselines diff --git a/easy_baseline/config/locales/es.yml b/easy_baseline/config/locales/es.yml new file mode 100644 index 0000000..3787082 --- /dev/null +++ b/easy_baseline/config/locales/es.yml @@ -0,0 +1,13 @@ +--- +es: + easy_baseline_plugin_author: Easy Software + easy_baseline_plugin_author_url: http://www.easyproject.cz/es + easy_baseline_plugin_description: '' + easy_baseline_plugin_name: Easy Baseline + easy_baseline_project_prefix: Referencia para + field_easy_baseline_for: Referencia + label_create_easy_baseline: Crear referencia + label_easy_baselines: Easy Baselines + label_try_again: Inténtalo de nuevo + notice_easy_baseline_created: La referencia para el proyecto %{project_name} se + ha creado correctamente diff --git a/easy_baseline/config/locales/fi.yml b/easy_baseline/config/locales/fi.yml new file mode 100644 index 0000000..e173d18 --- /dev/null +++ b/easy_baseline/config/locales/fi.yml @@ -0,0 +1,2 @@ +--- +fi: diff --git a/easy_baseline/config/locales/fr.yml b/easy_baseline/config/locales/fr.yml new file mode 100644 index 0000000..d51ff8e --- /dev/null +++ b/easy_baseline/config/locales/fr.yml @@ -0,0 +1,13 @@ +--- +fr: + easy_baseline_plugin_author: Easy Software + easy_baseline_plugin_author_url: http://www.easyproject.cz/en + easy_baseline_plugin_description: '' + easy_baseline_plugin_name: Easy Baseline + easy_baseline_project_prefix: '' + field_easy_baseline_for: Point de comparaison (baseline) + label_create_easy_baseline: Établir un point de comparaison + label_easy_baselines: Easy Baselines + label_try_again: Essayer à nouveau + notice_easy_baseline_created: Ligne de base pour projet %{project_name} créé avec + succès diff --git a/easy_baseline/config/locales/he.yml b/easy_baseline/config/locales/he.yml new file mode 100644 index 0000000..de35edf --- /dev/null +++ b/easy_baseline/config/locales/he.yml @@ -0,0 +1,2 @@ +--- +he: diff --git a/easy_baseline/config/locales/hr.yml b/easy_baseline/config/locales/hr.yml new file mode 100644 index 0000000..662a199 --- /dev/null +++ b/easy_baseline/config/locales/hr.yml @@ -0,0 +1,2 @@ +--- +hr: diff --git a/easy_baseline/config/locales/hu.yml b/easy_baseline/config/locales/hu.yml new file mode 100644 index 0000000..0bab1a9 --- /dev/null +++ b/easy_baseline/config/locales/hu.yml @@ -0,0 +1,12 @@ +--- +hu: + easy_baseline_plugin_author: Easy Software + easy_baseline_plugin_author_url: http://www.easyproject.cz/en + easy_baseline_plugin_description: '' + easy_baseline_plugin_name: Easy Baseline + easy_baseline_project_prefix: Baseline erre + field_easy_baseline_for: Alapérték + label_create_easy_baseline: Alapvonal létrehozása + label_easy_baselines: Easy Baselines + label_try_again: Próbálja újra + notice_easy_baseline_created: Alapvonal létrehozva a %{project_name} projekthez diff --git a/easy_baseline/config/locales/it.yml b/easy_baseline/config/locales/it.yml new file mode 100644 index 0000000..79f0eb6 --- /dev/null +++ b/easy_baseline/config/locales/it.yml @@ -0,0 +1,13 @@ +--- +it: + easy_baseline_plugin_author: Easy Software + easy_baseline_plugin_author_url: http://www.easyproject.cz/en + easy_baseline_plugin_description: '' + easy_baseline_plugin_name: Easy Baseline + easy_baseline_project_prefix: Baseline per + field_easy_baseline_for: Baseline + label_create_easy_baseline: Crea baseline + label_easy_baselines: Easy Baselines + label_try_again: Prova di nuovo + notice_easy_baseline_created: Baseline per il progetto %{project_name} creata con + successo diff --git a/easy_baseline/config/locales/ja.yml b/easy_baseline/config/locales/ja.yml new file mode 100644 index 0000000..39effd1 --- /dev/null +++ b/easy_baseline/config/locales/ja.yml @@ -0,0 +1,13 @@ +--- +ja: + easy_baseline_plugin_author: Easy Software + easy_baseline_plugin_author_url: http://www.easyproject.cz/en + easy_baseline_plugin_description: '' + easy_baseline_plugin_name: Easy Baseline + easy_baseline_project_prefix: Baseline for + field_easy_baseline_for: ベースライン + label_create_easy_baseline: ベースラインの作成 + label_easy_baselines: ベースライン管理 + label_try_again: 再度実行してください + notice_easy_baseline_created: "%{project_name} のベースラインは正常に作成されました" + project_module_easy_baselines: 簡単なベースライン diff --git a/easy_baseline/config/locales/ko.yml b/easy_baseline/config/locales/ko.yml new file mode 100644 index 0000000..a8302fd --- /dev/null +++ b/easy_baseline/config/locales/ko.yml @@ -0,0 +1,12 @@ +--- +ko: + easy_baseline_plugin_author: 이지 소프트웨어 + easy_baseline_plugin_author_url: http://www.easyproject.cz/en + easy_baseline_plugin_description: '' + easy_baseline_plugin_name: 이지 베이스라인 + easy_baseline_project_prefix: 를 위한 베이스라인 + field_easy_baseline_for: 기준 + label_create_easy_baseline: 베이스라인 작성 + label_easy_baselines: 이지 베이스라인 + label_try_again: 다시 시도하세요 + notice_easy_baseline_created: 프로젝트 %{project_name}를 위한 베이스라인이 성공적으로 생성되었습니다 diff --git a/easy_baseline/config/locales/mk.yml b/easy_baseline/config/locales/mk.yml new file mode 100644 index 0000000..0ae1092 --- /dev/null +++ b/easy_baseline/config/locales/mk.yml @@ -0,0 +1,2 @@ +--- +mk: diff --git a/easy_baseline/config/locales/nl.yml b/easy_baseline/config/locales/nl.yml new file mode 100644 index 0000000..f943cca --- /dev/null +++ b/easy_baseline/config/locales/nl.yml @@ -0,0 +1,12 @@ +--- +nl: + easy_baseline_plugin_author: Easy Software + easy_baseline_plugin_author_url: http://www.easyproject.cz/nl + easy_baseline_plugin_description: '' + easy_baseline_plugin_name: Easy Baseline + easy_baseline_project_prefix: Baseline voor + field_easy_baseline_for: Baseline + label_create_easy_baseline: Maak baseline + label_easy_baselines: Easy Baselines + label_try_again: Probeer opnieuw + notice_easy_baseline_created: Baseline voor project %{project_name} aangemaakt diff --git a/easy_baseline/config/locales/no.yml b/easy_baseline/config/locales/no.yml new file mode 100644 index 0000000..38901c6 --- /dev/null +++ b/easy_baseline/config/locales/no.yml @@ -0,0 +1,2 @@ +--- +'no': diff --git a/easy_baseline/config/locales/pl.yml b/easy_baseline/config/locales/pl.yml new file mode 100644 index 0000000..303816b --- /dev/null +++ b/easy_baseline/config/locales/pl.yml @@ -0,0 +1,12 @@ +--- +pl: + easy_baseline_plugin_author: Easy Software + easy_baseline_plugin_author_url: http://www.easyproject.cz/pl + easy_baseline_plugin_description: '' + easy_baseline_plugin_name: Bazy Easy + easy_baseline_project_prefix: Baza dla + field_easy_baseline_for: Linia bazowa + label_create_easy_baseline: Utwórz bazę + label_easy_baselines: Bazy Easy + label_try_again: Spróbuj ponownie + notice_easy_baseline_created: Baza dla projektu %{project_name} została utworzona diff --git a/easy_baseline/config/locales/pt-BR.yml b/easy_baseline/config/locales/pt-BR.yml new file mode 100644 index 0000000..3206531 --- /dev/null +++ b/easy_baseline/config/locales/pt-BR.yml @@ -0,0 +1,13 @@ +--- +pt-BR: + easy_baseline_plugin_author: Easy Software + easy_baseline_plugin_author_url: http://www.easyproject.cz/en + easy_baseline_plugin_description: '' + easy_baseline_plugin_name: Easy Baseline + easy_baseline_project_prefix: Linha de base para + field_easy_baseline_for: Linha de base. + label_create_easy_baseline: Criar linha de base + label_easy_baselines: Easy Baselines + label_try_again: Tentar novamente + notice_easy_baseline_created: Linha de base para o projeto %{project_name} criada + com sucesso diff --git a/easy_baseline/config/locales/pt.yml b/easy_baseline/config/locales/pt.yml new file mode 100644 index 0000000..e40c485 --- /dev/null +++ b/easy_baseline/config/locales/pt.yml @@ -0,0 +1,2 @@ +--- +pt: diff --git a/easy_baseline/config/locales/ro.yml b/easy_baseline/config/locales/ro.yml new file mode 100644 index 0000000..0df6ab3 --- /dev/null +++ b/easy_baseline/config/locales/ro.yml @@ -0,0 +1,2 @@ +--- +ro: diff --git a/easy_baseline/config/locales/ru.yml b/easy_baseline/config/locales/ru.yml new file mode 100644 index 0000000..1b78d24 --- /dev/null +++ b/easy_baseline/config/locales/ru.yml @@ -0,0 +1,12 @@ +--- +ru: + easy_baseline_plugin_author: Easy Software + easy_baseline_plugin_author_url: http://www.easyproject.cz/en + easy_baseline_plugin_description: '' + easy_baseline_plugin_name: Базовый план + easy_baseline_project_prefix: Базовый план для + field_easy_baseline_for: Базовый план + label_create_easy_baseline: Создать базовый план + label_easy_baselines: Базовые планы + label_try_again: Попробуйте еще раз + notice_easy_baseline_created: Базовый план для проекта %{project_name} создан. diff --git a/easy_baseline/config/locales/sk.yml b/easy_baseline/config/locales/sk.yml new file mode 100644 index 0000000..354e579 --- /dev/null +++ b/easy_baseline/config/locales/sk.yml @@ -0,0 +1,2 @@ +--- +sk: diff --git a/easy_baseline/config/locales/sl.yml b/easy_baseline/config/locales/sl.yml new file mode 100644 index 0000000..1cee476 --- /dev/null +++ b/easy_baseline/config/locales/sl.yml @@ -0,0 +1,6 @@ +--- +sl: + easy_baseline_project_prefix: Referenčni načrt za + label_create_easy_baseline: Ustvari referenčni načrt + label_try_again: Poskusi znova + notice_easy_baseline_created: Uspešno ustvarjen referenčni načrt za projekt %{project_name} diff --git a/easy_baseline/config/locales/sq.yml b/easy_baseline/config/locales/sq.yml new file mode 100644 index 0000000..9c092f0 --- /dev/null +++ b/easy_baseline/config/locales/sq.yml @@ -0,0 +1,2 @@ +--- +sq: diff --git a/easy_baseline/config/locales/sr-YU.yml b/easy_baseline/config/locales/sr-YU.yml new file mode 100644 index 0000000..24e1796 --- /dev/null +++ b/easy_baseline/config/locales/sr-YU.yml @@ -0,0 +1,2 @@ +--- +sr-YU: diff --git a/easy_baseline/config/locales/sr.yml b/easy_baseline/config/locales/sr.yml new file mode 100644 index 0000000..43a1014 --- /dev/null +++ b/easy_baseline/config/locales/sr.yml @@ -0,0 +1,2 @@ +--- +sr: diff --git a/easy_baseline/config/locales/sv.yml b/easy_baseline/config/locales/sv.yml new file mode 100644 index 0000000..ed425ea --- /dev/null +++ b/easy_baseline/config/locales/sv.yml @@ -0,0 +1,2 @@ +--- +sv: diff --git a/easy_baseline/config/locales/th.yml b/easy_baseline/config/locales/th.yml new file mode 100644 index 0000000..3596914 --- /dev/null +++ b/easy_baseline/config/locales/th.yml @@ -0,0 +1,2 @@ +--- +th: diff --git a/easy_baseline/config/locales/tr.yml b/easy_baseline/config/locales/tr.yml new file mode 100644 index 0000000..3be79e7 --- /dev/null +++ b/easy_baseline/config/locales/tr.yml @@ -0,0 +1,2 @@ +--- +tr: diff --git a/easy_baseline/config/locales/zh-TW.yml b/easy_baseline/config/locales/zh-TW.yml new file mode 100644 index 0000000..44cf0f8 --- /dev/null +++ b/easy_baseline/config/locales/zh-TW.yml @@ -0,0 +1,2 @@ +--- +zh-TW: diff --git a/easy_baseline/config/locales/zh.yml b/easy_baseline/config/locales/zh.yml new file mode 100644 index 0000000..b7946b6 --- /dev/null +++ b/easy_baseline/config/locales/zh.yml @@ -0,0 +1,12 @@ +--- +zh: + easy_baseline_plugin_author: EASY软件 + easy_baseline_plugin_author_url: http://www.easyproject.cz/en + easy_baseline_plugin_description: Baseline-原始时间线 + easy_baseline_plugin_name: Easy 原始时间线 + easy_baseline_project_prefix: '下列项目的原时间线(Baseline):' + field_easy_baseline_for: 原时间线 + label_create_easy_baseline: 创建原时间线(Baseline) + label_easy_baselines: Easy原时间线(Baseline) + label_try_again: 重试 + notice_easy_baseline_created: 项目 %{project_name}的原时间线(Baseline)创建成功 diff --git a/easy_baseline/config/routes.rb b/easy_baseline/config/routes.rb new file mode 100644 index 0000000..d15556d --- /dev/null +++ b/easy_baseline/config/routes.rb @@ -0,0 +1,6 @@ +resources :projects do + resources :easy_baselines, :except => [:show, :edit, :update] +end + +resources :easy_baseline_gantt, :only => :show + diff --git a/easy_baseline/db/migrate/20150810233049_add_easy_baseline_for_to_project.rb b/easy_baseline/db/migrate/20150810233049_add_easy_baseline_for_to_project.rb new file mode 100644 index 0000000..74303b2 --- /dev/null +++ b/easy_baseline/db/migrate/20150810233049_add_easy_baseline_for_to_project.rb @@ -0,0 +1,5 @@ +class AddEasyBaselineForToProject < ActiveRecord::Migration[6.1] + def change + add_reference :projects, :easy_baseline_for, index: true + end +end diff --git a/easy_baseline/db/migrate/20150812122322_create_easy_baseline_sources.rb b/easy_baseline/db/migrate/20150812122322_create_easy_baseline_sources.rb new file mode 100644 index 0000000..7dd3133 --- /dev/null +++ b/easy_baseline/db/migrate/20150812122322_create_easy_baseline_sources.rb @@ -0,0 +1,10 @@ +class CreateEasyBaselineSources < ActiveRecord::Migration[6.1] + def change + create_table :easy_baseline_sources do |t| + t.references :baseline, index: true + t.references :source + t.references :destination + t.string :relation_type + end + end +end diff --git a/easy_baseline/init.rb b/easy_baseline/init.rb new file mode 100644 index 0000000..b56030d --- /dev/null +++ b/easy_baseline/init.rb @@ -0,0 +1,11 @@ +Redmine::Plugin.register :easy_baseline do + name 'Easy Baseline' + author 'Easy Software Ltd' + url 'https://www.easysoftware.com' + author_url 'https://www.easysoftware.com' + description 'Allow to create a snapshot of a project in time.' + version '3.0' +end + +require_relative 'after_init' + diff --git a/easy_baseline/lib/easy_baseline.rb b/easy_baseline/lib/easy_baseline.rb new file mode 100644 index 0000000..c97c64e --- /dev/null +++ b/easy_baseline/lib/easy_baseline.rb @@ -0,0 +1,23 @@ +module EasyBaseline + + IDENTIFIER = 'easy_baselines-root' + + def self.baseline_root_project + project = Project.find_by_identifier(IDENTIFIER) + return project if project + + project = Project.new(identifier: IDENTIFIER) + project.name = 'Easy Baselines Root' + project.status = Project::STATUS_ARCHIVED + project.is_public = false + project.save!(validate: false) + project + + # Project.where(identifier: IDENTIFIER).first_or_create! do |project| + # project.name = 'Easy Baselines Root' + # project.status = Project::STATUS_ARCHIVED + # project.is_public = false + # end + end + +end diff --git a/easy_baseline/lib/easy_baseline/hooks.rb b/easy_baseline/lib/easy_baseline/hooks.rb new file mode 100644 index 0000000..78e99ef --- /dev/null +++ b/easy_baseline/lib/easy_baseline/hooks.rb @@ -0,0 +1,9 @@ +module EasyBaseline + class Hooks < Redmine::Hook::ViewListener + + def model_project_copy_before_save(context={ }) + context[:destination_project].status = Project::STATUS_ARCHIVED if context[:destination_project].easy_baseline_for_id + end + + end +end diff --git a/easy_baseline/lib/easy_baseline/issue_patch.rb b/easy_baseline/lib/easy_baseline/issue_patch.rb new file mode 100644 index 0000000..2a0b936 --- /dev/null +++ b/easy_baseline/lib/easy_baseline/issue_patch.rb @@ -0,0 +1,24 @@ +module EasyBaseline + module IssuePatch + + def self.prepended(base) + base.class_eval do + + has_one :easy_baseline_source, -> { where(relation_type: 'Issue') }, class_name: 'EasyBaselineSource', foreign_key: :destination_id, dependent: :nullify + has_many :easy_baseline_destinations, -> { where(relation_type: 'Issue') }, class_name: 'EasyBaselineSource', foreign_key: :source_id, dependent: :destroy + + after_save :create_baseline_from_copy, if: :copy? + + def create_baseline_from_copy + return if self.project.easy_baseline_for_id.nil? + + EasyBaselineSource.create(baseline_id: self.project_id, relation_type: 'Issue', source_id: @copied_from.id, destination_id: self.id) + end + + end + end + + end +end +Issue.prepend EasyBaseline::IssuePatch + diff --git a/easy_baseline/lib/easy_baseline/project_patch.rb b/easy_baseline/lib/easy_baseline/project_patch.rb new file mode 100644 index 0000000..f5fcfff --- /dev/null +++ b/easy_baseline/lib/easy_baseline/project_patch.rb @@ -0,0 +1,83 @@ +module EasyBaseline + module ProjectPatch + + def self.prepended(base) + base.prepend InstanceMethods + base.singleton_class.prepend(ClassMethods) + + base.class_eval do + belongs_to :easy_baseline_for, class_name: 'Project' + has_many :easy_baseline_sources, foreign_key: 'baseline_id' + + before_save :prevent_unarchive_easy_baseline + end + end + + module InstanceMethods + + def baseline_root? + identifier == EasyBaseline::IDENTIFIER + end + + def copy_versions(project) + super + + if self.easy_baseline_for_id == project.id + self.versions.each do |v| + v.copied_from = project.versions.detect{|cv| cv.name == v.name } + v.save + end + end + end + + def allows_to(action) + return true if easy_baseline_for_id && archived? + + super + end + + def validate_custom_field_values_with_easy_baseline + if baseline_root? && archived? + true + else + super + end + end + + private + + def validate_parent_with_easy_baseline + if @unallowed_parent_id + errors.add(:parent_id, :invalid) + elsif parent_id_changed? + if parent.present? && (!parent.active? || !move_possible?(parent)) && !parent.baseline_root? + errors.add(:parent_id, :invalid) + end + end + end + + def prevent_unarchive_easy_baseline + if (easy_baseline_for_id || baseline_root?) && status_changed? && !archived? + errors.add(:status, :invalid) + return false + end + end + + end + + module ClassMethods + + def allowed_to_condition(user, permission, options={}, &block) + condition = super + + if options[:easy_baseline].present? + condition.gsub!("#{Project.table_name}.status <> #{Project::STATUS_ARCHIVED} AND", "") + end + condition + end + + end + end +end +Project.prepend EasyBaseline::ProjectPatch + diff --git a/easy_baseline/lib/easy_baseline/version_patch.rb b/easy_baseline/lib/easy_baseline/version_patch.rb new file mode 100644 index 0000000..0cd5b7a --- /dev/null +++ b/easy_baseline/lib/easy_baseline/version_patch.rb @@ -0,0 +1,28 @@ +module EasyBaseline + module VersionPatch + + def self.prepended(base) + base.class_eval do + + after_save :create_baseline_from_copy, if: :copy? + + attr_accessor :copied_from + + def copy? + @copied_from.present? + end + + private + + def create_baseline_from_copy + return if self.project.easy_baseline_for_id.nil? + EasyBaselineSource.create(baseline_id: self.project_id, relation_type: 'Version', source_id: @copied_from.id, destination_id: self.id) + end + + end + end + + end +end +Version.prepend EasyBaseline::VersionPatch + diff --git a/easy_baseline/spec/controllers/easy_baselines_controller_spec.rb b/easy_baseline/spec/controllers/easy_baselines_controller_spec.rb new file mode 100644 index 0000000..453360a --- /dev/null +++ b/easy_baseline/spec/controllers/easy_baselines_controller_spec.rb @@ -0,0 +1,35 @@ +require File.expand_path('../../../../easyproject/easy_plugins/easy_extensions/test/spec/spec_helper', __FILE__) + +describe EasyBaselinesController, :logged => :admin do + let(:project) { FactoryGirl.create(:project, :add_modules => %w(easy_baselines easy_gantt)) } + + around(:each) do |example| + with_settings(rest_api_enabled: 1) do + example.run + end + end + + describe '#new' do + it 'accept name param' do + get :new, project_id: project, easy_baseline: { name: 'Specialni jmeno' } + expect(assigns(:baseline).name).to eq('Specialni jmeno') + end + + it 'baselines project' do + project + expect{ + get :new, project_id: project, easy_baseline: { name: 'Specialni jmeno' } + }.to change(Project, :count).by(1) + end + + it 'baselines project with identifiers enabled' do + project + with_easy_settings(:project_display_identifiers => true) do + expect{ + get :new, project_id: project, easy_baseline: { name: 'Specialni jmeno' } + }.to change(Project, :count).by(1) + end + end + end + +end diff --git a/easy_gantt/Gemfile b/easy_gantt/Gemfile new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/easy_gantt/Gemfile @@ -0,0 +1 @@ + diff --git a/easy_gantt/LICENSE b/easy_gantt/LICENSE new file mode 100644 index 0000000..1255e47 --- /dev/null +++ b/easy_gantt/LICENSE @@ -0,0 +1,60 @@ +LICENCE + +All Easy Redmine Extensions are distributed under GNU/GPL 2 license (see below). +If not otherwise stated, all images, cascading style sheets, and included JavaScript are NOT GPL, and are released under the Easy Redmine Commercial Use License (see below). + +GNU GENERAL PUBLIC LICENSE +Version 2, June 1991 +Copyright (C) 1989, 1991 Free Software Foundation, Inc. +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + +Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. + +Preamble +The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Library General Public License instead.) You can apply it to your programs, too. +When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. +To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. +For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. +We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. +Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. +Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. +The precise terms and conditions for copying, distribution and modification follow. + +TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION +0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". +Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. +1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. +You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. +2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: +a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. +b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. +c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) +These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. +Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. +In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. +3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: +a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, +b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, +c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) +The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. +If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. +4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. +5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. +6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. +7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. +If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. +It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. +This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. +8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. +9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. +Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. +10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. +NO WARRANTY +11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. +12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. +END OF TERMS AND CONDITIONS + +EASY REDMINE COMMERCIAL USE LICENSE +The Easy Redmine Commercial Use License is a GPL compatible license that pertains only to the images, cascading style sheets and JavaScript elements of Easy Redmine Themes and Styles produced by Easy Software Ltd. As stated by the GPL version 2.0 license, these elements of product that are not compiled together but are sent independently of GPL code, and combined in a client's browser, do not have to be GPL themselves. These images, cascading style sheets and JavaScript elements are copyright Easy Software Ltd. and can be used and manipulated for your own or if you have signed Easy Redmine Partner Agreement for your clients purposes. You cannot redistribute these files as your own, or include them in any package or extension of your own without prior consent of Easy Software Ltd. +Unauthorised distribution or making it accessible to a third party without prior Easy Software Ltd. consent, authorizes Easy Software Ltd. to invoice contractual penalty in the amount of 10 000 EUR for any breach of the this License. + diff --git a/easy_gantt/README.md b/easy_gantt/README.md new file mode 100644 index 0000000..93b6f5a --- /dev/null +++ b/easy_gantt/README.md @@ -0,0 +1,3 @@ +# Easy Gantt + +For documentation and requirements, go to https://www.easyredmine.com/redmine-gantt-plugin diff --git a/easy_gantt/after_init.rb b/easy_gantt/after_init.rb new file mode 100644 index 0000000..f7b4ee0 --- /dev/null +++ b/easy_gantt/after_init.rb @@ -0,0 +1,97 @@ +lib_dir = File.join(File.dirname(__FILE__), 'lib', 'easy_gantt') + +# Redmine patches +patch_path = File.join(lib_dir, '*_patch.rb') +Dir.glob(patch_path).each do |file| + require file +end + +require lib_dir +require File.join(lib_dir, 'hooks') + +Redmine::MenuManager.map :top_menu do |menu| + menu.push(:easy_gantt, { controller: 'easy_gantt', action: 'index', set_filter: 0 }, + caption: :label_easy_gantt, + after: :documents, + icon: 'stats', + html: { class: 'icon icon-stats' }, + if: proc { User.current.allowed_to_globally?(:view_global_easy_gantt) }) +end + +Redmine::MenuManager.map :project_menu do |menu| + menu.push(:easy_gantt, { controller: 'easy_gantt', action: 'index' }, + param: :project_id, + caption: :button_project_menu_easy_gantt, + if: proc { |p| User.current.allowed_to?(:view_easy_gantt, p) }) +end + +Redmine::MenuManager.map :easy_gantt_tools do |menu| + menu.push(:back, 'javascript:void(0)', + param: :project_id, + caption: :button_back, + icon: 'chevrons-left', + html: { icon: 'icon-chevrons-left' }) + + menu.push(:task_control, 'javascript:void(0)', + param: :project_id, + caption: :button_edit, + icon: 'edit', + html: { icon: 'icon-edit' }) + + menu.push(:add_task, 'javascript:void(0)', + param: :project_id, + caption: :label_new, + icon: 'add', + html: { trial: true, icon: 'icon-add' }, + if: proc { |p| p.present? }) + + menu.push(:critical, 'javascript:void(0)', + param: :project_id, + caption: :'easy_gantt.button.critical_path', + icon: 'summary', + html: { trial: true, icon: 'icon-summary' }, + if: proc { |p| p.present? }) + + menu.push(:baseline, 'javascript:void(0)', + param: :project_id, + caption: :'easy_gantt.button.create_baseline', + icon: 'projects', + html: { trial: true, icon: 'icon-projects icon-project' }, + if: proc { |p| p.present? }) + +end + + + Redmine::AccessControl.map do |map| + map.project_module :easy_gantt do |pmap| + # View project level + pmap.permission(:view_easy_gantt, { + easy_gantt: [:index, :issues, :projects], + easy_gantt_pro: [:lowest_progress_tasks, :cashflow_data] + }, read: true) + + # Edit project level + pmap.permission(:edit_easy_gantt, { + easy_gantt: [:change_issue_relation_delay, :reschedule_project] + }, require: :member) + + # View global level + pmap.permission(:view_global_easy_gantt, { + easy_gantt: [:index, :issues, :projects, :project_issues], + easy_gantt_pro: [:lowest_progress_tasks, :cashflow_data] + }, global: true, read: true) + + # Edit global level + pmap.permission(:edit_global_easy_gantt, { + }, global: true, require: :loggedin) + + # View personal level + # pmap.permission(:view_personal_easy_gantt, { + # }, global: true, read: true) + + # Edit personal level + pmap.permission(:edit_personal_easy_gantt, { + }, global: true, require: :loggedin) + end + +end diff --git a/easy_gantt/app/controllers/easy_gantt_controller.rb b/easy_gantt/app/controllers/easy_gantt_controller.rb new file mode 100644 index 0000000..907d7e2 --- /dev/null +++ b/easy_gantt/app/controllers/easy_gantt_controller.rb @@ -0,0 +1,261 @@ +class EasyGanttController < ApplicationController + accept_api_auth :index, :issues, :projects, :project_issues, :change_issue_relation_delay, :reschedule_project + menu_item :easy_gantt + + RELATION_TYPES_TO_LOAD = ['relates', 'blocks', 'blocked', 'precedes', 'follows', 'start_to_start', 'finish_to_finish', 'start_to_finish'] + + before_action :find_optional_project, except: [:reschedule_project, :project_issues] + before_action :find_opened_project, except: [:reschedule_project] + + before_action :authorize, if: proc { @project.present? } + before_action :authorize_global, if: proc { @project.nil? } + + before_action :check_rest_api_enabled, only: [:index] + before_action :find_relation, only: [:change_issue_relation_delay] + + helper :queries + include QueriesHelper + helper :sort + include SortHelper + helper :custom_fields + helper :icons + + def index + retrieve_query + end + + # Data retrieve method + def issues + retrieve_query + + load_projects + load_issues + load_versions + load_relations + build_dates @issues, :start_date, :due_date + end + + # Data retrieve method + def projects + retrieve_query + + load_projects + build_dates @projects, :gantt_start_date, :gantt_due_date + + @projects_issues_counts = Issue.visible.gantt_opened.where(project_id: @projects).group(:project_id).count(:id) + end + + def project_issues + # TODO: Global route to skip rights + @issues = Issue.visible.gantt_opened.where(project_id: params[:project_id]).order(:start_date) + @issue_ids = @issues.map(&:id) + load_relations + + version_ids = @issues.map(&:fixed_version_id).uniq.compact + @versions = Version.open.where('id IN (?) OR project_id = ?', version_ids, params[:project_id]).sorted + end + + def change_issue_relation_delay + if !User.current.allowed_to?(:manage_issue_relations, @project) + return render_403 + end + + @relation.update_column(:delay, params[:delay].to_i) + + respond_to do |format| + format.api { render_api_ok } + end + end + + # You cannot use issue.reschedule_on because it will + # also set start_date which is not desirable !!! + def reschedule_project + begin + # Do not used callback `find_project` because it will test access rights + # to project context. Method wont work if project does not have gantt enabled. + project = Project.find(params[:id]) + rescue ActiveRecord::RecordNotFound + render_404 + return + end + + project.gantt_reschedule(params[:days].to_i) + + respond_to do |format| + format.api { render_api_ok } + end + end + + def current_menu_item + @current_menu_item ||= if params[:gantt_type] == 'rm' + :resource + else + :easy_gantt + end + end + + private + + def check_rest_api_enabled + if Setting.rest_api_enabled != '1' + render_error message: l('easy_gantt.errors.no_rest_api') + return false + end + end + + def find_relation + @relation = IssueRelation.find(params[:id]) + rescue ActiveRecord::RecordNotFound + render_404 + end + + def query_class + @project ? EasyGantt::EasyGanttIssueQuery : EasyGantt::EasyGanttProjectQuery + end + + def retrieve_query + if params[:query_id].present? + cond = 'project_id IS NULL' + + if @project + cond << " OR project_id = #{@project.id}" + end + + @query = query_class.where(cond).find_by(id: params[:query_id]) + raise ActiveRecord::RecordNotFound if @query.nil? + raise Unauthorized unless @query.visible? + + @query.project = @project + sort_clear + else + @query = query_class.new(name: '_') + @query.project = @project + @query.from_params(params) + end + + if @opened_project + @query.opened_project = @opened_project + end + end + + # Load version from loaded task and from opened projects + # TO_CONSIDER: Send versions if there is no tasks? + def load_versions + version_ids = @issues.map(&:fixed_version_id).uniq.compact + @versions = Version.open.where("id IN (?) OR project_id = ?", version_ids, @opened_project.id).sorted + end + + # Load subproject of opened project which contains filtered tasks + # + # Project 1 + # |-- Project 1.1 + # | `-- Project 1.1.1 + # | |-- Task 1 + # | `-- Task 2 + # `-- Project 1.2 + # + # If Project 1 is opened, Project 1.1 must be send even if there is no task + # + # TO_CONSIDER: Send full tree and load only opened_project's issues + # + def load_projects + p_table = Project.table_name + + @projects = [] + + # Project gantt is opened, normally only subprojects will be sent + # but there is not any root project yet + if @project && @opened_project == @project + @projects << @project + end + + projects = @query.without_opened_project { |q| + scope = q.create_entity_scope + + # Not necessary, will take only subprojects + if @opened_project + scope = scope.where("#{p_table}.lft >= ? AND #{p_table}.rgt <= ?", @opened_project.lft, @opened_project.rgt) + end + + scope.reorder(nil).distinct.pluck("#{p_table}.lft, #{p_table}.rgt, #{p_table}.parent_id") + } + + if projects.blank? + return + end + + # All ancestors conditions + tree_conditions = [] + projects.each do |lft, rgt| + tree_conditions << "(lft <= #{lft} AND rgt >= #{rgt})" + end + tree_conditions = tree_conditions.join(' OR ') + + @parent_ids = projects.map(&:last) + + # From ancestors take only current opened level + @projects.concat Project.where(tree_conditions).where(parent_id: @opened_project.try(:id)).to_a + + Project.load_gantt_dates(@projects) + if Setting.plugin_easy_gantt['show_project_progress'] == '1' + Project.load_gantt_completed_percent(@projects) + end + end + + # Only between loaded tasks + def load_relations + if @issue_ids.empty? + @relations = [] + else + @relations = IssueRelation.where('issue_from_id IN (?) OR issue_to_id IN (?)', @issue_ids, @issue_ids). + where(relation_type: RELATION_TYPES_TO_LOAD) + end + end + + def load_issues + preloads = [:project, :author, :assigned_to, :relations_to] + + if Setting.plugin_easy_gantt['show_task_soonest_start'] == '1' + preloads << :parent + preloads << { relations_to: :issue_from } + else + preloads << :relations_to + end + + @issues = @query.entities( + includes: [:project, :status, :assigned_to, :fixed_version, :tracker, :priority, :custom_values], + preload: preloads, + order: "#{Issue.table_name}.start_date, #{Issue.table_name}.id" + ) + + @issue_ids = @issues.map(&:id) + end + + def build_dates(data, starts, ends) + starts = data.map(&starts).compact + ends = data.map(&ends).compact + + @start_date = (starts.min || ends.min || Date.today) - 1.day + @end_date = (ends.max || starts.max || Date.today) + 1.day + end + + def find_optional_project + # Easy query workaround + if params[:set_filter] == '1' && params[:project_id].present? && params[:project_id].start_with?('=', '!*', '*') + return + end + + super + end + + def find_opened_project + if params[:opened_project_id].present? + @opened_project = Project.find(params[:opened_project_id]) + else + @opened_project = @project + end + rescue ActiveRecord::RecordNotFound + render_404 + end + +end diff --git a/easy_gantt/app/helpers/easy_gantt_helper.rb b/easy_gantt/app/helpers/easy_gantt_helper.rb new file mode 100644 index 0000000..d8aa667 --- /dev/null +++ b/easy_gantt/app/helpers/easy_gantt_helper.rb @@ -0,0 +1,219 @@ +module EasyGanttHelper + + def easy_gantt_include_js(*javascripts, from_plugin: 'easy_gantt') + plugin = from_plugin + + result = '' + javascripts.flatten! + javascripts.compact! + javascripts.each do |javascript| + result << javascript_include_tag("#{from_plugin}/#{javascript}", plugin: plugin) + end + result.html_safe + end + + def easy_gantt_include_css(*stylesheets, media: 'screen', from_plugin: 'easy_gantt') + plugin = from_plugin + + result = '' + stylesheets.flatten! + stylesheets.compact! + stylesheets.each do |stylesheet| + result << stylesheet_link_tag("#{from_plugin}/#{stylesheet}", plugin: plugin, media: media) + end + result.html_safe + end + + def easy_gantt_js_button(text, options={}) + if text.is_a?(Symbol) + lang_key = text + text = l(lang_key, scope: [:easy_gantt, :button]) + options[:title] ||= l(lang_key, scope: [:easy_gantt, :title], default: text) + end + options[:class] = "gantt-menu-button #{options[:class]}" + options[:class] << ' button' unless options.delete(:no_button) + if (icon = options.delete(:icon)) + options[:class] << " icon #{icon}" + text = sprite_icon(icon.sub("icon-", ""), text) + end + link_to(text, options[:url] || 'javascript:void(0)', options) + end + + def easy_gantt_help_button(*args) + options = args.extract_options! + feature = args.shift + text = args.shift + + options[:class] = "gantt-menu-help-button #{options[:class]}" + options[:icon] ||= 'icon-help' + options[:id] = 'button_' + feature.to_s + '_help' + + help_text = raw l(:text, scope: [:easy_gantt, :popup, feature]) + + easy_gantt_js_button(text || '​'.html_safe, options) + %Q( + + ).html_safe + end + + def api_render_versions(api, versions) + return if versions.blank? + + api.array :versions do + versions.each do |version| + api.version do + api.id version.id + api.name version.name + api.start_date version.effective_date + api.project_id version.project_id + api.project_name version.project&.name + api.permissions do + api.editable version.gantt_editable? + end + end + end + end + + end + + def api_render_columns(api, query) + api.array :columns do + query.columns.each do |c| + api.column do + api.name c.name + api.title c.caption + end + end + end + end + + def api_render_issues(api, issues, with_columns: false) + api.array :issues do + issues.each do |issue| + api.issue do + api.id issue.id + api.name issue.subject + api.start_date issue.start_date + api.due_date issue.due_date + api.estimated_hours issue.estimated_hours + api.done_ratio issue.done_ratio + api.closed issue.closed? + api.fixed_version_id issue.fixed_version_id + api.overdue issue.overdue? + api.parent_issue_id issue.parent_id + api.project_id issue.project_id + api.tracker_id issue.tracker_id + api.priority_id issue.priority_id + api.status_id issue.status_id + api.assigned_to_id issue.assigned_to_id + + if Setting.plugin_easy_gantt['show_task_soonest_start'] == '1' && @project.nil? + api.soonest_start issue.soonest_start + end + if Setting.plugin_easy_gantt['show_task_latest_due'] == '1' && @project.nil? + api.latest_due issue.latest_due + end + + api.is_planned !!issue.project.try(:is_planned) + + api.permissions do + api.editable issue.gantt_editable? + end + + if with_columns + api.array :columns do + @query.columns.each do |c| + api.column do + api.name c.name + api.value gantt_format_column(issue, c, c.value(issue)) + end + end + end + end + + end + end + end + end + + def api_render_relations(api, relations) + api.array :relations do + relations.each do |rel| + api.relation do + api.id rel.id + api.source_id rel.issue_from_id + api.target_id rel.issue_to_id + api.type rel.relation_type + api.delay rel.delay.to_i + end + end + end + end + + def api_render_projects(api, projects, with_columns: false) + api.array :projects do + projects.each do |project| + api.project do + api.id project.id + api.name project.name + api.start_date project.gantt_start_date || Date.today + api.due_date project.gantt_due_date || Date.today + api.parent_id project.parent_id + api.is_baseline project.try(:easy_baseline_for_id?) + + # Schema + api.status_id project.status + api.priority_id project.try(:easy_priority_id) + + api.permissions do + api.editable project.gantt_editable? + end + + if Setting.plugin_easy_gantt['show_project_progress'] == '1' + api.done_ratio project.gantt_completed_percent + end + + if @projects_issues_counts && @projects_issues_counts.has_key?(project.id) + api.issues_count @projects_issues_counts[project.id] + end + + if @parent_ids && @parent_ids.include?(project.id) + api.has_subprojects true + end + + if with_columns + api.array :columns do + @query.columns.each do |c| + api.column do + api.name c.name + api.value gantt_format_column(project, c, c.value(project)) + end + end + end + end + + end + end + end + end + + # This method exist because + # 1. EntityAttributeHelper is for complex html formating + # 2. Redmine doest not have it + # Gantt should show light and non-html values + def gantt_format_column(entity, column, value) + if entity.is_a?(Project) && column.name == :status && respond_to?(:format_project_attribute) + format_project_attribute(Project, column, value) + elsif value.is_a?(Float) + locale = User.current.language.presence || ::I18n.locale + number_with_precision(value, locale: locale).to_s + elsif value.is_a?(Array) + value.join(', ') + else + value.to_s + end + end + +end diff --git a/easy_gantt/app/models/easy_gantt/easy_gantt_issue_query.rb b/easy_gantt/app/models/easy_gantt/easy_gantt_issue_query.rb new file mode 100644 index 0000000..580a954 --- /dev/null +++ b/easy_gantt/app/models/easy_gantt/easy_gantt_issue_query.rb @@ -0,0 +1,82 @@ +module EasyGantt + class EasyGanttIssueQuery < IssueQuery + + attr_accessor :entity_scope + attr_accessor :opened_project + + def default_columns_names + [:subject, :priority, :assigned_to] + end + + def entity + Issue + end + + def from_params(params) + build_from_params(params) + end + + def to_params + params = { set_filter: 1, type: self.class.name, f: [], op: {}, v: {} } + + filters.each do |filter_name, opts| + params[:f] << filter_name + params[:op][filter_name] = opts[:operator] + params[:v][filter_name] = opts[:values] + end + + params[:c] = column_names + params + end + + def to_partial_path + 'easy_gantt/easy_queries/show' + end + + def initialize_available_filters + super + @available_filters.delete('subproject_id') + end + + def entity_scope + @entity_scope ||= begin + scope = Issue.visible + if Project.column_names.include? 'easy_baseline_for_id' + scope = scope.where(Project.table_name => {easy_baseline_for_id: nil}) + end + scope + end + end + + def create_entity_scope(options={}) + entity_scope.includes(options[:includes]). + references(options[:includes]). + preload(options[:preload]). + where(statement). + where(options[:conditions]) + end + + def entities(options={}) + create_entity_scope(options).order(options[:order]) + end + + def project_statement + p_table = Project.table_name + + conditions = "#{p_table}.status = #{Project::STATUS_ACTIVE}" + if opened_project + conditions = "#{conditions} AND #{Project.table_name}.id = #{opened_project.id}" + end + conditions + end + + def without_opened_project + _opened_project = opened_project + self.opened_project = nil + yield self + ensure + self.opened_project = _opened_project + end + + end +end diff --git a/easy_gantt/app/models/easy_gantt/easy_gantt_project_query.rb b/easy_gantt/app/models/easy_gantt/easy_gantt_project_query.rb new file mode 100644 index 0000000..1117efe --- /dev/null +++ b/easy_gantt/app/models/easy_gantt/easy_gantt_project_query.rb @@ -0,0 +1,92 @@ +module EasyGantt +class EasyGanttProjectQuery < Query + + attr_accessor :opened_project + + self.queried_class = Project + + self.available_columns = [ + QueryColumn.new(:name, sortable: "#{Project.table_name}.name"), + QueryColumn.new(:created_on, sortable: "#{Project.table_name}.created_on"), + QueryColumn.new(:updated_on, sortable: "#{Project.table_name}.updated_on"), + ] + + def initialize(*args) + super + self.filters ||= {} + end + + def default_columns_names + [:name] + end + + def initialize_available_filters + add_available_filter 'name', type: :text + add_available_filter 'created_on', type: :date_past + add_available_filter 'updated_on', type: :date_past + end + + def from_params(params) + build_from_params(params) + end + + def to_params + params = { set_filter: 1, type: self.class.name, f: [], op: {}, v: {} } + + filters.each do |filter_name, opts| + params[:f] << filter_name + params[:op][filter_name] = opts[:operator] + params[:v][filter_name] = opts[:values] + end + + params[:c] = column_names + params + end + + def to_partial_path + 'easy_gantt/easy_queries/show' + end + + def entities(options={}) + scope = Project.active.visible + + if Project.column_names.include?('easy_baseline_for_id') + scope = scope.where(Project.table_name => { easy_baseline_for_id: nil }) + end + + scope = scope.includes(options[:includes]). + references(options[:includes]). + preload(options[:preload]). + where(statement). + where(options[:conditions]). + order(options[:order]) + + if opened_project + scope = scope(projects: { id: opened_project.id }) + end + + scope.to_a + end + + def entity_scope + Project.visible + end + + def create_entity_scope(options={}) + entity_scope.includes(options[:includes]). + references(options[:includes]). + preload(options[:preload]). + where(statement). + where(options[:conditions]) + end + + def without_opened_project + _opened_project = opened_project + self.opened_project = nil + yield self + ensure + self.opened_project = _opened_project + end + +end +end \ No newline at end of file diff --git a/easy_gantt/app/views/easy_gantt/_already_active_error.html.erb b/easy_gantt/app/views/easy_gantt/_already_active_error.html.erb new file mode 100644 index 0000000..e218045 --- /dev/null +++ b/easy_gantt/app/views/easy_gantt/_already_active_error.html.erb @@ -0,0 +1,3 @@ +

+ <%= l(:error_epm_easy_gantt_already_active) %> +

diff --git a/easy_gantt/app/views/easy_gantt/_includes.html.erb b/easy_gantt/app/views/easy_gantt/_includes.html.erb new file mode 100644 index 0000000..3550c0b --- /dev/null +++ b/easy_gantt/app/views/easy_gantt/_includes.html.erb @@ -0,0 +1,49 @@ +<% + heads_for_wiki_formatter +%> + +<%= content_for :header_tags do %> + <%= easy_gantt_include_css('easy_gantt', media: 'all') %> + <%= easy_gantt_include_css('modal_editor') %> + <%= easy_gantt_include_js( + 'utils', + 'dhtmlxgantt', + 'dhtmlxgantt_marker', + 'main', + 'data', + 'loader', + 'saver', + 'logger', + 'widget', + 'panel_widget', + 'gantt_widget', + 'view', + 'history', + 'dhtml_modif', + 'dhtml_addons', + 'dhtml_rewrite', + ('dhtml_relations' if Setting.parent_issue_dates === "derived"), + 'background', + 'pro_manager', + 'storage', + 'tooltip', + 'toolpanel', + 'print', + 'left_grid', + 'sumrow', + 'bars', + 'problem_finder', + 'collapsor', + 'flash_message', + 'libs/mustache', + 'libs/raf', + 'libs/svg', + ) %> + <%= easy_gantt_include_js( + ('sample' unless EasyGantt.easy_gantt_pro?), + ('libs/moment') + ) %> + +<% end %> diff --git a/easy_gantt/app/views/easy_gantt/_js_prepare.html.erb b/easy_gantt/app/views/easy_gantt/_js_prepare.html.erb new file mode 100644 index 0000000..e50c16f --- /dev/null +++ b/easy_gantt/app/views/easy_gantt/_js_prepare.html.erb @@ -0,0 +1,151 @@ +<%= content_for :header_tags do %> + +<% end %> diff --git a/easy_gantt/app/views/easy_gantt/_menu_graph.html.erb b/easy_gantt/app/views/easy_gantt/_menu_graph.html.erb new file mode 100644 index 0000000..d84be79 --- /dev/null +++ b/easy_gantt/app/views/easy_gantt/_menu_graph.html.erb @@ -0,0 +1,71 @@ +<% + unless defined?(show_menu_items) + show_menu_items = true + end + + zooms = ['day', 'week', 'month', 'quarter', 'year'] +%> + + <%= sprite_icon('folder', '') %> + <%= sprite_icon('milestone', '') %> + <%= sprite_icon('parent', '') %> + +
+
+
+ <% if Rails.env.development? %> + <%= easy_gantt_js_button l(:button_test), id: 'button_test' %> + <% end %> + + <%= easy_gantt_js_button("", id: 'button_jump_today', title: l(:jump_today, scope: [:easy_gantt, :title]), icon: 'icon-list') %> + + <% zooms.each do |name| %> + <%= easy_gantt_js_button(:"#{name}_zoom", id: "button_#{name}_zoom", icon: "icon-zoom-in") %> + <% end %> +
+ +
+ <% if show_menu_items %> + <%= easy_gantt_js_button(:problem_finder, { + url: 'javascript:void(0)', + id: 'button_problem_finder', + class: 'problem-finder', + no_button: true + }) %> +
+ <%= sprite_icon('settings', l(:tool_panel, :scope => [:easy_gantt, :button])) %> +
    + <% menu_items_for(:easy_gantt_tools, @project) do |node| %> +
  • + <% + opts = node.html_options.dup + opts[:url] = (node.url.is_a?(Proc) ? node.url.call(@project) : node.url) + opts[:id] = "button_#{node.name}" + opts[:no_button] = 'true' + + caption = opts[:caption].is_a?(Proc) ? opts[:caption].call(params[:gantt_type]) : node.caption + %> + <% if opts.delete(:trial) %> + <%= easy_gantt_help_button(node.name, caption, opts) %> + <% else %> + <%= easy_gantt_js_button(caption, opts) %> + <% end %> +
  • + <% end %> +
+
+ <%= easy_gantt_js_button(l(:button_save), { + url: 'javascript:void(0)', + id: 'button_save', + icon: 'icon-save', + no_button: true, + class: 'button-positive button-1' + }) %> + <% end %> +
+ <%= call_hook(:view_easy_gantts_issues_toolbars, project: @project) %> +
+ + +
+ diff --git a/easy_gantt/app/views/easy_gantt/easy_queries/_show.html.erb b/easy_gantt/app/views/easy_gantt/easy_queries/_show.html.erb new file mode 100644 index 0000000..bdb722c --- /dev/null +++ b/easy_gantt/app/views/easy_gantt/easy_queries/_show.html.erb @@ -0,0 +1,72 @@ +<% + unless defined?(form_options) + form_options = {} + end + + query ||= @query + form_path ||= easy_gantt_path(@project) + form_options[:additional_elements_to_serialize] ||= 'null' +%> + +
<%= title(easy_query_name) %>
+ +<%= form_tag(form_path, method: :get, id: 'query_form') do %> +
+ <%= hidden_field_tag 'set_filter', '1' %> + +
+
"> + "> + <%= sprite_icon(@query.new_record? ? "angle-down" : "angle-right") %> + <%= l(:label_filter_plural) %> + +
"> + <%= render 'queries/filters', query: query %> +
+
+ + +
+ +

+ <%= link_to_function sprite_icon('checked', l(:button_apply)), 'applyEasyGanttQuery()', class: 'icon icon-checked' %> + <%= link_to sprite_icon('reload', l(:button_clear)), { set_filter: 1, project_id: @project }, class: 'icon icon-reload' %> +

+
+<% end %> + + diff --git a/easy_gantt/app/views/easy_gantt/index.html.erb b/easy_gantt/app/views/easy_gantt/index.html.erb new file mode 100644 index 0000000..4b6a560 --- /dev/null +++ b/easy_gantt/app/views/easy_gantt/index.html.erb @@ -0,0 +1,176 @@ +<% + plugin = Redmine::Plugin.find('easy_gantt') + query ||= @query + + main_gantt_params = query.to_params.merge(key: User.current.api_key, format: 'json') + main_gantt_path = if @project + issues_easy_gantt_path(@project, main_gantt_params) + else + projects_easy_gantt_path(main_gantt_params) + end + + unless defined?(show_query) + show_query = true + end + + if EasyGantt.easy_gantt_pro? + sample_path = '' + else + sample_path = "#{home_path}plugin_assets/easy_gantt/data/sample_{{version}}.json" + end +%> + +
+ <% if show_query %> + <% if User.current.admin? && @project.nil? %> +
+ <%= link_to sprite_icon('settings', ''), plugin_settings_path(plugin, back_url: request.fullpath), class: 'icon icon-settings', title: l(:label_easy_gantt_settings)%> +
+ <% end %> + + <%= render query, easy_query_name: l(:heading_easy_gantts_issues), + wrapper_class: '', + form_options: { additional_elements_to_serialize: '$("input#easy_gantt_type")' }, + options: { show_free_search: false, + show_custom_formatting: false, + additional_tagged_url_options: { gantt_type: params[:gantt_type] } } %> + <% end %> + + <%= hidden_field_tag 'gantt_type', '', id: 'easy_gantt_type' %> + + <%= render 'easy_gantt/menu_graph' %> + + +
+ +<%= render 'easy_gantt/includes' %> +<%= render 'easy_gantt/js_prepare' %> + +<%= content_for :header_tags do %> + +<% end %> + +<%= call_hook(:view_easy_gantt_index_bottom, project: @project, query: query) %> + +<% content_for :sidebar do %> + <%= call_hook(:view_easy_gantt_index_sidebar, project: @project, query: query, gantt_type: params[:gantt_type]) %> + + <% # DEPRECATED %> + <%= call_hook(:view_easy_gantts_issues_sidebar, project: @project, query: query) %> +<% end %> diff --git a/easy_gantt/app/views/easy_gantt/issues.api.rsb b/easy_gantt/app/views/easy_gantt/issues.api.rsb new file mode 100644 index 0000000..f4c7177 --- /dev/null +++ b/easy_gantt/app/views/easy_gantt/issues.api.rsb @@ -0,0 +1,12 @@ +api.easy_gantt_data do + api.start_date @start_date + api.end_date @end_date + + api_render_columns(api, @query) + api_render_projects(api, @projects) + api_render_issues(api, @issues, with_columns: true) + + api_render_relations(api, @relations) + + api_render_versions(api, @versions) +end diff --git a/easy_gantt/app/views/easy_gantt/project_issues.api.rsb b/easy_gantt/app/views/easy_gantt/project_issues.api.rsb new file mode 100644 index 0000000..df7ebb4 --- /dev/null +++ b/easy_gantt/app/views/easy_gantt/project_issues.api.rsb @@ -0,0 +1,5 @@ +api.easy_gantt_data do + api_render_issues(api, @issues) + api_render_relations(api, @relations) + api_render_versions(api, @versions) +end diff --git a/easy_gantt/app/views/easy_gantt/projects.api.rsb b/easy_gantt/app/views/easy_gantt/projects.api.rsb new file mode 100644 index 0000000..b4948ec --- /dev/null +++ b/easy_gantt/app/views/easy_gantt/projects.api.rsb @@ -0,0 +1,7 @@ +api.easy_gantt_data do + api.start_date @start_date + api.end_date @end_date + + api_render_columns(api, @query) + api_render_projects(api, @projects, with_columns: true) +end diff --git a/easy_gantt/app/views/easy_gantt_easy_issue_queries/_filters_custom_formatting.html.erb b/easy_gantt/app/views/easy_gantt_easy_issue_queries/_filters_custom_formatting.html.erb new file mode 100644 index 0000000..7876c5a --- /dev/null +++ b/easy_gantt/app/views/easy_gantt_easy_issue_queries/_filters_custom_formatting.html.erb @@ -0,0 +1,5 @@ +<% # Just for hiding %> + + diff --git a/easy_gantt/app/views/settings/_easy_gantt.html.erb b/easy_gantt/app/views/settings/_easy_gantt.html.erb new file mode 100644 index 0000000..438d7b6 --- /dev/null +++ b/easy_gantt/app/views/settings/_easy_gantt.html.erb @@ -0,0 +1,44 @@ +<% + default_zoom_options = [ + [l('easy_gantt.button.day_zoom'), 'day'], + [l('easy_gantt.button.week_zoom'), 'week'], + [l('easy_gantt.button.month_zoom'), 'month'], + [l('easy_gantt.button.quarter_zoom'), 'quarter'], + [l('easy_gantt.button.year_zoom'), 'year'] + ] +%> + +<%= title l(:title_easy_gantt_settings) %> + +
+

+ <%= label_tag 'settings_relation_delay_in_workdays', l(:field_easy_gantt_relation_delay_in_workdays) %> + <%= check_box_tag 'settings[relation_delay_in_workdays]', '1', @settings['relation_delay_in_workdays'] == '1' %> + + <%= l(:text_easy_gantt_relation_delay_in_workdays) %> + +

+ +

+ <%= label_tag 'settings_show_project_progress', l(:field_easy_gantt_show_project_progress) %> + <%= check_box_tag 'settings[show_project_progress]', '1', @settings['show_project_progress'] == '1' %> + + <%= l(:text_easy_gantt_show_project_progress) %> + +

+ +

+ <%= label_tag 'settings_show_task_soonest_start', l(:field_easy_gantt_show_task_soonest_start) %> + <%= check_box_tag 'settings[show_task_soonest_start]', '1', @settings['show_task_soonest_start'] == '1' %> + + <%= l(:text_easy_gantt_show_task_soonest_start) %> + +

+ +

+ <%= label_tag 'settings_default_zoom', l(:field_easy_gantt_default_zoom) %> + <%= select_tag 'settings[default_zoom]', options_for_select(default_zoom_options, @settings['default_zoom']) %> +

+ + <%= call_hook :view_easy_gantt_settings %> +
diff --git a/easy_gantt/assets/data/gantt.json b/easy_gantt/assets/data/gantt.json new file mode 100644 index 0000000..2596648 --- /dev/null +++ b/easy_gantt/assets/data/gantt.json @@ -0,0 +1,84 @@ +{ + "start_date": "yyyy-mm-dd", + "due_date":"yyyy-mm-dd", + "columns": [ + { + "name": "name", + "title": "Jméno", + "mapped":true, + "type":"text" + }, + { + "name":"start_date", + "title":"Počátek", + "mapped":true + }, + { + "name":"end_date", + "title":"Konec", + "mapped":true + }, + { + "name": "assignee", + "title": "Přirazeno", + "mapped":true, + "type":"select", + "completes":"/easy_auto_completer/assignable_users" + }, + { + "name": "address", + "title": "Adresa", + "type":"text" + }, + { + "name": "priority", + "title": "Priorita", + "type":"select", + "completes":"/easy_auto_completer/issue_priorities" + }, + { + "name":"estimated", + "title":"Odhad", + "type":"text" + } + ], + "issues": [ + { + "id": 0, + "name": "název úkolu", + "start_date": "yyyy-mm-dd", + "end_date": "yyyy-mm-dd", + "estimated": 25, + "progress": 0.10, + "css": "issue-open project-35 tracker-5 status-1", + "assignee": {"id": 65, "name": "lukas"}, + "version":65, + + "columns": { + "address":"Levá 5, Horní Dolní", + "priority":"Vysoká", + "estimated":256 + } + } + ], + "projects":[], + "versions":[ + { + "id": 65, + "name": "název milníku", + "start_date": "yyyy-mm-dd", + "css": "issue-open project-35 tracker-5 status-1", + "milestone":true + } + + ], + "relations": [ + { + "id": 5, + "source": 6, + "target": 7, + "type": "precedes", + "delay": 0 + } + ] +} diff --git a/easy_gantt/assets/data/sample_1.json b/easy_gantt/assets/data/sample_1.json new file mode 100644 index 0000000..ebf2df2 --- /dev/null +++ b/easy_gantt/assets/data/sample_1.json @@ -0,0 +1,565 @@ +{ + "easy_gantt_data": { + "start_date": "2016-03-28", + "end_date": "2016-06-08", + "columns": [ + { + "name": "subject", + "title": "Subject" + }, + { + "name": "assigned_to", + "title": "Assignee" + } + ], + "projects": [], + "issues": [ + { + "id": 371, + "name": "Features Specification", + "start_date": "2016-03-29", + "due_date": "2016-03-29", + "estimated_hours": 1.0, + "done_ratio": 0, + "fixed_version_id": 58, + "overdue": true, + "project_id": 41, + "tracker_id": 5, + "priority_id": 9, + "status_id": 1, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Features Specification" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 372, + "name": "Creative brief", + "start_date": "2016-04-04", + "due_date": "2016-04-08", + "estimated_hours": 1.0, + "done_ratio": 0, + "fixed_version_id": 58, + "overdue": true, + "project_id": 41, + "tracker_id": 5, + "priority_id": 9, + "status_id": 1, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Creative brief" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 373, + "name": "Marketing brief", + "start_date": "2016-04-04", + "due_date": "2016-04-04", + "estimated_hours": 1.0, + "done_ratio": 0, + "fixed_version_id": 58, + "overdue": true, + "project_id": 41, + "tracker_id": 5, + "priority_id": 9, + "status_id": 1, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Marketing brief" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 374, + "name": "Wireframes", + "start_date": "2016-04-11", + "due_date": "2016-04-11", + "estimated_hours": 8.0, + "done_ratio": 0, + "fixed_version_id": 57, + "overdue": true, + "project_id": 41, + "tracker_id": 3, + "priority_id": 9, + "status_id": 1, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Wireframes" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 375, + "name": "Graphics", + "start_date": "2016-04-15", + "due_date": "2016-04-18", + "estimated_hours": 16.0, + "done_ratio": 0, + "fixed_version_id": 57, + "overdue": true, + "project_id": 41, + "tracker_id": 3, + "priority_id": 9, + "status_id": 1, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Graphics" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 376, + "name": "XHTML + CSS coding", + "start_date": "2016-04-25", + "due_date": "2016-05-02", + "estimated_hours": 15.0, + "done_ratio": 0, + "fixed_version_id": 57, + "overdue": true, + "project_id": 41, + "tracker_id": 3, + "priority_id": 9, + "status_id": 1, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "XHTML + CSS coding" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 378, + "name": "VirtueMart implementation", + "start_date": "2016-05-03", + "due_date": "2016-05-03", + "estimated_hours": 5.0, + "done_ratio": 0, + "fixed_version_id": 56, + "overdue": true, + "project_id": 41, + "tracker_id": 4, + "priority_id": 9, + "status_id": 1, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "VirtueMart implementation" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 383, + "name": "On-line payments", + "start_date": "2016-05-09", + "due_date": "2016-05-10", + "estimated_hours": 10.0, + "done_ratio": 0, + "fixed_version_id": 56, + "overdue": true, + "project_id": 41, + "tracker_id": 13, + "priority_id": 9, + "status_id": 1, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "On-line payments" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 379, + "name": "Web structure", + "start_date": "2016-05-11", + "due_date": "2016-05-11", + "estimated_hours": 5.0, + "done_ratio": 0, + "fixed_version_id": 55, + "overdue": true, + "project_id": 41, + "tracker_id": 4, + "priority_id": 9, + "status_id": 1, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Web structure" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 377, + "name": "CMS - implementation + setting", + "start_date": "2016-05-12", + "due_date": "2016-05-12", + "estimated_hours": 5.0, + "done_ratio": 0, + "fixed_version_id": 56, + "overdue": true, + "project_id": 41, + "tracker_id": 4, + "priority_id": 9, + "status_id": 1, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "CMS - implementation + setting" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 380, + "name": "Products", + "start_date": "2016-05-16", + "due_date": "2016-05-17", + "estimated_hours": 10.0, + "done_ratio": 0, + "fixed_version_id": 55, + "overdue": true, + "project_id": 41, + "tracker_id": 4, + "priority_id": 9, + "status_id": 1, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Products" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 381, + "name": "Specific features", + "start_date": "2016-05-18", + "due_date": "2016-05-18", + "estimated_hours": 10.0, + "done_ratio": 0, + "fixed_version_id": 54, + "overdue": true, + "project_id": 41, + "tracker_id": 13, + "priority_id": 9, + "status_id": 1, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Specific features" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 382, + "name": "Easy Redmine Integration", + "start_date": "2016-05-23", + "due_date": "2016-05-23", + "estimated_hours": 5.0, + "done_ratio": 0, + "fixed_version_id": 54, + "overdue": true, + "project_id": 41, + "tracker_id": 13, + "priority_id": 9, + "status_id": 1, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Easy Redmine Integration" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 384, + "name": "Google Analytics", + "start_date": "2016-05-25", + "due_date": "2016-05-30", + "estimated_hours": 3.0, + "done_ratio": 0, + "fixed_version_id": 53, + "overdue": true, + "project_id": 41, + "tracker_id": 4, + "priority_id": 9, + "status_id": 1, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Google Analytics" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 385, + "name": "Testing ", + "start_date": "2016-05-30", + "due_date": "2016-05-31", + "estimated_hours": 10.0, + "done_ratio": 0, + "fixed_version_id": 53, + "overdue": true, + "project_id": 41, + "tracker_id": 4, + "priority_id": 9, + "status_id": 1, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Testing " + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 386, + "name": "Debug", + "start_date": "2016-06-02", + "due_date": "2016-06-07", + "estimated_hours": 10.0, + "done_ratio": 0, + "fixed_version_id": 53, + "overdue": true, + "project_id": 41, + "tracker_id": 13, + "priority_id": 9, + "status_id": 1, + "assigned_to_id": 21, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Debug" + }, + { + "name": "assigned_to", + "value": "Ondrej Ezr" + } + ] + }, + { + "id": 387, + "name": "Training", + "start_date": "2016-06-06", + "due_date": "2016-06-07", + "estimated_hours": 4.0, + "done_ratio": 0, + "fixed_version_id": 53, + "overdue": true, + "project_id": 41, + "tracker_id": 4, + "priority_id": 9, + "status_id": 1, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Training" + }, + { + "name": "assigned_to", + "value": "" + } + ] + } + ], + "relations": [ + { + "id": 27, + "source_id": 374, + "target_id": 375, + "type": "precedes", + "delay": 3 + }, + { + "id": 28, + "source_id": 375, + "target_id": 376, + "type": "precedes", + "delay": 4 + }, + { + "id": 29, + "source_id": 376, + "target_id": 377, + "type": "precedes", + "delay": 7 + }, + { + "id": 30, + "source_id": 385, + "target_id": 386, + "type": "precedes", + "delay": 1 + } + ], + "versions": [ + { + "id": 58, + "name": "Analysis", + "start_date": "2016-04-04", + "project_id": 41, + "permissions": { + "editable": true + } + }, + { + "id": 57, + "name": "Design", + "start_date": "2016-05-02", + "project_id": 41, + "permissions": { + "editable": true + } + }, + { + "id": 56, + "name": "CMS + E-commerce platform", + "start_date": "2016-05-13", + "project_id": 41, + "permissions": { + "editable": true + } + }, + { + "id": 55, + "name": "Content + Products", + "start_date": "2016-05-17", + "project_id": 41, + "permissions": { + "editable": true + } + }, + { + "id": 54, + "name": "Easy Redmine integration", + "start_date": "2016-05-23", + "project_id": 41, + "permissions": { + "editable": true + } + }, + { + "id": 53, + "name": "Public Launch", + "start_date": "2016-06-10", + "project_id": 41, + "permissions": { + "editable": true + } + } + ] + } +} \ No newline at end of file diff --git a/easy_gantt/assets/data/sample_global.json b/easy_gantt/assets/data/sample_global.json new file mode 100644 index 0000000..8015eda --- /dev/null +++ b/easy_gantt/assets/data/sample_global.json @@ -0,0 +1,9663 @@ +{ + "easy_gantt_data": { + "start_date": "2016-03-28", + "end_date": "2016-12-25", + "columns": [ + { + "name": "subject", + "title": "Subject" + }, + { + "name": "status", + "title": "Status" + }, + { + "name": "priority", + "title": "Priority" + }, + { + "name": "assigned_to", + "title": "Assignee" + } + ], + "projects": [ + { + "id": 3, + "name": "3. IT Projects", + "start_date": "2016-03-29", + "due_date": "2016-11-01", + "status_id": 1, + "permissions": { + "editable": true + }, + "done_ratio": 4.335324043353241 + }, + { + "id": 62, + "name": "4. Product Development", + "start_date": "2016-06-20", + "due_date": "2016-07-04", + "status_id": 1, + "permissions": { + "editable": true + }, + "done_ratio": 100.0 + }, + { + "id": 63, + "name": "1. Administrative Projects", + "start_date": "2016-06-27", + "due_date": "2016-07-13", + "status_id": 15, + "permissions": { + "editable": true + }, + "done_ratio": 100.0 + }, + { + "id": 77, + "name": "2. HR Projects", + "start_date": "2016-05-05", + "due_date": "2016-09-14", + "status_id": 1, + "permissions": { + "editable": true + }, + "done_ratio": 4.366107838695061 + }, + { + "id": 10, + "name": "Client Project", + "start_date": "2016-05-30", + "due_date": "2016-11-01", + "parent_id": 3, + "status_id": 1, + "permissions": { + "editable": true + }, + "done_ratio": 32.664756446991404 + }, + { + "id": 28, + "name": "Continuous Software Development", + "start_date": "2016-06-06", + "due_date": "2016-09-21", + "parent_id": 3, + "status_id": 1, + "permissions": { + "editable": true + }, + "done_ratio": 0.0 + }, + { + "id": 40, + "name": "Implementation of IS", + "start_date": "2016-06-01", + "due_date": "2016-08-27", + "parent_id": 3, + "status_id": 1, + "permissions": { + "editable": true + }, + "done_ratio": 14.07035175879397 + }, + { + "id": 41, + "name": "E-commerce Project Implementation", + "start_date": "2016-03-29", + "due_date": "2016-06-07", + "parent_id": 3, + "status_id": 1, + "permissions": { + "editable": true + }, + "done_ratio": 0.0 + }, + { + "id": 88, + "name": "Trainings", + "start_date": "2016-06-16", + "due_date": "2016-08-27", + "parent_id": 77, + "status_id": 15, + "permissions": { + "editable": true + }, + "done_ratio": 0.0 + }, + { + "id": 106, + "name": "Design to Schedule Model", + "start_date": "2016-06-20", + "due_date": "2016-07-04", + "parent_id": 62, + "status_id": 1, + "permissions": { + "editable": true + }, + "done_ratio": 100.0 + } + ], + "issues": [ + { + "id": 741, + "name": "Managing projects", + "start_date": "2016-06-27", + "due_date": "2016-09-12", + "estimated_hours": 400.0, + "done_ratio": 0, + "project_id": 3, + "tracker_id": 1, + "priority_id": 9, + "status_id": 1, + "assigned_to_id": 31, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Managing projects" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "Project Manager" + } + ] + }, + { + "id": 415, + "name": "Subtask for Doc", + "start_date": "2016-05-30", + "due_date": "2016-05-30", + "estimated_hours": 20.0, + "done_ratio": 0, + "fixed_version_id": 10, + "overdue": true, + "parent_issue_id": 38, + "project_id": 10, + "tracker_id": 1, + "priority_id": 9, + "status_id": 1, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Subtask for Doc" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 416, + "name": "subtask", + "start_date": "2016-06-14", + "due_date": "2016-06-14", + "estimated_hours": 10.0, + "done_ratio": 0, + "fixed_version_id": 9, + "overdue": true, + "parent_issue_id": 36, + "project_id": 10, + "tracker_id": 1, + "priority_id": 9, + "status_id": 1, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "subtask" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 36, + "name": "Documentation for Block no 2", + "start_date": "2016-06-22", + "due_date": "2016-06-22", + "estimated_hours": 5.0, + "done_ratio": 0, + "fixed_version_id": 9, + "overdue": true, + "project_id": 10, + "tracker_id": 1, + "priority_id": 9, + "status_id": 1, + "assigned_to_id": 9, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Documentation for Block no 2" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "Robert Kovacik" + } + ] + }, + { + "id": 38, + "name": "Planning phase review", + "start_date": "2016-06-29", + "due_date": "2016-06-29", + "estimated_hours": 0.0, + "done_ratio": 100, + "fixed_version_id": 10, + "project_id": 10, + "tracker_id": 1, + "priority_id": 9, + "status_id": 2, + "assigned_to_id": 5, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Planning phase review" + }, + { + "name": "status", + "value": "Realisation" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "Manager Manager" + } + ] + }, + { + "id": 488, + "name": "Review meeting", + "start_date": "2016-07-01", + "due_date": "2016-07-02", + "estimated_hours": 0.0, + "done_ratio": 0, + "fixed_version_id": 10, + "project_id": 10, + "tracker_id": 1, + "priority_id": 9, + "status_id": 2, + "assigned_to_id": 5, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Review meeting" + }, + { + "name": "status", + "value": "Realisation" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "Manager Manager" + } + ] + }, + { + "id": 33, + "name": "Block no 2 constructing", + "start_date": "2016-07-15", + "due_date": "2016-07-17", + "estimated_hours": 20.0, + "done_ratio": 0, + "fixed_version_id": 8, + "project_id": 10, + "tracker_id": 1, + "priority_id": 9, + "status_id": 1, + "assigned_to_id": 8, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Block no 2 constructing" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "Dominka Support Operative" + } + ] + }, + { + "id": 37, + "name": "Documentation for Block no 1", + "start_date": "2016-07-16", + "due_date": "2016-07-23", + "estimated_hours": 50.0, + "done_ratio": 0, + "fixed_version_id": 9, + "project_id": 10, + "tracker_id": 1, + "priority_id": 9, + "status_id": 1, + "assigned_to_id": 9, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Documentation for Block no 1" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "Robert Kovacik" + } + ] + }, + { + "id": 32, + "name": "Block no 3 constructing", + "start_date": "2016-07-17", + "due_date": "2016-07-25", + "estimated_hours": 40.0, + "done_ratio": 0, + "fixed_version_id": 8, + "project_id": 10, + "tracker_id": 1, + "priority_id": 9, + "status_id": 1, + "assigned_to_id": 8, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Block no 3 constructing" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "Dominka Support Operative" + } + ] + }, + { + "id": 34, + "name": "Block no 1 constructing", + "start_date": "2016-07-25", + "due_date": "2016-07-25", + "estimated_hours": 24.0, + "done_ratio": 0, + "fixed_version_id": 8, + "project_id": 10, + "tracker_id": 1, + "priority_id": 9, + "status_id": 2, + "assigned_to_id": 5, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Block no 1 constructing" + }, + { + "name": "status", + "value": "Realisation" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "Manager Manager" + } + ] + }, + { + "id": 35, + "name": "Documentation for Block no 3", + "start_date": "2016-07-26", + "due_date": "2016-07-26", + "estimated_hours": 0.0, + "done_ratio": 0, + "fixed_version_id": 9, + "project_id": 10, + "tracker_id": 1, + "priority_id": 9, + "status_id": 1, + "assigned_to_id": 6, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Documentation for Block no 3" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "Peter Project Man" + } + ] + }, + { + "id": 39, + "name": "Review meeting", + "start_date": "2016-08-24", + "due_date": "2016-10-03", + "estimated_hours": 90.0, + "done_ratio": 70, + "fixed_version_id": 10, + "project_id": 10, + "tracker_id": 1, + "priority_id": 11, + "status_id": 2, + "assigned_to_id": 16, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Review meeting" + }, + { + "name": "status", + "value": "Realisation" + }, + { + "name": "priority", + "value": "Easy task" + }, + { + "name": "assigned_to", + "value": "Manager Française " + } + ] + }, + { + "id": 31, + "name": "Testing", + "start_date": "2016-08-29", + "due_date": "2016-09-12", + "estimated_hours": 50.0, + "done_ratio": 30, + "fixed_version_id": 7, + "project_id": 10, + "tracker_id": 1, + "priority_id": 9, + "status_id": 1, + "assigned_to_id": 9, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Testing" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "Robert Kovacik" + } + ] + }, + { + "id": 40, + "name": "Evaluation of draft documentation", + "start_date": "2016-10-09", + "due_date": "2016-10-10", + "estimated_hours": 40.0, + "done_ratio": 90, + "fixed_version_id": 10, + "project_id": 10, + "tracker_id": 1, + "priority_id": 10, + "status_id": 1, + "assigned_to_id": 9, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Evaluation of draft documentation" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "High" + }, + { + "name": "assigned_to", + "value": "Robert Kovacik" + } + ] + }, + { + "id": 30, + "name": "Installation of new products - prepare \"Instal team\"", + "start_date": "2016-10-13", + "due_date": "2016-11-01", + "done_ratio": 30, + "css": " closed", + "fixed_version_id": 6, + "project_id": 10, + "tracker_id": 1, + "priority_id": 9, + "status_id": 6, + "assigned_to_id": 9, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Installation of new products - prepare \"Instal team\"" + }, + { + "name": "status", + "value": "Done" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "Robert Kovacik" + } + ] + }, + { + "id": 330, + "name": "Data templates do not import estimated time", + "start_date": "2016-06-06", + "due_date": "2016-07-25", + "estimated_hours": 200.0, + "done_ratio": 0, + "css": " closed", + "fixed_version_id": 39, + "project_id": 28, + "tracker_id": 14, + "priority_id": 9, + "status_id": 6, + "assigned_to_id": 20, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Data templates do not import estimated time" + }, + { + "name": "status", + "value": "Done" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "Pavel Rosicky" + } + ] + }, + { + "id": 159, + "name": "Workshop with client", + "start_date": "2016-06-11", + "done_ratio": 0, + "css": " closed", + "project_id": 28, + "tracker_id": 1, + "priority_id": 9, + "status_id": 6, + "assigned_to_id": 7, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Workshop with client" + }, + { + "name": "status", + "value": "Done" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "Ricky Account Manager" + } + ] + }, + { + "id": 160, + "name": "Business Analysis", + "start_date": "2016-06-11", + "done_ratio": 0, + "css": " closed", + "project_id": 28, + "tracker_id": 1, + "priority_id": 9, + "status_id": 6, + "assigned_to_id": 6, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Business Analysis" + }, + { + "name": "status", + "value": "Done" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "Peter Project Man" + } + ] + }, + { + "id": 161, + "name": "Target Group", + "start_date": "2016-06-11", + "done_ratio": 0, + "css": " closed", + "project_id": 28, + "tracker_id": 1, + "priority_id": 9, + "status_id": 6, + "assigned_to_id": 6, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Target Group" + }, + { + "name": "status", + "value": "Done" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "Peter Project Man" + } + ] + }, + { + "id": 303, + "name": "Gantt Chart - new task from context menu", + "start_date": "2016-06-14", + "due_date": "2016-06-21", + "estimated_hours": 1.0, + "done_ratio": 0, + "css": " closed", + "fixed_version_id": 39, + "project_id": 28, + "tracker_id": 13, + "priority_id": 10, + "status_id": 6, + "assigned_to_id": 14, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Gantt Chart - new task from context menu" + }, + { + "name": "status", + "value": "Done" + }, + { + "name": "priority", + "value": "High" + }, + { + "name": "assigned_to", + "value": "Alena Staffer" + } + ] + }, + { + "id": 324, + "name": "Task list loading - ", + "start_date": "2016-06-14", + "due_date": "2016-06-21", + "estimated_hours": 1.0, + "done_ratio": 0, + "fixed_version_id": 51, + "overdue": true, + "project_id": 28, + "tracker_id": 14, + "priority_id": 9, + "status_id": 1, + "assigned_to_id": 19, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Task list loading - " + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "Ondrej Moravcik" + } + ] + }, + { + "id": 329, + "name": "Timer bug", + "start_date": "2016-06-14", + "due_date": "2016-06-21", + "estimated_hours": 1.0, + "done_ratio": 0, + "css": " closed", + "fixed_version_id": 39, + "project_id": 28, + "tracker_id": 14, + "priority_id": 9, + "status_id": 6, + "assigned_to_id": 5, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Timer bug" + }, + { + "name": "status", + "value": "Done" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "Manager Manager" + } + ] + }, + { + "id": 154, + "name": "Backend environment setup", + "start_date": "2016-06-27", + "due_date": "2016-07-03", + "estimated_hours": 35.0, + "done_ratio": 0, + "css": " closed", + "project_id": 28, + "tracker_id": 1, + "priority_id": 9, + "status_id": 6, + "assigned_to_id": 8, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Backend environment setup" + }, + { + "name": "status", + "value": "Done" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "Dominka Support Operative" + } + ] + }, + { + "id": 745, + "name": "Complete refactor of query system", + "start_date": "2016-06-27", + "due_date": "2016-09-10", + "estimated_hours": 400.0, + "done_ratio": 0, + "project_id": 28, + "tracker_id": 1, + "priority_id": 9, + "status_id": 1, + "assigned_to_id": 21, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Complete refactor of query system" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "Ondrej Ezr" + } + ] + }, + { + "id": 746, + "name": "Complete documentation of the software", + "start_date": "2016-06-27", + "due_date": "2016-09-10", + "estimated_hours": 400.0, + "done_ratio": 0, + "project_id": 28, + "tracker_id": 1, + "priority_id": 9, + "status_id": 1, + "assigned_to_id": 15, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Complete documentation of the software" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "Pavel Kucera" + } + ] + }, + { + "id": 162, + "name": "Backend features", + "start_date": "2016-06-28", + "done_ratio": 0, + "css": " closed", + "project_id": 28, + "tracker_id": 1, + "priority_id": 9, + "status_id": 6, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Backend features" + }, + { + "name": "status", + "value": "Done" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 163, + "name": "Backend testing", + "start_date": "2016-06-28", + "done_ratio": 0, + "css": " closed", + "project_id": 28, + "tracker_id": 1, + "priority_id": 9, + "status_id": 6, + "assigned_to_id": 6, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Backend testing" + }, + { + "name": "status", + "value": "Done" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "Peter Project Man" + } + ] + }, + { + "id": 164, + "name": "Ecommerce platform", + "start_date": "2016-06-28", + "done_ratio": 0, + "css": " closed", + "project_id": 28, + "tracker_id": 1, + "priority_id": 9, + "status_id": 6, + "assigned_to_id": 7, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Ecommerce platform" + }, + { + "name": "status", + "value": "Done" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "Ricky Account Manager" + } + ] + }, + { + "id": 165, + "name": "Payment gateways", + "start_date": "2016-06-28", + "done_ratio": 0, + "css": " closed", + "project_id": 28, + "tracker_id": 1, + "priority_id": 9, + "status_id": 6, + "assigned_to_id": 8, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Payment gateways" + }, + { + "name": "status", + "value": "Done" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "Dominka Support Operative" + } + ] + }, + { + "id": 166, + "name": "Shippment methods", + "start_date": "2016-06-28", + "done_ratio": 0, + "css": " closed", + "project_id": 28, + "tracker_id": 1, + "priority_id": 9, + "status_id": 6, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Shippment methods" + }, + { + "name": "status", + "value": "Done" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 292, + "name": "CRM - sales plans", + "start_date": "2016-07-11", + "due_date": "2016-07-11", + "estimated_hours": 8.0, + "done_ratio": 0, + "fixed_version_id": 41, + "project_id": 28, + "tracker_id": 13, + "priority_id": 11, + "status_id": 1, + "assigned_to_id": 18, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "CRM - sales plans" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Easy task" + }, + { + "name": "assigned_to", + "value": "Lukas Developer" + } + ] + }, + { + "id": 293, + "name": "Meeting Planner tweaks", + "start_date": "2016-07-11", + "due_date": "2016-08-09", + "estimated_hours": 4.0, + "done_ratio": 0, + "fixed_version_id": 40, + "project_id": 28, + "tracker_id": 13, + "priority_id": 9, + "status_id": 1, + "assigned_to_id": 5, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Meeting Planner tweaks" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "Manager Manager" + } + ] + }, + { + "id": 299, + "name": "Computed custom field - recalculation", + "start_date": "2016-07-11", + "due_date": "2016-07-11", + "estimated_hours": 3.0, + "done_ratio": 0, + "fixed_version_id": 39, + "project_id": 28, + "tracker_id": 13, + "priority_id": 9, + "status_id": 1, + "assigned_to_id": 14, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Computed custom field - recalculation" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "Alena Staffer" + } + ] + }, + { + "id": 301, + "name": "Help Desk - automatically enters external emails", + "start_date": "2016-07-11", + "due_date": "2016-08-23", + "estimated_hours": 1.0, + "done_ratio": 0, + "fixed_version_id": 41, + "project_id": 28, + "tracker_id": 13, + "priority_id": 9, + "status_id": 1, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Help Desk - automatically enters external emails" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 302, + "name": "Bulk time entries - save and go back", + "start_date": "2016-07-11", + "due_date": "2016-08-23", + "estimated_hours": 2.0, + "done_ratio": 0, + "fixed_version_id": 41, + "project_id": 28, + "tracker_id": 13, + "priority_id": 9, + "status_id": 1, + "assigned_to_id": 6, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Bulk time entries - save and go back" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "Peter Project Man" + } + ] + }, + { + "id": 310, + "name": "Issues filter - project is not planned", + "start_date": "2016-07-11", + "due_date": "2016-08-23", + "estimated_hours": 1.0, + "done_ratio": 0, + "fixed_version_id": 41, + "project_id": 28, + "tracker_id": 13, + "priority_id": 9, + "status_id": 1, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Issues filter - project is not planned" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 311, + "name": "Agile board - display custom fields", + "start_date": "2016-07-11", + "due_date": "2016-09-05", + "estimated_hours": 2.0, + "done_ratio": 0, + "fixed_version_id": 42, + "project_id": 28, + "tracker_id": 13, + "priority_id": 9, + "status_id": 1, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Agile board - display custom fields" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 312, + "name": "Help Desk - ticket to group", + "start_date": "2016-07-11", + "due_date": "2016-08-09", + "estimated_hours": 3.0, + "done_ratio": 0, + "fixed_version_id": 40, + "project_id": 28, + "tracker_id": 13, + "priority_id": 9, + "status_id": 1, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Help Desk - ticket to group" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 319, + "name": "Gantt - default filters", + "start_date": "2016-07-11", + "due_date": "2016-08-09", + "estimated_hours": 2.0, + "done_ratio": 0, + "fixed_version_id": 40, + "project_id": 28, + "tracker_id": 13, + "priority_id": 9, + "status_id": 1, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Gantt - default filters" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 325, + "name": "Meeting - repeating does not work", + "start_date": "2016-07-11", + "due_date": "2016-07-30", + "estimated_hours": 20.0, + "done_ratio": 0, + "fixed_version_id": 39, + "project_id": 28, + "tracker_id": 14, + "priority_id": 9, + "status_id": 2, + "assigned_to_id": 14, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Meeting - repeating does not work" + }, + { + "name": "status", + "value": "Realisation" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "Alena Staffer" + } + ] + }, + { + "id": 326, + "name": "Document inserts 2x", + "start_date": "2016-07-11", + "due_date": "2016-07-18", + "estimated_hours": 1.0, + "done_ratio": 0, + "fixed_version_id": 42, + "project_id": 28, + "tracker_id": 14, + "priority_id": 11, + "status_id": 2, + "assigned_to_id": 14, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Document inserts 2x" + }, + { + "name": "status", + "value": "Realisation" + }, + { + "name": "priority", + "value": "Easy task" + }, + { + "name": "assigned_to", + "value": "Alena Staffer" + } + ] + }, + { + "id": 295, + "name": "Agile Board - Fixed Backlog", + "start_date": "2016-07-13", + "due_date": "2016-07-14", + "estimated_hours": 2.0, + "done_ratio": 0, + "fixed_version_id": 39, + "project_id": 28, + "tracker_id": 13, + "priority_id": 9, + "status_id": 1, + "assigned_to_id": 5, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Agile Board - Fixed Backlog" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "Manager Manager" + } + ] + }, + { + "id": 327, + "name": "User edit - apply from template", + "start_date": "2016-07-13", + "due_date": "2016-07-14", + "estimated_hours": 1.0, + "done_ratio": 0, + "fixed_version_id": 39, + "project_id": 28, + "tracker_id": 14, + "priority_id": 9, + "status_id": 3, + "assigned_to_id": 5, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "User edit - apply from template" + }, + { + "name": "status", + "value": "Consultation" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "Manager Manager" + } + ] + }, + { + "id": 296, + "name": "Custom fields - confirmation", + "start_date": "2016-07-18", + "due_date": "2016-07-18", + "estimated_hours": 2.0, + "done_ratio": 0, + "fixed_version_id": 42, + "project_id": 28, + "tracker_id": 13, + "priority_id": 9, + "status_id": 3, + "assigned_to_id": 18, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Custom fields - confirmation" + }, + { + "name": "status", + "value": "Consultation" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "Lukas Developer" + } + ] + }, + { + "id": 297, + "name": "Booking calendar - permissions", + "start_date": "2016-07-18", + "due_date": "2016-07-18", + "estimated_hours": 2.0, + "done_ratio": 0, + "fixed_version_id": 39, + "project_id": 28, + "tracker_id": 13, + "priority_id": 9, + "status_id": 1, + "assigned_to_id": 19, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Booking calendar - permissions" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "Ondrej Moravcik" + } + ] + }, + { + "id": 298, + "name": "Meetings for others", + "start_date": "2016-07-18", + "due_date": "2016-08-05", + "estimated_hours": 70.0, + "done_ratio": 0, + "fixed_version_id": 42, + "project_id": 28, + "tracker_id": 13, + "priority_id": 9, + "status_id": 8, + "assigned_to_id": 14, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Meetings for others" + }, + { + "name": "status", + "value": "Sequence pending" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "Alena Staffer" + } + ] + }, + { + "id": 305, + "name": "Log time on task from more menu - modal window", + "start_date": "2016-07-18", + "due_date": "2016-07-18", + "estimated_hours": 3.0, + "done_ratio": 0, + "fixed_version_id": 42, + "project_id": 28, + "tracker_id": 13, + "priority_id": 10, + "status_id": 3, + "assigned_to_id": 14, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Log time on task from more menu - modal window" + }, + { + "name": "status", + "value": "Consultation" + }, + { + "name": "priority", + "value": "High" + }, + { + "name": "assigned_to", + "value": "Alena Staffer" + } + ] + }, + { + "id": 328, + "name": "Booking callendar - 25 hours per day", + "start_date": "2016-07-18", + "due_date": "2016-07-18", + "estimated_hours": 1.0, + "done_ratio": 0, + "fixed_version_id": 39, + "project_id": 28, + "tracker_id": 14, + "priority_id": 9, + "status_id": 1, + "assigned_to_id": 20, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Booking callendar - 25 hours per day" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "Pavel Rosicky" + } + ] + }, + { + "id": 322, + "name": "Printable templates - project fileds not visible", + "start_date": "2016-07-21", + "due_date": "2016-07-22", + "estimated_hours": 4.0, + "done_ratio": 0, + "fixed_version_id": 39, + "project_id": 28, + "tracker_id": 14, + "priority_id": 10, + "status_id": 3, + "assigned_to_id": 18, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Printable templates - project fileds not visible" + }, + { + "name": "status", + "value": "Consultation" + }, + { + "name": "priority", + "value": "High" + }, + { + "name": "assigned_to", + "value": "Lukas Developer" + } + ] + }, + { + "id": 331, + "name": "Gantt view - tasks do not line-up ", + "start_date": "2016-07-23", + "due_date": "2016-07-23", + "estimated_hours": 5.0, + "done_ratio": 0, + "fixed_version_id": 39, + "project_id": 28, + "tracker_id": 14, + "priority_id": 9, + "status_id": 1, + "assigned_to_id": 19, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Gantt view - tasks do not line-up " + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "Ondrej Moravcik" + } + ] + }, + { + "id": 158, + "name": "Current state evaluation", + "start_date": "2016-07-23", + "due_date": "2016-07-30", + "estimated_hours": 40.0, + "done_ratio": 0, + "css": " closed", + "project_id": 28, + "tracker_id": 1, + "priority_id": 9, + "status_id": 6, + "assigned_to_id": 7, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Current state evaluation" + }, + { + "name": "status", + "value": "Done" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "Ricky Account Manager" + } + ] + }, + { + "id": 294, + "name": "Personal Page Modul - Activity on my tasks/projects", + "start_date": "2016-07-25", + "due_date": "2016-08-10", + "estimated_hours": 80.0, + "done_ratio": 0, + "fixed_version_id": 41, + "project_id": 28, + "tracker_id": 13, + "priority_id": 9, + "status_id": 1, + "assigned_to_id": 19, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Personal Page Modul - Activity on my tasks/projects" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "Ondrej Moravcik" + } + ] + }, + { + "id": 321, + "name": "Social Wall tweaking", + "start_date": "2016-07-26", + "due_date": "2016-07-26", + "estimated_hours": 3.0, + "done_ratio": 0, + "fixed_version_id": 39, + "project_id": 28, + "tracker_id": 14, + "priority_id": 10, + "status_id": 2, + "assigned_to_id": 19, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Social Wall tweaking" + }, + { + "name": "status", + "value": "Realisation" + }, + { + "name": "priority", + "value": "High" + }, + { + "name": "assigned_to", + "value": "Ondrej Moravcik" + } + ] + }, + { + "id": 320, + "name": "Invoicing - default invoice template", + "start_date": "2016-07-31", + "due_date": "2016-07-31", + "estimated_hours": 8.0, + "done_ratio": 0, + "project_id": 28, + "tracker_id": 13, + "priority_id": 9, + "status_id": 1, + "assigned_to_id": 18, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Invoicing - default invoice template" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "Lukas Developer" + } + ] + }, + { + "id": 300, + "name": "Restrict roles creation of issues in certain trackers", + "start_date": "2016-08-02", + "due_date": "2016-08-02", + "estimated_hours": 4.0, + "done_ratio": 0, + "project_id": 28, + "tracker_id": 13, + "priority_id": 9, + "status_id": 1, + "assigned_to_id": 18, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Restrict roles creation of issues in certain trackers" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "Lukas Developer" + } + ] + }, + { + "id": 309, + "name": "Resource Management Setting in tab", + "start_date": "2016-08-02", + "due_date": "2016-08-02", + "estimated_hours": 2.0, + "done_ratio": 0, + "project_id": 28, + "tracker_id": 13, + "priority_id": 10, + "status_id": 2, + "assigned_to_id": 14, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Resource Management Setting in tab" + }, + { + "name": "status", + "value": "Realisation" + }, + { + "name": "priority", + "value": "High" + }, + { + "name": "assigned_to", + "value": "Alena Staffer" + } + ] + }, + { + "id": 304, + "name": "Advanced calendar on personal page - Ajax", + "start_date": "2016-08-07", + "due_date": "2016-08-07", + "estimated_hours": 8.0, + "done_ratio": 0, + "project_id": 28, + "tracker_id": 13, + "priority_id": 10, + "status_id": 1, + "assigned_to_id": 18, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Advanced calendar on personal page - Ajax" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "High" + }, + { + "name": "assigned_to", + "value": "Lukas Developer" + } + ] + }, + { + "id": 313, + "name": "CRM features - Accounts", + "start_date": "2016-08-07", + "due_date": "2016-08-07", + "estimated_hours": 5.0, + "done_ratio": 0, + "project_id": 28, + "tracker_id": 13, + "priority_id": 9, + "status_id": 1, + "assigned_to_id": 18, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "CRM features - Accounts" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "Lukas Developer" + } + ] + }, + { + "id": 314, + "name": "Help Desk - SMS notify", + "start_date": "2016-08-07", + "due_date": "2016-08-07", + "estimated_hours": 3.0, + "done_ratio": 0, + "project_id": 28, + "tracker_id": 13, + "priority_id": 9, + "status_id": 1, + "assigned_to_id": 18, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Help Desk - SMS notify" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "Lukas Developer" + } + ] + }, + { + "id": 307, + "name": "Long running operation to cron", + "start_date": "2016-08-08", + "due_date": "2016-08-08", + "estimated_hours": 5.0, + "done_ratio": 0, + "project_id": 28, + "tracker_id": 13, + "priority_id": 9, + "status_id": 8, + "assigned_to_id": 18, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Long running operation to cron" + }, + { + "name": "status", + "value": "Sequence pending" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "Lukas Developer" + } + ] + }, + { + "id": 317, + "name": "Gantt Relations - follows ", + "start_date": "2016-08-09", + "due_date": "2016-08-09", + "estimated_hours": 4.0, + "done_ratio": 0, + "project_id": 28, + "tracker_id": 13, + "priority_id": 9, + "status_id": 1, + "assigned_to_id": 18, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Gantt Relations - follows " + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "Lukas Developer" + } + ] + }, + { + "id": 323, + "name": "Sequnces - blank page", + "start_date": "2016-08-10", + "due_date": "2016-08-10", + "estimated_hours": 5.0, + "done_ratio": 0, + "project_id": 28, + "tracker_id": 14, + "priority_id": 9, + "status_id": 3, + "assigned_to_id": 18, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Sequnces - blank page" + }, + { + "name": "status", + "value": "Consultation" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "Lukas Developer" + } + ] + }, + { + "id": 315, + "name": "Redmine 3.0 mearge", + "start_date": "2016-08-14", + "due_date": "2016-08-30", + "estimated_hours": 30.0, + "done_ratio": 0, + "project_id": 28, + "tracker_id": 13, + "priority_id": 9, + "status_id": 1, + "assigned_to_id": 18, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Redmine 3.0 mearge" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "Lukas Developer" + } + ] + }, + { + "id": 308, + "name": "Agile - Sprint - Milestone", + "start_date": "2016-08-21", + "due_date": "2016-08-24", + "estimated_hours": 4.0, + "done_ratio": 0, + "project_id": 28, + "tracker_id": 13, + "priority_id": 9, + "status_id": 1, + "assigned_to_id": 18, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Agile - Sprint - Milestone" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "Lukas Developer" + } + ] + }, + { + "id": 316, + "name": "Attendance REST API", + "start_date": "2016-08-21", + "due_date": "2016-08-21", + "estimated_hours": 8.0, + "done_ratio": 0, + "project_id": 28, + "tracker_id": 13, + "priority_id": 9, + "status_id": 1, + "assigned_to_id": 18, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Attendance REST API" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "Lukas Developer" + } + ] + }, + { + "id": 318, + "name": "Attendance monitoring - remaining days of vacation", + "start_date": "2016-08-21", + "due_date": "2016-08-22", + "estimated_hours": 4.0, + "done_ratio": 0, + "project_id": 28, + "tracker_id": 13, + "priority_id": 9, + "status_id": 1, + "assigned_to_id": 18, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Attendance monitoring - remaining days of vacation" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "Lukas Developer" + } + ] + }, + { + "id": 291, + "name": "GitLab integration", + "start_date": "2016-08-25", + "due_date": "2016-09-21", + "estimated_hours": 60.0, + "done_ratio": 0, + "fixed_version_id": 41, + "project_id": 28, + "tracker_id": 13, + "priority_id": 9, + "status_id": 2, + "assigned_to_id": 5, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "GitLab integration" + }, + { + "name": "status", + "value": "Realisation" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "Manager Manager" + } + ] + }, + { + "id": 306, + "name": "Invocing 1", + "start_date": "2016-08-27", + "due_date": "2016-08-31", + "estimated_hours": 10.0, + "done_ratio": 0, + "project_id": 28, + "tracker_id": 13, + "priority_id": 9, + "status_id": 1, + "assigned_to_id": 18, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Invocing 1" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "Lukas Developer" + } + ] + }, + { + "id": 371, + "name": "Features Specification", + "start_date": "2016-03-29", + "due_date": "2016-03-29", + "estimated_hours": 1.0, + "done_ratio": 0, + "fixed_version_id": 58, + "overdue": true, + "project_id": 41, + "tracker_id": 5, + "priority_id": 9, + "status_id": 1, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Features Specification" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 372, + "name": "Creative brief", + "start_date": "2016-04-04", + "due_date": "2016-04-08", + "estimated_hours": 1.0, + "done_ratio": 0, + "fixed_version_id": 58, + "overdue": true, + "project_id": 41, + "tracker_id": 5, + "priority_id": 9, + "status_id": 1, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Creative brief" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 373, + "name": "Marketing brief", + "start_date": "2016-04-04", + "due_date": "2016-04-04", + "estimated_hours": 1.0, + "done_ratio": 0, + "fixed_version_id": 58, + "overdue": true, + "project_id": 41, + "tracker_id": 5, + "priority_id": 9, + "status_id": 1, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Marketing brief" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 374, + "name": "Wireframes", + "start_date": "2016-04-11", + "due_date": "2016-04-11", + "estimated_hours": 8.0, + "done_ratio": 0, + "fixed_version_id": 57, + "overdue": true, + "project_id": 41, + "tracker_id": 3, + "priority_id": 9, + "status_id": 1, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Wireframes" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 375, + "name": "Graphics", + "start_date": "2016-04-15", + "due_date": "2016-04-18", + "estimated_hours": 16.0, + "done_ratio": 0, + "fixed_version_id": 57, + "overdue": true, + "project_id": 41, + "tracker_id": 3, + "priority_id": 9, + "status_id": 1, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Graphics" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 376, + "name": "XHTML + CSS coding", + "start_date": "2016-04-25", + "due_date": "2016-05-02", + "estimated_hours": 15.0, + "done_ratio": 0, + "fixed_version_id": 57, + "overdue": true, + "project_id": 41, + "tracker_id": 3, + "priority_id": 9, + "status_id": 1, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "XHTML + CSS coding" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 378, + "name": "VirtueMart implementation", + "start_date": "2016-05-03", + "due_date": "2016-05-03", + "estimated_hours": 5.0, + "done_ratio": 0, + "fixed_version_id": 56, + "overdue": true, + "project_id": 41, + "tracker_id": 4, + "priority_id": 9, + "status_id": 1, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "VirtueMart implementation" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 383, + "name": "On-line payments", + "start_date": "2016-05-09", + "due_date": "2016-05-10", + "estimated_hours": 10.0, + "done_ratio": 0, + "fixed_version_id": 56, + "overdue": true, + "project_id": 41, + "tracker_id": 13, + "priority_id": 9, + "status_id": 1, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "On-line payments" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 379, + "name": "Web structure", + "start_date": "2016-05-11", + "due_date": "2016-05-11", + "estimated_hours": 5.0, + "done_ratio": 0, + "fixed_version_id": 55, + "overdue": true, + "project_id": 41, + "tracker_id": 4, + "priority_id": 9, + "status_id": 1, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Web structure" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 377, + "name": "CMS - implementation + setting", + "start_date": "2016-05-13", + "due_date": "2016-05-13", + "estimated_hours": 5.0, + "done_ratio": 0, + "fixed_version_id": 56, + "overdue": true, + "project_id": 41, + "tracker_id": 4, + "priority_id": 9, + "status_id": 1, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "CMS - implementation + setting" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 380, + "name": "Products", + "start_date": "2016-05-16", + "due_date": "2016-05-17", + "estimated_hours": 10.0, + "done_ratio": 0, + "fixed_version_id": 55, + "overdue": true, + "project_id": 41, + "tracker_id": 4, + "priority_id": 9, + "status_id": 1, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Products" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 381, + "name": "Specific features", + "start_date": "2016-05-18", + "due_date": "2016-05-18", + "estimated_hours": 10.0, + "done_ratio": 0, + "fixed_version_id": 54, + "overdue": true, + "project_id": 41, + "tracker_id": 13, + "priority_id": 9, + "status_id": 1, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Specific features" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 382, + "name": "Easy Redmine Integration", + "start_date": "2016-05-23", + "due_date": "2016-05-23", + "estimated_hours": 5.0, + "done_ratio": 0, + "fixed_version_id": 54, + "overdue": true, + "project_id": 41, + "tracker_id": 13, + "priority_id": 9, + "status_id": 1, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Easy Redmine Integration" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 384, + "name": "Google Analytics", + "start_date": "2016-05-25", + "due_date": "2016-05-30", + "estimated_hours": 3.0, + "done_ratio": 0, + "fixed_version_id": 53, + "overdue": true, + "project_id": 41, + "tracker_id": 4, + "priority_id": 9, + "status_id": 1, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Google Analytics" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 385, + "name": "Testing ", + "start_date": "2016-05-30", + "due_date": "2016-05-31", + "estimated_hours": 10.0, + "done_ratio": 0, + "fixed_version_id": 53, + "overdue": true, + "project_id": 41, + "tracker_id": 4, + "priority_id": 9, + "status_id": 1, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Testing " + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 386, + "name": "Debug", + "start_date": "2016-06-02", + "due_date": "2016-06-07", + "estimated_hours": 10.0, + "done_ratio": 0, + "fixed_version_id": 53, + "overdue": true, + "project_id": 41, + "tracker_id": 13, + "priority_id": 9, + "status_id": 1, + "assigned_to_id": 21, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Debug" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "Ondrej Ezr" + } + ] + }, + { + "id": 387, + "name": "Training", + "start_date": "2016-06-06", + "due_date": "2016-06-07", + "estimated_hours": 4.0, + "done_ratio": 0, + "fixed_version_id": 53, + "overdue": true, + "project_id": 41, + "tracker_id": 4, + "priority_id": 9, + "status_id": 1, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Training" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 748, + "name": "task 1", + "start_date": "2016-06-27", + "due_date": "2016-06-28", + "estimated_hours": 0.0, + "done_ratio": 100, + "fixed_version_id": 116, + "project_id": 63, + "tracker_id": 6, + "priority_id": 9, + "status_id": 4, + "assigned_to_id": 14, + "is_planned": true, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "task 1" + }, + { + "name": "status", + "value": "To check" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "Alena Staffer" + } + ] + }, + { + "id": 749, + "name": "task 2", + "start_date": "2016-06-27", + "due_date": "2016-06-28", + "estimated_hours": 0.0, + "done_ratio": 0, + "fixed_version_id": 116, + "project_id": 63, + "tracker_id": 6, + "priority_id": 9, + "status_id": 2, + "is_planned": true, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "task 2" + }, + { + "name": "status", + "value": "Realisation" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 750, + "name": "task 3", + "start_date": "2016-06-27", + "due_date": "2016-06-28", + "estimated_hours": 0.0, + "done_ratio": 0, + "fixed_version_id": 116, + "project_id": 63, + "tracker_id": 1, + "priority_id": 11, + "status_id": 4, + "is_planned": true, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "task 3" + }, + { + "name": "status", + "value": "To check" + }, + { + "name": "priority", + "value": "Easy task" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 751, + "name": "task 4", + "start_date": "2016-06-27", + "due_date": "2016-06-28", + "estimated_hours": 0.0, + "done_ratio": 0, + "fixed_version_id": 116, + "project_id": 63, + "tracker_id": 4, + "priority_id": 8, + "status_id": 3, + "is_planned": true, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "task 4" + }, + { + "name": "status", + "value": "Consultation" + }, + { + "name": "priority", + "value": "Low" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 752, + "name": "task 5", + "start_date": "2016-06-27", + "due_date": "2016-06-28", + "estimated_hours": 0.0, + "done_ratio": 0, + "fixed_version_id": 116, + "project_id": 63, + "tracker_id": 13, + "priority_id": 10, + "status_id": 10, + "is_planned": true, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "task 5" + }, + { + "name": "status", + "value": "Approved" + }, + { + "name": "priority", + "value": "High" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 755, + "name": "task 8", + "start_date": "2016-07-03", + "due_date": "2016-07-04", + "estimated_hours": 0.0, + "done_ratio": 0, + "fixed_version_id": 117, + "project_id": 63, + "tracker_id": 5, + "priority_id": 10, + "status_id": 8, + "is_planned": true, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "task 8" + }, + { + "name": "status", + "value": "Sequence pending" + }, + { + "name": "priority", + "value": "High" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 753, + "name": "task 6", + "start_date": "2016-07-04", + "due_date": "2016-07-05", + "estimated_hours": 0.0, + "done_ratio": 0, + "fixed_version_id": 117, + "project_id": 63, + "tracker_id": 3, + "priority_id": 9, + "status_id": 2, + "assigned_to_id": 5, + "is_planned": true, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "task 6" + }, + { + "name": "status", + "value": "Realisation" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "Manager Manager" + } + ] + }, + { + "id": 754, + "name": "task 7", + "start_date": "2016-07-04", + "due_date": "2016-07-05", + "estimated_hours": 0.0, + "done_ratio": 0, + "fixed_version_id": 117, + "project_id": 63, + "tracker_id": 1, + "priority_id": 11, + "status_id": 4, + "is_planned": true, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "task 7" + }, + { + "name": "status", + "value": "To check" + }, + { + "name": "priority", + "value": "Easy task" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 758, + "name": "task 11", + "start_date": "2016-07-10", + "due_date": "2016-07-11", + "estimated_hours": 0.0, + "done_ratio": 0, + "fixed_version_id": 117, + "project_id": 63, + "tracker_id": 15, + "priority_id": 11, + "status_id": 1, + "is_planned": true, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "task 11" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Easy task" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 756, + "name": "task 9", + "start_date": "2016-07-12", + "due_date": "2016-07-13", + "estimated_hours": 0.0, + "done_ratio": 0, + "fixed_version_id": 117, + "project_id": 63, + "tracker_id": 4, + "priority_id": 10, + "status_id": 2, + "is_planned": true, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "task 9" + }, + { + "name": "status", + "value": "Realisation" + }, + { + "name": "priority", + "value": "High" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 757, + "name": "task 10", + "start_date": "2016-07-12", + "due_date": "2016-07-13", + "estimated_hours": 0.0, + "done_ratio": 0, + "parent_issue_id": 756, + "project_id": 63, + "tracker_id": 16, + "priority_id": 15, + "status_id": 3, + "is_planned": true, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "task 10" + }, + { + "name": "status", + "value": "Consultation" + }, + { + "name": "priority", + "value": "Critical" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 2653, + "name": "Venue", + "start_date": "2016-06-16", + "due_date": "2016-08-27", + "done_ratio": 0, + "project_id": 88, + "tracker_id": 19, + "priority_id": 9, + "status_id": 1, + "is_planned": true, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Venue" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 2654, + "name": "Space", + "start_date": "2016-06-16", + "due_date": "2016-08-06", + "done_ratio": 0, + "parent_issue_id": 2653, + "project_id": 88, + "tracker_id": 19, + "priority_id": 9, + "status_id": 1, + "is_planned": true, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Space" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 2655, + "name": "List suitable possibilities according to size", + "start_date": "2016-06-16", + "due_date": "2016-06-20", + "estimated_hours": 10.0, + "done_ratio": 0, + "overdue": true, + "parent_issue_id": 2654, + "project_id": 88, + "tracker_id": 1, + "priority_id": 9, + "status_id": 1, + "is_planned": true, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "List suitable possibilities according to size" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 2699, + "name": "Venue booking", + "start_date": "2016-06-16", + "due_date": "2016-08-27", + "done_ratio": 0, + "parent_issue_id": 2653, + "project_id": 88, + "tracker_id": 19, + "priority_id": 9, + "status_id": 1, + "is_planned": true, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Venue booking" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 2700, + "name": "List suitable possibilities according to type", + "start_date": "2016-06-16", + "due_date": "2016-06-20", + "estimated_hours": 10.0, + "done_ratio": 0, + "overdue": true, + "parent_issue_id": 2699, + "project_id": 88, + "tracker_id": 1, + "priority_id": 9, + "status_id": 1, + "is_planned": true, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "List suitable possibilities according to type" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 2701, + "name": "List suitable possibilities according to location", + "start_date": "2016-06-16", + "due_date": "2016-06-20", + "estimated_hours": 10.0, + "done_ratio": 0, + "overdue": true, + "parent_issue_id": 2699, + "project_id": 88, + "tracker_id": 1, + "priority_id": 9, + "status_id": 1, + "is_planned": true, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "List suitable possibilities according to location" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 2707, + "name": "Programme", + "start_date": "2016-06-16", + "due_date": "2016-08-18", + "done_ratio": 0, + "project_id": 88, + "tracker_id": 19, + "priority_id": 9, + "status_id": 1, + "is_planned": true, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Programme" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 2708, + "name": "Official", + "start_date": "2016-06-16", + "due_date": "2016-08-18", + "done_ratio": 0, + "parent_issue_id": 2707, + "project_id": 88, + "tracker_id": 19, + "priority_id": 9, + "status_id": 1, + "is_planned": true, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Official" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 2709, + "name": "Main programme", + "start_date": "2016-06-16", + "due_date": "2016-08-18", + "done_ratio": 0, + "parent_issue_id": 2708, + "project_id": 88, + "tracker_id": 19, + "priority_id": 9, + "status_id": 1, + "is_planned": true, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Main programme" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 2710, + "name": "Define purpose of the event", + "start_date": "2016-06-16", + "due_date": "2016-06-17", + "estimated_hours": 4.0, + "done_ratio": 0, + "overdue": true, + "parent_issue_id": 2709, + "project_id": 88, + "tracker_id": 1, + "priority_id": 9, + "status_id": 1, + "is_planned": true, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Define purpose of the event" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 2715, + "name": "Secondary activities", + "start_date": "2016-06-16", + "due_date": "2016-06-26", + "done_ratio": 0, + "overdue": true, + "parent_issue_id": 2708, + "project_id": 88, + "tracker_id": 19, + "priority_id": 9, + "status_id": 1, + "is_planned": true, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Secondary activities" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 2717, + "name": "Find how the main activities can be supported", + "start_date": "2016-06-16", + "due_date": "2016-06-16", + "estimated_hours": 8.0, + "done_ratio": 0, + "overdue": true, + "parent_issue_id": 2715, + "project_id": 88, + "tracker_id": 1, + "priority_id": 9, + "status_id": 1, + "is_planned": true, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Find how the main activities can be supported" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 2720, + "name": "Workshops", + "start_date": "2016-06-16", + "due_date": "2016-07-03", + "done_ratio": 0, + "parent_issue_id": 2708, + "project_id": 88, + "tracker_id": 19, + "priority_id": 9, + "status_id": 1, + "is_planned": true, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Workshops" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 2721, + "name": "Create a list of most suitable workshops", + "start_date": "2016-06-16", + "due_date": "2016-06-27", + "estimated_hours": 10.0, + "done_ratio": 0, + "overdue": true, + "parent_issue_id": 2720, + "project_id": 88, + "tracker_id": 1, + "priority_id": 9, + "status_id": 1, + "is_planned": true, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Create a list of most suitable workshops" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 2744, + "name": "People", + "start_date": "2016-06-16", + "due_date": "2016-08-01", + "done_ratio": 0, + "project_id": 88, + "tracker_id": 19, + "priority_id": 9, + "status_id": 1, + "is_planned": true, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "People" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 2745, + "name": "Guests", + "start_date": "2016-06-16", + "due_date": "2016-08-01", + "done_ratio": 0, + "parent_issue_id": 2744, + "project_id": 88, + "tracker_id": 19, + "priority_id": 9, + "status_id": 1, + "is_planned": true, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Guests" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 2749, + "name": "Estimate attendance", + "start_date": "2016-06-16", + "due_date": "2016-06-17", + "estimated_hours": 2.0, + "done_ratio": 0, + "overdue": true, + "parent_issue_id": 2745, + "project_id": 88, + "tracker_id": 1, + "priority_id": 9, + "status_id": 1, + "is_planned": true, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Estimate attendance" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 2711, + "name": "Define desired outcomes of the event", + "start_date": "2016-06-17", + "due_date": "2016-06-18", + "estimated_hours": 8.0, + "done_ratio": 0, + "overdue": true, + "parent_issue_id": 2709, + "project_id": 88, + "tracker_id": 1, + "priority_id": 9, + "status_id": 1, + "is_planned": true, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Define desired outcomes of the event" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 2746, + "name": "Prepare online ticket/registration", + "start_date": "2016-06-17", + "due_date": "2016-06-30", + "estimated_hours": 30.0, + "done_ratio": 0, + "parent_issue_id": 2745, + "project_id": 88, + "tracker_id": 1, + "priority_id": 9, + "status_id": 1, + "is_planned": true, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Prepare online ticket/registration" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 2751, + "name": "Invite guests", + "start_date": "2016-06-17", + "due_date": "2016-07-21", + "estimated_hours": 15.0, + "done_ratio": 0, + "parent_issue_id": 2745, + "project_id": 88, + "tracker_id": 1, + "priority_id": 9, + "status_id": 1, + "is_planned": true, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Invite guests" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 2752, + "name": "Speakers", + "start_date": "2016-06-17", + "due_date": "2016-07-04", + "done_ratio": 0, + "parent_issue_id": 2744, + "project_id": 88, + "tracker_id": 19, + "priority_id": 9, + "status_id": 1, + "is_planned": true, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Speakers" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 2753, + "name": "Put together a list of desired speakers", + "start_date": "2016-06-17", + "due_date": "2016-06-24", + "estimated_hours": 4.0, + "done_ratio": 0, + "overdue": true, + "parent_issue_id": 2752, + "project_id": 88, + "tracker_id": 1, + "priority_id": 9, + "status_id": 1, + "is_planned": true, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Put together a list of desired speakers" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 2756, + "name": "Staff", + "start_date": "2016-06-17", + "due_date": "2016-07-04", + "done_ratio": 0, + "parent_issue_id": 2744, + "project_id": 88, + "tracker_id": 19, + "priority_id": 9, + "status_id": 1, + "is_planned": true, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Staff" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 2757, + "name": "Plan required capacities", + "start_date": "2016-06-17", + "due_date": "2016-06-23", + "estimated_hours": 8.0, + "done_ratio": 0, + "overdue": true, + "parent_issue_id": 2756, + "project_id": 88, + "tracker_id": 1, + "priority_id": 9, + "status_id": 1, + "is_planned": true, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Plan required capacities" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 2760, + "name": "Entertainers", + "start_date": "2016-06-17", + "due_date": "2016-07-04", + "done_ratio": 0, + "parent_issue_id": 2744, + "project_id": 88, + "tracker_id": 19, + "priority_id": 9, + "status_id": 1, + "is_planned": true, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Entertainers" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 2761, + "name": "Put together list of desired entertainers", + "start_date": "2016-06-17", + "due_date": "2016-06-24", + "estimated_hours": 4.0, + "done_ratio": 0, + "overdue": true, + "parent_issue_id": 2760, + "project_id": 88, + "tracker_id": 1, + "priority_id": 9, + "status_id": 1, + "is_planned": true, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Put together list of desired entertainers" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 2712, + "name": "List activities leading to succeed in the purpose", + "start_date": "2016-06-18", + "due_date": "2016-06-23", + "estimated_hours": 30.0, + "done_ratio": 0, + "overdue": true, + "parent_issue_id": 2709, + "project_id": 88, + "tracker_id": 1, + "priority_id": 9, + "status_id": 1, + "is_planned": true, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "List activities leading to succeed in the purpose" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 2764, + "name": "Budget", + "start_date": "2016-06-18", + "due_date": "2016-07-22", + "done_ratio": 0, + "project_id": 88, + "tracker_id": 19, + "priority_id": 9, + "status_id": 1, + "is_planned": true, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Budget" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 2765, + "name": "Budget for venue", + "start_date": "2016-06-18", + "due_date": "2016-07-22", + "done_ratio": 0, + "parent_issue_id": 2764, + "project_id": 88, + "tracker_id": 19, + "priority_id": 9, + "status_id": 1, + "is_planned": true, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Budget for venue" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 2766, + "name": "Compile 1st venue budget estimate", + "start_date": "2016-06-18", + "due_date": "2016-06-20", + "estimated_hours": 4.0, + "done_ratio": 0, + "overdue": true, + "parent_issue_id": 2765, + "project_id": 88, + "tracker_id": 1, + "priority_id": 9, + "status_id": 1, + "is_planned": true, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Compile 1st venue budget estimate" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 2769, + "name": "Budget for activities", + "start_date": "2016-06-18", + "due_date": "2016-07-22", + "done_ratio": 0, + "parent_issue_id": 2764, + "project_id": 88, + "tracker_id": 19, + "priority_id": 9, + "status_id": 1, + "is_planned": true, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Budget for activities" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 2770, + "name": "Compile 1st programme budget estimate", + "start_date": "2016-06-18", + "due_date": "2016-06-20", + "estimated_hours": 4.0, + "done_ratio": 0, + "overdue": true, + "parent_issue_id": 2769, + "project_id": 88, + "tracker_id": 1, + "priority_id": 9, + "status_id": 1, + "is_planned": true, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Compile 1st programme budget estimate" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 2773, + "name": "Budget for people", + "start_date": "2016-06-18", + "due_date": "2016-07-22", + "done_ratio": 0, + "parent_issue_id": 2764, + "project_id": 88, + "tracker_id": 19, + "priority_id": 9, + "status_id": 1, + "is_planned": true, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Budget for people" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 2774, + "name": "Compile 1st personal budget estimate", + "start_date": "2016-06-18", + "due_date": "2016-06-20", + "estimated_hours": 4.0, + "done_ratio": 0, + "overdue": true, + "parent_issue_id": 2773, + "project_id": 88, + "tracker_id": 1, + "priority_id": 9, + "status_id": 1, + "is_planned": true, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Compile 1st personal budget estimate" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 2702, + "name": "Contact venues for quotations", + "start_date": "2016-06-19", + "due_date": "2016-06-25", + "estimated_hours": 24.0, + "done_ratio": 0, + "overdue": true, + "parent_issue_id": 2699, + "project_id": 88, + "tracker_id": 1, + "priority_id": 9, + "status_id": 1, + "is_planned": true, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Contact venues for quotations" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 2716, + "name": "Define supporting activities ", + "start_date": "2016-06-19", + "due_date": "2016-06-20", + "estimated_hours": 12.0, + "done_ratio": 0, + "overdue": true, + "parent_issue_id": 2715, + "project_id": 88, + "tracker_id": 1, + "priority_id": 9, + "status_id": 1, + "is_planned": true, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Define supporting activities " + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 2723, + "name": "Entertainment", + "start_date": "2016-06-19", + "due_date": "2016-07-02", + "done_ratio": 0, + "parent_issue_id": 2707, + "project_id": 88, + "tracker_id": 19, + "priority_id": 9, + "status_id": 1, + "is_planned": true, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Entertainment" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 2724, + "name": "Music", + "start_date": "2016-06-19", + "due_date": "2016-06-23", + "done_ratio": 0, + "overdue": true, + "parent_issue_id": 2723, + "project_id": 88, + "tracker_id": 19, + "priority_id": 9, + "status_id": 1, + "is_planned": true, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Music" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 2725, + "name": "List suitable genres", + "start_date": "2016-06-19", + "due_date": "2016-06-19", + "estimated_hours": 2.0, + "done_ratio": 0, + "overdue": true, + "parent_issue_id": 2724, + "project_id": 88, + "tracker_id": 1, + "priority_id": 9, + "status_id": 1, + "is_planned": true, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "List suitable genres" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 2718, + "name": "Gather equipment requirements for secondary activities", + "start_date": "2016-06-20", + "due_date": "2016-06-23", + "estimated_hours": 10.0, + "done_ratio": 0, + "overdue": true, + "parent_issue_id": 2715, + "project_id": 88, + "tracker_id": 1, + "priority_id": 9, + "status_id": 1, + "is_planned": true, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Gather equipment requirements for secondary activities" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 2727, + "name": "Design form of music entertainment", + "start_date": "2016-06-20", + "due_date": "2016-06-21", + "estimated_hours": 8.0, + "done_ratio": 0, + "overdue": true, + "parent_issue_id": 2724, + "project_id": 88, + "tracker_id": 1, + "priority_id": 9, + "status_id": 1, + "is_planned": true, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Design form of music entertainment" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 2719, + "name": "Book sufficient equipment", + "start_date": "2016-06-22", + "due_date": "2016-06-26", + "estimated_hours": 10.0, + "done_ratio": 0, + "overdue": true, + "parent_issue_id": 2715, + "project_id": 88, + "tracker_id": 1, + "priority_id": 9, + "status_id": 1, + "is_planned": true, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Book sufficient equipment" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 2713, + "name": "Gather equipment requirements for main activities", + "start_date": "2016-06-23", + "due_date": "2016-07-02", + "estimated_hours": 16.0, + "done_ratio": 0, + "parent_issue_id": 2709, + "project_id": 88, + "tracker_id": 1, + "priority_id": 9, + "status_id": 1, + "is_planned": true, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Gather equipment requirements for main activities" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 2726, + "name": "Finalize music programme", + "start_date": "2016-06-23", + "due_date": "2016-06-23", + "estimated_hours": 5.0, + "done_ratio": 0, + "overdue": true, + "parent_issue_id": 2724, + "project_id": 88, + "tracker_id": 1, + "priority_id": 9, + "status_id": 1, + "is_planned": true, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Finalize music programme" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 2728, + "name": "Performance", + "start_date": "2016-06-23", + "due_date": "2016-06-24", + "done_ratio": 0, + "overdue": true, + "parent_issue_id": 2723, + "project_id": 88, + "tracker_id": 19, + "priority_id": 9, + "status_id": 1, + "is_planned": true, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Performance" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 2729, + "name": "Compile a list of suitable performance forms", + "start_date": "2016-06-23", + "due_date": "2016-06-23", + "estimated_hours": 4.0, + "done_ratio": 0, + "overdue": true, + "parent_issue_id": 2728, + "project_id": 88, + "tracker_id": 1, + "priority_id": 9, + "status_id": 1, + "is_planned": true, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Compile a list of suitable performance forms" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 2730, + "name": "Finalize performance programme", + "start_date": "2016-06-23", + "due_date": "2016-06-24", + "estimated_hours": 4.0, + "done_ratio": 0, + "overdue": true, + "parent_issue_id": 2728, + "project_id": 88, + "tracker_id": 1, + "priority_id": 9, + "status_id": 1, + "is_planned": true, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Finalize performance programme" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 2731, + "name": "Shops/Booths", + "start_date": "2016-06-23", + "due_date": "2016-07-02", + "done_ratio": 0, + "parent_issue_id": 2723, + "project_id": 88, + "tracker_id": 19, + "priority_id": 9, + "status_id": 1, + "is_planned": true, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Shops/Booths" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 2732, + "name": "List relevant merchandise", + "start_date": "2016-06-23", + "due_date": "2016-06-27", + "estimated_hours": 5.0, + "done_ratio": 0, + "overdue": true, + "parent_issue_id": 2731, + "project_id": 88, + "tracker_id": 1, + "priority_id": 9, + "status_id": 1, + "is_planned": true, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "List relevant merchandise" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 2734, + "name": "Interactive", + "start_date": "2016-06-23", + "due_date": "2016-06-25", + "estimated_hours": 0.0, + "done_ratio": 0, + "overdue": true, + "parent_issue_id": 2723, + "project_id": 88, + "tracker_id": 19, + "priority_id": 9, + "status_id": 1, + "is_planned": true, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Interactive" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 2735, + "name": "Find relevant interactive activites", + "start_date": "2016-06-23", + "due_date": "2016-06-24", + "estimated_hours": 6.0, + "done_ratio": 0, + "fixed_version_id": 0, + "overdue": true, + "parent_issue_id": 2734, + "project_id": 88, + "tracker_id": 1, + "priority_id": 9, + "status_id": 1, + "is_planned": true, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Find relevant interactive activites" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 2736, + "name": "Finalize list of interactive activities", + "start_date": "2016-06-23", + "due_date": "2016-06-25", + "estimated_hours": 4.0, + "done_ratio": 0, + "fixed_version_id": 0, + "overdue": true, + "parent_issue_id": 2734, + "project_id": 88, + "tracker_id": 1, + "priority_id": 9, + "status_id": 1, + "is_planned": true, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Finalize list of interactive activities" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 2737, + "name": "Schedule", + "start_date": "2016-06-23", + "due_date": "2016-07-25", + "done_ratio": 0, + "parent_issue_id": 2707, + "project_id": 88, + "tracker_id": 19, + "priority_id": 9, + "status_id": 1, + "is_planned": true, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Schedule" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 2738, + "name": "Compile main programme timetable", + "start_date": "2016-06-23", + "due_date": "2016-07-08", + "estimated_hours": 15.0, + "done_ratio": 0, + "parent_issue_id": 2737, + "project_id": 88, + "tracker_id": 1, + "priority_id": 9, + "status_id": 1, + "is_planned": true, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Compile main programme timetable" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 2758, + "name": "Find personal agency", + "start_date": "2016-06-23", + "due_date": "2016-06-26", + "estimated_hours": 5.0, + "done_ratio": 0, + "overdue": true, + "parent_issue_id": 2756, + "project_id": 88, + "tracker_id": 1, + "priority_id": 9, + "status_id": 1, + "is_planned": true, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Find personal agency" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 2767, + "name": "Continuously precise venue budget according known data", + "start_date": "2016-06-23", + "due_date": "2016-07-18", + "estimated_hours": 25.0, + "done_ratio": 0, + "parent_issue_id": 2765, + "project_id": 88, + "tracker_id": 1, + "priority_id": 9, + "status_id": 1, + "is_planned": true, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Continuously precise venue budget according known data" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 2771, + "name": "Continuously precise programme budget according to known data", + "start_date": "2016-06-23", + "due_date": "2016-07-18", + "estimated_hours": 25.0, + "done_ratio": 0, + "parent_issue_id": 2769, + "project_id": 88, + "tracker_id": 1, + "priority_id": 9, + "status_id": 1, + "is_planned": true, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Continuously precise programme budget according to known data" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 2775, + "name": "Continuously precise personal budget according to known data", + "start_date": "2016-06-23", + "due_date": "2016-07-18", + "estimated_hours": 25.0, + "done_ratio": 0, + "parent_issue_id": 2773, + "project_id": 88, + "tracker_id": 1, + "priority_id": 9, + "status_id": 1, + "is_planned": true, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Continuously precise personal budget according to known data" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 2739, + "name": "Compile secondary activities timetable", + "start_date": "2016-06-24", + "due_date": "2016-06-30", + "estimated_hours": 5.0, + "done_ratio": 0, + "parent_issue_id": 2737, + "project_id": 88, + "tracker_id": 1, + "priority_id": 9, + "status_id": 1, + "is_planned": true, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Compile secondary activities timetable" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 2722, + "name": "Finalize list of approved workshops", + "start_date": "2016-06-25", + "due_date": "2016-07-03", + "estimated_hours": 4.0, + "done_ratio": 0, + "parent_issue_id": 2720, + "project_id": 88, + "tracker_id": 1, + "priority_id": 9, + "status_id": 1, + "is_planned": true, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Finalize list of approved workshops" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 2740, + "name": "Compile workshops timetable", + "start_date": "2016-06-25", + "due_date": "2016-07-01", + "estimated_hours": 10.0, + "done_ratio": 0, + "parent_issue_id": 2737, + "project_id": 88, + "tracker_id": 1, + "priority_id": 9, + "status_id": 1, + "is_planned": true, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Compile workshops timetable" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 2741, + "name": "Compile entertainment timetable", + "start_date": "2016-06-25", + "due_date": "2016-07-09", + "estimated_hours": 10.0, + "done_ratio": 0, + "parent_issue_id": 2737, + "project_id": 88, + "tracker_id": 1, + "priority_id": 9, + "status_id": 1, + "is_planned": true, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Compile entertainment timetable" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 2742, + "name": "Set catering schedule", + "start_date": "2016-06-25", + "due_date": "2016-07-01", + "estimated_hours": 5.0, + "done_ratio": 0, + "parent_issue_id": 2737, + "project_id": 88, + "tracker_id": 1, + "priority_id": 9, + "status_id": 1, + "is_planned": true, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Set catering schedule" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 2754, + "name": "Invite speakers from list", + "start_date": "2016-06-25", + "due_date": "2016-07-01", + "estimated_hours": 5.0, + "done_ratio": 0, + "parent_issue_id": 2752, + "project_id": 88, + "tracker_id": 1, + "priority_id": 9, + "status_id": 1, + "is_planned": true, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Invite speakers from list" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 2762, + "name": "Invite entertainers from list", + "start_date": "2016-06-25", + "due_date": "2016-07-01", + "estimated_hours": 5.0, + "done_ratio": 0, + "parent_issue_id": 2760, + "project_id": 88, + "tracker_id": 1, + "priority_id": 9, + "status_id": 1, + "is_planned": true, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Invite entertainers from list" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 2704, + "name": "Decide on the best offer", + "start_date": "2016-06-26", + "due_date": "2016-06-30", + "estimated_hours": 4.0, + "done_ratio": 0, + "parent_issue_id": 2699, + "project_id": 88, + "tracker_id": 1, + "priority_id": 9, + "status_id": 1, + "is_planned": true, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Decide on the best offer" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 2733, + "name": "Finalize list of shops", + "start_date": "2016-06-26", + "due_date": "2016-07-02", + "estimated_hours": 8.0, + "done_ratio": 0, + "parent_issue_id": 2731, + "project_id": 88, + "tracker_id": 1, + "priority_id": 9, + "status_id": 1, + "is_planned": true, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Finalize list of shops" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 2686, + "name": "Transport", + "start_date": "2016-06-27", + "due_date": "2016-08-19", + "done_ratio": 0, + "parent_issue_id": 2653, + "project_id": 88, + "tracker_id": 19, + "priority_id": 9, + "status_id": 1, + "is_planned": true, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Transport" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 2694, + "name": "Transport of staff", + "start_date": "2016-06-27", + "due_date": "2016-07-22", + "done_ratio": 0, + "parent_issue_id": 2686, + "project_id": 88, + "tracker_id": 19, + "priority_id": 9, + "status_id": 1, + "is_planned": true, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Transport of staff" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 2696, + "name": "Plan capacity of staff transport", + "start_date": "2016-06-27", + "due_date": "2016-07-11", + "estimated_hours": 15.0, + "done_ratio": 0, + "parent_issue_id": 2694, + "project_id": 88, + "tracker_id": 1, + "priority_id": 9, + "status_id": 1, + "is_planned": true, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Plan capacity of staff transport" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 2759, + "name": "Acquire suitable capacities", + "start_date": "2016-06-27", + "due_date": "2016-07-04", + "estimated_hours": 10.0, + "done_ratio": 0, + "parent_issue_id": 2756, + "project_id": 88, + "tracker_id": 1, + "priority_id": 9, + "status_id": 1, + "is_planned": true, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Acquire suitable capacities" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 2687, + "name": "Transport of guests", + "start_date": "2016-06-30", + "due_date": "2016-08-12", + "done_ratio": 0, + "parent_issue_id": 2686, + "project_id": 88, + "tracker_id": 19, + "priority_id": 9, + "status_id": 1, + "is_planned": true, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Transport of guests" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 2688, + "name": "Plan capacity of guest transport", + "start_date": "2016-06-30", + "due_date": "2016-07-22", + "estimated_hours": 16.0, + "done_ratio": 0, + "parent_issue_id": 2687, + "project_id": 88, + "tracker_id": 1, + "priority_id": 9, + "status_id": 1, + "is_planned": true, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Plan capacity of guest transport" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 2703, + "name": "Book selected venue", + "start_date": "2016-06-30", + "due_date": "2016-06-30", + "estimated_hours": 2.0, + "done_ratio": 0, + "parent_issue_id": 2699, + "project_id": 88, + "tracker_id": 1, + "priority_id": 9, + "status_id": 1, + "is_planned": true, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Book selected venue" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 2706, + "name": "Agree on booking conditions", + "start_date": "2016-06-30", + "due_date": "2016-07-01", + "estimated_hours": 2.0, + "done_ratio": 0, + "parent_issue_id": 2699, + "project_id": 88, + "tracker_id": 1, + "priority_id": 9, + "status_id": 1, + "is_planned": true, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Agree on booking conditions" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 2714, + "name": "Book sufficient equipment", + "start_date": "2016-06-30", + "due_date": "2016-08-18", + "estimated_hours": 30.0, + "done_ratio": 0, + "parent_issue_id": 2709, + "project_id": 88, + "tracker_id": 1, + "priority_id": 9, + "status_id": 1, + "is_planned": true, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Book sufficient equipment" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 2755, + "name": "Finalize approved speakers list", + "start_date": "2016-06-30", + "due_date": "2016-07-04", + "estimated_hours": 2.0, + "done_ratio": 0, + "parent_issue_id": 2752, + "project_id": 88, + "tracker_id": 1, + "priority_id": 9, + "status_id": 1, + "is_planned": true, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Finalize approved speakers list" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 2763, + "name": "Finalize approved entertainers", + "start_date": "2016-06-30", + "due_date": "2016-07-04", + "estimated_hours": 2.0, + "done_ratio": 0, + "parent_issue_id": 2760, + "project_id": 88, + "tracker_id": 1, + "priority_id": 9, + "status_id": 1, + "is_planned": true, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Finalize approved entertainers" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 2743, + "name": "Aggregate complete schedule", + "start_date": "2016-07-01", + "due_date": "2016-07-25", + "estimated_hours": 15.0, + "done_ratio": 0, + "parent_issue_id": 2737, + "project_id": 88, + "tracker_id": 1, + "priority_id": 9, + "status_id": 1, + "is_planned": true, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Aggregate complete schedule" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 2750, + "name": "Launch online registration", + "start_date": "2016-07-01", + "due_date": "2016-07-03", + "estimated_hours": 4.0, + "done_ratio": 0, + "parent_issue_id": 2745, + "project_id": 88, + "tracker_id": 1, + "priority_id": 9, + "status_id": 1, + "is_planned": true, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Launch online registration" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 2658, + "name": "Services", + "start_date": "2016-07-04", + "due_date": "2016-08-20", + "done_ratio": 0, + "parent_issue_id": 2653, + "project_id": 88, + "tracker_id": 19, + "priority_id": 9, + "status_id": 1, + "is_planned": true, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Services" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 2659, + "name": "Accommodation", + "start_date": "2016-07-04", + "due_date": "2016-08-20", + "done_ratio": 0, + "parent_issue_id": 2658, + "project_id": 88, + "tracker_id": 19, + "priority_id": 9, + "status_id": 1, + "is_planned": true, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Accommodation" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 2660, + "name": "Verify capacity", + "start_date": "2016-07-04", + "due_date": "2016-08-01", + "estimated_hours": 12.0, + "done_ratio": 0, + "parent_issue_id": 2659, + "project_id": 88, + "tracker_id": 1, + "priority_id": 9, + "status_id": 1, + "is_planned": true, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Verify capacity" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 2748, + "name": "Plan reserve for unregistered guests", + "start_date": "2016-07-04", + "due_date": "2016-07-23", + "estimated_hours": 16.0, + "done_ratio": 0, + "parent_issue_id": 2745, + "project_id": 88, + "tracker_id": 1, + "priority_id": 9, + "status_id": 1, + "is_planned": true, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Plan reserve for unregistered guests" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 2665, + "name": "Catering", + "start_date": "2016-07-07", + "due_date": "2016-08-15", + "done_ratio": 0, + "parent_issue_id": 2658, + "project_id": 88, + "tracker_id": 19, + "priority_id": 9, + "status_id": 1, + "is_planned": true, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Catering" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 2668, + "name": "Decide on the form of catering", + "start_date": "2016-07-07", + "due_date": "2016-07-15", + "estimated_hours": 5.0, + "done_ratio": 0, + "parent_issue_id": 2665, + "project_id": 88, + "tracker_id": 1, + "priority_id": 9, + "status_id": 1, + "is_planned": true, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Decide on the form of catering" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 2695, + "name": "Book transport service for staff", + "start_date": "2016-07-07", + "due_date": "2016-07-22", + "estimated_hours": 6.0, + "done_ratio": 0, + "parent_issue_id": 2694, + "project_id": 88, + "tracker_id": 1, + "priority_id": 9, + "status_id": 1, + "is_planned": true, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Book transport service for staff" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 2690, + "name": "Transport of speakers", + "start_date": "2016-07-08", + "due_date": "2016-07-16", + "done_ratio": 0, + "parent_issue_id": 2686, + "project_id": 88, + "tracker_id": 19, + "priority_id": 9, + "status_id": 1, + "is_planned": true, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Transport of speakers" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 2691, + "name": "Book transport service for speakers", + "start_date": "2016-07-08", + "due_date": "2016-07-16", + "estimated_hours": 4.0, + "done_ratio": 0, + "parent_issue_id": 2690, + "project_id": 88, + "tracker_id": 1, + "priority_id": 9, + "status_id": 1, + "is_planned": true, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Book transport service for speakers" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 2692, + "name": "Transport of entertainers", + "start_date": "2016-07-08", + "due_date": "2016-07-16", + "done_ratio": 0, + "parent_issue_id": 2686, + "project_id": 88, + "tracker_id": 19, + "priority_id": 9, + "status_id": 1, + "is_planned": true, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Transport of entertainers" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 2693, + "name": "Book transport service for entertainers", + "start_date": "2016-07-08", + "due_date": "2016-07-16", + "estimated_hours": 4.0, + "done_ratio": 0, + "parent_issue_id": 2692, + "project_id": 88, + "tracker_id": 1, + "priority_id": 9, + "status_id": 1, + "is_planned": true, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Book transport service for entertainers" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 2669, + "name": "Prepare menu", + "start_date": "2016-07-09", + "due_date": "2016-08-04", + "done_ratio": 0, + "parent_issue_id": 2665, + "project_id": 88, + "tracker_id": 19, + "priority_id": 9, + "status_id": 1, + "is_planned": true, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Prepare menu" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 2670, + "name": "Find out client's preference ", + "start_date": "2016-07-09", + "due_date": "2016-08-01", + "estimated_hours": 0.0, + "done_ratio": 0, + "parent_issue_id": 2669, + "project_id": 88, + "tracker_id": 1, + "priority_id": 9, + "status_id": 1, + "is_planned": true, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Find out client's preference " + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 2656, + "name": "Prepare spacial layout for ALL activities", + "start_date": "2016-07-14", + "due_date": "2016-07-29", + "estimated_hours": 30.0, + "done_ratio": 0, + "parent_issue_id": 2654, + "project_id": 88, + "tracker_id": 1, + "priority_id": 9, + "status_id": 1, + "is_planned": true, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Prepare spacial layout for ALL activities" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 2662, + "name": "Assign rooms to entertainers", + "start_date": "2016-07-14", + "due_date": "2016-08-11", + "estimated_hours": 5.0, + "done_ratio": 0, + "parent_issue_id": 2659, + "project_id": 88, + "tracker_id": 1, + "priority_id": 9, + "status_id": 1, + "is_planned": true, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Assign rooms to entertainers" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 2663, + "name": "Assign rooms to speakers", + "start_date": "2016-07-14", + "due_date": "2016-08-11", + "estimated_hours": 5.0, + "done_ratio": 0, + "parent_issue_id": 2659, + "project_id": 88, + "tracker_id": 1, + "priority_id": 9, + "status_id": 1, + "is_planned": true, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Assign rooms to speakers" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 2664, + "name": "Assign rooms to staff", + "start_date": "2016-07-14", + "due_date": "2016-08-11", + "estimated_hours": 10.0, + "done_ratio": 0, + "parent_issue_id": 2659, + "project_id": 88, + "tracker_id": 1, + "priority_id": 9, + "status_id": 1, + "is_planned": true, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Assign rooms to staff" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 2666, + "name": "Plan number of meals", + "start_date": "2016-07-14", + "due_date": "2016-08-06", + "estimated_hours": 15.0, + "done_ratio": 0, + "parent_issue_id": 2665, + "project_id": 88, + "tracker_id": 1, + "priority_id": 9, + "status_id": 1, + "is_planned": true, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Plan number of meals" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 2689, + "name": "Book transport service for guests", + "start_date": "2016-07-21", + "due_date": "2016-08-12", + "estimated_hours": 10.0, + "done_ratio": 0, + "parent_issue_id": 2687, + "project_id": 88, + "tracker_id": 1, + "priority_id": 9, + "status_id": 1, + "is_planned": true, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Book transport service for guests" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 2768, + "name": "Finalize venue budget", + "start_date": "2016-07-21", + "due_date": "2016-07-22", + "estimated_hours": 4.0, + "done_ratio": 0, + "parent_issue_id": 2765, + "project_id": 88, + "tracker_id": 1, + "priority_id": 9, + "status_id": 1, + "is_planned": true, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Finalize venue budget" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 2772, + "name": "Finalize programme budget", + "start_date": "2016-07-21", + "due_date": "2016-07-22", + "estimated_hours": 4.0, + "done_ratio": 0, + "parent_issue_id": 2769, + "project_id": 88, + "tracker_id": 1, + "priority_id": 9, + "status_id": 1, + "is_planned": true, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Finalize programme budget" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 2776, + "name": "Finalize personal budget", + "start_date": "2016-07-21", + "due_date": "2016-07-22", + "estimated_hours": 4.0, + "done_ratio": 0, + "parent_issue_id": 2773, + "project_id": 88, + "tracker_id": 1, + "priority_id": 9, + "status_id": 1, + "is_planned": true, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Finalize personal budget" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 2675, + "name": "Reception", + "start_date": "2016-07-23", + "due_date": "2016-08-05", + "done_ratio": 0, + "parent_issue_id": 2658, + "project_id": 88, + "tracker_id": 19, + "priority_id": 9, + "status_id": 1, + "is_planned": true, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Reception" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 2676, + "name": "Arrange ticket office for new guests", + "start_date": "2016-07-23", + "due_date": "2016-08-01", + "estimated_hours": 25.0, + "done_ratio": 0, + "parent_issue_id": 2675, + "project_id": 88, + "tracker_id": 1, + "priority_id": 9, + "status_id": 1, + "is_planned": true, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Arrange ticket office for new guests" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 2678, + "name": "Arrange guest registration", + "start_date": "2016-07-25", + "due_date": "2016-07-31", + "estimated_hours": 16.0, + "done_ratio": 0, + "parent_issue_id": 2675, + "project_id": 88, + "tracker_id": 1, + "priority_id": 9, + "status_id": 1, + "is_planned": true, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Arrange guest registration" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 2747, + "name": "Finalize approved guestlist", + "start_date": "2016-07-25", + "due_date": "2016-08-01", + "estimated_hours": 5.0, + "done_ratio": 0, + "parent_issue_id": 2745, + "project_id": 88, + "tracker_id": 1, + "priority_id": 9, + "status_id": 1, + "is_planned": true, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Finalize approved guestlist" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 2657, + "name": "Finalize spacial layout", + "start_date": "2016-07-28", + "due_date": "2016-08-06", + "estimated_hours": 15.0, + "done_ratio": 0, + "parent_issue_id": 2654, + "project_id": 88, + "tracker_id": 1, + "priority_id": 9, + "status_id": 1, + "is_planned": true, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Finalize spacial layout" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 2679, + "name": "Prepare capacity of cloakroom", + "start_date": "2016-07-28", + "due_date": "2016-07-31", + "estimated_hours": 3.0, + "done_ratio": 0, + "parent_issue_id": 2675, + "project_id": 88, + "tracker_id": 1, + "priority_id": 9, + "status_id": 1, + "is_planned": true, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Prepare capacity of cloakroom" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 2680, + "name": "Child care", + "start_date": "2016-07-28", + "due_date": "2016-08-18", + "done_ratio": 0, + "parent_issue_id": 2658, + "project_id": 88, + "tracker_id": 19, + "priority_id": 9, + "status_id": 1, + "is_planned": true, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Child care" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 2681, + "name": "Plan child care capacity", + "start_date": "2016-07-28", + "due_date": "2016-08-14", + "estimated_hours": 4.0, + "done_ratio": 0, + "parent_issue_id": 2680, + "project_id": 88, + "tracker_id": 1, + "priority_id": 9, + "status_id": 1, + "is_planned": true, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Plan child care capacity" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 2671, + "name": "Prepare meal menu", + "start_date": "2016-07-29", + "due_date": "2016-08-04", + "estimated_hours": 10.0, + "done_ratio": 0, + "fixed_version_id": 0, + "parent_issue_id": 2669, + "project_id": 88, + "tracker_id": 1, + "priority_id": 9, + "status_id": 1, + "is_planned": true, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Prepare meal menu" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 2661, + "name": "Assign rooms to guests", + "start_date": "2016-07-31", + "due_date": "2016-08-20", + "estimated_hours": 25.0, + "done_ratio": 0, + "parent_issue_id": 2659, + "project_id": 88, + "tracker_id": 1, + "priority_id": 9, + "status_id": 1, + "is_planned": true, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Assign rooms to guests" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 2667, + "name": "Arrange seating order", + "start_date": "2016-07-31", + "due_date": "2016-08-15", + "estimated_hours": 12.0, + "done_ratio": 0, + "parent_issue_id": 2665, + "project_id": 88, + "tracker_id": 1, + "priority_id": 9, + "status_id": 1, + "is_planned": true, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Arrange seating order" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 2677, + "name": "Arrange guest welcoming", + "start_date": "2016-07-31", + "due_date": "2016-08-05", + "estimated_hours": 20.0, + "done_ratio": 0, + "parent_issue_id": 2675, + "project_id": 88, + "tracker_id": 1, + "priority_id": 9, + "status_id": 1, + "is_planned": true, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Arrange guest welcoming" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 2697, + "name": "Transport of equipment", + "start_date": "2016-07-31", + "due_date": "2016-08-19", + "done_ratio": 0, + "parent_issue_id": 2686, + "project_id": 88, + "tracker_id": 19, + "priority_id": 9, + "status_id": 1, + "is_planned": true, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Transport of equipment" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 2698, + "name": "Arrange transport of equipment", + "start_date": "2016-07-31", + "due_date": "2016-08-19", + "estimated_hours": 30.0, + "done_ratio": 0, + "parent_issue_id": 2697, + "project_id": 88, + "tracker_id": 1, + "priority_id": 9, + "status_id": 1, + "is_planned": true, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Arrange transport of equipment" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 2672, + "name": "Prepare drink menu and volume", + "start_date": "2016-08-01", + "due_date": "2016-08-07", + "estimated_hours": 20.0, + "done_ratio": 0, + "parent_issue_id": 2665, + "project_id": 88, + "tracker_id": 1, + "priority_id": 9, + "status_id": 1, + "is_planned": true, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Prepare drink menu and volume" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 2673, + "name": "Valet", + "start_date": "2016-08-05", + "due_date": "2016-08-13", + "done_ratio": 0, + "parent_issue_id": 2658, + "project_id": 88, + "tracker_id": 19, + "priority_id": 9, + "status_id": 1, + "is_planned": true, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Valet" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 2674, + "name": "Plan valet capacity", + "start_date": "2016-08-05", + "due_date": "2016-08-13", + "estimated_hours": 4.0, + "done_ratio": 0, + "parent_issue_id": 2673, + "project_id": 88, + "tracker_id": 1, + "priority_id": 9, + "status_id": 1, + "is_planned": true, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Plan valet capacity" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 2682, + "name": "Find suitable space", + "start_date": "2016-08-08", + "due_date": "2016-08-18", + "estimated_hours": 2.0, + "done_ratio": 0, + "parent_issue_id": 2680, + "project_id": 88, + "tracker_id": 1, + "priority_id": 9, + "status_id": 1, + "is_planned": true, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Find suitable space" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 2683, + "name": "Hygiene", + "start_date": "2016-08-11", + "due_date": "2016-08-19", + "done_ratio": 0, + "parent_issue_id": 2658, + "project_id": 88, + "tracker_id": 19, + "priority_id": 9, + "status_id": 1, + "is_planned": true, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Hygiene" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 2684, + "name": "Plan capacity of portables", + "start_date": "2016-08-11", + "due_date": "2016-08-18", + "estimated_hours": 4.0, + "done_ratio": 0, + "parent_issue_id": 2683, + "project_id": 88, + "tracker_id": 1, + "priority_id": 9, + "status_id": 1, + "is_planned": true, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Plan capacity of portables" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 2685, + "name": "Aqcuire portables", + "start_date": "2016-08-14", + "due_date": "2016-08-19", + "estimated_hours": 16.0, + "done_ratio": 0, + "parent_issue_id": 2683, + "project_id": 88, + "tracker_id": 1, + "priority_id": 9, + "status_id": 1, + "is_planned": true, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Aqcuire portables" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 2705, + "name": "Make the payment", + "start_date": "2016-08-25", + "due_date": "2016-08-27", + "estimated_hours": 1.0, + "done_ratio": 0, + "parent_issue_id": 2699, + "project_id": 88, + "tracker_id": 1, + "priority_id": 9, + "status_id": 1, + "is_planned": true, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Make the payment" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 2777, + "name": "Event", + "start_date": "2016-08-25", + "due_date": "2016-08-27", + "estimated_hours": 0.0, + "done_ratio": 0, + "project_id": 88, + "tracker_id": 19, + "priority_id": 9, + "status_id": 1, + "is_planned": true, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Event" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 702, + "name": "Analysis", + "start_date": "2016-06-20", + "done_ratio": 0, + "project_id": 62, + "tracker_id": 1, + "priority_id": 11, + "status_id": 2, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Analysis" + }, + { + "name": "status", + "value": "Realisation" + }, + { + "name": "priority", + "value": "Easy task" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 707, + "name": "Testing", + "start_date": "2016-06-20", + "done_ratio": 0, + "project_id": 62, + "tracker_id": 1, + "priority_id": 9, + "status_id": 1, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Testing" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 712, + "name": "fefe", + "start_date": "2016-06-20", + "done_ratio": 0, + "parent_issue_id": 705, + "project_id": 62, + "tracker_id": 1, + "priority_id": 9, + "status_id": 1, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "fefe" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 714, + "name": "fefe", + "start_date": "2016-06-20", + "done_ratio": 0, + "parent_issue_id": 705, + "project_id": 62, + "tracker_id": 17, + "priority_id": 15, + "status_id": 1, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "fefe" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Critical" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 708, + "name": "analyse client needs", + "start_date": "2016-06-23", + "due_date": "2016-06-23", + "estimated_hours": 0.0, + "done_ratio": 0, + "overdue": true, + "parent_issue_id": 702, + "project_id": 62, + "tracker_id": 1, + "priority_id": 9, + "status_id": 1, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "analyse client needs" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 709, + "name": "fefe", + "start_date": "2016-06-24", + "due_date": "2016-06-24", + "estimated_hours": 0.0, + "done_ratio": 0, + "overdue": true, + "parent_issue_id": 702, + "project_id": 62, + "tracker_id": 1, + "priority_id": 8, + "status_id": 1, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "fefe" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Low" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 703, + "name": "Launch to production", + "start_date": "2016-06-25", + "due_date": "2016-06-25", + "estimated_hours": 0.0, + "done_ratio": 0, + "overdue": true, + "project_id": 62, + "tracker_id": 1, + "priority_id": 10, + "status_id": 9, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Launch to production" + }, + { + "name": "status", + "value": "Estimated" + }, + { + "name": "priority", + "value": "High" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 711, + "name": "fefe", + "start_date": "2016-06-25", + "due_date": "2016-06-25", + "estimated_hours": 0.0, + "done_ratio": 0, + "overdue": true, + "parent_issue_id": 702, + "project_id": 62, + "tracker_id": 1, + "priority_id": 11, + "status_id": 3, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "fefe" + }, + { + "name": "status", + "value": "Consultation" + }, + { + "name": "priority", + "value": "Easy task" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 713, + "name": "fefe", + "start_date": "2016-06-25", + "due_date": "2016-06-25", + "estimated_hours": 0.0, + "done_ratio": 0, + "overdue": true, + "parent_issue_id": 702, + "project_id": 62, + "tracker_id": 1, + "priority_id": 9, + "status_id": 1, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "fefe" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 704, + "name": "Design", + "start_date": "2016-06-26", + "due_date": "2016-06-26", + "estimated_hours": 0.0, + "done_ratio": 0, + "overdue": true, + "project_id": 62, + "tracker_id": 1, + "priority_id": 11, + "status_id": 4, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Design" + }, + { + "name": "status", + "value": "To check" + }, + { + "name": "priority", + "value": "Easy task" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 705, + "name": "Front End Implementation", + "start_date": "2016-06-26", + "due_date": "2016-06-26", + "estimated_hours": 0.0, + "done_ratio": 0, + "overdue": true, + "project_id": 62, + "tracker_id": 1, + "priority_id": 9, + "status_id": 1, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Front End Implementation" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 706, + "name": "Back End", + "start_date": "2016-06-26", + "due_date": "2016-06-26", + "estimated_hours": 0.0, + "done_ratio": 0, + "overdue": true, + "project_id": 62, + "tracker_id": 1, + "priority_id": 9, + "status_id": 1, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Back End" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 710, + "name": "fefe", + "start_date": "2016-06-27", + "due_date": "2016-06-27", + "estimated_hours": 0.0, + "done_ratio": 0, + "fixed_version_id": 0, + "overdue": true, + "parent_issue_id": 705, + "project_id": 62, + "tracker_id": 1, + "priority_id": 10, + "status_id": 10, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "fefe" + }, + { + "name": "status", + "value": "Approved" + }, + { + "name": "priority", + "value": "High" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 3194, + "name": "Concept Creation and Requirement Gathering:", + "start_date": "2016-06-20", + "due_date": "2016-06-21", + "done_ratio": 0, + "overdue": true, + "project_id": 106, + "tracker_id": 1, + "priority_id": 9, + "status_id": 1, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Concept Creation and Requirement Gathering:" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 3195, + "name": "Planning and Designing", + "start_date": "2016-06-23", + "due_date": "2016-06-24", + "estimated_hours": 0.0, + "done_ratio": 0, + "overdue": true, + "project_id": 106, + "tracker_id": 1, + "priority_id": 9, + "status_id": 1, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Planning and Designing" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 3196, + "name": "Implementation:", + "start_date": "2016-06-26", + "due_date": "2016-06-30", + "estimated_hours": 0.0, + "done_ratio": 0, + "project_id": 106, + "tracker_id": 1, + "priority_id": 9, + "status_id": 1, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Implementation:" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 3197, + "name": "Evaluation", + "start_date": "2016-07-02", + "due_date": "2016-07-04", + "estimated_hours": 0.0, + "done_ratio": 0, + "project_id": 106, + "tracker_id": 1, + "priority_id": 9, + "status_id": 1, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Evaluation" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "" + } + ] + }, + { + "id": 352, + "name": "Areas of usage", + "start_date": "2016-06-01", + "due_date": "2016-06-02", + "estimated_hours": 3.0, + "done_ratio": 100, + "css": " closed", + "fixed_version_id": 52, + "project_id": 40, + "tracker_id": 5, + "priority_id": 9, + "status_id": 6, + "assigned_to_id": 6, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Areas of usage" + }, + { + "name": "status", + "value": "Done" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "Peter Project Man" + } + ] + }, + { + "id": 353, + "name": "Trackers and statuses", + "start_date": "2016-06-03", + "due_date": "2016-06-03", + "estimated_hours": 1.0, + "done_ratio": 100, + "css": " closed", + "fixed_version_id": 52, + "parent_issue_id": 352, + "project_id": 40, + "tracker_id": 5, + "priority_id": 9, + "status_id": 6, + "assigned_to_id": 6, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Trackers and statuses" + }, + { + "name": "status", + "value": "Done" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "Peter Project Man" + } + ] + }, + { + "id": 351, + "name": "Project templates", + "start_date": "2016-06-10", + "due_date": "2016-06-10", + "estimated_hours": 1.0, + "done_ratio": 100, + "css": " closed", + "fixed_version_id": 52, + "parent_issue_id": 350, + "project_id": 40, + "tracker_id": 5, + "priority_id": 9, + "status_id": 6, + "assigned_to_id": 6, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Project templates" + }, + { + "name": "status", + "value": "Done" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "Peter Project Man" + } + ] + }, + { + "id": 701, + "name": "dwdw", + "start_date": "2016-06-19", + "due_date": "2016-06-26", + "estimated_hours": 20.0, + "done_ratio": 0, + "css": " closed", + "fixed_version_id": 52, + "parent_issue_id": 349, + "project_id": 40, + "tracker_id": 1, + "priority_id": 9, + "status_id": 6, + "assigned_to_id": 22, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "dwdw" + }, + { + "name": "status", + "value": "Done" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "Client *" + } + ] + }, + { + "id": 350, + "name": "Project structure definition", + "start_date": "2016-06-23", + "due_date": "2016-06-23", + "estimated_hours": 1.0, + "done_ratio": 100, + "css": " closed", + "fixed_version_id": 52, + "project_id": 40, + "tracker_id": 5, + "priority_id": 9, + "status_id": 6, + "assigned_to_id": 14, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Project structure definition" + }, + { + "name": "status", + "value": "Done" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "Alena Staffer" + } + ] + }, + { + "id": 349, + "name": "User roles and personal pages", + "start_date": "2016-06-25", + "due_date": "2016-07-04", + "estimated_hours": 10.0, + "done_ratio": 80, + "css": " closed", + "fixed_version_id": 52, + "parent_issue_id": 354, + "project_id": 40, + "tracker_id": 5, + "priority_id": 9, + "status_id": 6, + "assigned_to_id": 14, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "User roles and personal pages" + }, + { + "name": "status", + "value": "Done" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "Alena Staffer" + } + ] + }, + { + "id": 354, + "name": "Users and groups", + "start_date": "2016-06-26", + "due_date": "2016-07-10", + "estimated_hours": 10.0, + "done_ratio": 100, + "css": " closed", + "fixed_version_id": 52, + "project_id": 40, + "tracker_id": 5, + "priority_id": 9, + "status_id": 6, + "assigned_to_id": 14, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Users and groups" + }, + { + "name": "status", + "value": "Done" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "Alena Staffer" + } + ] + }, + { + "id": 360, + "name": "Personal Pages Templates", + "start_date": "2016-06-29", + "due_date": "2016-06-29", + "estimated_hours": 4.0, + "done_ratio": 100, + "css": " closed", + "parent_issue_id": 354, + "project_id": 40, + "tracker_id": 4, + "priority_id": 9, + "status_id": 6, + "assigned_to_id": 9, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Personal Pages Templates" + }, + { + "name": "status", + "value": "Done" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "Robert Kovacik" + } + ] + }, + { + "id": 699, + "name": "second analysis", + "start_date": "2016-07-02", + "due_date": "2016-07-02", + "estimated_hours": 10.0, + "done_ratio": 0, + "fixed_version_id": 50, + "project_id": 40, + "tracker_id": 1, + "priority_id": 9, + "status_id": 1, + "assigned_to_id": 14, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "second analysis" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "Alena Staffer" + } + ] + }, + { + "id": 358, + "name": "Extensions uninstall", + "start_date": "2016-07-09", + "due_date": "2016-07-09", + "estimated_hours": 4.0, + "done_ratio": 0, + "fixed_version_id": 52, + "parent_issue_id": 352, + "project_id": 40, + "tracker_id": 4, + "priority_id": 9, + "status_id": 1, + "assigned_to_id": 7, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Extensions uninstall" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "Ricky Account Manager" + } + ] + }, + { + "id": 355, + "name": "Desired Outputs", + "start_date": "2016-07-10", + "due_date": "2016-07-12", + "estimated_hours": 10.0, + "done_ratio": 0, + "css": " closed", + "parent_issue_id": 352, + "project_id": 40, + "tracker_id": 5, + "priority_id": 9, + "status_id": 6, + "assigned_to_id": 14, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Desired Outputs" + }, + { + "name": "status", + "value": "Done" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "Alena Staffer" + } + ] + }, + { + "id": 361, + "name": "Users \u0026 groups", + "start_date": "2016-07-12", + "due_date": "2016-07-12", + "estimated_hours": 4.0, + "done_ratio": 0, + "css": " closed", + "fixed_version_id": 52, + "parent_issue_id": 354, + "project_id": 40, + "tracker_id": 4, + "priority_id": 9, + "status_id": 6, + "assigned_to_id": 7, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Users \u0026 groups" + }, + { + "name": "status", + "value": "Done" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "Ricky Account Manager" + } + ] + }, + { + "id": 357, + "name": "Roles, Trackers, Statuses, Custom Fields", + "start_date": "2016-07-13", + "due_date": "2016-07-17", + "estimated_hours": 10.0, + "done_ratio": 0, + "css": " closed", + "parent_issue_id": 354, + "project_id": 40, + "tracker_id": 4, + "priority_id": 9, + "status_id": 6, + "assigned_to_id": 6, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Roles, Trackers, Statuses, Custom Fields" + }, + { + "name": "status", + "value": "Done" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "Peter Project Man" + } + ] + }, + { + "id": 366, + "name": "Deployment into Production", + "start_date": "2016-07-17", + "due_date": "2016-07-19", + "estimated_hours": 20.0, + "done_ratio": 0, + "fixed_version_id": 50, + "project_id": 40, + "tracker_id": 6, + "priority_id": 9, + "status_id": 1, + "assigned_to_id": 19, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Deployment into Production" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "Ondrej Moravcik" + } + ] + }, + { + "id": 359, + "name": "Projects Structure + Templates", + "start_date": "2016-07-18", + "due_date": "2016-07-18", + "estimated_hours": 10.0, + "done_ratio": 0, + "css": " closed", + "parent_issue_id": 350, + "project_id": 40, + "tracker_id": 4, + "priority_id": 9, + "status_id": 6, + "assigned_to_id": 9, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Projects Structure + Templates" + }, + { + "name": "status", + "value": "Done" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "Robert Kovacik" + } + ] + }, + { + "id": 362, + "name": "E-learning for Admins", + "start_date": "2016-07-23", + "due_date": "2016-07-24", + "estimated_hours": 15.0, + "done_ratio": 0, + "fixed_version_id": 51, + "project_id": 40, + "tracker_id": 4, + "priority_id": 9, + "status_id": 1, + "assigned_to_id": 22, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "E-learning for Admins" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "Client *" + } + ] + }, + { + "id": 363, + "name": "E-learning for users", + "start_date": "2016-07-23", + "due_date": "2016-07-27", + "estimated_hours": 10.0, + "done_ratio": 0, + "fixed_version_id": 51, + "project_id": 40, + "tracker_id": 4, + "priority_id": 9, + "status_id": 10, + "assigned_to_id": 22, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "E-learning for users" + }, + { + "name": "status", + "value": "Approved" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "Client *" + } + ] + }, + { + "id": 367, + "name": "Testing in production", + "start_date": "2016-07-23", + "due_date": "2016-08-15", + "estimated_hours": 15.0, + "done_ratio": 0, + "fixed_version_id": 50, + "project_id": 40, + "tracker_id": 4, + "priority_id": 9, + "status_id": 1, + "assigned_to_id": 14, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Testing in production" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "Alena Staffer" + } + ] + }, + { + "id": 369, + "name": "Review Meeting ", + "start_date": "2016-07-23", + "due_date": "2016-07-26", + "estimated_hours": 5.0, + "done_ratio": 0, + "fixed_version_id": 49, + "project_id": 40, + "tracker_id": 4, + "priority_id": 9, + "status_id": 2, + "assigned_to_id": 20, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Review Meeting " + }, + { + "name": "status", + "value": "Realisation" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "Pavel Rosicky" + } + ] + }, + { + "id": 364, + "name": "Admin Training", + "start_date": "2016-07-26", + "due_date": "2016-07-26", + "estimated_hours": 2.0, + "done_ratio": 0, + "fixed_version_id": 51, + "project_id": 40, + "tracker_id": 4, + "priority_id": 9, + "status_id": 1, + "assigned_to_id": 9, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Admin Training" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "Robert Kovacik" + } + ] + }, + { + "id": 370, + "name": "Re-Set Up", + "start_date": "2016-07-28", + "due_date": "2016-07-29", + "estimated_hours": 10.0, + "done_ratio": 0, + "fixed_version_id": 50, + "parent_issue_id": 366, + "project_id": 40, + "tracker_id": 4, + "priority_id": 9, + "status_id": 1, + "assigned_to_id": 7, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Re-Set Up" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "Ricky Account Manager" + } + ] + }, + { + "id": 365, + "name": "Training for users", + "start_date": "2016-07-30", + "due_date": "2016-07-30", + "estimated_hours": 10.0, + "done_ratio": 0, + "fixed_version_id": 51, + "project_id": 40, + "tracker_id": 4, + "priority_id": 9, + "status_id": 9, + "assigned_to_id": 31, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Training for users" + }, + { + "name": "status", + "value": "Estimated" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "Project Manager" + } + ] + }, + { + "id": 558, + "name": "Final check", + "start_date": "2016-08-20", + "due_date": "2016-08-20", + "estimated_hours": 10.0, + "done_ratio": 0, + "fixed_version_id": 50, + "parent_issue_id": 367, + "project_id": 40, + "tracker_id": 1, + "priority_id": 9, + "status_id": 1, + "assigned_to_id": 15, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Final check" + }, + { + "name": "status", + "value": "New" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "Pavel Kucera" + } + ] + }, + { + "id": 368, + "name": "Help Desk Set-up", + "start_date": "2016-08-20", + "due_date": "2016-08-20", + "estimated_hours": 4.0, + "done_ratio": 0, + "fixed_version_id": 50, + "project_id": 40, + "tracker_id": 4, + "priority_id": 9, + "status_id": 2, + "assigned_to_id": 15, + "permissions": { + "editable": true + }, + "columns": [ + { + "name": "subject", + "value": "Help Desk Set-up" + }, + { + "name": "status", + "value": "Realisation" + }, + { + "name": "priority", + "value": "Normal" + }, + { + "name": "assigned_to", + "value": "Pavel Kucera" + } + ] + } + ], + "relations": [ + { + "id": 10, + "source_id": 39, + "target_id": 40, + "type": "precedes", + "delay": 5 + }, + { + "id": 65, + "source_id": 37, + "target_id": 40, + "type": "copied_to", + "delay": 0 + }, + { + "id": 66, + "source_id": 321, + "target_id": 304, + "type": "relates", + "delay": 0 + }, + { + "id": 27, + "source_id": 374, + "target_id": 375, + "type": "precedes", + "delay": 3 + }, + { + "id": 28, + "source_id": 375, + "target_id": 376, + "type": "precedes", + "delay": 4 + }, + { + "id": 29, + "source_id": 376, + "target_id": 377, + "type": "precedes", + "delay": 10 + }, + { + "id": 30, + "source_id": 385, + "target_id": 386, + "type": "precedes", + "delay": 1 + }, + { + "id": 136, + "source_id": 754, + "target_id": 757, + "type": "precedes", + "delay": 0 + }, + { + "id": 135, + "source_id": 755, + "target_id": 756, + "type": "precedes", + "delay": 0 + }, + { + "id": 109, + "source_id": 708, + "target_id": 709, + "type": "precedes", + "delay": 0 + }, + { + "id": 110, + "source_id": 709, + "target_id": 713, + "type": "precedes", + "delay": 0 + }, + { + "id": 111, + "source_id": 713, + "target_id": 706, + "type": "precedes", + "delay": 0 + }, + { + "id": 205, + "source_id": 3194, + "target_id": 3195, + "type": "precedes", + "delay": 0 + }, + { + "id": 206, + "source_id": 3195, + "target_id": 3196, + "type": "precedes", + "delay": 0 + }, + { + "id": 207, + "source_id": 3196, + "target_id": 3197, + "type": "precedes", + "delay": 0 + }, + { + "id": 22, + "source_id": 358, + "target_id": 357, + "type": "precedes", + "delay": 3 + }, + { + "id": 23, + "source_id": 362, + "target_id": 364, + "type": "precedes", + "delay": 1 + }, + { + "id": 24, + "source_id": 363, + "target_id": 365, + "type": "precedes", + "delay": 1 + }, + { + "id": 25, + "source_id": 366, + "target_id": 367, + "type": "precedes", + "delay": 1 + }, + { + "id": 26, + "source_id": 369, + "target_id": 370, + "type": "precedes", + "delay": 1 + }, + { + "id": 31, + "source_id": 361, + "target_id": 366, + "type": "precedes", + "delay": 4 + }, + { + "id": 32, + "source_id": 355, + "target_id": 362, + "type": "precedes", + "delay": 10 + }, + { + "id": 33, + "source_id": 367, + "target_id": 368, + "type": "precedes", + "delay": 2 + }, + { + "id": 35, + "source_id": 360, + "target_id": 364, + "type": "precedes", + "delay": 2 + }, + { + "id": 93, + "source_id": 350, + "target_id": 349, + "type": "precedes", + "delay": 0 + }, + { + "id": 105, + "source_id": 364, + "target_id": 365, + "type": "precedes", + "delay": 0 + }, + { + "id": 107, + "source_id": 357, + "target_id": 359, + "type": "precedes", + "delay": 0 + }, + { + "id": 108, + "source_id": 360, + "target_id": 699, + "type": "precedes", + "delay": 0 + } + ], + "versions": [ + { + "id": 9, + "name": "Phase 1. - Development", + "start_date": "2016-07-26", + "project_id": 10, + "permissions": { + "editable": true + } + }, + { + "id": 8, + "name": "Phase 2. - Construction", + "start_date": "2016-08-28", + "project_id": 10, + "permissions": { + "editable": true + } + }, + { + "id": 7, + "name": "Phase 3. - Testing", + "start_date": "2016-09-12", + "project_id": 10, + "permissions": { + "editable": true + } + }, + { + "id": 6, + "name": "Phase 4. - Installation", + "start_date": "2016-09-19", + "project_id": 10, + "permissions": { + "editable": true + } + }, + { + "id": 10, + "name": "Phase 0. Planning - Analysis", + "start_date": "2016-10-10", + "project_id": 10, + "permissions": { + "editable": true + } + }, + { + "id": 39, + "name": "Easy Redmine 2014 - Minor 3.1 (2014-08-14)", + "start_date": "2016-07-30", + "project_id": 28, + "permissions": { + "editable": true + } + }, + { + "id": 51, + "name": "Settings \u0026 Training", + "start_date": "2016-08-02", + "project_id": 40, + "permissions": { + "editable": true + } + }, + { + "id": 40, + "name": "Easy Redmine 2014 - Minor 3.2. (2014-09-04)", + "start_date": "2016-08-20", + "project_id": 28, + "permissions": { + "editable": true + } + }, + { + "id": 42, + "name": "Easy Redmine 2014 - Major 4.0 (2014-10-01)", + "start_date": "2016-09-05", + "project_id": 28, + "permissions": { + "editable": true + } + }, + { + "id": 41, + "name": "Easy Redmine 2014 - Minor 3.3. (2014-09-18)", + "start_date": "2016-09-21", + "project_id": 28, + "permissions": { + "editable": true + } + }, + { + "id": 58, + "name": "Analysis", + "start_date": "2016-04-04", + "project_id": 41, + "permissions": { + "editable": true + } + }, + { + "id": 57, + "name": "Design", + "start_date": "2016-05-02", + "project_id": 41, + "permissions": { + "editable": true + } + }, + { + "id": 53, + "name": "Public Launch", + "start_date": "2016-05-10", + "project_id": 41, + "permissions": { + "editable": true + } + }, + { + "id": 56, + "name": "CMS + E-commerce platform", + "start_date": "2016-05-13", + "project_id": 41, + "permissions": { + "editable": true + } + }, + { + "id": 55, + "name": "Content + Products", + "start_date": "2016-05-17", + "project_id": 41, + "permissions": { + "editable": true + } + }, + { + "id": 54, + "name": "Easy Redmine integration", + "start_date": "2016-05-23", + "project_id": 41, + "permissions": { + "editable": true + } + }, + { + "id": 116, + "name": "Milestone 2", + "start_date": "2016-06-28", + "project_id": 63, + "permissions": { + "editable": true + } + }, + { + "id": 117, + "name": "Milestone 1", + "start_date": "2016-07-13", + "project_id": 63, + "permissions": { + "editable": true + } + }, + { + "id": 52, + "name": "Analysis", + "start_date": "2016-07-12", + "project_id": 40, + "permissions": { + "editable": true + } + }, + { + "id": 49, + "name": "Review", + "start_date": "2016-07-26", + "project_id": 40, + "permissions": { + "editable": true + } + }, + { + "id": 50, + "name": "Deployment", + "start_date": "2016-08-27", + "project_id": 40, + "permissions": { + "editable": true + } + } + ] + } +} \ No newline at end of file diff --git a/easy_gantt/assets/fonts/EasyMaterialIcons-Regular.woff2 b/easy_gantt/assets/fonts/EasyMaterialIcons-Regular.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..6fbc47406ef0bcbe8b57b6da13670dcb60fd0f2d GIT binary patch literal 80288 zcmV)0K+eB+Pew8T0RR910Xd)m6951J1C%@f0XZ}P0Rqqf00000000000000000000 z0000#Mn+Uk92!0wHXMP{PzGQCjCu$R34~=42nvh7Jc;IO0X7081CTHap9la1AO(b8 z2g^DPEm?6)WI!evHk1~c4oPZ85#c60iKtKzxo%u!*>+0N zTX2OFoHh4+KQg`-1^qRs0bj;6HiT8oZ06BLGo(L|cMBsSAf`fWxtWLT|q5k)au zU5bi__-QniH$AV8 z`lMdBINg+Z;zqhDNhH(mR4#(TsMKNzdYoo1@51oL_#s^QkIz?mop}^I6V8IR7LTS8 zcf5RzLG#Zt{BksNQqw|hq3Qt)|I`@r6_P4;3!6cC`vz|??SZIMGc1P8u{I1?pt-ot|EJzh@(W+T{Vy*qD;3nX2W&c$l|8-X66^wD+ z4?sv{R0%yGCd)16VK!HJ%Fn}-9siy?yYEA;K$MyfsWOsOM4e3H8de9$f1fwi4$Dx< z$YoTeY4u;= zT|@+^T@gzy$kkAhQ)z%q>0A1wbaCtTzxMm>`-sRA?s5@({EDdDMQyrR|Fek_$jkwc z8j@Lx+`IA@ezR9;C+CKp9DgRsHgJ24M43xigywxC5enhp|6c~&x$oNug2^USsc6w; zv1vu?=3nRPi^)&&5noR>*3)5PKhsIv5iMfDixsh=7CcyAU$7!qJl7X=;Ist87tUP! zr~)0C8>8fQH4lK1wWhh8n!IGjIswQbe5`7Yxl1s_qDau26ghZ$iwPQB2|LzFFVUQj*n;fCmY9SUC zq=%e?J1Co@>(@g)-<1eGRcrumN~krFI{a7MzXG?lW1l2$Otr6Heg?z^;t{bX0Fa18 zEFkQVwOupIU~rA?ZeYwBIXkjkM3sUxSY^3_RF%J(Tw7=yIk@e`_u}9~Y=}uOEX*tY zN!}&o2f?7Oin1-q0!y~QGD4LLxT|rXhi-Rv4>?OejsNqB;wRMu6M8W|IQN-;g0?9D zz1-b^qU3~eFGFEw&8+`rZF**7_YV^e5DH}xDhCzih19#Q>JJE+qDxBhPN6~Ay%<1Y zsvsvF`#3w8SB<eAx7`@IbV0>;I5S-(|d1YtEs-fCQ;ebNmv)M0|L8%*F#y7DS&o? zuASd24YBVDgA+eiEtCg3V9doAonM(?8wWr#{5UD>X5ETQzEITt6wPaa|1VSh8nTeS zorX1pC1`s{Q7wya)|nvQAc)R=ZwA=fAlo2O!uG{lXtV!+Yk^FvHVk;s1gE{NC0(ZM zQIKWy)B2&Q;t%}t4lF`e(;GU~uPCS99YZ1l;J=nxMF)@MgVHE1VHvy1)!QuFez?qK zsui0MOp_4I(P~q8U)|>s;&%gpw#4yBwn@Tal5NQniI2FxXy5)~Oe8U3qh~iiK52Zy z^;WOHO+$dh-k6S=HWRbo{J|GHG5d?e%GtHDZ4|zyFGd@%)NX}MH6GRG9kD1qhH2C|sYpyxO?KOrQfTp0SB@SBd zIUI`Jy-U^Xf~SHZs{kBi2;!;$oTWK$<$R=4lo=mq%0m7BrE1^yJ`^dvg5++CKT1D@ zwAfIR-IK#=HcQoe_f-jXUsVwm00YJFiJ}4lN-_aZZjrRR08&%|bbCPCr;qfb{G;tj z?unjkk?LgsWKW2@MfZfXn&pzMRnKykGrJGZ$K2}H;)cGv-wV?4>v%O!jTXf{baEw< ziR6$9k-5;})dtysaf950PQ3VOV`js(KI13Xa>Zyznl4R1 zll&pu_I;_9Fiun?1J6ScL)8U2d(;0I3h?B5wUZzPMsNHlVsDjjp#pobj(;T(-NLXoVID zkc0%SAV}!By zB~>3(pNQ(y8>|LCGefA6sJK)2fTz&QXpyra9qnsUeJ%f%EZ$M{99usAavk4|!Jm_} z!{^}0Xzwfg1MuB%x`-psn< znbTbEF5A80{S?v!5@kv*e5^FmAFP^H5GBwQ3m67i$`03(lomxwIIP{DsCN!)7uYwB zppYJRj0zO}ytsdQ)+V6q-c6jq;BcK6ZnBGF+4$(R^5^=h)&LR~yZ1}`1Q%!Aw^w`% zM?aPzDR_UJ+^dZsIEk&W(%EuJE6bewb`5A=#IpmZ#+b*;eRL=-Mps$Pb{Vgoc&c_V z97#AkusDtE{cUg%@S;d5SO!4KDhZ3oj`e-bgU}<}J2>2Gf{`EE21#sYYSc+ZvyXao zX;~?leUXc0h(9D+6G{2|cVI9;vJ9fDxVc#_FPlJMBdSLW7pP9+Zkn)U4hq;NB&G>p z*SeB7?SWnd0O6hn_9DGHCYvX{dc-VVEvFV2VwYV!kq07LF}DYn+`GZv;#zj@dE=L| z@q2D71Q6Ryu&E@?%?K0CPzqYAZ*ReT=-BD0Fc`NE+t3mwYlS7-&O zzc>IrC4h_fO&^DRu?_kd?OQg|rz>_|VLza&Wj%nrh|M{CRY-3m)9k}Yv9;3QKWbNG z8g!2Ra#D1@Sdlk{q<>svDO4tcF*G1Qj%=tK$~0!5&+wOZhovN{TU-*9k@IE6>Z;Es*vL8cM>o`#doPF zl=)jR($En3oPaD9*@!6I5kh%bCr~slj3eJ;xu2R58L7b+vk~Oz;6UlkBn8(~%q)vi zyvz%$93Ot99UwiHVh%MlV{8+Tp!Y|-&7*~dA2*4=nx_06fgXN*mdnSgj|Eow>;NaB zR&s-b!MbFof=xmYpo~2z%(Y!9uSz&7f9CSwF=-S{dKAudGSxvt!s(|g;0QbbH)9gc z+FtqJ&w#f|zjFV?{rjV7@t@BAn_K7fn==I`B*>wJ6)dgaNy9cfffWm?&X}nqDp&N> zt&yccq)M7e)P59A%k6+0Yp$HK=$iFTvDGbB?X)|Z=%BXsAWdQcfhnb(Ux`jia!2;I z#0MpxpqlyvqzOai20XY`)y9W#qx&Ik3zx(6V-sH@f>m2e#Y^;#fozvp$0NGpKAxhi z#YBn+Y1xPrHMSPj^Pp6)R~Dneta5<2#k!(mrB&7F2D6zYF|y*J%dttB(YLd%J_ci# z$R#kF#54gu%W7^-4PQMp#%Ffx$edItRa$HC@R|l2J^4)#v&%3rcI-;(C5_sy_*(@2 z$2wU$fof{Dq&3$JbiuwHLX$Y+NX$}TA;T=e93m(rw_yKBWR12-Z-mxKHAx74Tywao zuLcARCdx{2R>}gGTT>!$koyvoaOYzZm=EyyDom7YT$5%7a4M14$|Ac?t$s4DAv@7| zfd+IZ@!1mC?Q}q{ER$HIhVstXQwn76fcwGXy#^GpncFnw;NTfi(Y`n0HNi#9d4hqqeMX~lq^ zDj;0~YnWQNI?rj~M1ZJvG)}K2ZSHRO2tA}^0yo?>dS96IH#60M%o{q@X{Ny$t@gsR z>)gWGWx$}u{&YAi6fqHBX?CHC+E;f=uhLozft9EuJo@Zz>hl*QNO}s?5Qvb^fpAu1 zWvn~!wbrgbaVsRrrR9@|bH!5#GS3fzJG7TT&i4c4Wp!0S;cjjrfSDBTP-JF{#RW2p zR)n;FKb1(2eIal(KQ)BIb;8=6I14tvueezlaDbEl_X(sA6}z{(TiMM9yY%{Hvv=Jd-b*m)03H!G(6G=Auy;*E z5$ezbwU6msSk2N6avHKGnFwuAXT0s+q-MMpKr|kp2-D*dSjcF#OP$UD8PVXMC(vr# z*chp$!j4-34T3Ne1Kt84x*pnT1om1i&RI}vlQ`=2l;bL&=|B(;5TL>t3{bPv>gyg- zUp*z`>e#<9s32Rhl(y3GVh{AnHm`}OV)JoTSQ;^3FyrjA3-p-gbjFd01@k7C1`OeJ zmQglDBlae1R?Qy=bkT(k{z?QpBBo~3@Q*0@B~0|6AA(PDkj&hz1^(JLmLwfo3}L0# z5z_+!{F6&T52Yex>EzPUElSI{X%T)JbgDt-6T`^(YY4RH zTx7j=k2C&GB}4ep!_b2~KTWM8oEwRolgwbOZ79o@IQ>+$2`w?$@*2_0P3`_$WsWV)eNU_$+sB8IdMdC- zL1Zkf9^{cLa_ejrf`*;G(m2ro&xtcH;1gnn!}l}wH~Hmd96^fmKkCEl)SYJ)ppQan zcBkKDX*!+n`hiU_)lq3ZCJMQ%m7a$Tf{mNP7oGSP1kQR^#o!>vT6hL~z>ydz3%}r) z_%*|-?-XD`kDwlqj=&ARWLP7k9zD~`!e9krT8(8!p!wNmjsfyB>1y#6=aG3SNUe63)S3PPy~-+kt*cOfT{u2fIny>Cd)2|0R;wpeKJY1>l}b`gpsF$( zGU}9}E+wbXvqCbfRGk)#;L3&v(+P#Tu|=Kj))PnjzTRcY^#idIMWt?!H0uph3G?+{-x+hzt zQ^_^4Z&(kG>|v;N&}n4dHluI~S{GR=YACO&m(K@2lKsg>y!|Q{(a)X}$mrnZXtl+$ z;3{IRL=xRfZeXpu?6KO#)+ZbIl)a;pd{_Nl-^#g|YrB!ki zBLvW1OUIX%Hg%4fzm|;l@h~EV*Af$30upN;QpH9Oxd~$9BbJrB#>V(UXB&)1U>ejc zvFGhl7U3aXwdv$M+_C@zAbutexv;$g0nvfva8(Yq018SX` z`^z!S;gupOCAC0?!S5h!nvg>YLb+D7`sq}x+%~Yuti-D)W#>)l11+kH<|amP^C3P+ zxeYv>1;nFR0`1Aq(SZhSwipE=Yl|3{@j!63!qm=-+Q1cDX)HgZD3C1()^MwaBYh$d zsGU5-P$RL7hL1eZv@*VziaUSeS=#EE%^=Ow7iovD25Vs+4}ljtIr~bm113F_pUHJP zR78>ouu&YGp<4{1kJZ$GmueeAl77Rc!I=TbnufKxQ1b@~T&|J zBf_V{rCNbl>(WJ4&U`YRhHj?^T$HJ5n zrStLWQ&FmBev~p+360Eu03uEecjja_Z1#W>A#*Q(jXtTF}qrn%A6ln zx-?*m*gYeNcL>vVK6d5MKQ1@a>Lz<2ltmkj5fGy|5-WBCznxXL$kHmPx|Wp2?l3V4 z9);~E4!X`39D@w;dZA4kEcKOnq&Y@hX#=Cbz99w_B~M=&==p@P71>9LRVI=;Ns}Yi zbdxxM|1wfUml#H10Gd=*-mjc>1+vJDE6$N$7~ase*m_Y0arjhEcB^1=XI4peR=D<8 zZp&Pu(&hyM`p4O0(ybz19N&U5jW?wsM&g#E#}{S>t_XSM&JbqJUTUrnGId!A-f60K zpU^GtI@|t^v5c>3E@La)Po)qk7bNHeq!SI+3O?VC6LidMgx$P!lh;5RI&82i7cq%) zGnoi!c?B%l1~%b};-wNwSB-+C+;+x=&($r5<0gx?`C}uVt=3(HX7Tp4{#4k#CNT9( zokT^7AQgGwi48D%^o?VD!DKpB#R~bqeL%-zh$QE@G>b)@T8Xx_R02=pC^%*(v<|Z#?ot$E>S~j_#ni86K{W-PJ}5BW6jojdqkdOAlwG(kOy`Y-7)T zvVIpHSxQ`-NzQM@RX5HZ3~G})E|JaWMgn@E#^;fSRa}pLj&m)X__+4*#(5FmuIqW@ ze1P{v=N4q+Jn`?^E;>x!lo*J&SDdPVL%8obw}uqZD&u=u6Qt<3+nG#CQ&7vB(B3?e z(d#dBEt@{mr+(y zi;CB2Z5mB6xyiS47Z8`0wmWGN0_h}^))O!f&kzm>P9_QWD+D6isgL_{IlggHB%px% zOPrExS4Y}tC|}2CtGwVx@*$uhP}Ck0<$*akjXJ3IIXhBOYVdzL*YGgU<--#uzORDH zjjP@Vrg_Ow*OmB+M^w47lM zZ`|6#=r}pFikPbSCW~Ge`ev*<`T!Vx@ija}rv)&{b_Jr(9$ilb$)9H9;(VuHSi}8V zK+zrBe-y<5qhZHyejS2c!1KtzkgAD?PD5Gly_(N21~#(YswNxiJ6jufN$?siY>)hO zz`>#yPYD8Ry%U6~wxS$F#;Df4VLUZ3$&OKU!_py`&Q#J;}2?$4)?`~)nhnz-K%bTiN#O{NBwT|`3FRq%N6>phRPP)qXOz$ zKT4D{lPZ_~QDcbOeNPnELoy+{eA9azx;rh7!?al=&ss5@C>l61q|B5;ux;7 zxGip0R>HMEtfz8fW&Mf+t4z~jFNNpCTxT(O#e~po50m0LTMKPKlC$JDdRGoWvxw*N zKmshijL;P2TlRdDVAyI5N3~X3Fk+3hpk=vKr6KP`5r`o2R?rl*nq>qde}VACi?A`| zTUt0Pm;1j~fQ2pQ=cnaB#aEm@6ezZVzTV1M*FrxO8Rjm;`&6ZiPw_a`U7K>WJwK*! z|J#5$!%LWD)$7>cRGxwrT(O_~WVkHHrH?>vJa)<_xH-0NQ_z6;ww zd#I@t`ouS>TQARX%LCXQ6HgD#CKy#Q4p3usuyq(Ada=9$-#wWA|2a^zmTWk);gTM_ z8vHj@t4G3u+l&@LJ_~B6#@~c_(uNH|c|9a5j0sHg1>zSDnhyL=a(2RF14OrgceOK! zK`{z6U=_InC5ba!K&M-vz#p&6=!F$eDxo_52Y5txO*vat>kF9({c#XzU4f7d4Qm`f z@2^A<1TGwr_u5~Kb=@nW)k&FQL55DQxX(L4SPwsdA~hh{a+6nyT~CLa$I4=(6Eqm` z#02USLdOQ0eo{zzU8=2@GipaO6qybtl97+<``g`^!awGGTiaX*4{!$89eEt@IZkWn z?g!@@ZfJl;lVK*a8l*O-M=D*_RYxCElc0w2xVS-f8SYk`u(4QgcBvIFgkiOaHkCmN z>jOmWd$^g2xUyd=UZX{0rE9g_&?Yr^^dZ$$r4yFxC*SD*$D$%*rt%#r8%Z%D@G(V}oCPbInLK%L&B#KwKnNFBLG05|n+(%ywflLp;6ThK zGkg{m*8B@OJ3CV%my!bGRyKneR{*WxF{X$^V5-Om>G!zYz+UX|l2Vx~;3|KFCWXbJ zUDBULLSgE0f_ZY&y<)iV#LBzRsTtURdT11XYTim%Y^DCjJ=DTJ$WPS#9AFd(vQHMS zpfu=HeqfO9fCioymw2}WDP@2x@3uE-t!u*p))%bhj#7RyK-0n)BqLaO(urzmzB7u7 z?FX8sz~b@FqqcT1oUs?&H%CR^FeCD9$sr)FTnxW`<*>DqRLd|z@ z6q_h8*^Iy}V2TQV!#W7&wdLr6ekIhBO&b5)&75#rJiX0^Iw_<^igT5y9EfPVReus8 zI60(DYsP3!B!!PhvuL}BF0&m$Kvrx!uGaGKHsN)r<0-)PxMDn{MKdK2lNjA6iw@;ZEUGeWDUk-@RF$WHs3;2iX?gH*MALC~VI4ogL2&zq>^$>b^GU!-5qX>tj{ba78*j{4D@29F_~RyNx=rL>rk zNwfwu)LV64yt_ES)TZg}l2oPn^X!<5|rgqsJ&LC6xEYqR_nXt*??c& z+}K<>fW6&C%CFb8JJ1<=iEdK_hl>^hf@f}#aeanw!Fp&5l37d-ZhT`!5lX{K7dus< zc)J|}paJqX&bAu3J^(4PyIoj65jLD^nPXgYX&(IN!5OdO@#mRwEjA)1ey$V58^?8{ zooI$K+uFd%_yjCHMvgjj;rMs;wVpf-V6K)Jn8_Uj>Q@n!8Pel`s4#4{Ska>^VX{Lc zi2T;Q(CiZKG`slqy-N=7xAg5;bC(3hy^%v#Hm0@vQGOZ52v+Zn0(z4529PYbEJuFg ze6oznQx0z=fa1}q`HL=hN5c`WKa6`t!zx?g%?}!}%n5N_bNwY!<14e`L$LK!9xDN+ z`8vESoQoC|@~oZpzp#ol9Hz;=YF23v=Lk~$-bkMzP_H;^4SJPFjow8Fns+3@2{H<=NqGI(Mqb4sUal$jXkY|Q`PhVmny{P3Nh5ac}gIH}ibvp=QgIJZ}J zyu*XK+JKIQI`a~rr24-X=M<2;y2-MulHu8>&UlD0bXbw2 zLZe;Xs}L-1(nqs1iohw{JNZODGiL;^I)1GXvKLsR_t}Ck74If$d>=5SDMfN|7T+H4@%Aze zxf`wT1A`T-jU5rb&#=(-nN~{}c-Qp&XY*&utK}+-p%hv@=RYz0t_`F&(NnvF3SXU1 zyFOf-gS8G_y#X@VSt!(#M?DTN8F|~e^Hf(1s}zB~dH+ID%tW->s%Oq~gDUk+jo6>n z%>)aNYUrlw=>?M&>U?|`H(0#+>OvfBlG9@t*~$A-rysiAyE37I_KXemnETQNp+Z{c zp>qMvRmC(UiempFV}521N)LuOo>C&tx(SkMT48u42FOwOl8pJlkxO#IarSwT2&`zl zc9m$F3wJKT9o#wff#GDBoD>l)cLqHju#TUxZ|o7I36NTtIs(Pf;n(XyY`0OhZua^` zP!X|=wcUx!5m<`Va8P)K<~BcDWy)jgL#oDGV&k(U)F)@gvqF#z!2#W~g5fY^Xo9EP zGGG+RO_HiRYWbO4S6<~rr&#W+5{aN#{w&H!%JSkrNoW)nNu7W5(;KH(rq(4NdI$T- zB{ElSG1b9)vn>Aop!{m^qc*FGZ)^$9wEXOta5p)%wCid)$jH{*>8gM(4BP0C+Vf|q#!v17A0t8qyA z(rWaGt6BFUzJsy9clQKqz5G<2$%Aep5mC~{9;&28r4VqbEZB@13KOV}eR z*9PXaN$s*uvTE$kl%x=GBC5?QAR%lDE_f7Wrhb2I1f*{hFs)ih54Do!d!QZcQ1qi- zlj7$DPMtLRS{!lY<^vi--obXx&kap8=^`(hOxK*LL7W*NFwv(64VWi17fOHGpeWSR zPZo*lpq|uJb6Q{S3TCGPFuJRhXgC|N8sw(35y&4&a$KC1Hg(*ct(2;%na1EwbQUJ= z(B_znv`ePxD7FF%5E92(4t9(PS8*JDnh1W_;G9)CdJ^@1Cpbr@Cwd#+T+}fJSFedf zqfHH`w_rer-G6YBE(~Ad__M}6i|=|QUPs=991BDzE9fZg7wI zIYKHv$;m-k#!5w95;%b+boIT9r~XEsb6xf)GfiR}!E~G>?VlF7Ixwh$CgKNdr;uec6d)n|4 zcD>63n`(UOkn;en@%)+;y%$T2;99q?92}y+h&~^}E@|@c@b!1+T#3}!->hG^i7-qE z-~#%v@xlqgM>M-&g6C;zBSn^|{n0_;N0_79&ToJuG_*+}xRa6`H<|M$0J;r09mL@j z)Pk;Lt~yO!wl{tj$P^usSxe-v@{XHfz4U+)o8scYHD`arlSM`>4uS5GG~twJhW8s6 zEtt#M+VBO`vfwr)uJ9)sajv%~T)aR{eR4#)X8*Uvc|rkowNRNT38Fh7Uu#p7U3Mdl zum)7zmwnaObB;o|vnTF)xn^)C$>w?2cGoWt2KYxOh>2aCC?zbD&3ke2W_-fwK(g}|gd=OSFPTx3f*rAK_iRj|^A5_N-Yh$HWW8&CX+a| zb}=HxY7{iK?xR~0zm^wH6roF1V;`--wp%A)#&kKC9q#{8MfAf-c?j}s0a;qZL&gLs zdS$)nM}RQwxYS5!qzgyv-)ogidZ@CSmw08wegn_lqGCN zOL z7bwl>D8k-_Odl}6AqxaMNo}gbLJE7SQrQ3G$n--T&@K(`Z`zS)h*V1rdDSs-G>+*G z*%l@QF*yo-U(7Ur8>`tV;@MMdy+tF{TY&ol{K*Cq=+jmPwV@K&dGvnGR3J@v2R2 z?1HT)M(|GXzbz4%)(EWI$4c5X5Gul8ZJH73GahgFf-ZuCb%wqOn}IgxyhD(_THr;W zWh5UwZ(2OsRi&;nl!msbeiH0=<$%1vHsbL#w(-7<+W4DR@b7TS8fPG{KIf4`940(bJl+I{yiDFp@ZqN&U z1^4uwKfnY1;a}j1xaxi2MevnqE+7z^ksY8w=+`iSFed*1LkQCvCreo1GB`QHs(a(x z90)uVz^M_!>i#V zFM>~?%g_NnAH!ns!;oNk_z5U7W4^gH{4)mL-!=q(nxWlS^Xq(Xe`^YUL!1Bi&c*NG z#D0Y@#4%uas`OL-0q#$uHr9a;F*W~xL82fPvDNN$d;P(1G@eXn^TqOFwcc#E@DzV7 zo+vnVk_Ax#C#@HKLxP@d3&oAu@FDef_r{Jay+ZuTY#*+oZv^=8RPTtivo%KtpfgLN%?1d9j&1vvF{o+`sf3xS(fR6Z@IsjN6#CB@ z4X2C&{B!&ClBVT}N=>2apbx6(s0Q*h*%Sec-AsT}57dyjRkui&{3>mX)$U=(2&_$D zgvF9|vZ*W)D0-%4fa_TVfiWXQo!qgwMM5h}`C%4bU=#=3YeSwhni zum+zrG_8IC*=dNSxc)tx)M$ElJODW|-2`WxENaqZBE|Gxvm=xAj%ow3xwX|eMHU`DQLOn+-p2E6|{_;)jT|^ ztw4;*aQ^*u@UPt@{BKDv?(4K%2S{kuZq?d5WoPCfIi|vp)cnXqvS-cpW+0Mk_IOu5JWz#yuqC=oMg1oBrpcE zqbq4hS*l&vO-M(!7b9{EB?)C{6=|g%IZKZps}M%nq)jM082w5XClx(Q!LA+XL2|D& z?T{@aG_;SP`zErY>Q4RnNjE_UA;L}?=o(Cd=(g_0m4H3)#HQmv6j$goJ}fqb7^cta_rHq$6? z$M}!^_ECw z%LX1TavJJ+WoeR!3y|2Ltn%jNHF!wmL&|LhXywlubFt~LuoWRutx9y10ST?pyKS~r zr8ShIl|i{1$EF7CrvtKQyzc14a(Oji*zov;o|307H7T+w3nH<6*yh%|oq_=rO=)V= z!L%tzL2ALR)~sC7gF}3Gn~|WE?KAEvgO`*_%FlusA zn{iE-Y9-R*LW$&|P1GAb1sH1>@TL?AQPxNj9xd+XdXh|<5Jcok&#j<0HwuNt*tw7! zAK*%W;29*n*GIed-S*;|`xqE`*==K!HqX+|bIRagc5ve+8UBp>OW?L>#kvso{zfmA z@PS~WgKq%k91!RfoD%zh*4Uwuf3FsV)M`kz&+*E5-GS={(7S^0Ab{u9t`_>+0{d~3 z*@v%^c>{Uq1qamH#-aDg=hv4@4u6C1l z2_mnyx^zf-5xNWQFL@JaV>c+!O_x`zOp&<+ZywEI<1Q9;kb7$^_L+2T^elz13Q5*< zOf*{u*wobhk)=lq>mb`kireUSmo}zkoa$MggJccFDi*8-7c3S1!pk(Qi2cFN1yFOeFNrlB0`51-{uUh7+2XG~gKgE|W zmz1aI&sMX3+=_5V2+LXgeKcumKKm4Xr6w&dxhj7HQjcFw8fD1(Fk@;fm1?8+=+a3f z(aZ*A%$&Cz{zceh+8(Xv1^~2`PMF#FAi5k(MYXX&=ffBqg^7{_n zk*TGWIDlryA1W=YEt%GJFOioee*@QbO-UgmM2-M!e}h_w6d@VLgjt2Y(JEaow+?sh zYt6j?s^}3#6iT61Kal)tOUt>t2(Tono&Mq=SpX411XNGQq53c{Bxk_x~jvxLD#e5tWLmE0JDc2z*> z;L=YwrKK!hnw!O(xQM`Wh1w)Rt)RLK+;JX$OB#J}DqGy-n{dfN4Ymz0$KaWRr6ft? zZ>~R|pFs6nZvjsjf5f;?J&RV@z1!o(YWJ?C=nF}GzVvhyhy0`;ZYKNklf8N0m5W;G zQyWRmzZeMg$xJNLjMsT%(oI>+`ARXOvd4!WAN;SQ z$7FV4wZMlO|9;e``p(emWd=cpgdEV}mR|O(q0_eqAz(P9tTL13=l_;T${{1ofIy=% z0kE5ZNS&GsmIk#Xp)2lrDr5Pp6N^G6GVhC$v3U4#)Z7Zd9g&vct-?XKjYE>Z6-%;b zw74y7O|7UUYwCoZs3W}2?>CBv5v?D2PJJt#NgT(n1BKE#17p)pp?$1fOxpbRO}Z05 zimgymquB9^4NRC!o)@AJr#g8^oUw4G@Ak;W~y494urcV!;;)nP9OYv}8_LxB{eb zI&f3Oj{v z3W7PZn4oS&8by(~<5OxVY5D=1^TUYeEVy@5K1T^m>-y9g<-l5s5(zQJF^(c@C}$Ch zOkp)Y`@}y9CUzI2`NhustQ4L8>_Wf05FLPQe|s;sY(if8nZ>o;M9?>pnQ}?QpKZ=l zh<^@c#rtph)n;LxW^nR~9$2^u3DnUK_$y!}3jy%j zm!Gli6v>pefq`Y6NII%?$K8)tX_po>gXs%+DoZNlbHvfIxpv2r)kM@Zk(>t^iI-A? z8B-)3)4%b=rg{x7vO*nwqqvxjb4)~%33PR)5h_O|0{cTw%lK!tMtL3c8Yr7+Va37a zNoYTb0At&u1T>T%X&AYBb{D2etE}`W*65&)S_-jOaXxszs=yOz`%JWYPp3VvNj?|1 zfheceu5W!u6VT>wz4CfO#Ee~bJEx}1(gSoC|7`+voUGIJP^E&sq>XhC-4tp(;Mxbz zsGHL7PV{TApC$b!PVHs3*3$-bD>v)JY&mu}%56sc3R=7M4H*+84@ioUoY|K!?_s*@&(!B1&=~nO zJL5dUZ3IulS`!= zE=?nK=;#f-+M|u)+> z6+b%*{}{b|xi2#LyDzHmitl)5bW&}e)|;L{mb{?c-FBtT430wzHd+&1CQY^4z(n+@ z1zn8fVACx{_wZ5|6vvVUuaaARm%S~!+18%I- zhFhA1t(hD z6jF4i|56ECK7$@x>f4V93js1_UVz9KgyEkL8A%-JT5YYQ>}FJJeN3aWXb_dCasfMGNh}K^0nG- zj}zXb1RN1(sBfQf3vZvtaN;miy5*A4Qg4qER4rAmQ%o2J;QHaj9l|hndmNID;*L1! z1HwEz&;=qOB8E{yIvg9;NL2t4TlNT2q%&)<@?}YKE{usOau8;hSLzCU>4WRcSeA8K zdfV1#fm=>o!bvaN9WF(e-@TgLiM0KnDeCrZ;?hY&f<|g^R&;mTvQC)J$^E@O(ny~6 zKGJ4tDx_|LY}X-NG_6hQJy$4P>fKm}iw%NC5sdA5g$T8^t1@+-j+eJ9mC4VosPdh5 zhUG|#sq@3AYsf&4;UHl`k-WWXWKsm#5QX^Js*T--?+mS?2j2&116CGdML`c}|G=qm zz5QHeuiRM8WN@Z6%h7ZVFO|<#r3>2Xq`8Eg$7mRyQ+#EnrA{!_Do#aG;SYcBgqoU4 zW_QJ-({Q#59Axz;4KRu3{?-=B_VSMVr2g{*T}* ze=6_hd!kN1%IB$j&ys8s$5c|5BOS`MI2H$!OSZvvL;frWLT%a;?wAn=)cUSaLgYKeMIAW39qVr4_`L6-59)xnjhBXqt93>L*J$m=r> z2F+Sl;))~wJF61_#!z?NDs?L4K8|`x3|~+rXPgvt0jncyOMnSBG5|x8%3;F|9H@gA z1=cjm77Q?w0#|%l!&CsZ?}qRde^baQr7W(BzNv}?(++22D`hV;2C$IUgL34*O6sQ0 zwLWAdpznwa=UNYLeer%VNzOOgA!BHO+X65s`oD=*jK4^iiSs}%X62Rji`+}VZ1+W< z+@JDI$n=a*F5<`zg4H2Zgm+|63OCNZjDi{X98)?X-+jOel^bTNmlprz!n&wyYGTTS ziz0UQDs9TE(A#qs9GJcr9}_YE#KfIe%=0AEIOz*Ji@!43+j2a2dlJ0GBgiAoVh7|0 z7{w0E>`^gc7$XbI8eHE55;=l!1b662#6)rr)!V73#v=2Y4$fU%3w@&?->GilfgYNP zbNZQ@!x-vlSqJ>s!UKk9PPJ^|`+aiBA$)e;rm&hUhnc{8Q9m%@*sI~hVnSXzR`Lj2 z1|Mw#$yx<&jj;IjE;To-iq0f|)H0I=c^4R;GR}(}+p-BUCeM5Wq->yJ2cGu=6f8s_ z${lmKm1~FP(;-$1-N8=WXTzcau)|~V`EkS;P^;>EaYUKKT*ZM|yGt)5e9I|mk^vU> zs2L>^euh6(*S{V}xqR!HdpWi{r7`*I|Gz&{EFZmK`T+2M9AsLdd@j6X@N9HhZi=!Y zojLMmMSdDE8|CNoL~Y#HQl zVjcG?WoH-}WUR4~;0@t0sPX?*hD7}riu-T0fD07LG!A`Rmel8^jYnP->UHy(+}_D{ zOoh&N8?FQl>?$4fB&nWOCs=yi(b!A4l;!T4U?f&&nhnXz`f8P7F%gg4dDU_CvPbbS zxsju+RVNA9F=auXp$uBYskn_#pv**D>eK~$Q+E#Gz` zCXybc`@Xg)&zo@gyG*3B&3qpuLLVp$pr!wRv|KA3TDa@P&mt^Kv6W_pdTh0hBa3vc zl3=4l6n#{k0$&739dOW8xgzMNT@GU5=jUlo{^@JWIsNYD|^8G-gE9s0*eMM6EslE61o-*yMos@M?t zi-RF!p_LADk~Hy14_)@*o{4-JNh3-Sp*j3NQJjH78tmRb*#@GQ&89*B5di1Ze88buMJF5I6M|$7-+PWgDhMT! zg|eQfFhhrLCbJ99aWIx%V_zG#qs~ru@y0uI#^8q4Zr3;E0Rf z?VIk1J;Ahe6?K2#+N~)Vim*g?PYA9~{eDs1WTawsGIwjDn^KEM1ka}SY}>Qg!-IE& zHfVwHY#$&+Wh*5B!wW+OQ1Byi75vEG%7u|;O-RFjteD$xxVfQ5+m_Q1<*ab@1Y*3} zC(sR{7LG|!A<({+0wrZcD|`r@a>yOF;alHX^uXRiAi?*NgmzfFAQzuP66$_Vdjml& z=MW#BQYXf2rVu8+5-_bWJ`VYH59atQXwCIzy+5gl5o<`zsZnPFe-7$>n#_yX$`)9- z8xCR_8D&YKJBSICguq~9=b14#s$1zR%xlE| zGz^O!0VDAjY9DZ?9zF`yqu9$fJHGGavOAklAY2!>5+!YUqeTUQ16cT+I3Dr}YuAo6 zIM|)CrWXSXf_Lz(N93seI0{*HcsDxRW4Xd_{?|(7tsITh>KH-tb;bfMa*B3RvS3yd zY>_Or|0rjfRMaFo!8?etAn>;{)R;Z?@H5tek)yZP8uve3+Wo@`gBzJ$I5VY>vx&mI zA*Vp_Af6spfm1=TmW%8NOP8v@>%w7&EbZ z8PAw2gfBhXR;yTLivG+wBz=cPjhAufJJI4oca{}SUt-U{fSwgPq-{xvX=Y7X7)*}*TkwrTZ5&0-yhqaD?glZ0Q5knyeqvjxjDy>2bla}MAZmrl;E`9zk> zCd}39l-?ii;67RDK;f8$VknlGRqxMXZ!7=?wQ7+6b@h}AsEc%wg2Cto;%!j|z`U2^ zAPj-1lG6>F7LHvP%w!Q;%?zNKE<*}bM#{EAPTgk<4)#IuNVU*v%0D3GT`0h53X))) zKyHo@xHVsw+$n|zUHKQT z>;!TZ4AIB%;M6O8ja6@1%n4@1?@@8Kd{fz&ChTok7%Kn4U32CSK69@jX${?zw z*HCy7B(-y3iO5+=utLRGsOXAGS4`%jsz^qO7Q4<>hO{rKB^T(-IM0j#wE{I<8;2;^ z0}P{U5fD1Cki&Hr(ctZuw*brIE5;q?@mx-P*&4v$9|vY0rI4C!+)Zi{bTY90Vp*{e5qrlz{cMkrU5I_cscAnOzL!hkw7drZT)SP&oo^=S-C{sgUE_&3BJ>lxo)2CgRojK!H zezmQ6e9}!Cuy)W#-#Mp5$6-fS4^TSAr1xk_G0Lh2vGRkM?`51nKH(ehOOzg@Y_41K zw3MhGo*M5gE{6bU#Q2^6#IOq00xC(3^Yn#dT=Q7NF@_|UUKop#NFZ<{=m+=Sm&gis zTTdJ|GZX}GWGgD)mbjO3snN*KY2;ZnAjTBXOvNs?Vs&<48RE&!2TvRwtsANKs>5%- zJC|t6nM zF>mfCesL&1YXZ9G(PW+8{R?OhXySn`fyUlfzY}3n8sFL7Bs%>pjK?~n_Eg~J#m#Ad z*p#(I2`O#E)rrrzGQG4SZ&2*u?3@^+!bJgp(*jWWAovJ(q>UK(mea8N-)_N9q4`Xk znAP9ztsiXoX2!C{;i~1pjpEwUBv2(u$6r=~8tICbKJcqAgBx`7#N=l}TCUoSZ;n2U zSzdx3mpx`*x(6^oz@V2~KYYf@qJt}Y45cbcbJ2myx3zfRUqyqlGN1#`_9uevTUfWe z)J*?8LBJGiV<3~S8qPB~72P7DS}i7hQ+=!=1f0i}9mYUm-7a)89xhH=vnfPqZl^1F zo(I6nevZ7)$rzaM2)3p8ZUZXUHDuIb&O!rrx1+Ks} z^|uGXv-@eqr3i4s9xJ`4o26>8$T_~R_;TIx&}rUvWbdBKG>`fgTF3hSJx8t8^ZE4w z@x-uF%A-A>5MK_el=>p3Ukw8`VY`D0ae^6j1EJRMau+4lWJnb-y0rkKJpDEe&Y7XZ zHXTfv)SzC}6^p^c%sMZCy{-htP@A6O0=ILqB z%JmK#PhisQA^!Z~TM<}(y&BspR{u&LBw(RTofvZ^aZq%xk(JYHS6i)~C=b{JPF2T* zdu)Tk{O8-4N{{P~ac=X{!FyUYnsx)XejBa+NCiB|CanPOXB9t>aQ*?2&AwKj+t^Kn zFe>I*nU_lTGE91^`vCKU9@L=_^FR1V4Q0SsWP+>Vj?I3e+ z2MZg8ki3$L-EdWUtO!KdGJNnIs{xT{x%32Bl+RylZOYwN0yO)Jve5ow^bFeH8#8Er z&YH-*F<_7~8AJ(a;Hlyd4B?cx!!nB0%(2=U#yLUG!^zRXy=D~EkP8any4h;kJ*QTk zQPW9!vziX2|AookipLHe6drh&zVAH6;p$|W}EnUrPE z=(Z6aK!uzcfpf4}jt^jNl@(VlE|X=0}#|@=Ugnj!(VNz}%n;72Y;USq-}zw>P6*nK#ib)NRvcZszvhxO>pu?;Dv2 z6U9=CBl^p7QUe2BWxz7?QKf)OP5)c zws)Gq=Po5idV2B%cZ2RlqEE+_UzAi#L_BaLVdTw8TA`qo;Ak6}a?dbew7aj)Re%I>BT|b|aJs9(r9;V4 z-)2wN0wzRPk`&ZHwdU?K$h(l_a+bkC$C^eJT4zual!NlQJ~tb!){1$Bhjm-5#Y7Hd zA~qQ)UY6Z2*ux17MLIV1NbHGftjf}eq1YTir#e5bFg{XpO zWjL%P;N!I6cE0wD6u&9PFU~KXwxd;(G5v-9$=kts@0yHvUeILcAKb6X#hMyUQR3rz zeu7&uD+Y-7Jm0%#lDd1sU_L}$lBU>@wy@(g!p)I%3WI~(bR1F?Xf+A~Q7lLymJu*% zm1AGyGZL(yyA8m2uDrN!r8Fl6`#O)qg#K5N4HCd<7v25^8*t;`C^(_30#f#5K^8iwUsvYTPNh&8N^Hbgu9qts zyB%=-lal9ZiUxhXn>K0Q34%e2-qq3&-}r^9-k-PXLdPF}M4IK+rZ`PJZ3lnDz;6b` z4rw)ksGbPE=y1kThO-@o(`JzH0_J2aKIY7dUvPjyt2v-N$m1W#G^`2c&No*~eiel- zf=wKQo>s?jHlJN`sp{Hkk=h#!~@ngx9~PWzdZzXZD*`Y+65_XrmLQ z7ZN@k*r$qRrgC!zIUvWBi2)-yrII5LNkfX^8G#n*4%~RA21Dl?Iy|D&06GFH=3E=Q z>5WToU(T@1sQv`IiW;%e*pactObgqdr1GcC7&(nLI?tXPCK;7Wb()5lUq{9*kE2l` zR(%e~>OKH_unCuPU=;-w3>8s|ThjPBNdz&XtQMVM(UNGv>i-NvF}1By^9HkYz#Ou# zSa~@M7!C=3XR4EE$qr8Qe#oPID77Lds?JeH)p8(zdc%Naf<@Y`fY62E*Mp2fnNGh` z&Z6vM3uQ8N@`s~fnWm2rTZu0E-Y0;R)#8=!ZA!X4l z+hie@pw+mwVlDBge_6a~0*&!kuNw|%#rflcgjz<2%%YP>#En42%ycHUfc?xs5aH&` z$ZrV?<>1QFm#u9I`eCTO;lq*YyA630gSVS{9Htsev2YkyS|l?vbtRD_3%Vk)+nWHX zxD_TXgKecw+)>+bgZ)gqUkBX#wsWzip_M4>8_o9I`7&omRaKS>oD+wJ8O;cicb-^q zV)<&oighPit12aW9xH^}r^G)Xpo$mr!n~x{IGc_x<1^xxEByY76=j7WJjo)dPs0KU zy*ST(MY`k!EzA5d%Bt#O-egsJwd}eaj|l>Jb^i=!%7Dk zltaDjt8}iZ^prLz!};RdPv4hzXn6ryU0ViM^9p= zSE*z%-CTpIZJgh7GF^m~7SEUCSMH)N#@`x;jPss_STQK!psJfrH#jF&YM7xjzNZ2p z>O^5WSWE?c9Cp!xX^?XVyDTV}>Bbt+3L)-dMN)?P^L!R)XLB>NQ0$J)!OM0#)INN2 z5AAuIvMaB!kQuc7-24j{M0!sX*E$)w+T{WdN`|)w*E#nhF&W8z-!3M40&i&% z#zqCd)AXvpo4`aW&7@W~3{boEVL?1%XZA4VdkMhm4-qJgYy;qMj51nky@kINB)GVX zsHof5FcWW=MZCoK;QBNTG>wcfBRjRoWk(^KoqB@(W=e9kuIxmV97XvK z%tO7o1M%GY;ogqUQ*R7QTbB{((ON#tXWzxNO1EMJL8E5(Cro-3bx)ge?-;6{FEC=3 z##BeTPou*nUFL5AT2==+OpPcYMMGme9<=^ug3Fu@HOj0#Hk%4;c?Yk`;z$A6o{)&uHG6S02I5qZTta% z@MTb?I0sT%xNAg7^97O_V(HZf_p)aSHU}}%)YNE2+ z`1Rz&H}mqiF&s-!MNNU$nU%zfDQJ8NmNNVr)xLZzH;g^ymH;RFqY5m1ab3AEn0~YU zP=(f%Lu=xocI=S4>)CLzBlW>!0p_)S5}BB(qBaC}D_`g9h&C;}x6=d)A&b2^ZIV(s zt`S(ST~0xTn;BV_QUVhZ?Vi%UksKJa#&W`PSj(q=OftY41SDspjEX{c=n2bX9`R8p3VyvEd`p-_{Lcq_80Ucz|i!9&$~_d)I($%kWEH{!FB0%TBbBnt3+&d zu667cn#9MJFflQ}zD)D^$Uzn$;w2%<>IfLx=4+i_8@m<&I40>{>w`|JPYb6)A%>|=W=C)}&|5zDi(!h53Jl;>ZOy2En*%J8{#qd|}Y6pOxxBAVPOsp8#QR^}f(ltt1A6#VkS3+-{A(mJ^!n zO!OyRC${7K3qPjtKymKCidb=AXWL|SDkG^EC^^!W7tjnQAvHtron2Zkt>uk&jnE?H z(g?#)o@fd!PuYSM)HeV`K%(1|MtWUvawD!e=E5h27gdXiN2~ACPTb0!meWy5SsWrw zU@xav_r|YN$ymoDJ)qWUl(r+Odk=cc;Co9(TK6MRbTC z&Q@Dl`;bpMl3AN7RDzF{`dpmw=J@{~IzTY9fcR zs1G1wbd>Z@e<7z@i&!cA=ozSqqkyvLw13)VAA>B1yI}mxhAIX>OPos!Hzf``+>|Eb zjzrQYEjX#^|9&aIIvEZ|yzJt1|Dm~5ZH8kw#`3A4aBsw(D-+0pa4x+t#kYSR;~U6f z6ru%8GIHDa1Utxf=z;Ojwy?h`wwM?GznGp`>(DrMC}O}lgB=`0+83>FiZq%_{w(g3`Zx zXOclG9Ga>CuR;>?-YO(zk7UHifxoA-ykz90hZmY?-U2E|DA5D^Rzk9`8&j4ho@o%Q z=BH};!H`6wCOjeeC|d-+qe4vL$Wn?%1QQ$wo|b~=5j&{&?N z7t4{YPE&VQN=;j^u(^8%sCM>be5RZhVB=kdb#rzM)bqNCHqyI&6J_gCuh~74&uMUw zQs5!3Ioev#D_O4PUD|K?`7Ih~(b+ME>~_l=IlaG|C9%3&f3GgeY7>se%Un=d=ddED zJ{FBZA%D5Wl#RdewgibML;^zrHjmWS@b;vI+d!T)>`1Z+>6BQYht)SxpC95E=`=RrY8y<-^IBo!bO&jSD;umeP{Gx!A`;<0cu{#c-Z`0Q>6tJ!tqcY1 zhZl%qWr%{40Lpr$b1f68h-f0}HT(o2at8GlTa-N7Yh!hC7^ALH?~wev82?oZ$TZ=1 zhgcyMd-jf?M%xVq^j4mi=R(#h^KZ(3@AoI-lX}ALRVv^?t7S7UK_F6$^8lj_}KKZK&2z60X@l_n2#mrxsl=`LfWmlFg)ih zbgg7GHO5s>0zrfD=V1{-7t57)LxK8$j^M7d;Qlnpms{cDK?ZSk*GkHt~BA7yl zoPTF`MjV-=X(3B|m9~_NiO4Y%Ta5V9`;yu;$!AY>CR92G*$P3hRajYu_yDyk@Jk4i zwOqZ&*3zmwc_#T(DuJ0-FG3ho8hD_~&#xG*$M-x)qLjO_^A*6swKG6qS>A93g2CmB z{)0k^Gc;p14&uJeB#qJx{xngF!EpwRf7p4Jopr5C^%zknD46ZxZcoORcAxE#{X+O0 zt($1ge(wKO8{0Azx-m@mBD@XH^v+y_9X|6nZHc08rW#lYLSUAHf@%)BA8U8C%U5a@ zce5s%g4SA+OLP#ywu9lq9uXk-HK~Zo<`!iHL3LB5U9dDR1Nd3g_rqNf(wM4OgNkv$(HJ(AYc=nW@!UFq%-se^=dm3Ru zOGQg|QPT!(m>;=iX#kzj!&_Z%{(DL}B`;GhU!+z2cR^v1a{rK-*DA6t+6i0v`8Z)Z~1XdyaMO?0biF;=#}+~R<|lX0a1ypR?-mYk0}5$8l( z7VNP#R8o`uX4WsvtEX=Ig%;-3h%{0DxSjvsX6p?U>fPxt2!Uq$< z#!>_lGS4@?_;&k3K-%$N>bdAYqyt)sd_kpTTx|WkOrDP}%3PrnoU<_A8=gc71(HZ>sg zzx{QJD=8zB&WmO%|HIeRox~6%c$EL2<(%GlzVRLKH{(CG-)^sB1FD9HCj#-AG;}aO zkb7&q>g5OizWw3)??6eUA|O`R#9A zW*_{J0T4hp1<5)yJnjeBrxxVn zq%cOtK&N+%OF1#no)l*i;0{9e)o>DrauOlL9M(X>ahzyo_zwt*(d2oIw19FDjhA8& z5CPvIT~x#Ep)7R~qJuZA5m2h|CA~JPKRtCxRmHLfSer8HmitU5NxHa5!JOORExe1c zZ&~&o-fedhoz8BGmfb>5*pwU@st|FJvAO&B{CMr~aBZemf`M}jE3zdioR5^0@Qxzy z$cqO4t{g&i3cEN3YHRp31ySGcONsZ*K3nXKDh{Birv2b`p(M*5Vdd1}I0dwiV!=K6 z{Jr%2Ywhs4!r}GI}~AOxKw;-hC_xfBZn2#Ph5>ZHmIb$f@Y*T-1@xc7}V&gm1?fOV`28ObOC|ak!g48u0TGwqL2EFh%jQM_6Vp&M zi>JiGk9z^C-?xzaF0_HQv7kGj2(j{xL%HBnckYsd>8u>x(ZA9VBhT@kjk!Q@az0i%h6vm z!i~(Wl3FyjKcfEbau2L}G8N;Zm=v0CV)3nfEbP?rJi0nKhj~%)$!N*?qYFMSQ@tjN zcGRDM2+x(fg6HQC7+oDvNbh9x@|>a{?eaE)=2<4Snz=!F(1oYW#efR^5uItkgA!pwMl* zbm^XvmoFVqhj-G~0{wyVEd7J3yUKa;Wop9Max~(w|S(L=(bZqMIP?pQL;6*+!NjY}-$*%TV3#;+?ATk9?hli1y z)0zR_Sl5PW0*9^;K|f5PtRp7z}Y>?|^|ezYe^CJpu`b5M+1G zgct8a0$l833YKB?m6!7)&VL5wa$YQr@9>sLKoEQgbvl(w0;zk=`0beYIJu*9hfBtN zwxpyT!s7Fm`==z?0=BhNbGO_ZFoQx3jP6UTi|jPxFK4SZh8fK9tN3()g9{1L#`2eF zZ&C;n?&h31MFEP(Ce6dTlQlZ>Bz(L}k96dbTC>oHhewCMr@uaU6nWu%=lsp;ff(;w zorZ9VP-5T(m2`~J7+@z*L0d&U0rKwkbaXhE8)={;CoYGEYb$Vy7dM40(*@Vy{LFAg znVwWClCjU-H`WpB9U?g$uMn#nR747n*ZW57LsDDm!-|pW8?2Gl#C6;=GD^@W8I8{M z!O7{->B+@fMsBn=qVMP@I%Il8>08cy6eW|PF^TF>WMq_NRA-d-h||vMG|bV-)cZL? zL{bS`l!vEK?IRC)F^A%}PsMHUDt(g*)#)?V^zx*s_I&zrZ>XO&Ut4nHUGDWD1#q-tUW(T0rgW(Q)ftW z1t7M!3>jQE#ecdfG*c%p*9D~|!0&j1Jxjv@cs2djeDhu5v~PH)K-GF@U?ywl!fj2y zP~e|JG?yI(jC)jdZqeEsdk5GzjwnX#a(nV82_qau{L&6S#rLoHvHqj1XMV?NK=yn- zOw7bI{rkV8Sk;kL?}-M zt5@st{spZ4dj|PShG>n^I(>IjxO{x7rg}JVwzuJwWV1EY$evs~2GCD#HT&e_b+PB@ zQuq*XUtPJ;VE1{o0d#npg8RLL(_q}!*n(#DN?OUKIdObut@#~Hy@NJJ1Om}yr9r_$ z3blb9)?wT&n{J}%6+y5#?@-z=%7IXqeAJHpZR)O02R{MW5)pv7X>5Z%XeaG60r&RQJ%+S{! zX!-kIVU}L4Xx|U~%Fz#ds!-gMG0W|&RCe)^b~DxWrhaffIa#Tg5ltOI9$2YJQT6-o zg$mSZ;Ttmb^2^2Qty^ z95`eoiu15e(%*hV5%#pWREtx9=ik5kQ*A_iNDzK>fR7IAf^cmDBbA}9zyeQ=UYZk9TuT)_C=#qr_cZsAv;x#14HfYkQ7+xrhd zclokh5d_Gh=zn*Ww7%_+$Nk<+#uk`4P1=ClBP>gDy87a2)l-bXd?R_8{k`=5 zq5LM+ZpYrphl&h~+gHXVI3HFN)|R-dk(;ow*{pZ$MoVI>{Wo~TYWYQK^ul|%cO3b{ zXx_(?MWb#Am)yWVxuflvq35`qR*|BvS>cX;5M5JL6_wMT<6k7?6ek)GLL5N^Po;ih z9JoFw85iBBsCu>@*7$1UR+3#VNg5w7yERJ&$+Cj%a{R$JzSuCC0Nu&8G?tXh<+p$m z^2-T1zvmyFw#)V_on5j|bJz8CU=k>vCI|xO=lPLaksS2)S3R`zYQ0eFtx<(V4l@od zQiaD9?bJc9@}rMBO9~SPB=qcgWz?0>K$QUr$6(w9wlQ!*+5Ihv!-mt|wWqke@-(M# zDLZjG?X=fE`$3+`F!%Ww^i;g}g{d4&|G9VxM% zeL_{fG}erRVu^jDKXz#^>XGY(9@UTb7mpSFlbqv4dR+5vbb;^&s*h)e_!NVgb2jvh zThL2`OeY5a8Pv>^Qa;p0QX?e~0&#m$B*lTl>Lk>Afp zgWMeJIy=iKtY=po5nHX-*gtISnUthV=KOpWL+4cpzYh(M1Ys*4-|CO|(TpJ6zKPx3 z#NNE+`Of9-*hFJ+?DjyGe|W!vH5dzda}QDp4nmsav{enmnrjUa-^PB(v}?-~0aA;>=9Fph;V-7pP*DCJN%nZy2%x4%p6rvPKq9_+7Nv@4d zQLcJ`f~nr-Dt9ekTFFI>>B(0z>=S#4Z0E3`ByB{1hs1$GjkY-NpJRdEC5~tfYd=Qf zF#DVw!fiM6;M=U3yrXuF$!@-AdnICmd!5(5*vNeCo{v8*{u zNQr7N!_Qe+A2@n2Qec~`0SB=P*P}QyS{e6TM_Ot6CGk50Qq6>%Wut z4G;Ya5rpMOS!sDGxtT0Fa%S2T9Ub!oSXRi@Z(eDo^MTCi>519Y>8vbl7Ux%Ptmori zck}OpeW7Kep(h%yWgUn0^=U^gknsv0mZFpOnkardy*(lhPbKq)f@D<+KO;!nR$%Gl zrbw_gsunrc{00~Xf2Y4O6YF$$xNUB}SbPDQ*GG1GS*-_U2zN8CQHg8Of>!o|h>5DtvGy0Jz)e0j1RDYu-@I-Fzx%b10 z>AVojPKqYRg6zvy&Kw=g7;s%L3uJHa6~T;y-H~K{hTWD9@~FVEe&SWba`~rtkL*dO zRLbGx3;bumS?!yE;b&+(xeS%lK=3yLSr0~hdttd8P&~ZK`mcNAK`86N&$Iw$3N%s#jveEj2-|CE0#^4OpIpK~5ti6VF290*oQy1!pv{L~?8 zv+pX#3AyYb>%sb!qW%3Tywc%!pDy~#-wB(&C9z|Oxu>+m-AbZpt()lfRWJP6*u;#G z_g+~jX)P_xrrrY1w71k&Vb4AlU?C)!nXdfbkhr(xGODBpt?kk~ds(-UhNr>$w|Q1wnM zV30L25gPv}OcwIu7f{K@Km2~Y?gbR|HxjAIKxhFqL51O{*;QX4AlH}%J*8aT+TP;l zIDICw%3NJ0Dbv}^Y9`~NH{~yFz-;pVZih#EwTFf#z~ZbyMX=uc_a074(vS5oC8^O! z3NhfZQ?;o$m88?9PiHqbhX%Wgd7q@e4^?0?G#cZ4Y%a(0IjF{1TM*>K>-6yh-2u4U zvc7IH2h#vO`z+eM?NN;sY@3K?xa8f@7yuhaHyUrOO;fU*qvk&XwwmP2E>HQ>9piH6 z7A|Qk_>Ofv{PK#qXtaEBktXlkxj5|oQF;diJ8PGZ_5;O>^msDyOhWu$-SdOu@(&o| zN|NM~7)|3ejX|kfZyDkR`3KlqIFf<%zh!_biN^qxXB>zlhTC{QGuFOP3LP;VGs}}P zP%{xU!FgU|_U>j`^`lQY<7fHLduXifSOyejz_O$53|JJ!GO%-`sG$ytSGJ34o#)+4 z>2~-d%;)(^xhyhUQpum20eM)gKZ0i^d6M?arJD=cn>WQ=YASj~MR5pAQT}&a5*!DK z&0>*y8+8naITZqhC(tGu2U<)KlVAEFLcjQpGaED~@Fjf9S2KjEZ_}yM2RSgwArb?) zfY2wGF<{jo1D06V+>8RQ5$OI@GzzL7BHl11eg@X7A_x)bN0)>U3^?$?Z-4}bNhL8t zLPj;}WYM=8491HWmvF}yix}gni;Jll2yt+8d|ZKua|*KK*DqXHjyO>7!Gd(c*=t5ss+2D;rqjsjIcs%BuhH#06(|-d3O_^^DN|P7AxC64) zn!e4i7vH1^n;-KBpj>T48M0=h@a&ej6#qjoRPP4IjOzWE!z6lc@SiifMK9+1Y|PK4 zbJ1QSL{Asgyg|@&a?ZhS-#68m@GIq+3W;%=v9Zj!NNdmRYjbnMkhI$ts~|)^-><8v z=pQ$ppI?B)wKlrZALvg(^9d{}6zk?)iYMnNj#o@iVTQSx=gsjh)=ep5`9kvTw%fZe z9TIXdqwdkvtRy|1PLk@6F{9Kd&ZK$-YEv=!S|G28rFa?hv)%? zptbB?cqAmZcFv=PxLXfAI_$*VtDp^aN@@8^mpHN0>Fm65WD(q}@~J|0DI{eIOcNPW zk278{-`~J}RWcFY^2mD(9rm>&dKH(tRGUfQU17U99!LSHXs-rL1O$|@#kXPA{3FA2bpC&`6{eL z`%4ep;N(K_gDi1@5UE>oZ~6CtYXCofoqc`dZeRe9y>0oYaZE(7j^nL)iA7CIH1Qt? z8=|ZX@VRiwB$ddi_@dTz6)Z9?Ka)&PTs&WEsn-9uq?!gSH;5>QxE}kX82=A?5|t-w zP!%i5#D(Zk9!RjLFMQ2Yc{U?a;_8_c=JA<`R$g^(nb^nh-D{`O@)+gcBdut3ul>#1 z;`TR7Ztt=Tb=vo971cdm5L@k9r|0K^Fa&_~GqQz#hOXs}Ma#EBQkzw&3-$&wyxuq* z%|@#5WVKRTjWnGV&;tpQQ%-;}uoHMs^7Cs!5dTnr+RyItGVKN{tu8WR@XL(1QugGY zk9N48&-fP;Qnh+?BukyHln@_7Y;kfzI%^gplf}XZY5xQ)C>2y&@Y8*D-@o_ZL`Yt5 zfc)YYd1bG9<+`iMGu3eAP|%sBwJ}}kut@TGK6IUbe?n!uUzZ}}o}zuq&wS0&S+v7j zKqP7UX;+`WfVJ;qCoFx4mGyWzsVW2(!yJxZ61oO`DcDh^HFuir+JG?iR&iAz`DQ$} z9d3RNjS8JT^$e93-+4>0CMsvH<0hW+y@h`P^Pt05Yrz0if1O@)v;+QVcdPpeK3s%#~^H`rWQ`JXkm6dP%9`VyswXW)SY)gi~N;NdP znMtg**SYGeF9twfMKjQ#3|1JkZ*4$#{-2F=`U+*CgKq{x&^+g>9G>eL=R3SK~8RiD_>uCu+`D&Zbw|zclGW6sb(@2fnND$1q*|sOgd0; zy2J;;_$yi^*_}Sgp;^J)s=^ua0x=VZD5IE2LJ*Z_sNwi z)j)qkJAWTp05w3$zo(S9hmLNFy01RKz(J0}`>tE_Xk)%PTXNuX zz@xp|9-v;liAOHn^V32&w%fa@^$+MT#%47sCoEkQz`=4VVSOdVZ z{)X`(CQXS;wZpa3i==Lf!lm%)7&G>zA2|*CQ_I}Vn_hNe=fMJV&}*xkV9{0Iy^h)( z7o~x#wzkvl?l9YU%{Q~97-&^%?kRke63n8j{vFA zfnasOj=9gASjUfgTP-?(iqz{c8Aa#l4txkbUZaXdelK##mlZ{p(}9h*0`BpY^T1#@ zSdLJ?ObN%tQs3AMJ|tV6x+6cm=FOqMSmB(2k0Zsh)y|A717M=eq2!1{w>Kk6wcGpF zawyVi5T`Le67UB5f{Sfh+_}d4P8!eo+8UUL_-^?#7`u#z36xg*n6Xa$9b_n`BJ;9d zjI86?y6eBB6J6sIny)YRGog6F7%Ph%ekAk&T;$|)YvTU?!LF=KJI!tc?=!5A|D7xL z(sz*ujRGZ==Ga&eSR~)@C63Q|IFzgcCn(Fs?LO{ZC8|$l%4N_&waPL2xW}o>QC0HT z?Ewr=7+n&KTX>7CfR8Q^4o^u~LbIQQkvqm7m#1CPC84ZRIY5V{ImciiV{?F$Mm#Z> zj89?vn2c#rF}p4FuCe%a^phF=PwA6Pe-*RMdzO2+!{N<8__LFYHWERnSYcUZZ|_VZJoiV-i)kfJxx|sVLl_JXQ7Dv9e~conSUjJy+mNfrMcXQQ@j({ zmGIeHd;B#WUzJhu$2$$Nd z(G4Rle@yw<$DVj12+l0ePENkwR($$nymEwoAa*nyDxW4uJ2FelJ3aK1JGU>e zpO3Ed{OZ4lgfGDz=-vXH@8591NgC-^U;5P#w$#c{{8yiuyts999X8u-w+NfmRc69#MdfiBLb2A!%p>16tlR}9|`^THVI?Mo8y&-qj zc)azaG<5?F&S`hDggs@q@47X^!FG@edV{WvKJepFK^-|Sjfd-`-IS;dA;eI6iN`=w z8zvScjt|}l?SPf5Y5xHICCR^U(YEQQP@(Z#XlDctgRZn`a?mDajm@LrJq@Qi7=|hO z8BR&g?ojfPL&m;JGfp>2wInBZ>%Pms{CqwscxktBEc)7GM)BrG4tOUfL!rqDZ=m`O zc%#(b{+M{TF`5CKk-8Y;dk>*IN$pX*d93Z3{Bq3q{$(X*aVa6Fqrsm*23?7 z0hUptZaYD?pvD_+?XX#1fc%#tEO=JRQl*RS*^;)E_7BY(&irY7HN6`xXba!OY3ckE zxARc-Z}U}Nu$p!|)xL==j|BLW2L_3H@_Npv!`$@;+A zoK*x@;@d#t9!q;l-3dNBfQWmR`RmyPO%Ot?4?;-D#IWN_?7aSSGg#pLv=DhTJi-^I z$;lliIlX|kHW_2E!D0NA10n;l6{10@el}EEI}To3 za~I*K#X!{-gPDP+kA*I1*(h&hX;&3)8~7%ZFHCD10_5?rgVQdc^*q2M#oT|K1BA*^ zro4~zV+}78C_7r?mm+PPi`8;gDz*wl8LxEr8(vD=TJptxdT`}Zk6NxfCM;=P%XJ$? zrL_QRi0PuQ+?*I^cO!@Pixf!pQw^&w-Hx>##=A%3;sZV0H}&|NI)+(hh*mFmA6|Tdier z-gJA~R}*I#sTTX+zNI_eIf?1%jwQaZqkkQh6|uAR>F=i-6)Kl6c~9NRIA~X3>y>`X zL3gA&SC={tdaK|bK=+lQJg@DDuw0I&mFD5oamXEf*m)9W(I+n$Pp^OrkhR33#ft?k z1U;?d^n4P6@&Q>7R>||doUdgze}5hmSOq`)8seK*+5MLux+oZVs~wNF@>te$7!aqv zazUneHjA&O8FPJN=%S-@kMb#%s{C*z&jN}3a1M;+{sg6BsCUAX9|-gQ(me*&p@JsN zBg3c9n`!|=P%+EqfZzTAB9}QVmkOMfVHYCLZshWS^#QYhV&UF_@dPRxU|E=JXP2E^ z`tO$(K9_B7nbjNe-bewN5SYszlRzEOT9;$<6IBhrm}8tEGhemI(k}w9KcEMyeLmk| z^4#*d@uEw$2c5;a(Xq7{7OF`M~J(-4(sNQ~FwOdP$NpCI6HE+Fz>M zX6Cl!_Xl=WPE9se?Bz%dbfsZ=8Dx+bzeoBXug|?dG1-s;7Z%6n=EfE;fVKQv7kzQO zFA=CfF3{Dhl?Tw;hVj>xC6!7!%59O!Ly&yIiO8GlTZ1AS$ESdGK_HOMs?jyTWV|~B zrfrJ}Z~S~eO63)IzFu-?j4VTRBVU^;mU0_`V^`)~6ua?%0=okh`rF{eBJkucvU7JYJvPXb*R=rIp^_oJQD8_77K{ zM?0yDe}$Ffv5?F0x$aZJHZ1j0WKzchM^{s<&ykNTNOg>Huq59Zij?b?U~>2cnLL) zn7in0HhVAh>6zMuw&V~Lg;n?PyUb;`dzpEM_f%F<1jmv#QClbb*+Tg+giA0{NK2Q5 zPhI~&J}njGc|V7_GKq1IAe>gduf=kA#_-!{^cs|D5^K%<)3VYC&7^-PIE}-;v$Qqe zq1jZ_Oz{`N_`?MYL1f+#7F~$gm=?fCKA<XEPyqpaL)a$UxR6D>5(WcxyPdWvfC&8Mw|G+= z=WJi;GYD5UrNaBlPlnEF3H$BzLzKOqn&|5&*6@53a8%C&(YaVCy5){8D*As7 z&-;|DvTG6*pt5Dwxw;h%?Zfn%fw|bfM(yE1uQ?PN5WNX8^SI_vp!cXC+Ii-=WZe^+ z+1##~KOJ^jA(Xnd`}eN(4Z&%L zGOSxp!)EAE`z^gAba`#L~9?AblMe-h%L(ulC`(zJ)FmNmc0+X& z|B5OtYD~b;9bB3nq-41x*7RP{9Wr;tw#V&!T=tOQE4t`ye1jgwGj+FL3qDP(%%yA* z(Ydja7!Oy+wB34$Hu*Nx+-M=BdbP*VKqPFh6(+&Rr>9wu|B81|bCaYwOa)xA$FDLI zGIjfw9lf5K*zJ?`UaA_9UtW=SKYFSDS2FreVxdoejwHG5rt5LlX5X}l;;vp2(=?6i zvC5kt`o1H$>+{#Tv$I3xrSDj+yH@HB_3gfY{UtX$@^B@chQx1nom+@v>8S6Y40j7 zdS81hc@;zLpSq3!4PvlgAa5+SGr&3pBo!A;(hh{iph_CJXb{zf7Lreqj6C61F|uHu zokOu>NBSue<9z4}_Bf81Ek&L}S{6iJNt*&&jH=HV^37sdtBB4x&nG|8HhQ+xzOMy>Wz0C~27CgKhaV+tt3c2~2a z3{`spmO2o`r3%7~UJYlCr4F_S8!;0-i9rOc*ufy*oOkoVz+?2&es2)!3AcVSUg7Z(>^K_6e(Zngeb%>>U%RR`c8e+(J@_An7; z%R;^5@U#;846W5DbLhl)(jJJSB`_} zQPfdYM77A}0?GQ9LCFTuTYZiHTSEh{#P=9?oZ_+e*tfS5olc_7;_ZZF`z{VaWHaTr zZcmWh+z|JuUMeZd4u1Ev%@vQ2Y7L{NnQqA44$?Qxuj&rg5|fS;Zzn_>A!X8`{PzEc zhTx+M%Pf^DsSAV972 znqY$#OL)_*Kgg@Wr!eb4UnUl75(EB#n$DYXbS{_CvZ6RakD?eSpz}gJeaq!Qw&b(r7XuDDOcW5=Gww9Wnau3}5pjUTn?^eqB{Fwdt~i}@ zV#j~a-R?}J2M5?pux3cF1X6&x6o@^5 zx>c}RZDe}EDX5<^s*ML9kKrZVxdz(gRQq??p&u!l1R=)k`J?#197dZ>c%{`>rP3tr zr%3#Cx^r9v6Jw5d-fg4$nN+1Ci%@L0YCv8@ui{7Ed(GY0AN{azHl3c0Mn~5b~a; zYSl96mSj=nWZNLOdTC`N#ifS5O1D=hX(tO}AGiCYvN|Dvw}ZSJ-i1B(1N9zb_sA7h(WplLe08A8Z4a$(gp{ zl_eYs_8(&+7Y|s1iHr052<1_fk;6b~>3j}|fI6f85^<#P5X0T5+$ZJTMr?l&1c2gA zFdFlgmN~Y?IolOy2k1sa@fu-fwE$hIv@1^Tqff^;!Kly(N5?G=^(CNiJi~#E1<0%-chIMzdHE0_Pis#U|IazYC6TB9TgE zbFG~)l@nLITGpY%Qnfcb}y=`#%V0X99~lMEU1g;b=uO+2XnH{>sGTkpoV~v zV~*ii0f_Y+Z#j3U(ozty^DqYX$kfRX;U~Z0^nI1St1!m5EC;-LFsIh-4x2w zd4y0(`EX2fu<>Ib?eDd2qNvtr`0ed>6~1}9ZTB}CW?Ps)>CH?p5zJZhhYXThqu{sW)xxKKFO6$(Sz74+T(4MPHGT%U+G-lu!x+5F^(LHA}lv#O=) zG<{TOk7%~ux&Be1a(UL9aY@9L@oWMjffz(VX?ytjTiS*|!!uyTX@aG56GVprdgdKb zD~vkYyj8A`$2*>!4Km@e9~kwZ#S!lKADr$gsmYD5O{N&4ec&I>VCEVsT$%`Zm=5g0RIHH6MdUl|m73%rp z@uzibC&NazT8gLBBsiNDO%ZuNmi@e&di_WgAL$_Azj!Mk=)XrhPpdZksVs#uwO(dq=*X{+q?xmf zLt5%vD&|E1fRBs}L2JOC2VipiQ96x~*OOv8QakOvc3VaE5XKOS$0rmeWRHQwAcNUt zNK(q?Pc!Ysa%A$X8L=@KlFIt_Hd;c>dIo1Ic#7oX->Maa$TM>myG4 zSM`td)o6HnhW}dK6kQ#~8;z={W;Ho(O#IOiLdD`I#-7*l_#c0^5sycsoni3ZSDTBt zP}{WGI@{j)K?k3?>4lTWqY}dq0tW{lOz|cf5Od5t5O?B!;}+u}sEm#kCc*Zw3BX_P zd3Yiu6L(Wdp>=as7<84P4i?V9GksCAxn<4nQte1x{(-^lw(ovCx0ToA32=A|s%+G* zXe(%wTJy&G?w!R|E}L}g?_8F0`PBV7XZ#JULUdP_P{?xiK%6=LQ0S3ZfQ^^+ZhTSf zt7tLp$x0F%7Ek-!yqC_FZ&|F^e8gAT zG-Q`BCd-YelJsqQVrc*rO@*l0C6DS0U{(s`|s1Tyl*^6AS_azhOx!Ke2f`rpN zYB3a2!Xh)OOfK|#FTx)K)^_GnFsw`VUg3Vu;9+<?TA$Nook1pv%+N`SNZZR6|&n~te%q9T%vk8f_oc_hU*{sBZUqX`DY zhsSvD6D7iEtDl-nGpQD1^Bs^dpD7efVK%_2Bz&}+Ua1eBw%B@TjHAQj{22K@m5=_z zM+emzQi3%O1|pY(^w*8LjdT2%xe(1pq@l!4*kB?S4zN+lE=8jOZ!D3^&n5EtXU8F7 z_eP>2dM@$D^YJWqOo7pra&;wDs;tgfkGq7dIN%I$s-(y}=%46|xYfctE>$iW+&9$J zc|5YltE(&`EuPGc?L=?M4-m*v#im}(5qIBe`h&l%nYC2tyZ65*)Nq8oG(^}4JEoD| zP`LSrf>DgkaK}EzWvQURr zolvXN!PYiY3q4Ea)&Hdb;5AR(48I9a9p|&G!OQgAz~^2{%icN^EFH<33*9qr>9lz3 zB%o0NzWMeZ2j-pCiG>%-KF+uJEHO8`;IFm0HMc+}W_c>(L#SN!?tOx?lj&ea^^`&$NeXyEGtFy6 zB|D+bv|@`c$Y4&)M{N#Wtj#X}9EZHjveimSjYMv82M*>Rl7E5}MzBO-ruLo;17&s> z5uEe;r<~e&1Kb!n={wFRlcZBql(>6&DSInQSE?tI&4Ql9496fAPj!FAxyDBRhN|Kw zjL#6rF^F*l>Kg`#w(7&6i9OWM_)C7sYKA0HuJdyWU5CGU&rj3J z0Te+9Zf34{1X&hJY7}Y{% zI2hpYt~BCabh%M!SG)D_Q97G7#~7+D)vCKBWwTM*!{2JV(kOvzBpct-VkxKaueykx-Qgh+^Yn}3ONr=OA<(gfZ@lNHi+Os zk0H8h%>`H(u8Se~-3J^yKkLK3_jQSiVVi+wE^GlS1OQ`C1>+Pt$-M$25ilt(#Ws6Q z{&-um#^wCkOhES@ct{8|3Gp$))UC9)o+jo!gQpW9s6+$y!jtV^0PSX>#yRZxfu8~A z@7#71w?X!%BqgcjSGRfpoIudoE_KqjI2s(kiY)uaLS%H4VB<{7&MSP9A5`wWQIz8G z9cz3uw-46&Xu5Nm>-TDURnC$ki&c_xUI0KY(w_!4_-dY+o#fZ1_>gb%XIY!DM$@%S zL@#Hk_rhTW{i#?wvrjmd@-rC46k9Qv;${T}$R8mP5y^vKG{-XEh)lHl;4JZwY4Is3 zr@fx6+gs+k!zAJA-chH2)Bm~iV8HO~x=@k-`q}$u_ra6naJysb$by_~*6Y=lfjz&6 z5n5!=e+7?w`>V@ISul0lo#U`tX9SgT^o3F!7BE)qw{FFfd_0J4hz+d(v+8UgxKP(r zrP+m1EGACM5UzmDK@J6(la(?D(g0&Dn+{Rk6qu@oLT1^nZaG5D;AlBJlMj^7nj@-w z)Jbs6>oPWX-7DL}Q1}NT#*O)k!Ka~Xta88$ANKeG^7Z->Bqh3DTu5@c{N`;mavK#9 z=>bkfaj38KL0fR$*K2394lslDE`@^ticC#n<-}n1j zlJ~&ua|PnhV7O+8>QN; zI5)o8Ro@l!inYVtb5FV+l)Y{vR5@7+rXvA0IMELsthU3-MMSMfzqHbfcgjWNV$__B z(N{?U1txW4(=?}oI!XH{FvH*pZGpcaN)*WD0JMJZ$66D{JTAUH#76P<1vIc7^<0Ps z4)lwan)%h2A?%@B>KC^4H2l|1|59XAXKXlUYXb13E||Gzkj2_8C(9wtj06VC$1_VQ z^3*$6$mq(L=u+$tcJ+VmTtCBLhS4A_6;uEhe;Ob-zjM0w_<{ET4j>vNyubgsBqp3w zyk7$C#tutwYWLGd=)&v-@9A_TDK!tDaMCwOouw|1ma_9_IRsYW87i7$y+-}9% zvFu@6$9}x`h*Ye*=96$*74$R1#tWIVrAp+jOftXt0)ya((ny~q^^HpBlv0b(dau!= zj|6H8%q#A=gUE)Yad-_FC*2X_4!74bwMf7-(&yDjXo6jp&ga)^2JwE8NUh@Gq0!X8 z=co?fTLFp5ya(xM@GrBi2vEYZau&X8@i7=IhD8Cr5r@(wX-Q1jTsEMG$^ga(k(fD) z+ea^|p2n{s(Lh10+4rkoojed6e(wNOJwxgu`z`&HYMjx7lpe(HygF6{DhLcQuc%5T>sj#B1}YR9?|PaLdaBk7ag-Ys z=LdMmN>fef>7sEQC0cVvjy~qjF#YKl>l=ryX=AtxoJM1=Zc0E>oM>^_wmFU^^ zQ&pngPPn}!6}NvCpczJ2@bw*SO46pV_f}jQlyMso1L2`xqoeZ+F|zxwlESvDpbPT1 zq$itzddKvC@f2e`ON3OnX~wAotYM5H(tvET9QUirGw=P(vWjwgswn*+RJd6kl6pT_ zaij&Y9?Wj_o^h;1+QWbBI0x7sKQPvT|Gn1-!OnG#q=?NntWfqILJBqf16LL4hkta= z71fi#2@}FSG@v}z4pL)`20VEqFyRvt+Ow)BWQTQ^MDLPxo;!DkCV&?ufY-ReNYuPp0TOj)or;J5`l}Mv%ukR(1Rf@kxqoB zulHuBCgPyVp|k!*s+f4MfP`zJA0^~vHLR!K^{)UHrjm2Hzdiv^07D0K?GV z7f1#A)2A5BOwmXC5B$G&xh;ekpoM5O$n{z=aN*Dc78RsGNI1;>074r6BOk!f2l*(w zscEXl2bo?NV{L#|*jN)b30Rq5_4x6acWgs+i7|1L4byRabCWiOY;uZA+5RP~cQEWm zJg`cCWAe;Cp5NAuvfA7Y_OlhD8U0@zBa5HeBJvLJ7<$=FHC8?Y!MY^5J=RxF{;=U} z%TnRjR`gy|v=~1NrkVHmXUSGlO6d>P8V(?h)9kYNhK9R2O_femW$8Y@(*;g6A0?me z8+ATPW|q32@G5o^iGB4LFdf|sLgSAP3J1(8Kn?6`Oh(yn&*}9urwpe5h2Cp8OCER3 zC4LMQ+!2L0F(lBD;Q(Jg1ONgM{kdTb7oRrs9cV$^R6D%fVjb8FD86x&8P$WMvmDfH z-v3@VZ1opFHkzR1AqmvT{&2sJqZGb8>u9|I}iuo)7uVC+Nc_#@ub*H4{O zlv<+cn!V~c_Uw(3vH3xz(XfSQe7m8Oe0R2H>rP_Q(K1|Fl?^=`Hd3=bL0(95=%+pO z&}v=i~~Tn-GGO4r?7B3q$vzxogphA6BsvU9!I*Imz()|o42CL5fa4O3ZR8I^u^koIjeH*wUfv1E z!J0f{=pWtz(aGP?P@=FfM5ZwaI5{2BIN+LKGs2L#4t@d!(O7Ulhnqz`5<~~%PvE`R znW1nGsEqM*A!yU_hYRIx)|$8Pg9CwhGfKD4%fy#!t)?oXn#U3fJey9yTe%Svvr$(% z-OMuQVI`e*McJ#O=jn7e{gf&@)-A*MmOTn%Dlh5a?-*MeHS%?KfT`+cK_^yHui`>0 zn3k`Ep~cph1wAD3Sb3gY&T6yabkq}Or(~Bta6gN%hX0;;*OQz}%K7Ll>xA7Zlgy)! z`PyIMBqj+V79)z1dK0;!7j|HeVtB=|-Tnp!4n%%uEro*`#P(}o|1&kRO7xQy&=K9_ zQ`s6mRFnH9M~l=YFBPe@S(oQf3Pf&{zx>LGA_ zGK9<*Y8f~9qeJ0uPjz@Z9El1WE1?jC3mYR(Rsb%ahfJ$`y%<+bR?*k!qx@t41r4c23W29Qrt^Q1s@91KC7Q_1aCVO_y{^aUB==@|ql%XPm*d?_gtv4tht5(K(ryEX*$7z9+EK8Hw)pnEB(!458LSktQb2iffDBMf65;fStjW# zdYk95(803=;ccCCBV?N~dK=z=XTm>8eeJW^#3r+KstP_=>DTw6WD>zA^{c#fCuE7H z8KJ$u22o3B$C|&YKiTPY)X3YInq(=xz9uE*T=ALq@~||P5NW5H_V;X>c<;pjjF83@ z2zcKkY*Y3TaO1%sD??1^{nc>*3C_;7j)EZ)=!ADVzecm**eVOW1Xy+V5O8;7L>}S182-*xh~X7#azlL zF>;j0#Q@xyvR%l*Y7UOG#ZifPq%4t}4g4Ls9v;2}l0-Kcz(qAbz7rB>o!u`Hn$VV5 zwlr{YgH2Sf-)`^Dm;H>SA5SQX&q41slgXPl2-!@lvARJcd0L#khO*z1Z%$-5eIYA9}LgoPL z$!y9JMckE++gmfyb%$ECRzl+o#)UdZrD*N{tgm*gC9O5FqV!Cf95X{p3`#S{M}w~dgc@?4V2)n1C~ zp^ZXZU$u|O$>X=eKnad-uGj7CTq_MB`4g}K!V`b? zo9ia}5Rk>Bgp|n;U__J_d0GeIHlFDv@HXsyI8B6pCASejE>)`!!Ahm?uDYuHu)g@Z zb0(7{XLA_mva)0{%`Nz4>A~m=vN=h%9vYd1D8HH{CNIuK@pK5)VeASZmgF9HoQJTO zWf`g0&HTUqr2#`KlM$lnWE8UOt*U-p8A3}Z6u4=o)2RVN=pSE6@;AhH-OsySz20oj zSZVXcMZ0}?IG}W3-F(!rbajQBIx^Q`M;SOgY`6IzB&P1;>eIqN zT>(4{2N;t$UZ%ZAm*N9|kkhz-YedHu)>&-{RHuIc?!&V1k9DqxzMfDF(R4myp$lU4 ztUh)vcG!BvlFl2Vinm+IW@~cynbaNpBA4WJp+Mi(Q#H@c2Fw8_TRAuNDKN8Ozj&Ml z$Ocii1Pmp?i-Zuz|I~lLV0##S`6^f2b`r}7EjIvSU>B{FUjQ^X=T4EWNDWD2?VQz` z#@Es;EfR76KOmXNYZ;N1v{b3c(lv2iC^NZ8N%r>w_sB-`QY1uEwDD6A&JovR4BCLs zl3sna_a^G|J9v$@AMHQ6qAo;h?s6mPmJ&75LyE=a(SS9cVh-WP-u!? zrM5+r5DJTF#a#Px)ena-lexQc67q6!Yb294SG7ZlW$qrF5Xs2Hrmgi)N^-dk zt8UVPJ<-Wp#2uSWxi&Q>`?cMZQ}S#K`LJiD;DsW}il*JhurEX#>eu1Z?c-21YO^X8 z?}XFKQqTBdc%2j8ZrfyntfDyB>=wN2iS!^>d-!8;XA7I|Qk{{kW>X`bF2$w|Iy*vu z3C4w=e2yDD_IFsH9==x9#gw0gK7S7gkgAGknCqvKSWLy}L`#uBUvFh(EOi zq2$hAt?#6E)|+`y19MW(>_P?s?4(Cx`MS%fu(+05P^{h6fh7A<<`UU>R$1Fm?XNcu z@95_53;!m0fQ|Lu^ieR~uW?2;y9CZ*TRD%%;zV={ng=PJ|1jGiR2I23vr2n7OSZ(rXeRh7h~@1U&H_i3_B z-C3zBKUw_Oh>rWFVIkludqkbp0EJ#hr%w9m-mVpPpg|NJS#$N2^IzvQj5r`wt%#B? z8Wlq3ZpV==nJQIk3M5_$)^)Xvm6ej=cHJY}k~z0t?23a%(NM};=x)94@eWC-P7PAL zBK6fQtTLv!rHH9kr>#cgOrIR4$7wz@Xk?7Wh-D<(Yc+_&GlTAAAyYKp+&@@w#y;u- z27D3QUu~k7y6bLXEUC*EUcyY&2OZmHpc*yZ?eahKdA)mEYp(hJ$#5_Jrup{Iy&JUJ z2k_d4#==hi|4H4)HHw7*q@pNNM}UV@+#LnZBs$D{C;aLD@!91>5Y_NXS7B-7Uss--wA56Vi7Wp-wRVud7LhKl z%CuQ$S$MN*DDS`dB7F?v&Ku3+ZqAFdyG;H#8;ZPv^x}r8IZFn)a=t1ZPIs}KcF!1} zLPg>ddo`gP4{TxedmL2gS6R}~!5YjIXNPE+kgDBWM;c!GN+ZLS^+&^N|2TIhNC>3t zfp^7q18$jKhlbeY{75CB${rT*ubHI+!zuxf7G2(D%U_o59UoVYJ$hW zN0FdT!s{jB11_3W) z5?O@-X!1g%K$3w;J5@3gC-Bb(pfcGkl?}B7HVS-k`nuiH)Bl?$nEYo@yIxO%1ymJk7?QPV?1-4V?G;2Yh%y7wbg zdJ?K92N7)C^W7Mj-<_?+Pr|-#zM+=r4R?EG>gmLSyH%I*d)Zwhv%g*rv7HMkbG=~?*s_Iq2{m&X4o`D#(Zg(4Mq2S%qB;j>_eEOhGjZmEZ?q z>S@4aWo8+W=+@eWgB9{hd-ooOChcj+FG97d%dK>@qf`1CKMjFrUCz~QS~k3zRcR2F z{}WS_6_Fxtpn?Iy<&c>7&(K;as3_Z7l@^xMV3B#(E(de5hA|sJD{g7mS`ScIwt_6N zUL#$nG?KU@bPWaLgt&QFUjyfsOv~jU=|1+ky%B|B<7Ph0){2IDp>iNwxDp8AhGRBn zrI8BcxILy4(ipIY(0Jv*EeFKUIWh|Z$t>lF-;KwCrZR~2V|QN1d8bNLwR@(nPDQ9; zU;-;7Tqt0U$YuaYdo2=l(#w#vMu*e|-3wdurjalKl@uJvDulRUSH&_Xz z0}88J*-+Ozjvg2=g2~gWF;uEo;D4?Xz)B;a8Z^AGx@B1+$toqav(EZeN|qB`$lH=~ zuWtzR2-Ul5(qqq!1nue6n%CKa?AWUS-0_g-LsZb4kqSfd>50qh2Q)}1bf-Ai53dbC zL&0Oz@3Lp`O|KvD2=0_2HDDF35#hjQ_5>EObB`+Am!V zgb3yPWvHjk(1DQGhOSQ9iFHOz`3HJ25rrg}!UE`y;jW7dHJfXxqtf~@8hzK%=By*PTs%8l`XGjU)v&U4(uA=M zq)`!$CzLR@HP|&W%818+LDwGHLlo<)v$olyOb$*|VWe{WBM;cyuwiaRu-{qP(JqMF zM6sKLim}OAJ?ixvTFqzYs(L;$ch%X=>E=EQs(zMBPYd<2KuUcE%a3SII|sDdLGrxa z2#crb{@w@owwpelk1RDs&ZDT`?(1I&ISZ3NS~xbcYK!T zAi~+vyhy_c!={>$+EA|fkX`^uK(@b9x}@avc|Bj0|KR=ip2X7aPv+jy@0i1+v~$iB zBEXr-1fzV-y$xVC>dX8*(g2Y$kdS`(06UJrio=qUGj4Gj*Y@%&fwQ6|XGHPc?~~bZ zN5SR-z(NaHJLT!Qe`rvgn4ge7Uyrgskpk>wEKeZ`q2MbkAMt@c z86UiUe3?6td9TpsvA^T_(??NIawY>wq!fqFsg8vYW*@tL=pWN-y8f^OjDNmshee19 z=kNDJb^a`8Vi+^Oz5&aNpZb`fc4vFIAO1Q33}O+UJc|b@2P(BPzp;*A3-@e#_Edj*Smy*_iEaa7}Rqe~1A2@tr zH~Dh1YV-bvwA_rK--!*&^p$egy~Tdr^)7C!$E4G??GK@_u^I&rNn1b1zeBTxIZ$J> zoryq}m-uUuIF%u}i)$PxRyW=;;yI;hY-NWZwD`ewfvF1GeCN=hd`c4mv;~p;N?(IL{ z7Us-l<}CcFkeMOwd`I25xui|W(a-+6XZzVcxQ2l^&(Hs|G$lA~j}5`klV8ynkij;_AbqxJh&dvvE2Q=f(;v{jvLhA2#D}uN;_C%14 zRuXGPmW?Jn>+Gm!xlXq$Lzn+YLN6*fzIJ8S&&`>=-EPc=R$>lLB{Re}`v!{5?b@3B zncJr|P4-Qiaz6T|JWy#ocnYHXO4Ht#ZG3jc3X$`Wa=pGX9@!=A09zpN9O^cG*s$a2 z<&S{>y^mlMIS9KL-0~u7Jmhz0OumPjniA*^o_D)l-n?7&=(PN1DcI*XpRK_hBdLrnm;t)h-u1CUkg>T{GO@4jCCiEN!Bdo1mCdpcD;J zmKWtT}0z(>l=yLdzI7> zAvjG~3bqZg$LM{_xZ3RESO~bo1Z&#lOez|G-9;&|*pXCqkbiEcFw~*!CFFNS^g}X~ z(E&27!l}KrVU^)%dSo8J`DR~nD*=xHyR%~5`h|#ejs{~Jc;_B-WJ-fiSlg#)PuN)*F^Okk8RL3gp)*@D%3uOt;)3cY0f?Htep-%IuI;*Ok$-C9i}M5gE`w z(d4xpnVlq~{U+C=%|Q#mV5ShLj3Nc&O(h0D-jnOj4MqVZ6Xj=%CLJ~6kZ40sD*xTU z2ljD&B}P(06iL)|3nc2H9fir90tq4`Y9fBQh$2Q(B^_c#a+T}JW02tMQ9&gJysEAx zL{sIJV|R=kN}ea{Zm3vxPARpHq2g1y=|+fE$9GByXPW)fs#0}ZW>s_GnR%R5rhtp5 zeg4{rP1VvNJ_$Fc1#^>B5*MRt`J=qe*h(#F-HLf%u8GK2x64=Ws_e8L?R9nF(YVty zkD?JE@9oN=>7*>Jk{*0ngnOMGjBsMRIPTa`!t25sq!mqadE~-`nR=n>ET+0CtwCAK zj65-Uv_skJ%ATb&Q*aq6`?l&Yq|X>9x<3JzF*o#!H5z6sz?Zn-tHA~|N*-DE_fhOB zO{Rz!J=WK8eJoM_Unagh`ylTz0tXKZOr)7~4ZkPg5e~`zbAE ztmL72cSOa2sIne|MY?_yDfN{JE&iud-)Ij{oe>u2R27-BX0 zdLu?*4}{v6+k@&eBvTAado&jB_jBwSf(yDHC%pP=<~^J#vF^yjZ4CMjt7_L#*wB7Q zeV~eZup+7$=4P-%dCWa9PoKlkJ@gQMkRkVeAQA~QE z<%l~XNLAYniljV~PlRBR8ruW)AfupL9@;}P88>%6fihi?h`A4eFel+1gN0rvy*4zI zcgwND_d zhYzt}|1OR&E=iq9o<=yKWq}uDEC>HP6^)VQq(SA;7~d0XBGidMVFlhhcRH&qIrYL* zB9En@sMQA8$GK;QIZ#=0Ov^o0xwZ@|Y09zd^OacMT;v_fN1VyhUf-qivAkx_!s~Eu z;&1ImiwIId$UzJ+hz*$8^Fs84RpoXo-M*ehw#>z_!GTZ2X^6`tgEsZ&lg_y zQ}$_fb=~=Ze@?57b(B!QW#7_@y4^+agRC>gj#DGbbEw#Zt{UBE#mG+CSw{zH2vIMf za5Wfb1yW}iz7etFb9l;lvEud*8*1oigcDgPFn&~f@jEFm7gmw%c2P3@xx0t6P;#i- zxd>&?dAPPc7!qo%F&6Go?{7RXGxttkP5*|HagPF}ULzY0Dty>e`kDA9bysaL&FR)X zKZBwEW!{|<14oMv-NPXmAY)KX(U=4X4+<_QeRX?p+*%Q_&&FLX-60?0@fp4zZ^5lCXBrjjvV+iaXp+D2+tkd+CA%oi@dKf66O~i zS1}~;p}l3(-Y}UayA(Lx5f07*(jdD22dPIN#VXL#WFGv1)Oni(2o@Wn6CG5%nMUB1 z<;5%{P39e*$O~&Rn*6Pvo65YH-~SPDI-1Uw-1yYf{8wVV>F-Z`|JD27W6s~BE$ozM z`fvvL*8cgcZ$EYTw=!+;m%!P6WZ+K~4n)jj&0YR+<-7SWapkf3_7!d{WRwfiyyAT0 zGb0l>$u;@wUsH%%^>{6#v-Fd*dHxY5e%$6Y^!`!p?DOZJp1SqAEu!LDaeV_zmg~HT zzGyapKOd~d)4VS?@d57i*Qw}A2VZmR502wR5+bKjMig9uBugk}bHQq5+!D#QB%2~= zu$NmkRU73ZQV-!x3_$HOxfIgODO0UGkfJ5u*BG-PemT<6LN%^i($^f;e|xx?YyB+z z3K1GkV6`&rnXRpAAP4l6+NdkD-l#v2IH_vjz?@zqsLot9h$<2Y_<+b5QzpJf^r3_d zr-Mb|5dub7Bx~K)OF}AEdc7FZx)2tONvjicxHrlRHIRy zQ`a;WNY2DCUnRvEI+7@iq($`+jWqlkFs;fcx(L2C0D`W<8}PClj9}0yM~dZYx<)Pm z(SW;tJB@s=s-7eQat0o>D2Hi4ZEGe$a+xif@-|=Fvojm8g0#=29h>F3IiAP1yM(zx z9)yU@XyGk7@51DYqaeJyCH{sXOUjsmEM}QOnP)O0<~4QdUdfN8fJvA3q0X&Y_Jo~^ zPl<9m>^YlTDBxNK5hlsXSQrq9K}=$OBC-oou8IVXG91ed>PqtO0DuY$?UfKV*7bqX z(9C18rOkS3XLS9wwwv~-c&a}?D6fWvYj;J7$(Q=UGRy)?4EJeb5;q)0!bA}~-{|#Tw9l6qQet0~ ztjZpc!@+4Dmt$lU(FJp9i5(O84XkBA=*7c$@ zOW9viSzdw~v|w5Y@Opp(kMDMdIa6$pCvkp`nqyJ&zttTs^IeaSyZ-aILA2*7`2pLE z zS6eNDp#dQr3q1~uB5k$*iRsUPLh6hB|G(ymCu@0Sor_C1w=MhT1QHdWm*q*(@CEz# zL3|D{c)wDJrtdkwbmb54wY}M=KiP)(Y} zbKP7(Wpn-3*wCJL{dqWv0c>Bm>P2vUZLsWbU;pOM(~0M^AHV(4AJktx>|GI2_&1sf zgepcp1#378`n4PnWt~K`W%(&cL8^jMH^}SCauSVGCl=67sL~0BZ7O{crSk4+CLYbR zRFt@a++3$#HK0bS7{f|EI$fw3q2svvXE0_IjT>(=#&ElXb{!YHY3NatBVmt%S%^Zo z&ci0NmG@1<$`XgZOvqHFO`75w;l3^n;v#50>+{aYnG=M)Xj9#&Jyo4QM-KU8J*hFP%{^ebacu5|!dyG)?d)1t)c|h6NUAi18tf2iy zsy#u>Xp`h=sYUbR%w`NLFo^DD*2^aQGJ?7ub^(RGEyF3;2^)#iPOL1iV2o7m~tPsFCY>lT=_)X>9{kaTGIMQNh9bCzaooQlD*WMsTn zFpngX8#=ON;a%Ync(`Wr`n!sHzT7rci)6m6E<$Q>ZdT{=V!oCn=Y}dP7t7V6?|ry` z0F~(;q;sesFNX=zIYba{E?uFBq^H_{vG8z$0m>xhpt3o{UFtH=4&N73BKr$0w0MjL z*SzBBiT#f?W;*%>J3J3>pV4{QlZ$Jfo^~~-?d19O%*?X`F0k8Sr>E`CG`O+eTW)9I z`Sx<{3c9|k*Z7Ga{u3`wZaRVt1l!A{W_~Y)_NiI~4tlK787O8!DC;~Rfo(0Dn)7Vp za$Mj-vrJRUfw1XCEw8RuP1tj(79WB_8dk7Fv~;{)UsFHNI|3=K@dq;7lSRcITgx8F z@>LSHeUDu`D{C+bC8TQefV903VW>#tm$@Uu5uMhe-Obr7FX*d(5jxY=LECE?rN=pQ zq+F+RI{#5HTIdp&)0>FA(ucTs31;&tT~G@^!HmhrGbADsmTxp1h*E|1AsN^1Q{tq! zjhKBXxn(|j{D&H4w`*O{8^4Mq4L|();`<{(?jVyAx zL(%GWcT}0%kK-dSk*z4&YyNh_M)=0w@-Q?u>S2o;2w{1|kK=GY<^qbG z4K~ny6!@~JLNGUtq>v1#01^%W>mDX~;FNZpQ4OB-WL<*l=pC;+jGN%fTmR5|qnon! z9BtLjisj2MJcBt` zx8%o>yk@i}hBpz2)W|0^K@`01K{#T?bKA64GaC70h-UoOP#K$VUyW+dw6=9h?2H2xs*N|vr1KrIWC=k^YyJtWwWTslt)qac=_$?cgBDG8{{+4q-R6TrVQ%G!KysRQBJph___$;O5Ib4vslij>CA;=#JN{R(Bn%+Xs9_B zWl!OfMw1xlGinfsHAorpjyUkR{GGu@Gz z+I~ZCWE~}yQ?a>*6U6NZ_kYS{b9t9m1?P2F#K!J69z%Q?l5_Qo^nU%ZJ4Za0Q0e4I zF|C}O>?F7Sh-?M{Qzhcj^WN$xHuQ`|6-*aP)U>)2@ocw^#*F|RS2g;(jRfw*HYd!N zBp7M}ROQ2V+`Z4KirbJtPzQkHVbzw2vC5b?OV zdcW$GP@HYA^QxENqUj3@Ak6tokms`P-4WE;e0Tp#v%#0;di9(k(QzGYJ=A#3*Jnmb z1us!t%ev*xpr>WS`tnEhx+m+56J2@NFI#3bvCJ8)C*TH3zAT-s3k=`FlsX}}{or;% zMCbHj-RYzHZ(BV$*nivFq2b~5W!q_>{o6K^$>q0frPC`?bE@9Z;qsu$$V&bs4=t^3 zZ7q;nwKlNZ_QQjfhW>L0I8m+jlYAK<`CfD63gI~`re)_giG6&!1?Mc(B2~uH+}fP$ z-kgyK8Cw@a7`yTUD{T>Z)E{r$mLE znb3s^i85*np~T7q3w6ic$ke)R6t|Y-yx?h{L!a(H*85_tQXbSiJhi>PZtLwFQ!KE59#+tUgyoI)%U*0FCix=v<4>(VM1bEJ&)g7Q2wYnb&?ULBCLxZA1rrg)zhk%wp z8XWiX@d)C6_vw1=b8xvKlD8i2?+AnBRgcs0++-Jqm*iK#EKy#2OUp4WDl262Q#e=s zA=!og<(msX${Hg1jN}cN_Uqlu7k6ITEgLf%Kb?=DQ{$9s zc*TlH5GU}dD2!orl!drpg05I5Qi`d^=iOV;V(#4tzvVu7}u0iPK%xFJNwV<%v++ruuW^RUl(^pAIOBM zJ=BfASO04-4(xN*#7H@IT@EpPfQpmFAVib6bQKZw`86;-99tX%rmnj;*>H^FX;$)W zGlFW2Q7`uV6p`h0I)45sg;`4@B&m23T>`&iyuW#!&Q80|4&tTzf&K3ALS#z0^{^VD zN^o0VLMk~t=BtWFMVarO?L7@I<`eC;B|Mnnlt`b(98Y*50yvdt(cX4eM&k?&;j{9B z6^Z3WE=~y_2pqYQ#1Djy+!TZh<8`vQWEI{gPjNL3)x}Xnv;LgSCceqlxbt(A*K^{=-vK4&I9Lg-?Q8|+{)LS5q_RpHgset0gStXWKg1{JE-(uoG|E80044_~K zL3NT~-t_(kA2Y1RShOJ0nfN$}1Pqnhky#S9NGZsbbwr`~5J$>ppn!-$#{H&1V&V)n zEgVH2f>R8F!`YbxWS_E5jA1ViJY!094&^BG$Tf}6;w0BEg1g%TbSd>jo~nhK(LymG zBKN!IY=R43s9m9mFDVLvmh$9ZG4BNjKaxUK9rvu2A8^k`I4^y`9Y%Dd^+Jh)0?;2T zaIKky`+@}f0zFEKazEWobqf72c?o6N;Fs8x3j{=z30)W94*CT zifWsO^2Ckupo?bKHb~v4pk1m|{#Ih2EI1k&DdD)q@V1AFkK&QH+;+b!QLY7}enO5V zJAaV!vWT*{wTK0q>#r@oL6rrjK)9QcL4etWw#Jkbhqop`^les2N_PmapG45*)T-Mn zSWvn)$DX`|k9zPv|N3ZE!UP*1FgVnF;9ZMfq?4;kUh>0?e2Dk2-vy+CYmpEkEu*g@ z_6B$#mndncOl-IOp}bq5fv$BT6Q}K%nXwKxBIdMo(yh|H|I+iRSrj3|MIs+`9p zq^@z_T;vrC){eMA#DFF~6P0;H0~S*KEJPw>b*5K-6CIqdlco6CGjZq8$QDo)ZLgo? zA(IoP_P9vg&OFM7%GCXTjf! zY3x#Zml72oj@1*ZSBr^RGX&*uoH6KY9)%#ji6)!E6`z;O4{WVasYzj+iSrQvhq`MM zP^S2#F+D_<^T;#>tnJr7%`0v%#!Hbvhph`8V8>&YM&nHj1CrDdplh>T^xn~tiT3tg zDiRFsmXqh43hyo-0bjcFVxy@(iwuG+8}3xFIR+?~s78)$H!g_VV0L>x&UK;5k|XBF zlO8f7)m(#o944}u#TUzC88IMNbZ)@ddJFp`qd6Is2KoqGiAv>;#~=xjRKPQeM6Tdx zyXZ|6g#b_$UOjCOUKEYJx?-Z_ABY0^^F%O#ovq{>IMSpIJ{i~JX(6D{CLxz$LG4_) z0X{9sBY@@AiNOwh#0=u`S`~LlH|UF(^K?K_dmtc3;iN*Eu=m%_WjtH0(aygcUS$Jz zkwgU7Xyl)Nc?y;3};51@+vQ|_&mWl<#(XewvbmN&422n za%o0hSkBTxy+@guk{vHyYJNe@KzlHqz9JGn5j8G~T!Sc`o^=Bf*lKQ&X1iC>7brzI zc=!ojgPIe#Ue@DMF3%5(Lt&NWY?U@oR$<`NYcDxA59pu?f(cl3C(tn+Ju!XCQx#7g zRpjmt1`BM;%Z@KwD6~Vb9)^n88X=uWJVtbsuDL$1+Ww|gH72KuJN?lcsau6aZ z1i|t+zMYA#S)gdEug*7jIfv(0PO|L2J*4;p+S*B|(s4tjz=vAtGWfk!MciudO?gha zr&F12$uaYkH@i0aBPHPDC;=N|_Uvi1;>#N`@@ZMOelkW{@hE?>esApP2uPUdvW#R( z2W>#wM3;MvPVP9NUolir#LsDgk%W=!pd&PQ`B&?&Xih2HP{)z&VOO5wkNqI|m6vsm zlQCs14){bygo8}{0Z@1^YSP$OhEyrIc`7FI45J7|Z$`;}@e~z?U0kGJLL>%b`7ROi zklPhcE~h&;B#e54k%#zPx4nDcovIJ}^g%=Tzke^7czeZVO-f3e&@WH4@R*o`4eXP% z=>+sDe@=-9O$+^?S41yKt6s#0Pq&Yds8nBY++xp#o{@SUSZ=u^eFeiQf2LuV)87+#-0FMSDGDQOgD$Kd-auq9`lvCO}}I!^n9?2Kxej z#Q+TFtM9>v^w(4%@P8&!hCgN#qrAX$C<{g83u6eQTVAr1H{dHBmP{H7htbY1mE|&t zI7+rtt2hIb+SERdBu!w$5oj_)0hm{I6E#`OVX{#B=gvb__2C7KP}RB(l$<#jokKMw zxSzFzy77W^SX#}Z6C}}Ez8oiaT~7)FrF951aWN>b-Q20pj8GTsFg7$9j;NlE2G=Y< z)}Sc=EZt$IukH;8ch@E{u`Q>7)#GKJQ{an$W04i;rW70q73AC2!U?x%nF1P$>Y+Vx z>P4~mqe62z_g1KEw?Qu<@8r1r|5E{L6% zpH_9)Z!5EnQEoUzC;fz<_Pyt_n9VO%^xfrP+G6q>Sd`O&NZa512_sA+xYs@VoZx&> z5mm}CEO}a~!e)5ERUQlBUU}11!;z7E1dPPfi8kQX=y~yS=P?dKDB;sQryt+G1q+5T z1Mb2-=5~C;BKkpJbctcPRgM9QomNY`AR^VzKb(TdqnL@L36`C6jk6l?)s*2240^f9 zhZzibGDR0~^rJQsLsOZQeT<;z>Lu!_RT+#W3@;t?noSNdFOrwF2|5yY>zG1KtAQvx z8(t;wFqea-Cu65_ALD8+_+;>maF#z_8X0NxIvq5X24vcg`rnehs9+N26#ZTAAjfHv zWTv?}*0CM%`3}g@=x@h{CSFo^9CZcm0Ev-yluvL4H3mXS`QHtiR@ONPcO}jkGk8#zEbqK;XM1c-gAP2@|*0?wikyhh0l}#R_Y>z)xj^I)~c3(1BoW*qZDp;VZqaex` z=PMwH)z$SZ7?7ho1-(ZNX5!ISb_4v18^XME-C3f%w{~WkfN*TG`G0?ZzErN&(=t=t z_0u>FJfTP`V@LM-J97%4lP8)otIn^t_S1HEWwMUN1*_}Z$)9$5s8Zbv#FB$_ckdKT z4JSmujWx0+^P^MV&P)r4t6aLUbNkU5Zzx#?xE0SOOdE`4!w7cH*!@%B2Eey^g|Xab zH@5hNvIPLfFk3oJT1o5f#c?7ghkmjO%}TB`^%J1xrkR(>XAo!)%0;mc7>^hWy+JExq3kh0K^u6F?bajNhpI6i!~Pf*yn@rXa$x@+5GW8x)HMxk z7*L`fLFdu*aSho`6qqPl5ygTDxXxs_H@OTwAo|h zc&YIjywU7~ZVb#vcmMmG6376X*$>k6%tbd||HkFw4*i)C;b+%{nDFuk zKj35rI{$`r{RY%G>uT^%d)Il)Fx9fftr<%_b7*yE;b-~ye-B_46cE+3((9lc8ah(o zdRV|$m;c3_1fCO=Kwi;U{SUP=7+_Z{;8qWe8(<*ri9lMt@LQ-txzaG+Y}GN&Zhide zW6yPT@9W>6f3VFy8OM*qzQS+K{u?LB%bw0+3dgk`t#z#`7bLey|nabvZ%oZZ-)>*Gt10GBu{3i z%!xhcu;JMQu%EWMEs^|w{Pi2`{K~lE7kB-w9rdzfk6By_Pw355-cGJ*h;>wgvj=RM zAP4py`lS`ridjw4D6P5YCA(=J>?;C+EEMtisbD1Kf5RBG%I-LK2Q{lDA0vtDNl?3` zM~#oY;mBpAT!bvt#+a3qx_!X6Thck-b#R2mrKF9UN2cG?+8=^e2Tl@T->LH=LKt~R z10I`?h$I89Tzg(GXgBFpnd^l!teACj#{5k-s=O)6+tm>ny_oHjaY>8N-La6N_~1DL zOLcO&Rawd)Yw1v~^PrEFDINfa-W#hpRA=IOOdldd zI>dk+Y8k0*rYDutuFT2AE`Zw(7bC7+X-8?OK$eV_{5`6HE3%z2o<9Yfbl@(#z=ZL- z(oCTigHf((US&;2!^S=ZV>o{_X3U1+i5R={_qB_~Gw!0xrOxn_*n~Nu%V)J|ll4j2 zS)^m4ojt3|L0?L&>hrRq?<_6qX8BZ>pZ9YVp9Xtn2hd)Gi}4!BN%I zMP9q4T$Q|hsd{xw$ zv0q}kHC#;!7uIBJC|@0|=;Ws?{P9QiU+*|!leqIHp&zfZEaJ&^nc9OFjiv|NWgQcp zQjcRT7vr`KL4?zHzzsBJbljo4hq|H`ShkGFizp%IBe9U(Im}5y5ENi#$KUKYh0m4Ry^TJ5$O5zqKQP>^TcZGfS8Rd zu<9arrE2+XNjuh3lbmGXiL%-n(=;M1ev!0%yUpws&GsVW`^PUugUR6oo>yRtc+ZNl z6MQTs$Wooy*fvgm1gn>ZUr;ptnN3ZRS|uk ziR$O|Zvt0w30l;WwB>Pk)1uLK?`j=;+*GObBuA!JwYkH<$v@U(c4t5BH+oj7Zt~1< z(x>^Kw$@y*J=in2ITecDFe|I>S(PfUL02of+?v~lusYipYlG`y#l6Y=TK95{!O*_5 zTfk8r{8=IO%4|%EOyD*Rs$ceR9%H9tb@LI7P3ua6cb2P9cG!n}R#r{}J(x)l!E`Jy zd%4QS>b&98j{4AGZ3bf(><)IZGaTuHYfWa77IW`>+Qr$?C1tEH4kT@Tn%t9gu}5e} zx&yot`n|irY_u1d!*lN&eNsMGPa9bd^J%}h7>l(|u6*Wuy*@bbj!rT5>NME*_^qta z&mX?{FN$*vK7Un2o!g1Q?{D%ex7=PmJy46&moB+l1;8C|?+yb5HkGZ}ZK|>^OFp(| zZrb5XGwz|ID}OwhAN%nA6iL0DRB=Jp=Z;06eLyiq)9~66@Cob7scP>~SK+JcN^fqe z5qO3xLu0>cBkLrl>ake#sMyf67|X)yiRUXl5rWG>oat;YScg*zamx0_nqInw`a9;= zRtOY-D#9W2IRREX%O$fd=U6KqjY}PQvBE}>OMbm7ifkr75wOr)By|n8lHStEWYyy# z4%QXv2HI@pGRo{okBdxnw>Ws1yRjqjrmZkncqBE>a@_f;S!}lKksRebF87yL_o+Om zupL_c;W_I!tE+n`f12x}_;&mF4(yMhr5W!pCF}$X+(?X$&{#2R=n!0Z*Whq?{P^kX zu|&<7^sMSuk1wUS4pvkPaerfE@*q(RN+%HMJWqIIW|opkqcPp>t&@fJx*%>YJ*I?k z984QFLCOx6BzKV*me`OLw)>LaF1CkNFH!OO)&5p=h>pm;S;>muuV@+@+_% zbLEl0q>@YBBX(71vf=eYzyo}!T;UQ{!#jP?yXfz2Oq1U7;~4RMs15p=@}t!t7iTv; z%45L1NEQjlbb~0JGRspc=Np`LjZ+2DE_)D3b#e1#?u*%3-Bo6Q{}0`1-RRj;hTUS3G^jIG$z83>T!7G!%V5v`*aC zrXuy7nzq=w$l5^FQ1|)aO38gjph%j%r1Ukke%S2 z%<;`Uj%MiLkR-tRAxqD#eC`-kvwtqab=a+@s^QZbGIKh=U8!2x_BJHHZ*XfMmC9zY zBz?*y=5`ite@^Y8TopOB*$o>AI8xEJ`Sx{S+eL_Pm3WGB=|ihlPDy#aXqlzS00F{G zfXumR2IydPXS8&1MTaNd5Ox&R5e2?t8soOhC4{Jkj(g_tB{D(qJ?DVS^r{69TpOyf zr1Gj6WHT3c{Pkkh3si*7^6o15v`pNv?Gebew^PL>r#}Es3Aj>|P)jXyglweqvx3%W zS-no#i{?ZwIv+xtO%a(Q9T;N3-jDqOb1d42BYAk~H9>#_KaGo03aAOlW4p&{ALh+}Zaap-^)$YD!B5p<9eFzpVS-^J2flNEYn^=@l=Vt^ z?5oOfq96sOYHW~Q^lNrlwz6g@bCQG)07)t*rHD^Vj{8yYaVT6Tx7VQcfW__rPWs`! zb{v`yWCe)^9nIxYzbexG?$%UKV5#iFhv)GNcIGs2K$dMz5~rg$VrC04c17CK-cp@<2leB1p)NMDq> zc%kgNUVN#bWpQhvaO%wo(94V1e_AudJ6C6kABItFu>KI@FfB&7$2X0J;~ zzPb}^yIsVNYctY9X>%uN&#+1KKll=0vxMS|H#SAEnQv6zPo ztvOS{acRBU_yUyX#pp%KikyWX;2YI5vAt=H0`R`sGWgIMHUiJNx`dp|a>k_}!K;eb z)MnailDfFdK{lniy4pq!hci9jipT>()A-a)FE1+9=Y$tJ827#{k=R5D_h))(AA*RC zug<(H44^sZ#whB35%0KLn`9)fUZO3p3#V9*6z1uPiwdN!M(r^^t|!M3MR8B_(7V_* z+~(Yg=Iu?rV8uOrnNpbWv4=@@`C2V22RZF7I$?#TMzIE}sn%lIEmxC&?ulX5H~$l= zRD6%~wzGYfN|_J(MNeh4KqITp)=+Tq;ma z2f=vh;}!kjvx186TY=5qs8ET$WP3dNbsaO7kevOD3sypjgFCsXtr};ybKbP-z9C6* zQBOGKpp8)kaebBnphkqJld?LQ3B5FzB6_6gE&l|dP(19v2{i%8Dd!!WRq7h5gf)v zW2*vh5tO6lGKv>rgDh(~YkJY z5dXj~-9ZSI@yJ&kPEMp>WGj|SQ*|?-U@gxkSjk076b!Wdqr_p`AMS(ybIpUi_7wu= z6=y4niD+!h@Z{3O+1)ypc8BsV)z$syMGK3{voTvsm8WXA^KT~uInvc7HQW5FD4LsT z)0Y!p*q0ZX`6bkSnVt7ng{B$m!9-F+;VJ}UYn79rwmRgjTp&4uij9+m; zdQPtK(Wv~s3BdBRw-hDWj6@0mNatL#Ut|0lpl-Y#itW6MU75*E^Q`Cfc)OOgo}+kl zT+gdp2GsC8FD^gZP>X}hGc5~nzqP!6W@em&P)D=v^{vT%hss&|gJDCGToVKVep0sR zS4wd*2Wz8$WCFkFjujRh5#pzl@nW;;m7nP%iKJ0V+YRvNxNWK!e2ggVsMAIRZxw2P ztfYV{*@X~nogs=vN*MtMg+;DErE>h<^X3PJ1yZvQA#@DkB-j;G7L61BXP|tE^&W9n zF2RP)0Y?{*lNynXSo`e?gezbSkg^dHuLmxgd|SC1W{@IH%4|hL?WKG5F<8I z5{2zN%4vPGV16pO6<%5ft8zx0MrzE)g5_iQkcuW@ake>P{JbhlrK0j>-37&Du~Pz? zQRN=UOx#=ny=L3%ls7Vc4r~ga)4sY$pksu$F?mzJ3|Nrdyt4AAbczHb0k9>TL zsy{Y-d`@`qp&;FSHLmgdO}rPD_zmvcjQdRs3>fHqg(-S&>k1#NR4$q#8kbDPsw3NiuLF5 zWHw)1tg|&Vv=0%F@}W zjzWSVcDpVUi_%6pP+xDMWS73u(NNUy$`Z*9`~1olPIOgn=9+M32nam&22$BPr20ZH-shx|k}*>>gsYre9Tq6+2E3{l7|)hEh?v@l#C}wyUS`L8PIcvr zC>0pI%v;NHV8E9fItIs8uY@P4`&TEqQ>lU1(Y;)AGj)@Tay=X4UV4mpv8F4pM2}Q) zI^QWuR5e2xF81EKjV`|*?>Py_xt$hecr5!hJ^gs7`ZgT2N82|ij=bCZ$6r>X-wKNd z%SCgse(dtG5vj zOdA8N^4&HYPZt2aI(v}H>652XSuf$)<4tnyNcfAqVC7U}ZxlltRJ$?K)|Xf{|0+sJ zClLs%cvB#oLy=-Pfy6tNAfBo!sza>?y-7yzlmw)q_t@(vVy?yNQ}o1Lw~(}pqOdo8 z%S8`Bh`kzaWu%?;G8(4F<`@^-bzBzkhiDIXpY!$b>fX z)y2&%f#U-JOczzmiYQs%+a;ydrXZ;2YLGcn(@jDRH_V~?_WzH7d!%bV^M8MJaZA9Dc<4hP z|K3?ETQ()@mDkKnF?zQ(Qd6I}E#MG`ZdT*n@XhY!-fv#M$8}pl#0brB(nR;aLEHvY z1EX_kgLxF%Eovht2(aOHEo6|B1nUf{@Gsh)(6gp5l3@G0_!MT>$8n`hS<{L$xMcKD zR6LRUy^Da0Wn z1I?IoNw}AULc+qM3|5U?o--ni#Fo>iBt3{kadwgbQz4kKi>VcyC%Le!c5G_=Y1Hgd z+A;WyI>$yokCoWOmE^Pbv){B-t2=?L>4zOwaY2C;*sm^ZH*}s~JfbcAEm=fGK|bCP z6~wn2Rn?TG$69i`7&G3Ojd?7?(frPYLfh=JN@L}$!Hu;g#IQE-PXvFP~~MJ{_E3`M?|RE$o`W*?1<cCEZUgiJq^G}Xrt2R>-cg9-)sV0>1}#?pNKuPxY?8_O5HZQ=C{w=J}eK{%I+t) zA8#}9AtN4D%cW@o3Kj7s8Zy(xZ_tMxVU!{;xR^<;>=^oFWVsOvII49P!BzJQO}u(k zf`ZVjmup6p%)Dtf#tUi}#FuG!_51@YpFQNg09aI&mEdX1SWi~Qt-*Y@D##(fxOFnM zO%u_^3vmw|lnV|xjqJ=;gdK9F^3P zD03UmB1I`3QGSC8+TiqsWZNQEI$7;5d4kfhP~nIcV&o9$sHvsgnn6gqbto8Fs_p^AZ3n-7GeknJve2kyHzQbI*e_I8S=-`M@CfSJa}E9ZGNbWr$U)15<|Pv zamE;2^zkVz|D|lgrSkl#dJ3uS9{?&ise^~i<%An(dK(STTl?eYP9P(P&EK~sBAoxY z+WjJ7x2lux;#$-a0chbescWy?&9ixnuIpqox_+hV1dzcRB}pax0nn5Mu{KNnMC zF#A<-Azq_A>kGLccO_*{CVq-mh)LBqr1M}8zZ!daJ9}4lD_$lGG9vfXsr{VIb-hz~ zCQ-9CoJlgVZQIGjwx8IxZQHhO+qP{R6FZss&wls&o$bG_KIy*dUR8B>AFNfqR@M3X zn;uLJ$9YbMXwi(wJ2hxUuA87J5ik4$)tF3$$fV9*X~khv1aVAL4g%M(rIfSyLjaUz zqWKj__F3w?jf;UB!;~b9rdNKcj6sdcZ#q<&tQr8Oh_w+#r%^KTT_hY(4{7F5a=lO( z6)p|HSL^*QCD(-@ZQ2CY%mFfRxEs1uM%22DzlO(soo8gt1@ zioo68D?htWr&YpzLDrvgbNKUp7F!Yn5gget? zQ&UyAtI9a`auEyL1(C9%pcLVS*I|^hZd?zYyXaK#F z&WzqRV_$6m0Ozko&HhjI)636$gq>K`IZ&?O7Jx$3vwNSSTtBQHLqWrIXAul4b|LA3}{N8Y4RZe@xGu62X^?3PxgYR{R9NQl$Gm z+@7-HX7jS%E$vw>dfRuG-qFi}NG>YY1!G|8B2-aM!`R9G=w)hEF-W%ho5>kpCv7!% zp*A-viYTdZKWnLbrMp6I2pHqX-D^>wmZcS{!^7KsBjh+y3@;@6R4VC_^%q}(bDe*@ zm6`I6ZWwf1@tG4NorJ8}8JCciEj;k}2bRQ}uImUdbmuWnKYD`;h?(rEwggC~E(vyM zuxO~nfTve>9+yQuqM4S@N^C;(X5qI&q3Gsq9>rD(HsO?ABqm6WZOUBwtnY%)?_-#O z)VFj5-u=FAMMeGDXmFiYyoCbWf7%VcAx{vw$v5{JDkwQi;9+vgcl!NJta?T}k|?tp zLvvli5trFt%hq(fZ^S$J_NZ`68VFdt2;n61(SdL2DO1c%2`-;tA&SV1sBqpyYJ29j zv~lfR4qMgXhu;KlI(4Kz5)BF1ll+SkKCG|EL&Y$CQ}t!^K;@pt{rVOY!H#H++KOZJ zdnebO6C}$uHmR2$$zR%wG#O&K1#tP{g92u#Hvn!p3V{d@m!{#-*cCCUR*{VZDMo- z9)N?fN05et>3s^ro=d=Pd@Npgi9At;`E5)Y9 zc(36SQ{s__3bhDC8TvFYrm@_THYEp$0t<*c74in$m~abJ&}$!7LkL0Nqt0|b9~2pk zsWsHgJjEc?>$Yz~lq4e#kcdu~8xY2uX0K{bdi}3)qyeh65EK^^u`c$B@^V!pwJ$Y$ z^Y#e2TH_XNnAGc&6xt%Wt*1b5U@PB2_->5hcCj0FUd%3=RNQ{#V zjCk=!Fl*^bU*k)>yJpXygqlOa+guD7*XIF?$ zI$$3x0g?wo1!VTcpLFMy`xTM`aZXnli^x$7WZjXBBScu`G-bL(yl4xyh`kOR@zO;j zy8Dedb*ZUdMdx~yf#=4G;-Yb$(y+2!>|*_{co(4gt7%2B27!(p`X}2Y48NqV{qeRc z6b2wUmoUDW+eii@H`G@sWR6M`vqF}wH{Cy;w@pt%9H!2kJxs@=gQyUuK&GZg3`$n@ z$}_SUCwa_f_68j|)Zq8NRb4Votuq(N7DDwRa4yWj?K#_-rM-q_k_|PZbK9A*T3 zbJL3>D)}dDc+l{9W~KPs3*|5C5dJ zs?OLwn(hp)x&m&|duu-BIRmkpK5#q358HQre%kLGV85_kmp@d5k?YmFrf`L<6^<1| zLZO9l~Cr)x%VS7+un>PhROpF~?G*^?l+QZdvR7d&2V z#SljnCw@QzxSK4u1ZobMos_ga!toEH6Mk92e}4;urioK_ww6rj@7#TC_Nwjd zDmi|Aipcq;Z{$m0!k7Q$eNwp+Pa6iHjsO|xTr0Oln&r(AP&ZxfGiqDfC0>&InRM!f%FMdZi+7#Y~Zx~ni|{U#vxzYa`2?YW16zI<6LKEkZT@y+LcRd^yyjI7C(uB>c$)0 zW<)03w@!R(njmbTjy_tNs*tk`TUIEOd?ulm5`??=7yv?~#FGQVpm_6qpoEw8;!mqj zoWHh$9nSn&6?XyC=h`S5M?Tit#M5(F*1QAWI1Z~iy!gr557ke>fv1_-7e8+2s7=E# zM(&*Kw)LPK5vjze>l;vK6m&&C9$wKnH(kiP*28XqBl3-t8zuVuhw31?k?#;(9T0nV2h(V1h!GN<=t-8L{4MkJihRZyceB zZdxnCnI@z{goZKvVZnGcjOG@K5;*n5d8|#8ZRT+`g)j`76~g;Y}JZ1 zmV;Eh=IsjTHiVCDgv8jpLusr`+ADA>q&QLPu`J^j7fAoY9q!Uxm}=oc%wU}=T$)3w=&)6X-V>(tq9c|`cHsbDe*aqUU7Jcjtlr%9_c z>f#8!mOg&Bh6z5PRuGG<$!SUjp`lBxXOPE7{8Hl*1x5hS?!tN}9=suICb?vWdXvol zIWrKPgee;MLa`RH=LoY*5F3KNA}(jMV;b%CK2WKU@Wr?cQ4u$@K3_wfX&I|=Uu1+`P@4w|Wj!D(6~kYRyfly6`#`v4?J zi;b-pjIe=Qx%jtF|0X)wOv99UzqAaBzGB{wuIYJ1Ch-JB_$_|wrYVwA>NuI;nxVfW z9jAiiUIL8lk3cg00R}}FX1cRM9-~$nn*grd9}Uo*Dwp=Y;?89)a5)*V=w#(I!wx2f zh867-_;TP2H1OnTOTGZoqw56IW#i9+tkz5pDqJ{WsTYl;S%xzLp_6fWIrH+g789<5 zqn&2qzK;_y%*ZWU3tYsy(}S z&Z1O7A>oV#V&$wJqi(okDLy_k-ZB@9T0=h(O8ad4f++48(J$MNT1S6WrKz5;JeJ^q z6B#)epn$(xc}h_&L2tPqOb=o9z1-LCZJj~bR>LT+XbHtSx$uQ(I(|K`2t|jGvgOM> zQ~i=VIXjHN^>So?8H3d_tn0Py=avHV9@cu$+5SxZBcf1#@1@Qg^%|I=io)5>dR7~Q zlKvsyGXNn_C@=K$_whij&Ym&e06x^wTEAAaN=)BfQp)eKm!pi!Up!}&Q77e0g5$_P z7fE2OWr1JmshbbODWocUaiHDLc(1i zJU>IhSdMhqS-O!t1}^;qslM(Wv+6-{j_Zc3%pL+UQzo20%V>|bRqh(IbvK&jg)&D& z({Vn)q0#seb3Nb5V&lvzK36BhMI}DBG)=MJ_B7l8`H2EgKuz@(&s}=@f|BH=Xo`7B zIliJv!Tfme&ASH;_-<6zAb&_6G6|dJQcbK};F|itq`N9=ZF-qD19N5@I>tZv1F6fb zhcfok6b1ZQ+t$3^J1>jfA(U8g9*vuB1#{z@Wln;WF?*}p1KhGyNJ$>9TU-PEVBtOV zSnxRDJ*7EY2+3#6yE8iZUJ$>&M+|3OeK|QF{?=*-9^X#k?f{?E(?{57}X1H;azUzFvnr3LU)JPSKkH!6)(fh5N7ovo4+YGeenK@&Li1B2nV zNhCx9kUu)Dg^~EfT;>q&L77&F0P0 z8KWbk9$M?ZX1(gZ(DwAJ_OS{VQHX|`LLV#gh5qSA>Etn*` z_cu?9so`=mwjZD^TGk0sww@}Y*k)9Vi!Kq*3zdgACL)C3njd8srtvoE;dzdRpIm6J z*MzB{g^;UTNCXFH)FHigBTfQ&@W5Du)S3*G25- zA;EO80ieTvq5Ts^bMST{o$J=&vSkHi=E6?9A1`-l(!+7?a_isXpx(J0{?5Uv)ofpU zIJ2@V$!Phuc38rdV=0P~gMnf;gL<~}(8BFAzK{jc&RG$Y`hgTXeflISY z5f}my$k9Uj;lD7vF`Oid@wpfc#)e>1c*c|yZX1w&JZ8W{C)uYWVpN2{VMrL%+5_tp zKkRM0RFI24lzM^<`q^P$RmbzK?A>Xvtbb{i;{v{hLk&=A^wjvPn>9I>iDIDctydRIm(QIWw9*xUbrcDU2+s@dku_{n;E8 zK76C2JM9Exl*VyB9qm>Z3bw})6>|u7t=8-(PTQGaqZ3-1$`Y&2-Fr37 z2i-TkQxg}l)z6m{10!eq#tey@{wesBh&WMpT+=YZuZzU$^)p+FIh!@xE1U2)` zEY+*~8ybzNAc2!w(X?WYavKxMwc|5}O&3PdiF2wLSu@;ihpXu22tKQGL8q%Yh;rZ7 zD&xtJoXgX>ok_B*x?{STRgk?O#XU%g?=Bf|D9_bW8Ngj)CSMVtBBF?|ste>)?eQy) z*ze*!4^=8C=}4KKc9I8X)gOato?x1@wpDT90bD!n@em7r-FWfq_FQ%|-5)R~7PT*b z-&ee?l|@&`-)^`PdF zX{)}XtFld(`EZRsxE>Pg-oJFEx4D_Dls%GiL-cj)eccn?il0EdleGwUzEx;j8}D2b zuk%RBmJ(Nup? z&W9{MOQ9b0mymifsD*b#$FW-J6c~$TNvUWMf~bW0{CW$!y5B!~8{#Bd#da0G5@^OY z__SSkD62kVk}9~bh)r=^j{})&j4a3BiB)cw=Ik<6OzM$B$c#|j>|01O#cYO*0= zdozO2BTjD9JBp(01T9GFiW3oto;$=&I>*7H7^6#C)p}p$YO?dThUQgMf>Spt7qk0h z=c*k+v~vDyDnk*G^0sA&%gJn_61+p`D1`pb=RGx0n$`-&B!y?+F?zHi?&WHkF+_1x z?0d2GDFcX?ey;$WF)?)3idGly>JC~mS`+NwV?;%fiOqeF$Ftwi>Hg7=2S}g#N|v=W zn&%3Ep>N$~7QN}ph15dG*6_Z8m-%j}g;nTrnv|>jHtO;~^GC_6nMHrC zNq|(VKyvX}QEy~BaoJp{OMRHC~b4m$!hgiEcRRx#)T)f?rJYC_>pOHPr4!w$g`!24gcIv3|o3gjY0w(zGh=YbdCG=mx zFuj`Byk0`WF8<{RkGiWIbY7P1A-YcU#Pb z)-l(a8i@3{YYZ&nQBt_+bYAqHg?~(7*M0T=ET)@tF8kwI-&)&aVsb^?l!?S6=aToD zY0|U9lb7P+8}bf6d3vDP?fY@5MzH@06|sU6 zzeurb7O9nH?w$j=5dA9wfJqg1z8QC4VMIXYPc0Cj-j+;u{|Azd!oZaj;&LIbYdP{d zSTydM`dK86GI7_9Xe<3hGr*jthIU+Gw6Ejod6R{GhB6SRalfLKxcbUfXvLAKe z(BHr=aNiD>O|w>S&c7$~yV|6e`iL&b$E!QyTc+7*B?RdvM7;=>iiFEO;!kCEtv+H)k#jOPWs2`T zn(0JSd-r~MHE1I1j)e9Rdv|B-4k2^v=fu^(+9xE8(OWv#dR-@NZ(3am$P1_brhu3s z5lG?4{7bD}PEGYt03QG0250R8o5Vb(UeN_ic*Y3Lj3M@Z$74m?>7{UlK&z_@*JFl@ zr}y%OzC+5(>dvHa%(PPx?(dy7CV#S|d3dbOoW-GK6JEe>3~xLCrX-9D((&yPY;Hnl zJtnDn|1sDZE7y2eI@^S=P&4S+QfL|drRj$E`H9jsR zbZ;bq872gRe-v`afuLe_qU_-BEXrCaG$HiWDbq)^vZ_ZvWzU_u>da4_PQWF_n;bqT zlwmqfE9SO!MelxCwarbkpbkb{&m*7NF+)lUw!GLMk_?;xunQN7^(_8VHRLo!cH@MBKV;r62_j~B zM=T0>fjEA0*8~kby*hFoHq4(&zlsE#Q7I@aRji2I?2_TE1+}0yT{Eb}~va-Hv z4TC^CGszIamIV_a?G-FGs)HDs&hd!t zJY`%SUNr^La!L28!!KL3tt~rEF&uz@8D86Cx3_r5EAmHxSqSU~SDDvL;1&4A?xUUA zBa*&f1h8S+Wf4mX=al2Hjo_Ps^=|`&`(te=G{acr7gEa$`N3;he{5d|z zyWG-#{zO+`09RTaGH5Ri$N&3l&cG5~%(LV6?;MQSgv_=!`=zBU-jrv%Pe_-VazjI< z*1E4-XEo>PCL1#bHT?0mm@?dbc9WxCV`?4d?-YAPDbNpwES2gx-J|9tkLes*iX+Sq zo1KFmQmSfv@H^v!R;Gu+Xxa1#bf2O$oa?4P@F0`L{%!mM_)+@#IYBjHdf*tZn7jOI zevMTXh!-s|drYP+o=~fze$rSxm-A|kU7SbF+v$S%JwV`gG5*!$q!yHDBagqAL9%Re zWT4tHEYwv}ry2v`yV9wc6xdhNy{CVB|7O>E2oEiU27biV9PBEU26JC}N zf{#sguixj;{mBT{)%5I}ImuSO7*a^$cU9|`C(92f5~a}9eKnfq)V8)xx$VSx{xfo} zOW+*_^DF*E;*t6rlXWO9!nk0S0FovH^lipSk-ww0y#YrT$Y6gp$;xqXF$Q+J`NT09lF&{3-cy0@KaYf z##Pzk9j9vjLf*NBmU0t-mq( zr(+UxJcH;!Np40|tXk9LM_Xwu-cce;#5o2wI-3*8mbwl7#aELW?th$ey^nN-YCGf! zKs45Rp5~Ay7nILB)uDd2t;cBmyQG`KXk#Q0=(25tDyANv76}7xNU1hQ0|S1!nKTnfs$0Z!(p6Y55;$p zBcSU{&;o`snPALhA&sukU)7l@dl>eU175cSkoFh|eEr65m*03`ZoB)|Tls~~ICoR~ z&13Z~@Wy_i$mQ0jS#hx~ZE&GSb@0uwLOjBiX-HPqQvZBVc;ag;-MozXuX?L&-*Ach zcJzo(A7m}8s?Zp``cNx3m)LqH-*BoTN zG;wl3sRoO0M&#-?Vz0%T=!g{F2WBu};Y^S~Iib~{SFO&?00okU(ZvL|cy?`YT`w?z zP~M5azGG;q*y2dy^(0_Bd1KhW!GH*!yw5y;Ooq%09jJ5DG^<{snuvwv!8?nnI>VY` z`5>PyQc_Vzk~bhus!4YqEn%8|$?(Uy$*sHxbA`DS3?pc0(bhizKcJMdwBTXmj>-gp;!u4L3-% zI300xI`iwIaXx3iLSM(8A4)E^FGD=5Pqzpe%5!Sew$41VcKTnK{F@T&-h}J}4Y=p_ zDBvy=(OX(Jmc-^HMA#3=4t%&3FQajH(Ny+l4kuaL7ZNj9O=wAWaq?^Duo&VcxM)eh zAXw_xVGaZnWm5e8c)L>M90lfKiDE_PA*8A3l7vB%lxFhYr9K+5=N_-HrN5aoQ%sSr~pE9~hHpPS*a ze!{^2oal%+|AkCFvU{#u8(j6Y@^N-KnrL``9BiiCRhn@A|9PRgj zt^~CIX;wS*rC|oVXN&K=jcoXxQ5NbbSQf2Wg{O0!9(>q?t6k@jAj!n9Lx6%X80FIy z=HzMjIHJ~QieFQ#>sji&1Ba%90}%N-UR6cXpnjWjdkB=u;b(C5Q7R0ZR>{M*!g+w6!F3dI6zHsNU1O4k zfO_JssP3}#AUQ~%F3^fEv&FV&uiJjUTe$)oDcj{Jw9i)uJ)acmf~(JKbFYK!cJ?83 zpF0H1X|JG8k8G5^{976p6yJEB<7GyCcQYRG;C zqEjg4Mf0N3wua=9P?DQesuvyIU$Z8MYgI>(=QhNM)RAbQQua)9NsA6c2l8lEtTww1 z#eE=^b5_`3N~K8#i#x`e_CoK4idbZIl$=wrHd$>*r==Sk`xTBYTzQgz9%L)xG;(g##QoJn zkB?TQ?|&w%oSZIAzFqDPnV1~brG9PNRFoI&()gyKnL)|?HXOp^@ANb3SfJrTj*qj* zxxnZh*sY6tn;A0^vDEUnE(o)>)29C#)|$OdT?a?Ie6`y>y;~XAdT%YC;$zS6bn!FVEx%K71K9GyDIZXq`E(p)G!Ori-Soh`V*f`^*gnL(jNRYP%`3xlL>;% z#C|cgH6t)!4+iB50X0x=RHSQyq9Cnt46*}zV?Dz)a8rPVLQ;`>DCDBzz4(fn_-Mz) zE3ByC)aNvLX^2bpktZagF&uOf1-oJxS82X(r^G&B>kR(itZg_Ef75DGs0_T`VTnTq zt1EXPHcq`$3oax&qH7aD4L05DUAGYr&mr>;R}Y**y+SVvn}k@Q29-sZ0gryFr_M&B z#5I(xUQ&xmN2QTK_#E_F*2YtHS!ve{%p4^APf!2t+#6+>CdNWQwvzTBc zrQWDXOTnxKd>??wLr-?ojFyw^*kT(GWu{H3ePAFuNBoOm&KQ(Ev{6OV*PuAKXQQ#2 zD~<>yQug#TJ1JHg%CH<$CWJZ&S{K%>VL{08($Vq|F+FC)WX1UCfhLPqf$8zJq1CNz zBY^~<7%tF$M=C7qRvT63)=mnazHz%f4RSA318E&tipQ=QCX{Wzs+7kjP~dZS}glj*MSj)2|`X=>k6pAy^Np?4J-h4r1ce@_Uv zvb#@ejOwQ_&OD`@y_rwFXT1yE0C4)tuWYw~J3bvz&<}!t(ztkza09^l2K0Lz-5`sT zcS0wvnO8l@xaWD&Xp`h65{XdY^W zSn*_?Dqu>&)*2bZ_kz2UCWLwB23bMu%5k~Mn;7ra_>O@^Ei^beaUNShY@|?hr%E#vdiMN^1RA7k zk(Aw*LEK}8t&9V|X5iS!7g_N_&;a^%Uo5IeP;{`t^T(3qRdy8}TrN-GHw_l)+M4wX z=wH;QmYqxN=no3>rL#M6^>7o>6`ogv!vo<%IRXbl$vG!{MPNLkcpm zmYAg*(d}+$JS++$JVK)a1*uVeuAh#=%m%E9%bwX7-ai!$f+#3cZUBnS!J-JS@?wz@7A9so2Bv~-2y2Gl<8JvN z%bAz8FbrBkieKRxG+|D?0bIAv0QXe^TBcjusy9@MFS-ixl;3nniT zZ7<(Ow-k3oe)6iI}th#ZL^#<1DssOyA#;EW}r!!XX9_BoW;&=7) zGh6&l>LxkncM0eDBT8Z_k16SgEF<F4X3x2>b7W4 z9csq?0DeT38zv-Wyc8^f6?n0IP7*vu{jaE-4Yl#I_0eTO!*}AR9al8iM8#0?JntuR zNmljqT}0{Hf-N%o2X0OP9rJn|*g}F(RcQd4(wR?21xauG zz8UvhU?;Azc)iIi+Vr~z3f1p@M^lJS2OQB->RXOoObYQm+`2VsHb|4h6`5YXLg#j< z3NvNHi&U6fUwA&W6}inn3y$G1zt+c?Qqw>=6(tN9a-EY;buHzV3SxG}mQ z8$vGq*Q?DB8?Ps|0y*7LGc zs~5h(xXaKqJHM>p;p&EWc)^%Fl7T&KLvysGJw_@eW%k=K@-}R^oTCl2T%NK+uwoP&qy?K0Y?IXZL80 zoVKriSn8!y8X0*E*(`KBMg3Y_#iIJjc%)JDC;w`oKov`x7g+U1{Kh_#QNQu7w>7y) zl?wL~m>s8`p7c-`hDGoxlwSDw@M=Hia*7nuQ4(2VbOQ3Pt}Jyxpj3Oera>;8K`X_8 z@wPyQR4jDYngy2R=r3BZ%s&xJwn_@I`KO&-(5zFLVKkJi`rC_r$i4^1hFAh+ycrCG zAGWT8*}Kw+&YYH;j=Ur7AO{-}#zc01)|1szNl1a<2l#9S z`3YqCMYi20ReOJl$a!D;X!q4>nUV{B%)Oh_YSkh+D^`JH8nQfM7(A0z<7{fGAIZL~ zfW4d92HJ?+SR3iRdgd;c_jJ!a5-o7q&nvp=Y z%DFSHZlXVHT|*Vnu~Sh`eQ6tGXKWDQ<=&GtE}q%64aM&e`u~<*f!3=33k3JrBfUn5 z0yWn}KJIx=8PFH{a+5%7+COhkB>}4+B_Lk{dMted6X?P3*-Cf* z*aHeDpAapG*l+>iqZLJxFyBTg(!;G)Cut|t1(L>?5tAy+o!PUrnYf`$dcDlA5N?_W zbNh(Ck=1*?09p^H-kP+Ss;aWOQpwanoCz(+-Q$ZP7_UZ@JdP@vtWmgp1}mD_UZwj_ z{bn`v$=+KdmmU^Z$r4l_rnd?4Yu5^8x(XQDL)D zgFj*p#t$JX2SY9%VWJj;E-ay<^na`AtXT8ST=;P7+5!dXfezaR6q$t_y9p#2i7Yz} zHd&26eGo()kWAkfRGpVxz3FG27;irxc3q!-`T#-nLqzO@O3cGd-UU#MM^v1Jn6JlN zyb53pOJp32s4oLHp9a~dCp)f2-G4gBFCJ(DW0nkCG;(FpwT@m4LM9YorX2o8LWS5kSEB59~pZKt=Z-+=3E?OXn~Ag&~Wa-oO6`oG(rjKXC+AELlB&{s>k$ZdJc= z30FFKwSRUD3lz|YKmilOUoeM311C$6For@EJ)_sKhC>%ij}S40R4!d3SFwauEn~}& zF@;t+eQnpVg;zTZ4+QE@D2IXLFPzV)hLgij5>KgwmECVt&!L5aO1U$zJ>J-m85?EXa~UTWx)Xo2|Xg)9uYJpujxn&_w|JLrMhmHUsUZJr5s5 zA-hCl7e!@%~VukK$60f;F7k2CoMROLn3 z#RlBfhd_piMCQrJhKkIVi%|QFROijuhmPEr_g@5{#1tXOBqD!kMxhHxs4GgaON_Z{ zPQmq2$jwvGjg{G~SK$wt=`ULFPo4Q}p)t@{fdm->4)zZ8Z|RNNz9;?vizV_)s45fx z|FL$8POsbT^YFYK!2iD?|9p-X9r_wAhaNr%qV@?U?(!@xmo?=1yRV!zXs`UjA|x=W9je*e`j)AOt}OLIlMB$f*x| zRG|2#IrxuX7B}M}Engo?!v7JthI-bKw2vbj(VY_7Xeiay&36vlNKIme_KiPA7rS2^ zgxYBp73bBhZa-#z{d5WcANkMF{~e!ES3+4?xf>Tk^0 zd4!tZ`J(YL*Sj}9-2ajhVzp$CV=CkKA5RU(F$@g}CNTcT)<&@74997fqmE3A zZ#s!YLo<=C!_1E@f>9h2iG=@N<3AN$lxm(~*43maqZvp31N>jI@TiXtwia7Jk<>eb?N7@9y4wJ7R$Q z|HmueFP&BMP}4G(+(C9FtI@`Ah4y6-eZ>L;)%3`(Xh4!yCbWDJiI^sL+=Gp?mg$d-h>rne!(~8=JKM;MXmc3w5kFTdX%( ze-LshUqcHE3knyfS&%?x+7SF8|3kl@E#JaUgJ?_Jw345l&{DWNI2Z>dP02PJPeY+@E!PQ8C8 L9dbwN`w;&hrg=M9 literal 0 HcmV?d00001 diff --git a/easy_gantt/assets/images/.gitkeep b/easy_gantt/assets/images/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/easy_gantt/assets/images/yt.png b/easy_gantt/assets/images/yt.png new file mode 100644 index 0000000000000000000000000000000000000000..7c752df0c42df7ac7f86ad1385a6959cdc5999aa GIT binary patch literal 763 zcmV~IaFWTHGxI*r`~B+o zl@W+J;GEzXW{B8lvsYr;W(JrN08AMWWD8_6@X#<8JOv(EYym%EU0%3z_r88x8NFot zHfSV)F#sx`vIO-SS4~)yqd~$3wL(Sy7sESE4xa{(TG{6=_Z~YA6RrOG?pB2jFcO?; za`*$uJ2*PSLYIiZ(W_a@Cjb?de&x5O9zfRg-?A*ar7jZG9+(zrYULB&n^x5hw-+Vh0@2{;V^B_fQSWIve#lu0$Q7q1tIGbEaa|PsH_B9yr z+L0sEhd%jq*(-1T++wzKm9O7ilukFvhM-IP&Z;oBIu8J1JRWaexVZk;wQI{nygSKi zuezeGI**$ikqi(jWmZ$IUUTIzPAP4E{>9BdzWnNNv28%+*>5Bj)6`1!O(px70PHk|$3 zIV#5fLEr749}3?Fy*XjmizXiJ@Obuqr~3kY7qr^g-ZGp3p9NNddGHLduOeRVu?>pN t&h 255 * (stronger - 1) / stronger) { + strongColorArray.push(255 - (255 - component) * stronger); + } else { + strongColorArray.push(Math.floor(component / stronger)); + } + } + return "rgba(" + strongColorArray.join(", ") + ", 0.15)"; + } + + var backgroundColor = getBackgroundColor(); + return { + fullCanvasRender: false, + container: gantt.$task_bg, + renderer: true, + filter: gantt._create_filter(['_filter_task', '_is_chart_visible', '_is_std_background']), + lastItems: null, + lastPos: null, + svg: null, + lastElements: {}, + _render_bg_canvas: function (svg, items, limits) { + var rowHeight = gantt.config.row_height; + var cfg = gantt._tasks; + var widths = cfg.width; + var fullHeight = rowHeight * (limits.toY - limits.fromY); + var fullWidth = 0; + var width; + var partWidth; + for (var i = limits.fromX; i < limits.toX; i++) { + fullWidth += widths[i]; + } + svg.size(fullWidth, fullHeight); + svg.node.style.left = cfg.left[limits.fromX] + "px"; + svg.node.style.top = rowHeight * limits.fromY + "px"; + // -- CLEARING -- + svg.clear(); + // -- SELECTED -- + for (i = limits.fromY; i < limits.toY; i++) { + if (gantt.getState().selected_task == items[i].id) { + svg.rect(fullWidth, rowHeight).x(0).y((i - limits.fromY) * rowHeight).attr('fill', colors.selected); + break; + } + } + // -- HORIZONTAL LINES -- + var commands = []; + var lineCmd = "l " + fullWidth + " 0"; + for (i = 1; i <= limits.toY - limits.fromY; i++) { + commands.push("M 0 " + (i * rowHeight - 0.5)); + commands.push(lineCmd); + } + // -- VERTICAL LINES -- + partWidth = -0.5; + lineCmd = "l 0 " + fullHeight; + for (i = limits.fromX; i < limits.toX; i++) { + width = widths[i]; + if (width <= 0) continue; //do not render skipped columns + partWidth += width; + commands.push("M " + partWidth + " 0"); + commands.push(lineCmd); + } + svg.path(commands.join("")).attr("stroke", colors.line); + + if (gantt.config.scale_unit === "day") { + var weekendGroup = svg.group().attr('fill', backgroundColor); + if (ysy.settings.resource.open) { + // -- USER WEEKENDS BACKGROUND -- + partWidth = 0; + + for (i = limits.fromX; i < limits.toX; i++) { + width = widths[i]; + var top = 0; + var mDate = moment(cfg.trace_x[i]); + var iDate = mDate.format("YYYY-MM-DD"); + var lastWeekend = false; + var firstEntity = items[limits.fromY]; + if (firstEntity.type !== "assignee") { + var assignee = ysy.data.assignees.getByID(firstEntity.widget.model.assigned_to_id || "unassigned"); + if (assignee) { + lastWeekend = assignee.getMaxHours(iDate, mDate) === 0; + } + } + for (var j = limits.fromY; j < limits.toY; j++) { + if (items[j].type !== "assignee") continue; + var hours = items[j].widget.model.getMaxHours(iDate, mDate); + if ((hours === 0) === lastWeekend) continue; + if (lastWeekend) { + weekendGroup.rect(width, (j - limits.fromY) * rowHeight - top).x(partWidth).y(top); + } else { + top = (j - limits.fromY) * rowHeight; + } + lastWeekend = !lastWeekend; + } + if (lastWeekend) { + weekendGroup.rect(width, (limits.toY + 1) * rowHeight).x(partWidth).y(top); + } + partWidth += width; + } + } else { + // -- WEEKENDS BACKGROUND -- + if (!cfg.weekends) { + cfg.weekends = []; + for (var d = 0; d < cfg.trace_x.length; d++) { + cfg.weekends.push(!gantt._working_time_helper.is_working_day(cfg.trace_x[d])); + } + } + partWidth = 0; + for (i = limits.fromX; i < limits.toX; i++) { + width = widths[i]; + if (cfg.weekends[i]) { + weekendGroup.rect(width, fullHeight).x(partWidth); + } + partWidth += width; + } + + } + } + if (ysy.settings.resource.open) { + // -- ASSIGNEE BACKGROUND -- + var assigneeGroup = svg.group().attr('fill', backgroundColor); + for (i = limits.fromY; i < limits.toY; i++) { + if (items[i].type === "assignee") { + assigneeGroup.rect(fullWidth, rowHeight).y((i - limits.fromY) * rowHeight); + } + } + // -- DARK LIMITS -- + var darkLimitGroup = svg.group().attr('fill', backgroundColor.replace("0.2)", "0.3)")); + var ganttLimits = ysy.data.limits; + if (ganttLimits.start_date) { + var left = gantt.posFromDate(ganttLimits.start_date) - gantt.posFromDate(cfg.trace_x[limits.fromX]); + if (left > 0) { + darkLimitGroup.rect(left, fullHeight); + } + } + if (ganttLimits.end_date) { + var right = gantt.posFromDate(ganttLimits.end_date) - gantt.posFromDate(cfg.trace_x[limits.fromX]); + if (right > 0 && right < fullWidth) { + darkLimitGroup.rect(fullWidth - right, fullHeight).x(right); + } + } + } + // -- BLUE LINE -- + if (gantt.config.scale_unit === "day") { + partWidth = -0.5; + commands = []; + lineCmd = "l 0 " + fullHeight; + for (i = limits.fromX; i < limits.toX; i++) { + width = cfg.width[i]; + var first = moment(cfg.trace_x[i]).date() === 1; + if (first) { + commands.push("M " + partWidth + " 0"); + commands.push(lineCmd); + } + partWidth += width; + } + svg.path(commands.join("")).attr("stroke", colors.line_month); + } + }, + render_bg_line: function (canvas, index, item) { + + }, + render_item: function (item, container) { + ysy.log.debug("render_item BG", "canvas_bg"); + }, + render_items: function (items, container) { + ysy.log.debug("render_items FULL BG", "canvas_bg"); + container = container || this.node; + if (items) { + this.lastItems = items; + } else { + items = this.lastItems; + if (!items) return; + } + if (this.fullCanvasRender) { + this.render_full_svg(items, container); + } else { + this.render_shrunken_svg(items, container); + } + var fullHeight = gantt.config.row_height * items.length; + container.style.height = fullHeight + "px"; + var lastEvent; + $(container) + .off("mousedown.bg") + .on("mousedown.bg", function (e) { + lastEvent = e; + }) + .off("click.bg") + .on("click.bg", function (e) { + if (!lastEvent) return; + if (Math.abs(lastEvent.pageX - e.pageX) > 2 || Math.abs(lastEvent.pageY - e.pageY) > 2) return; + var order = gantt._order; + var offsetTop = $(container).offset().top; + var index = Math.floor((e.pageY - offsetTop) / gantt.config.row_height); + if (index < 0 || index >= order.length) return; + var taskId = order[index]; + if (!gantt.isTaskExists(taskId)) return; + if (gantt._selected_task == taskId) { + gantt.unselectTask(); + } else { + gantt.selectTask(taskId); + } + }) + }, + render_shrunken_svg: function (items, container) { + var cfg = gantt._tasks; + var scrollPos = gantt.getCachedScroll(); + // var nodeWidth = this.node.innerWidth; + // if(scrollPos.x > Math.max(nodeWidth - window.innerWidth, 0)){ + // scrollPos.x = gantt.$task.scrollLeft; + // } + this.lastPos = scrollPos; + //ysy.log.debug("render_one_canvas ["+scrollPos.x+","+scrollPos.y+"]","canvas_bg"); + var rowHeight = gantt.config.row_height; + var colWidth = cfg.col_width; + var countX = cfg.count; + var countY = items.length; + var limits = this.getCanvasLimits(); + var partCountX = Math.ceil(limits.x / colWidth), + partCountY = Math.ceil(limits.y / rowHeight); + var startX = Math.max(scrollPos.x - (limits.x - window.innerWidth) / 2, 0); + var startY = Math.max(scrollPos.y - (limits.y - window.innerHeight) / 2, 0); + var startCountX = Math.floor(startX / colWidth); + var startCountY = Math.floor(startY / rowHeight); + if (startCountX + partCountX > countX) { + startCountX = countX - partCountX; + } + if (startCountY + partCountY > countY) { + startCountY = countY - partCountY; + } + var svg = this.svg; + if (!svg) { + svg = this._createSvg(container); + } + this._render_bg_canvas(svg, items, { + fromX: Math.max(startCountX, 0), + toX: startCountX + partCountX, + fromY: Math.max(startCountY, 0), + toY: startCountY + partCountY + }); + }, + render_full_svg: function (items, container) { + ysy.log.debug("render_items FULL BG", "canvas_bg"); + var cfg = gantt._tasks; + var countX = cfg.count; + var countY = items.length; + var svg = this.svg; + if (!svg) { + svg = this._createSvg(container); + } + this._render_bg_canvas(svg, items, { + fromX: 0, + toX: countX, + fromY: 0, + toY: countY + }); + }, + _createSvg: function (container) { + var svg = SVG(container); + $(svg.node).css({position: "absolute"}); + this.svg = svg; + return svg; + }, + switchFullRender: function (fullRender) { + if (this.fullCanvasRender === fullRender) return; + this.fullCanvasRender = fullRender; + this.render_items(); + }, + isScrolledOut: function (x, y) { + if (this.fullCanvasRender) return false; + if (this.forceRender) return true; + var lastPos = this.lastPos; + if (!lastPos) return true; + var limits = this.getCanvasLimits(); + if (x !== undefined) { + var bufferX = (limits.x - window.innerWidth) / 2; + if (Math.abs(x - lastPos.x) > bufferX) return true; + } + if (y !== undefined) { + var bufferY = (limits.y - window.innerHeight) / 2; + if (Math.abs(y - lastPos.y) > bufferY) return true; + } + }, + getCanvasLimits: function () { + return {x: window.innerWidth + 600, y: window.innerHeight + 600}; + } + }; +}; diff --git a/easy_gantt/assets/javascripts/easy_gantt/bars.js b/easy_gantt/assets/javascripts/easy_gantt/bars.js new file mode 100644 index 0000000..b10fde7 --- /dev/null +++ b/easy_gantt/assets/javascripts/easy_gantt/bars.js @@ -0,0 +1,278 @@ +/* bars.js */ +/* global ysy */ +window.ysy = window.ysy || {}; +ysy.view = ysy.view || {}; + +ysy.view.bars = ysy.view.bars || {}; +$.extend(ysy.view.bars, { + _dateCache: {}, // for faster parsing YYYY-MM-DD to moment + _rendererStack: {}, + + registerRenderer: function (entity, renderer) { + if (this._rendererStack[entity] === undefined) { + this._rendererStack[entity] = []; + } + var renderers = this._rendererStack[entity]; + var found = false; + for (var i = 0; i < renderers.length; i++) { + if (renderers[i] === renderer) found = true; + } + if (found) return; + renderers.push(renderer); + this.reconstructRenderer(entity); + }, + removeRenderer: function (entity, renderer) { + var renderers = this._rendererStack[entity]; + if (!renderers) return; + for (var i = 0; i < renderers.length; i++) { + if (renderers[i] === renderer) { + renderers.splice(i, 1); + this.reconstructRenderer(entity); + return; + } + } + }, + reconstructRenderer: function (entity) { + var renderers = this._rendererStack[entity]; + if (renderers.length === 0) { + gantt.config.type_renderers[entity] = gantt._task_default_render; + return; + } + gantt.config.type_renderers[entity] = function (task) { + var i = renderers.length - 1; + var nextRenderer = function () { + if (i < 0) return gantt._task_default_render; + return renderers[i--]; + }; + return nextRenderer().call(this, task, nextRenderer); + } + }, + + getFromDateCache: function (allodate) { + var alloMoment = this._dateCache[allodate]; + if (alloMoment === undefined) { + alloMoment = moment(allodate); + this._dateCache[allodate] = alloMoment; + } + return alloMoment; + }, + insertCanvas:function (canvas,rootDiv) { + var taskLeftElements = rootDiv.getElementsByClassName("task_left"); + if (taskLeftElements.length === 0) { + rootDiv.appendChild(canvas); + } else { + rootDiv.insertBefore(canvas, taskLeftElements[0]); + } + }, + canvasListBuilder: function () { + return { + __proto__: this.canvasListPrototype + }; + //return canvasList; + }, + canvasListPrototype: { + limit: 8170, + build: function (task, gantt, start_date, end_date) { + // initialization + this.canvases = []; + this.contexts = []; + this.starts = []; + this.gantt = gantt; + this.isAssignee = task.type === "assignee"; + this.el = null; + this.height = this.isAssignee ? gantt.config.row_height : gantt._tasks.bar_height; + this.columnWidth = gantt._tasks.col_width; + + var startX = gantt.posFromDate(start_date || task.start_date); + var endX = gantt.posFromDate(end_date || task.end_date); + //var fullWidth = gantt._get_task_width(task); + var fullWidth = endX - startX; + this.startX = startX; + this.fullWidth = fullWidth; + var config = gantt._tasks; + + if (fullWidth < this.limit) { + this.el = this.createCanvas(fullWidth); + this.starts.push(startX); + this.staticPack = { + ctx: this.contexts[0], + canvas: this.el, + start: startX, + end: startX + fullWidth + }; + } else { + this.el = document.createElement("div"); + var lefts = config.left; + for (var i = 0; i < lefts.length; i++) { + if (lefts[i] >= startX) break; + } + var partX = startX; + for (; i < lefts.length; i++) { + if (lefts[i] > fullWidth + startX) break; + if (lefts[i] >= partX + this.limit) { + var canvas = this.createCanvas(lefts[i - 1] - partX); + this.el.appendChild(canvas); + this.starts.push(partX); + partX = lefts[i - 1]; + } + } + canvas = this.createCanvas(startX + fullWidth - partX); + this.el.appendChild(canvas); + this.starts.push(partX); + + } + this.el.className += " gantt-task-bar-line"; + if (this.isAssignee) { + var y = this.gantt.getTaskTop(task.id); + this.el.style.left = startX + "px"; + this.el.style.top = y + "px"; + } + }, + createCanvas: function (width) { + var el = document.createElement("canvas"); + //var height = this.gantt._tasks.bar_height; + width = Math.round(width); + el.style.width = width + "px"; + el.width = width; + el.height = this.height - 1; + el.className = "gantt-task-bar-canvas"; + this.canvases.push(el); + var ctx = el.getContext("2d"); + ctx.textAlign = 'center'; + ctx.textBaseline = 'middle'; + this.contexts.push(ctx); + return el; + }, + getElement: function () { + return this.el; + }, + inRange: function (date) { + var pos = gantt.posFromDateCached(date); + return pos + this.columnWidth >= this.startX && pos < this.fullWidth + this.startX; + }, + fillTextAt: function (date, text, styles) { + var x = gantt.posFromDateCached(date); + var pack = this.getPack(x); + var posPack = this.getPosPack(x, pack); + var ctx = pack.ctx; + if (styles.backgroundColor) { + this.fillRectAtPosPack(posPack, pack, styles.backgroundColor); + } + ctx.font = styles.fontStyle; + ctx.fillStyle = styles.textColor; + text = this.fitTextInWidth(text, posPack.width, ctx); + ctx.fillText(text, posPack.middle, this.height / 2 + 1); + }, + fillFormattedTextAt: function (date, formatter, value, styles) { + var x = gantt.posFromDateCached(date); + var pack = this.getPack(x); + var posPack = this.getPosPack(x, pack); + var ctx = pack.ctx; + if (styles.backgroundColor) { + this.fillRectAtPosPack(posPack, pack, styles.backgroundColor, styles.shrink); + } + ctx.font = styles.fontStyle; + ctx.fillStyle = styles.textColor; + var text = formatter(value, posPack.width); + text = this.fitTextInWidth(text, posPack.width, ctx); + ctx.fillText(text, posPack.middle, this.height / 2 + 1); + }, + fillTwoTextAt: function (date, textUpper, textBottom, styles) { + var x = gantt.posFromDateCached(date); + var pack = this.getPack(x); + var posPack = this.getPosPack(x, pack); + var ctx = pack.ctx; + if (styles.backgroundColor) { + this.fillRectAtPosPack(posPack, pack, styles.backgroundColor, styles.shrink); + } + var bottomLine = this.height / 2 + 1; + ctx.fillStyle = styles.textColor; + if (textUpper) { + ctx.font = styles.fontStyle.replace("12px", "9px"); + bottomLine = bottomLine * 13.0 / 10; + textUpper = this.fitTextInWidth(textUpper, posPack.width, ctx); + ctx.fillText(textUpper, posPack.middle, this.height * 3 / 12); + } + if (textBottom) { + ctx.font = styles.fontStyle; + // ctx.fillStyle = styles.textColor; + textBottom = this.fitTextInWidth(textBottom, posPack.width, ctx); + ctx.fillText(textBottom, posPack.middle, bottomLine); + } + }, + fillRectAtPosPack: function (posPack, pack, fillColor, shrink) { + pack.ctx.fillStyle = fillColor; + if (shrink) { + pack.ctx.fillRect(posPack.start + 1, 1, posPack.width - 3, this.height - 3); + } else { + pack.ctx.fillRect(posPack.start, 0, posPack.width, this.height); + } + }, + roundTo1: function (number) { + if (number === undefined) return ""; + var modulated = number % 1; + if (modulated < 0) { + modulated += 1; + } + if (modulated < this.MARGIN || modulated > (1 - this.MARGIN)) { + return number.toFixed(); + } + return number.toFixed(1); + }, + fitTextInWidth: function (text, width, ctx) { + width -= 2; + if (text.length * 7.2 < width) return text; + ctx.font = ctx.font.replace("12px", "9px"); + if (text.length * 5.3 < width) return text; + var splitPos = Math.floor(width / 5.3 - 1); + return text.substring(0, splitPos) + "#"; + }, + getPosPack: function (x, pack) { + var start, end, width = this.columnWidth; + if (!pack) return null; + start = x - pack.start; + if (x < pack.start || x > pack.end - width) { + end = start + width + Math.min(0, pack.end - width - x); + start = Math.max(start, 0); + return { + start: start, + end: end, + middle: Math.floor((start + end) / 2), + width: end - start + }; + } else { + return { + start: start, + end: start + width, + middle: Math.floor(start + width / 2), + width: width + }; + } + }, + getPack: function (pos) { + if (this.staticPack) return this.staticPack; + //var pos = gantt.posFromDateCached(date); + // var min = this.startX; + var mid = pos + this.columnWidth / 2; + // if (pos + width < min) return null; + // if (pos >= this.fullWidth + min) return null; + + for (var i = 1; i < this.starts.length; i++) { + if (this.starts[i] > mid) break; + } + if (i >= this.starts.length) { + i = this.starts.length; + var end = this.startX + this.fullWidth; + } else { + end = this.starts[i]; + } + + return { + ctx: this.contexts[i - 1], + canvas: this.canvases[i - 1], + start: this.starts[i - 1], + end: end + }; + } + } +}); diff --git a/easy_gantt/assets/javascripts/easy_gantt/collapsor.js b/easy_gantt/assets/javascripts/easy_gantt/collapsor.js new file mode 100644 index 0000000..83892ae --- /dev/null +++ b/easy_gantt/assets/javascripts/easy_gantt/collapsor.js @@ -0,0 +1,148 @@ +/* collapsor.js */ +/* global ysy */ +window.ysy = window.ysy || {}; +ysy.pro = ysy.pro || {}; +ysy.pro.collapsor = ysy.pro.collapsor || {}; +$.extend(ysy.pro.collapsor, { + templateHtml: null, + patch: function () { + var $sourceDiv = $("#close_all_something"); + this.templateHtml = '
' + $sourceDiv.html() + '
'; + $sourceDiv.remove(); + + }, + extendees: [ + { + id: "close_all_parent_issues", + + bind: function () { + this.model = ysy.data.limits; + }, + func: function () { + var openings = this.model.openings; + var issues = ysy.data.issues.getArray(); + this.model.parentsIssuesClosed = !this.model.parentsIssuesClosed; + if (this.model.parentsIssuesClosed) { + for (var i = 0; i < issues.length; i++) { + openings[issues[i].getID()] = false; + } + } else { + for (i = 0; i < issues.length; i++) { + delete openings[issues[i].getID()]; + } + } + this.model._fireChanges(this, "close_all_parent_issues"); + return false; + }, + isOn: function () { + return this.model.parentsIssuesClosed; + } + }, + { + id: "close_all_milestones", + bind: function () { + this.model = ysy.data.limits; + }, + func: function () { + var openings = this.model.openings; + var milestones = ysy.data.milestones.getArray(); + this.model.milestonesClosed = !this.model.milestonesClosed; + if (this.model.milestonesClosed) { + for (var i = 0; i < milestones.length; i++) { + openings[milestones[i].getID()] = false; + } + } else { + for (i = 0; i < milestones.length; i++) { + delete openings[milestones[i].getID()]; + } + } + this.model._fireChanges(this, "close_all_milestones"); + return false; + }, + isOn: function () { + return this.model.milestonesClosed; + } + }, + { + id: "close_all_projects", + bind: function () { + this.model = ysy.data.limits; + }, + func: function () { + var openings = this.model.openings; + var projects = ysy.data.projects.getArray(); + this.model.projectsClosed = !this.model.projectsClosed; + if (this.model.projectsClosed) { + for (var i = 0; i < projects.length; i++) { + if (projects[i].id === ysy.settings.projectID) continue; + delete openings[projects[i].getID()]; + // gantt.close(projects[i].getID()); + } + } else { + for (i = 0; i < projects.length; i++) { + if (!projects[i].needLoad) { + openings[projects[i].getID()] = true; + } + //gantt.open(projects[i].getID()); + } + } + this.model._fireChanges(this, "close_all_projects"); + return false; + }, + isOn: function () { + return this.model.projectsClosed; + } + } + ] +}); +//############################################################################################# +ysy.view.Collapsors = function () { + ysy.view.Widget.call(this); +}; +ysy.main.extender(ysy.view.Widget, ysy.view.Collapsors, { + name: "CollapsorsWidget", + _postInit:function(){ + this.model = ysy.settings.resource; + this.model.unregister(this); + this.model.register(this.requestRepaint,this); + }, + _updateChildren: function () { + for (var i = 0; i < this.children.length; i++) { + this.children.destroy(); + } + this.children = []; + var collapsorClass = ysy.pro.collapsor; + for (i = 0; i < collapsorClass.extendees.length; i++) { + var extendee = collapsorClass.extendees[i]; + var button = new ysy.view.Button(); + $.extend(button, extendee); + button.init(); + this.children.push(button); + } + }, + repaint: function (force) { + var $target = $("#gantt_grid_collapsors"); + if(this.repaintRequested){ + if(this.model.open){ + $target.hide(); + return; + }else{ + $target.show(); + } + $target.off("click").on("click",function(){ + return false; + }); + } + for (var i = 0; i < this.children.length; i++) { + var child = this.children[i]; + child.$target = this.getChildTarget(child); + if(!child.$target.length) continue; + child.repaint(force || this.repaintRequested); + } + this.repaintRequested=false; + }, + getChildTarget: function (child) { + return this.$target.find("#" + child.elementPrefix + child.id); + } +}); + diff --git a/easy_gantt/assets/javascripts/easy_gantt/data.js b/easy_gantt/assets/javascripts/easy_gantt/data.js new file mode 100644 index 0000000..92a4fa3 --- /dev/null +++ b/easy_gantt/assets/javascripts/easy_gantt/data.js @@ -0,0 +1,1046 @@ +/* data.js */ +/* global ysy */ +window.ysy = window.ysy || {}; +ysy.data = ysy.data || {}; +ysy.data.Data = function () { + this._onChange = []; + this._deleted = false; + this._created = false; + this._changed = false; + this._cache = null; +}; +ysy.data.Data.prototype = { + permissions: null, + problems: {}, + init: function (obj, parent) { + this._old = obj; + $.extend(this, obj); + this._parent = parent; + this._postInit(); + return this; + }, + _postInit: function () { + }, + set: function (key, val) { + // in the case of object as a first parameter: + // - parameter key is object and parameter val is not used. + if (typeof key === "object") { + var nObj = key; + } else { + nObj = {}; + nObj[key] = val; + } + var rev = {}; + for (var k in nObj) { + if (!nObj.hasOwnProperty(k)) continue; + var nObjk = nObj[k]; + var thisk = this[k]; + if (nObjk !== thisk) { + if (ysy.main.isSameMoment(thisk, nObjk)) { + ysy.log.debug("date filtered as same", "set"); + continue; + } + rev[k] = thisk; + if (rev[k] === undefined) { + rev[k] = null; + } + } else { + ysy.log.debug(k + "=" + nObjk + " filtered as same", "set"); + } + } + if ($.isEmptyObject(rev)) { + return false; + } + rev._changed = this._changed; + $.extend(this, nObj); + this._fireChanges(this, "set"); + ysy.history.add(rev, this); + this._changed = true; + return true; + }, + register: function (func, ctx) { + for (var i = 0; i < this._onChange.length; i++) { + var reg = this._onChange[i]; + if (reg.ctx === ctx) { + this._onChange[i] = {func: func, ctx: ctx}; + return; + } + } + this._onChange.push({func: func, ctx: ctx}); + }, + unregister: function (ctx) { + var nonch = []; + for (var i = 0; i < this._onChange.length; i++) { + var reg = this._onChange[i]; + if (reg.ctx !== ctx) { + nonch.push(reg); + } + } + this._onChange = nonch; + }, + setSilent: function (key, val) { + if (typeof key === "object") { + var different; + var keyk, thisk; + for (var k in key) { + if (!key.hasOwnProperty(k)) continue; + keyk = key[k]; + thisk = this[k]; + if (keyk === thisk)continue; + if (ysy.main.isSameMoment(thisk, keyk)) continue; + this[k] = keyk; + different = true; + } + return different || false; + //$.extend(this, key); + } else { + if (this[key] === val) return false; + this[key] = val; + return true; + } + }, + _fireChanges: function (who, reason, onlyChildChanged) { + if (who) { + var reasonPart = ""; + if (reason) { + reasonPart = " because of " + reason; + } + if (who === this) { + var targetPart = "itself"; + } else { + targetPart = this._name; + } + var name = who._name; + if (!name) { + name = who.name; + } + if (!name) { + ysy.log.warning(who); + } + ysy.log.log("* " + name + " ordered repaint on " + targetPart + reasonPart); + + } + if (onlyChildChanged) { + var onChangeArray = this._onChildChange; + } else { + onChangeArray = this._onChange; + this._cache = null; + } + if (onChangeArray.length > 0) { + ysy.log.log("- " + this._name + (onlyChildChanged ? " on ChildChange" : " onChange") + " fired for " + onChangeArray.length + " widgets"); + } else { + ysy.log.log("- no " + (onlyChildChanged ? "childChange" : "change") + " for " + this._name); + } + for (var i = 0; i < onChangeArray.length; i++) { + var ctx = onChangeArray[i].ctx; + if (!ctx || ctx.deleted) { + onChangeArray.splice(i, 1); + continue; + } + //this.onChangeNew[i].func(); + ysy.log.log("-- changes to " + (ctx.name ? ctx.name : ctx._name) + " widget"); + //console.log(ctx); + onChangeArray[i].func.call(ctx, reason); + } + }, + remove: function () { + if (this._deleted) return; + var prevChanged = this._changed; + this._changed = true; + this._deleted = true; + if (this._parent && this._parent.isArray) { + this._parent.pop(this); + } + ysy.history.add(function () { + this._changed = prevChanged; + this._deleted = false; + if (this._parent && this._parent.isArray) { + this._parent._fireChanges(this, "revert parent"); + } + }, this); + this._fireChanges(this, "remove"); + }, + removeSilent: function () { + if (this._deleted) return; + this._deleted = true; + }, + clearCache: function () { + this._cache = null; + }, + getDiff: function (newObj) { + var diff = {}; + var any = false; + for (var key in newObj) { + if (!newObj.hasOwnProperty(key)) continue; + var newItem = newObj[key]; + if (newItem != this._old[key]) { + if (moment.isMoment(newItem)) { + if (newItem.format("YYYY-MM-DD") === this._old[key]) continue; + } + diff[key] = newObj[key]; + any = true; + } + } + if (!any) return null; + return diff; + }, + isEditable: function () { + if (!this.permissions) return false; + return !!this.permissions.editable; + }, + getProblems: function () { + if (this._cache && this._cache.problems !== undefined) { + return this._cache.problems; + } + var ret = []; + for (var problemType in this.problems) { + if (!this.problems.hasOwnProperty(problemType)) continue; + var res = this.problems[problemType].call(this); + if (res) ret.push(res); + } + if (ret.length === 0) { + ret = false; + } + if (!this._cache) { + this._cache = {}; + } + this._cache.problems = ret; + return ret; + } +}; +//###########################################################################################x +ysy.data.Array = function () { + ysy.data.Data.call(this); + this.array = []; + this.dict = {}; + this._onChildChange = []; +}; +ysy.main.extender(ysy.data.Data, ysy.data.Array, { + isArray: true, + get: function (i) { + if (i < 0 || i >= this.array.length) return null; + return this.array[i]; + }, + getArray: function () { + if (!this._cache) { + var cache = []; + for (var i = 0; i < this.array.length; i++) { + if (this.array[i]._deleted) continue; + cache.push(this.array[i]); + } + this._cache = cache; + } + return this._cache; + }, + getByID: function (id) { + if (id === undefined || id === null) return null; + var el = this.dict[id]; + if (el) return el; + for (var i = 0; i < this.array.length; i++) { + if (id === this.array[i].id) { + this.dict[id] = this.array[i]; + return this.array[i]; + } + } + }, + pushSilent: function (elem) { + if (elem.id) { + var same = this.getByID(elem.id); + if (same) { + var needFire = false; + if (same._deleted !== elem._deleted) { + needFire = true; + } + elem._onChange = same._onChange; + same.setSilent(elem); + same._fireChanges(this, "pushSame"); + if (needFire) { + this._fireChanges(this, "pushSame"); + } + return same; + } + } + if (!elem._parent) { + elem._parent = this; + } + elem.register(function () { + // registered for observing changes in own children + // do not propagate to full this._fireChanges() + this.orderFireChildChange(elem); + }, this); + this.array.push(elem); + if (elem.id) { + this.dict[elem.id] = elem; + } + return elem; + + }, + childRegister: function (func, ctx) { + for (var i = 0; i < this._onChildChange.length; i++) { + var reg = this._onChildChange[i]; + if (reg.ctx === ctx) { + this._onChildChange[i] = {func: func, ctx: ctx}; + return; + } + } + this._onChildChange.push({func: func, ctx: ctx}); + }, + push: function (elem) { + //var rev=this.array.slice(); + elem._changed = true; + elem._created = true; + elem = this.pushSilent(elem); + this._fireChanges(this, "push"); + ysy.history.add(function () { + //this.pop(elem); + this._deleted = true; + this._parent._fireChanges(this, "push revert"); + //this._fireChanges(this,"push revert"); + }, elem); + + }, + pop: function (model) { + //ysy.data.history.saveDelete(this); + if (model === undefined) { + return false; + } + if (!model._deleted) { + model.remove(); + return true; + } else { + + } + this._fireChanges(this, "pop"); + /*if(model._created){ + var rev=this.array.slice(); + this.cache=null; + var arr=this.array; + for(var i=0;i 200) return; + ysy.log.debug(this.name + " - correctingPosition", "moveRequest"); + if (ysy.settings.milestonePush) { + var milestone = ysy.data.milestones.getByID(this.fixed_version_id); + if (milestone) { + var milestoneRequest = milestone.getMoveRequest(allRequests); + request.setLimits(null, milestoneRequest.softEnd); + } + } + var relations = ysy.data.relations.getArray(); + for (var i = 0; i < relations.length; i++) { + var relation = relations[i]; + if (relation.getTarget() === this || relation.getSource() === this) { + relation.sendMoveRequest(allRequests); + } + } + if (ysy.settings.parentIssueDates) { + var issues = ysy.data.issues.getArray(); + var childRequests = []; + for (var j = 0; j < issues.length; j++) { + if (issues[j].parent_issue_id !== this.id) continue; + var child = issues[j]; + var childRequest = child.getMoveRequest(allRequests); + childRequest.setLimits(request.hardStart, request.hardEnd, true); + childRequests.push(childRequest); + } + for (j = 0; j < childRequests.length; j++) { + childRequests[j].entity.correctPosition(allRequests); + } + var parent = ysy.data.issues.getByID(this.parent_issue_id); + if (parent) { + var parentRequest = parent.getMoveRequest(allRequests); + parentRequest.resetByChildren(allRequests); + } + } + }, + getMoveRequest: function (allRequests) { + var request = allRequests[this.getID()]; + if (!request) { + ysy.log.debug(this.name + " - new moveRequest", "moveRequest"); + allRequests[this.getID()] = request = new ysy.data.MoveRequest(); + request.init(this, allRequests); + } + return request; + }, + isOpened: function () { + var opened = ysy.data.limits.openings[this.getID()]; + if (opened === undefined) { + return true; + } + return opened; + } +}); +//############################################################################ +ysy.data.Relation = function () { + ysy.data.Data.call(this); + this.unlocked = !ysy.settings.fixedRelations; +}; +ysy.main.extender(ysy.data.Data, ysy.data.Relation, { + _name: "Relation", + _postInit: function () { + //if(this.delay&&this.delay>0){this.delay--;} + }, + getID: function () { + return "r" + this.id; + }, + getActDelay: function () { + var sourceDate = this.getSourceDate(); + var targetDate = this.getTargetDate(); + if (!sourceDate || !targetDate) return this.delay; + if (ysy.settings.workDayDelays) { + return gantt._working_time_helper.get_work_units_between(sourceDate, targetDate, "day"); + } + var correction = 0; + if (sourceDate._isEndDate) correction -= 1; + if (targetDate._isEndDate) correction += 1; + return targetDate.diff(sourceDate, "days") + correction; + }, + getSourceDate: function (source) { + if (!source) source = this.getSource(); + if (!source) return null; + if (this.type === "precedes") return source._end_date; + if (this.type === "finish_to_finish") return source._end_date; + if (this.type === "start_to_start") return source._start_date; + if (this.type === "start_to_finish") return source._start_date; + return null; + }, + getTargetDate: function (target) { + if (!target) target = this.getTarget(); + if (!target) return null; + if (this.type === "precedes") return target._start_date; + if (this.type === "finish_to_finish") return target._end_date; + if (this.type === "start_to_start") return target._start_date; + if (this.type === "start_to_finish") return target._end_date; + return null; + }, + getSourceCorrection: function () { + if (this.type === "precedes") return 1; + if (this.type === "finish_to_finish") return 1; + return 0; + }, + getTargetCorrection: function () { + if (this.type === "start_to_finish") return -1; + if (this.type === "finish_to_finish") return -1; + return 0; + }, + //getOtherDate: function (date, forSource) { + // var otherDate = gantt._working_time_helper.add_worktime(date, forSource ? -this.delay : this.delay, "day"); + //}, + getProblems: function () { + var del = this.getActDelay(); + var diff = (this.delay || 0) - del; + if (diff > 0) + return [ + ysy.settings.labels.problems.shortDelay + .replace("%{diff}", diff.toFixed(0)) + ]; + return []; + }, + checkDelay: function () { + var del = this.getActDelay(); + return del >= (this.delay || 0); + }, + getSource: function () { + return ysy.data.issues.getByID(this.source_id); + }, + getTarget: function () { + return ysy.data.issues.getByID(this.target_id); + }, + makeDelayFixedForSave: function () { + var delay = this.getActDelay(); + if (this.set({delay: delay})) { + this._fireChanges(this, "makeDelayFixedForSave()"); + } + }, + sendMoveRequest: function (allRequests) { + var source = this.getSource(); + var target = this.getTarget(); + if (!source || !target) return true; // HALF LINK + var sourceRequest = source.getMoveRequest(allRequests); + var targetRequest = target.getMoveRequest(allRequests); + if (this.getSourceCorrection() === 1) { + var sourceDate = sourceRequest.softEnd; + sourceDate._isEndDate = true; + } else { + sourceDate = sourceRequest.softStart; + } + if (ysy.settings.workDayDelays) { + var earliestTarget = gantt._working_time_helper.add_worktime(sourceDate, this.delay, "day", this.getTargetCorrection() === -1); + } else { + earliestTarget = moment(sourceDate).add(this.delay + this.getTargetCorrection() + this.getSourceCorrection(), "days"); + } + gantt._working_time_helper.round_date(earliestTarget); + targetRequest.setLimits(earliestTarget, null, true); + + if (this.getTargetCorrection() === 0) { + earliestTarget = gantt._working_time_helper.add_worktime(earliestTarget, targetRequest.duration, "day", false); + } + + if (targetRequest.hardEnd && earliestTarget.isAfter(targetRequest.hardEnd)) { + var targetDate = targetRequest.hardEnd; + targetDate._isEndDate = true; + if (this.getTargetCorrection() === 0) { + targetDate = gantt._working_time_helper.add_worktime(targetDate, -targetRequest.duration, "day", false); + } + if (ysy.settings.workDayDelays) { + var latestDate = gantt._working_time_helper.add_worktime(targetDate, -this.delay, "day", sourceDate._isEndDate === true); + } else { + latestDate = moment(targetDate).add(-this.delay - this.getTargetCorrection() - this.getSourceCorrection(), "days"); + } + if (this.getSourceCorrection() === 0) { + latestDate = gantt._working_time_helper.add_worktime(latestDate, sourceRequest.duration, "day", false); + } + latestDate._isEndDate = true; + gantt._working_time_helper.round_date(latestDate, "past"); + ysy.log.debug(source.name + " - milestonePush to " + latestDate.format("DD.MM.YYYY"), "moveRequest"); + source.getMoveRequest(allRequests) + .setLimits(null, latestDate); + return; + } + ysy.log.debug(target.name + " - classicPush to (end) " + earliestTarget.format("DD.MM.YYYY"), "moveRequest"); + target.correctPosition(allRequests); + + }, + isEditable: function () { + var source = this.getSource(); + if (!source) return false; + if (source.isEditable()) return true; + var target = this.getTarget(); + if (!target) return false; + if (target.isEditable()) return true; + }, + isHalfLink: function () { + return !(ysy.data.issues.getByID(this.source_id) && ysy.data.issues.getByID(this.target_id)); + } +}); +//############################################################################ +ysy.data.SimpleRelation = function () { + ysy.data.Data.call(this); +}; +ysy.main.extender(ysy.data.Relation, ysy.data.SimpleRelation, { + _name: "SimpleRelation", + isSimple: true, + sendMoveRequest: function (allRequests) { + return false + }, + isEditable: function () { + return false; + } +}); +//############################################################################## +ysy.data.Milestone = function () { + ysy.data.Data.call(this); +}; +ysy.main.extender(ysy.data.Data, ysy.data.Milestone, { + _name: "Milestone", + ganttType: "milestone", + milestone: true, + _postInit: function () { + if (this.start_date) { + if (typeof this.start_date === "string") { + this.start_date = moment(this.start_date, "YYYY-MM-DD"); + } else { + this.start_date = moment(this.start_date).startOf("day"); + } + } + if (!this.start_date) { + this.start_date = moment().startOf("day"); + } + }, + getID: function () { + return "m" + this.id; + }, + getIssues: function () { + var retissues = []; + var issues = ysy.data.issues.getArray(); + for (var i = 0; i < issues.length; i++) { + if (issues[i].fixed_version_id === this.id) { + retissues.push(issues[i]); + } + } + return retissues; + }, + _fireChanges: function (who, reason) { + var prototype = ysy.data.Data.prototype; + prototype._fireChanges.call(this, who, reason); + var childs = this.getIssues(); + for (var i = 0; i < childs.length; i++) { + childs[i]._fireChanges(this, "milestone change"); + } + }, + getProblems: function () { + return false; + }, + getParent: function () { + if (ysy.data.projects.getByID(this.project_id)) { + return "p" + this.project_id; + } + return false; + }, + correctPosition: function (allRequests) { + if (allRequests === undefined) allRequests = {}; + var request = this.getMoveRequest(allRequests); + if (!request.needBroadcast) return; + request.needBroadcast = false; + ysy.log.debug(this.name + " - correctingPosition", "moveRequest"); + var issues = ysy.data.issues.getArray(); + for (var j = 0; j < issues.length; j++) { + if (issues[j].fixed_version_id !== this.id) continue; + var child = issues[j]; + child.getMoveRequest(allRequests).setLimits(null, request.softEnd); + } + }, + getMoveRequest: function (allRequests) { + var request = allRequests[this.getID()]; + if (!request) { + ysy.log.debug(this.name + " - new moveRequest", "moveRequest"); + allRequests[this.getID()] = request = { + allRequests: allRequests, + entity: this, + setPosition: function (sortStart, softEnd) { + this.softStart = sortStart; + this.softEnd = moment(sortStart); + this.softEnd._isEndDate = true; + } + }; + request.setPosition(this.start_date, null); + // request.init(this, allRequests); + } + return request; + }, + isOpened: function () { + var opened = ysy.data.limits.openings[this.getID()]; + if (opened === undefined) { + return true; + } + return opened; + } +}); +//############################################################################## +ysy.data.SharedMilestone = function () { + ysy.data.Milestone.call(this); +}; +ysy.main.extender(ysy.data.Milestone, ysy.data.SharedMilestone, { + _name: "SharedMilestone", + isShared: true, + ganttSubtype: "shared_milestone", + _postInit: function () { + this.real_id = this.id; + this.id = this.id + "p" + this.project_id; + this.css = "gantt-milestone-shared"; + this.__proto__.__proto__._postInit.call(this); + this.real_milestone.register(function (reason) { + if (reason === "revert") return; + if (this.start_date.isSame(this.real_milestone.start_date)) return; + this.set({start_date: moment(this.real_milestone.start_date)}); + }, this); + }, + isEditable: function () { + return false; + } +}); +//############################################################################## +ysy.data.Project = function () { + ysy.data.Data.call(this); +}; +ysy.main.extender(ysy.data.Data, ysy.data.Project, { + _name: "Project", + ganttType: "project", + isProject: true, + needLoad: true, + issues_count: 0, + has_subprojects: false, + _shift: 0, + _postInit: function () { + this.start_date = this.start_date ? moment(this.start_date, "YYYY-MM-DD") : null; + //this.end_date=moment(this.due_date).add(1, "d"); + if (this.due_date) { + this.end_date = moment(this.due_date, "YYYY-MM-DD"); + this.end_date._isEndDate = true; + } + delete this.due_date; + if (this.is_baseline) { + this._ignore = true; + } + this.project_id = this.id; + if (ysy.settings.projectID === this.id) { + ysy.data.limits.openings[this.getID()] = true; + this.needLoad = false; + } + if (ysy.settings.global) { + this.needLoad = this.needLoad && this.has_subprojects; + } + this._transformColumns(); + }, + _transformColumns: function () { + var cols = this.columns; + if (!cols) return; + var ncols = {}; + for (var i = 0; i < cols.length; i++) { + var col = cols[i]; + ncols[col.name] = col.value; + if (col.value_id !== undefined) { + ncols[col.name + "_id"] = col.value_id; + } + } + this.columns = ncols; + }, + getID: function () { + return "p" + this.id; + }, + problems: {}, + getProgress: function () { + return this.done_ratio / 100.0 || 0; + }, + getParent: function () { + if (ysy.data.projects.getByID(this.parent_id)) { + return "p" + this.parent_id; + } + return false; + }, + isOpened: function () { + return ysy.data.limits.openings[this.getID()] || false; + } +}); +//############################################################################## +ysy.data.MoveRequest = function () { + this.needBroadcast = false; + this.counter = 0; +}; +ysy.main.extender(Object, ysy.data.MoveRequest, { + _name: "MoveRequest", + init: function (issue, allRequests) { + this.entity = issue; + this.allRequests = allRequests; + this.softStart = issue._start_date; + this.softEnd = issue._end_date; + this.duration = issue.getDuration(); + if (!this.entity.isEditable()) { + this.hardStart = issue._start_date; + this.hardEnd = issue._end_date; + } + return this; + }, + setLimits: function (hardStart, hardEnd, silent) { + var startSet = false, endSet = false, oldHardStart = this.hardStart; + if (hardStart) { + if (!this.hardStart || this.hardStart.isBefore(hardStart)) { + this.hardStart = moment(hardStart); + startSet = true; + } + } + if (hardEnd) { + if (!this.hardEnd || this.hardEnd.isAfter(hardEnd)) { + this.hardEnd = moment(hardEnd); + this.hardEnd._isEndDate = true; + endSet = true; + } + } + if (startSet || endSet) { + if (this.hardEnd) { + var lastStart = gantt._working_time_helper.add_worktime(this.hardEnd, -this.duration, "day"); + if (this.hardStart && this.hardStart.isAfter(lastStart)) { + this.hardStart = lastStart; + } + if (this.softEnd && this.softEnd.isAfter(this.hardEnd)) { + ysy.log.debug("start: " + this.softStart.format("DD.MM.YYYY") + "=>" + lastStart.format("DD.MM.YYYY") + + " end: " + this.softEnd.format("DD.MM.YYYY") + "=>" + this.hardEnd.format("DD.MM.YYYY"), "moveRequest"); + this.softEnd = moment(this.hardEnd); + this.softEnd._isEndDate = true; + this.softStart = lastStart; + this.needBroadcast = true; + } + } + if (this.hardStart) { + var earliestEnd = gantt._working_time_helper.add_worktime(this.hardStart, this.duration, "day", true); + if (this.softStart && this.softStart.isBefore(this.hardStart)) { + ysy.log.debug("start: " + this.softStart.format("DD.MM.YYYY") + "=>" + this.hardStart.format("DD.MM.YYYY") + + " end: " + this.softEnd.format("DD.MM.YYYY") + "=>" + earliestEnd.format("DD.MM.YYYY"), "moveRequest"); + this.softStart = moment(this.hardStart); + this.softEnd = earliestEnd; + this.needBroadcast = true; + } + } + } + // if (this.needBroadcast) { + // ysy.log.debug(this.entity.name + " - setLimits(" + (hardStart ? hardStart.format("DD.MM.YYYY") : "null") + "," + (hardEnd ? hardEnd.format("DD.MM.YYYY") : "null") + ") => " + this.needBroadcast,"moveRequest"); + // } + if (!silent) { + this.entity.correctPosition(this.allRequests); + } + }, + resetByChildren: function (allRequests) { + var issues = ysy.data.issues.getArray(); + var lastStart; + var earliestEnd; + var startLimit = undefined; + var endLimit = undefined; + for (var j = 0; j < issues.length; j++) { + if (issues[j].parent_issue_id !== this.entity.id) continue; + var childRequest = issues[j].getMoveRequest(allRequests); + if (!lastStart || lastStart.isAfter(childRequest.softStart)) { + lastStart = childRequest.softStart; + } + if (startLimit !== false) { + if (childRequest.hardStart) { + if (!startLimit || startLimit.isAfter(childRequest.hardStart)) { + startLimit = childRequest.hardStart; + } + } else { + startLimit = false; + } + } + if (!earliestEnd || earliestEnd.isBefore(childRequest.softEnd)) { + earliestEnd = childRequest.softEnd; + } + if (endLimit !== false) { + if (childRequest.hardEnd) { + if (!endLimit || endLimit.isBefore(childRequest.hardEnd)) { + endLimit = childRequest.hardEnd; + } + } else { + endLimit = false; + } + } + } + this.needBroadcast = true; + this.softStart = moment(lastStart); + this.softEnd = moment(earliestEnd); + this.softEnd._isEndDate = true; + this.hardStart = startLimit || undefined; + this.hardEnd = endLimit || undefined; + ysy.log.debug(this.entity.name + " - resetByChildren (" + this.softStart.format("DD.MM.YYYY") + "," + this.softEnd.format("DD.MM.YYYY") + ")", "moveRequest"); + this.duration = gantt._working_time_helper.get_work_units_between(this.softStart, this.softEnd, "day"); + this.entity.correctPosition(this.allRequests); + }, + setPosition: function (startDate, endDate, silent) { + if (!this.entity.isEditable()) return; + this.duration = gantt._working_time_helper.get_work_units_between(startDate, endDate, "day"); + if (!this.hardStart || !this.hardStart.isAfter(startDate)) { + this.softStart = startDate; + this.needBroadcast = true; + } else { + endDate = gantt._working_time_helper.add_worktime(this.hardStart, this.duration, "day", true); + } + if (!this.hardEnd || !this.hardEnd.isBefore(endDate)) { + this.softEnd = endDate; + this.needBroadcast = true; + } + ysy.log.debug(this.entity.name + " - setPosition to (" + this.softStart.format("DD.MM.YYYY") + "," + this.softEnd.format("DD.MM.YYYY") + ") => " + this.needBroadcast, "moveRequest"); + if (!silent) { + this.entity.correctPosition(this.allRequests); + } + } +}); diff --git a/easy_gantt/assets/javascripts/easy_gantt/dhtml_addons.js b/easy_gantt/assets/javascripts/easy_gantt/dhtml_addons.js new file mode 100644 index 0000000..84a0238 --- /dev/null +++ b/easy_gantt/assets/javascripts/easy_gantt/dhtml_addons.js @@ -0,0 +1,478 @@ +/* dhtml_addons.js */ +/* global ysy */ +window.ysy = window.ysy || {}; +ysy.view = ysy.view || {}; +ysy.view.addGanttAddons = function () { + gantt.ascStop = function (task, start_date, end_date, ancestorLink) { + var diff; + if (task.soonest_start && start_date && start_date.isBefore(task.soonest_start)) { + diff = task.soonest_start.diff(start_date, "seconds"); + start_date.add(diff, "seconds"); + if (end_date) end_date.add(diff, "seconds"); + } + if (task.latest_due && end_date && end_date.isAfter(task.latest_due)) { + diff = task.latest_due.diff(end_date, "seconds"); + end_date.add(diff, "seconds"); + if (start_date) start_date.add(diff, "seconds"); + } + var sumMove = 0; + for (var i = 0; i < task.$target.length; i++) { + var lid = task.$target[i]; + if (ancestorLink && parseInt(lid) === ancestorLink.id) continue; // skip link sourcing moved parent + var link = gantt._lpull[lid]; + if (link.isSimple) continue; + var targetDate; + if (link.type === "precedes" || link.type == "start_to_start") { + if (!start_date) continue; + targetDate = start_date; + } else if (link.type === "finish_to_finish" || link.type == "start_to_finish") { + if (!end_date) continue; + targetDate = end_date; + } else continue; + //if (link.type !== "precedes") continue; + var source = gantt._pull[link.source]; + var sourceDate = gantt.getLinkSourceDate(source, link.type); + if (ysy.settings.workDayDelays) { + var linkLast = gantt._working_time_helper.add_worktime(sourceDate, link.delay, "day", targetDate._isEndDate === true); + diff = -targetDate.diff(linkLast, "seconds"); + } else { + diff = -targetDate.diff(sourceDate, "seconds") + (link.delay + gantt.getLinkCorrection(link.type)) * 24 * 60 * 60; + } + // ysy.log.debug("sourceDate="+sourceDate.toISOString()+" diff="+diff); + if (diff > 0) { + sumMove += diff; + //ysy.log.debug("ascStop linkLast=" + linkLast.format("YYYY-MM-DD") + " diff=" + diff, "task_drag"); + if (start_date) { + start_date.add(diff, "seconds"); + } + if (end_date) { + end_date.add(diff, "seconds"); + } + } + } + return sumMove; + }; + gantt.multiStop = gantt.ascStop; + gantt.moveDesc = function (task, diff, ancestorLink, resizing) { + if (typeof(diff) === "object") { + diff = task.start_date.diff(diff.old_start, "seconds"); + } + // runs after task dates are already modified. + ysy.log.debug("moveDesc(): task=" + task.text + " diff=" + (diff / 86400) + (ancestorLink ? " ancestorLink=" + ancestorLink.id : ""), "task_drag"); + var first = !ancestorLink; + if (first && task.type === "milestone") { + var oldTask = gantt._pull[task.id]; + oldTask.end_date = task.end_date; + } + var bothDir = false; + // diff in seconds + if (diff === 0) return 0; + if (diff <= 0 && !bothDir) { + gantt.moveChildren(task, diff, first); + return 0; + } + var mileBack = 0; + if (diff > 0) { + if (ysy.settings.milestonePush) { + mileBack = gantt.milestoneDescStop(task, first ? 0 : diff); + if (mileBack !== 0) { + diff -= mileBack; + ysy.log.debug("task=" + task.text + " oldDiff=" + ((diff + mileBack) / 86400) + " newDiff=" + (diff / 86400) + " mileDiff=" + (mileBack / 86400), "task_drag_milestone"); + } + } else { + gantt.milestoneMoveBy(task, first ? 0 : diff); + } + } + if (first && mileBack !== 0) { + if (resizing !== "right") { + task.start_date.add(Math.floor(-mileBack), 'seconds'); + } + if (resizing !== "left") { + task.end_date.add(Math.floor(-mileBack), 'seconds'); + } + gantt.refreshTask(task.id); + ysy.log.debug("FIRST " + task.text + " corrected by mileDiff=" + (mileBack / 3600 / 24) + " to " + task.start_date.format("YYYY-MM-DD"), "task_drag_milestone"); + } + var movedByChildren = gantt.moveChildren(task, diff, first); + + if (!first && (diff > 0 || bothDir)) { + if(!movedByChildren){ + ysy.log.debug("Task " + task.text + " pushed by " + (diff / 86400) + " days", "task_drag"); + task.start_date.add(Math.floor(diff), 'seconds'); + task.end_date.add(Math.floor(diff), 'seconds'); + } + // var oldStartDate = moment(task.start_date); + task._changed = gantt.config.drag_mode.move; + gantt.refreshTask(task.id); + } + if (!first && diff < 0) { + var ascDiff = gantt.ascStop(task, task.start_date, task.end_date, ancestorLink); + if (ascDiff !== 0) { + // ysy.log.debug("diff=" + diff + " ascStopDiff=" + ascDiff); + diff += ascDiff; + } + } + var start_date = +task.start_date / 1000; + var end_date = +task.end_date / 1000; + for (var i = 0; i < task.$source.length; i++) { + var lid = task.$source[i]; + var link = gantt._lpull[lid]; + if (link.isSimple) continue; + var desc = gantt._pull[link.target]; + if (diff < 0 && bothDir) { + // ysy.log.debug("desc=" + desc.text +" diff="+diff); + gantt.moveDesc(desc, diff, link); + // ysy.log.debug("task=" + task.text + " diff=" + diff + " reduced by " + backDiff); + } else { + var descDiff = gantt.getFreeDelay(link, desc, start_date, end_date); + if (descDiff <= 0) continue; + // ysy.log.debug("desc=" + desc.text +" diff="+diff+" descDiff="+descDiff); + gantt.moveDesc(desc, descDiff, link); + } + } + //ysy.log.debug("Task " + task.text + " pushed back by " + (diff / 86400) + " days", "task_drag"); + return 0; + }; + gantt.moveChildren = function (task, shift, first) { + if (!(gantt._get_safe_type(task.type) === "task" && ysy.settings.parentIssueDates)) { + if (task.$open && gantt.isTaskVisible(task.id)) { + if (task.type === "milestone") gantt.moveMilestoneChildren(task); + return; + } + } + var branch = gantt._branches[task.id]; + if (!branch || branch.length === 0) return; + ysy.log.debug("Shift children of \"" + task.text + "\" by " + shift + " seconds", "parent"); + ysy.log.debug("moveChildren(): of \"" + task.text + "\" by " + shift + " seconds", "task_drag"); + var parentStartDate = +task.start_date; + if (first) { + parentStartDate -= shift * 1000; + } + var shiftedParentDate = moment(parentStartDate + shift * 1000); + for (var i = 0; i < branch.length; i++) { + var childId = branch[i]; + //if(gantt.isTaskVisible(childId)){continue;} + var child = gantt.getTask(childId); + if (!child._parent_offset) { + child._parent_offset = gantt._working_time_helper.get_work_units_between(parentStartDate, child.start_date, "day"); + } + var childStartDiff = gantt._working_time_helper.add_worktime( + shiftedParentDate, child._parent_offset, "day", false + ) - child.start_date; + child.start_date.add(childStartDiff, 'milliseconds'); + var childEndDiff = gantt._working_time_helper.add_worktime(child.start_date, child.duration, 'day', true) - child.end_date; + child.end_date.add(childEndDiff, 'milliseconds'); + gantt.ascStop(child, child.start_date, child.end_date); + child._changed = gantt.config.drag_mode.move; + gantt.refreshTask(child.id); + gantt.moveDesc(child, childStartDiff); + } + }; + gantt.moveMilestoneChildren = function (milestone) { + var branch = gantt._branches[milestone.id]; + if (!branch || branch.length === 0) return 0; + // ysy.log.debug("Shift children of \"" + milestone.text + "\" by " + shift + " seconds", "parent"); + // ysy.log.debug("moveChildren(): of \"" + milestone.text + "\" by " + shift + " seconds", "task_drag"); + for (var i = 0; i < branch.length; i++) { + var childId = branch[i]; + var child = gantt.getTask(childId); + if (child.end_date.isAfter(milestone.end_date)) { + var shift = milestone.end_date.diff(child.end_date, "seconds"); + child.start_date.add(shift, 'seconds'); + child.end_date.add(shift, 'seconds'); + child._changed = gantt.config.drag_mode.move; + gantt.refreshTask(child.id); + gantt.moveDesc(child, shift); + } + } + return 0; + }; + gantt.milestoneStop = function (task, diff) { + var issue = task.widget && task.widget.model; + if (!issue) return 0; + var milestone = ysy.data.milestones.getByID(issue.fixed_version_id); + if (!milestone) return 0; + var ganttMilestone = gantt._pull[milestone.getID()]; + if (ganttMilestone) { + var milDiff = ganttMilestone.end_date.diff(task.end_date, "seconds"); + } else { + milDiff = milestone.start_date.diff(task.end_date, "seconds"); + } + milDiff -= diff; + if (milDiff < 0) { + ysy.log.debug("milestoneStop() for " + task.text + " milDiff=" + (milDiff / 86400) + + " diff=" + (diff / 86400) + " at " + milestone.start_date.format("YYYY-MM-DD"), "task_drag_milestone"); + return -milDiff; + } + return 0; + }; + gantt.milestoneDescStop = function (task, diff) { + ysy.log.debug("milestoneDescStop(): task " + task.text + " moving by " + (diff / 86400), "task_drag_milestone"); + var backDiff = gantt.milestoneStop(task, diff); + var start_date = +task.start_date / 1000 + diff; + var end_date = +task.end_date / 1000 + diff; + for (var i = 0; i < task.$source.length; i++) { + var lid = task.$source[i]; + var link = gantt._lpull[lid]; + if (link.isSimple) continue; + var desc = gantt._pull[link.target]; + var descDiff = gantt.getFreeDelay(link, desc, start_date, end_date); + if (descDiff <= 0) continue; + backDiff = Math.max(backDiff, gantt.milestoneDescStop(desc, descDiff)); + } + return backDiff; + }; + gantt.milestoneMoveBy = function (task, diff) { + ysy.log.debug("milestoneMoveBy(): task " + task.text + " moving by " + (diff / 86400), "task_drag"); + var issue = task.widget && task.widget.model; + if (!issue) return; + var milestone = ysy.data.milestones.getByID(issue.fixed_version_id); + if (!milestone) return; + var ganttMilestone = gantt._pull[milestone.getID()]; + var taskDate; + if (diff === 0) { + taskDate = task.end_date; + } else { + taskDate = moment(task.end_date).add(diff, "seconds"); + } + if (ganttMilestone) { + if (ganttMilestone.end_date.isBefore(taskDate)) { + var moveDiff = taskDate.diff(ganttMilestone.end_date); + ganttMilestone.end_date.add(moveDiff, "milliseconds"); + ganttMilestone.start_date.add(moveDiff, "milliseconds"); + ganttMilestone._changed = gantt.config.drag_mode.move; + gantt.refreshTask(ganttMilestone.id); + } + } + + }; + gantt.getLinkSourceDate = function (source, type) { + if (type === "precedes") return source.end_date; + if (type === "finish_to_finish") return source.end_date; + if (type === "start_to_start") return source.start_date; + if (type === "start_to_finish") return source.start_date; + return null; + }; + gantt.getLinkTargetDate = function (target, type) { + if (type === "precedes") return target.start_date; + if (type === "finish_to_finish") return target.end_date; + if (type === "start_to_start") return target.start_date; + if (type === "start_to_finish") return target.end_date; + return null; + }; + gantt.getLinkCorrection = function (type) { + if (type === "precedes") return 1; + if (type === "start_to_finish") return -1; + return 0; + }; + gantt.getFreeDelay = function (link, desc, ascStartDate, ascEndDate) { + if (!desc) desc = gantt._pull[link.target]; + var sourceDate; + var daysToSeconds = 60 * 60 * 24; + if (!ascStartDate) { + var asc = gantt._pull[link.source]; + ascStartDate = asc.start_date / 1000; + ascEndDate = asc.end_date / 1000; + } + if (link.type === "precedes" || link.type === "finish_to_finish") { + if (!ascEndDate) return 0; + sourceDate = ascEndDate; + } else if (link.type === "start_to_finish" || link.type === "start_to_start") { + if (!ascStartDate) return 0; + sourceDate = ascStartDate; + } else return 0; + var targetDate = gantt.getLinkTargetDate(desc, link.type); + var correction = gantt.getLinkCorrection(link.type); + var delay = (targetDate / 1000 - sourceDate) / daysToSeconds; + return (link.delay + correction - delay) * daysToSeconds; + + }; + gantt.updateAllTask = function (seed_task) { + ysy.history.openBrack(); + var toUpdate = {}; + // sort + reverse in order to process milestones before tasks + var pullIds = Object.getOwnPropertyNames(gantt._pull).sort().reverse(); + var allRequests = {}; + if (ysy.settings.fixedRelations) { + for (var i = 0; i < pullIds.length; i++) { + var task = gantt._pull[pullIds[i]]; + if (task._changed) { + //gantt._tasks_dnd._fix_dnd_scale_time(task,{mode:task._changed}); + gantt._tasks_dnd._fix_working_times(task, {mode: task._changed}); + gantt._update_parents(task.id, false); + delete task._parent_offset; + var parentId = gantt.getParent(task.id); + while (parentId) { + var parent = gantt._pull[parentId]; + if (!parent) break; + toUpdate[parentId] = parent; + parentId = gantt.getParent(parentId); + } + + toUpdate[task.id] = task; + // var issue = task.widget.model; + // if (issue.getMoveRequest) { + // var request = issue.getMoveRequest(allRequests); + // request.setPosition(task.start_date, task.end_date, true); + // } + task._changed = false; + } + } + } else { + for (i = 0; i < pullIds.length; i++) { + task = gantt._pull[pullIds[i]]; + if (task._changed) { + //gantt._tasks_dnd._fix_dnd_scale_time(task,{mode:task._changed}); + gantt._tasks_dnd._fix_working_times(task, {mode: task._changed}); + gantt._update_parents(task.id, false); + delete task._parent_offset; + toUpdate[task.id] = task; + var issue = task.widget.model; + if (issue.getMoveRequest) { + var request = issue.getMoveRequest(allRequests); + request.setPosition(task.start_date, task.end_date, true); + } + task._changed = false; + ysy.log.debug("UpdateAllTask update " + task.text, "task_drag"); + } + } + } + for (var id in allRequests) { + if (!allRequests.hasOwnProperty(id)) continue; + request = allRequests[id]; + request.entity.correctPosition(allRequests); + } + for (id in allRequests) { + if (!allRequests.hasOwnProperty(id)) continue; + request = allRequests[id]; + task = toUpdate[id]; + if (task) { + $.extend(task, {start_date: request.softStart, end_date: request.softEnd}); + } else { + if (!request.entity.set({start_date: request.softStart, end_date: request.softEnd})) { + request.entity._fireChanges({_name: "UpdateAll"}, "updateAll"); + } + } + } + for (id in toUpdate) { + if (!toUpdate.hasOwnProperty(id)) continue; + task = toUpdate[id]; + ysy.log.debug("UpdateAllTask update " + task.text, "task_drag"); + task.widget.update(task); + // for (var j = 0; j < task.$target.length; j++) { + // var link = gantt._lpull[task.$target[j]]; + // if (!link) continue; + // if (!link.unlocked) continue; + // var source = gantt._pull[link.source]; + // if (!source) continue; + // var targetDate = gantt.getLinkTargetDate(task, link.type); + // var sourceDate = gantt.getLinkSourceDate(source, link.type); + // var correction = gantt.getLinkCorrection(link.type); + // var delay = targetDate.diff(sourceDate, "days") - correction; + // // ysy.log.debug("Link from "+source.id+" to "+task.id+" delay="+delay ); + // var relation = link.widget.model; + // if (!relation) continue; + // relation.set("delay", delay); + // } + } + ysy.history.closeBrack(); + }; + gantt.applyMoveRequests = function (allRequests) { + for (var id in allRequests) { + if (!allRequests.hasOwnProperty(id)) continue; + var request = allRequests[id]; + if (!request.entity.set({start_date: request.softStart, end_date: request.softEnd})) { + request.entity._fireChanges({_name: "applyMoveRequests"}, "applyMoveRequests"); + } + } + }; + gantt.checkLoopedLink = function (target, bannedId) { + var relations = ysy.data.relations.getArray(); + for (var i = 0; i < relations.length; i++) { + if (relations[i].source_id !== target.id) continue; + var relation = relations[i]; + if (relation.target_id == bannedId) return false; + var nextTarget = relation.getTarget(); + if (!gantt.checkLoopedLink(nextTarget, bannedId)) return false; + } + return true; + }; + //############################################################################### + gantt.render_delay_element = function (link, pos) { + if (link.widget && link.widget.model.isSimple) return null; + //if(link.delay===0){return null;} + var sourceDate = gantt.getLinkSourceDate(gantt._pull[link.source], link.type); + var targetDate = gantt.getLinkTargetDate(gantt._pull[link.target], link.type); + if (ysy.settings.workDayDelays) { + var actualDelay = gantt._working_time_helper.get_work_units_between(sourceDate, targetDate, "day"); + actualDelay = Math.round(actualDelay); + } else { + actualDelay = targetDate.diff(sourceDate, "hours") / 24; + actualDelay = Math.round(actualDelay) - gantt.getLinkCorrection(link.type); + } + var text = (link.delay ? link.delay : '') + (actualDelay !== link.delay ? ' (' + actualDelay + ')' : ''); + return $('
') + .css({position: "absolute", left: pos.x, top: pos.y}) + // .html(link.delay+" ("+actualDelay + ")")[0]; + .html(text)[0]; + }; + //############################################################################## + /* + * Přepsané funkce z dhtmlxganttu, kvůli efektivnějšímu napojení či kvůli odstranění bugů + */ + //########################################################################################## + gantt.allowedParent = function (child, parent) { + if (child === parent) return false; + var type = child.type; + if (!type) { + type = "task"; + } + var allowed = gantt.config["allowedParent_" + type]; + if (!allowed) return false; + if (parent.real_id > 1000000000000) return false; + var parentType = parent.type || "task"; + return allowed.indexOf(parentType) >= 0; + }; + gantt.getShowDate = function () { + var pos = gantt._restore_scroll_state(); + if (!pos) return null; + return this.dateFromPos(pos.x + this.config.task_scroll_offset); + }; + gantt.silentMoveTask = function (task, parentId) { + ysy.log.debug("silentMoveTask", "move_task"); + var id = task.id; + var sourceId = this.getParent(id); + if (sourceId == parentId) return; + + this._replace_branch_child(sourceId, id); + var tbranch = this.getChildren(parentId); + tbranch.push(id); + + this.setParent(task, parentId); + this._branches[parentId] = tbranch; + + var childTree = this._getTaskTree(id); + for (var i = 0; i < childTree.length; i++) { + var item = this._pull[childTree[i]]; + if (item) + item.$level = this.calculateTaskLevel(item); + } + task.$level = gantt.calculateTaskLevel(task); + this.refreshData(); + + }; + gantt.getCachedScroll = function () { + if (!gantt._cached_scroll_pos) return {x: 0, y: 0}; + return {x: gantt._cached_scroll_pos.x || 0, y: gantt._cached_scroll_pos.y || 0}; + }; + gantt.reconstructTree = function () { + var tasks = gantt._pull; + var ids = Object.getOwnPropertyNames(tasks); + for (var i = 0; i < ids.length; i++) { + var task = tasks[ids[i]]; + if (task.realParent === undefined) continue; + gantt.silentMoveTask(task, task.realParent); + delete task.realParent; + } + } +}; diff --git a/easy_gantt/assets/javascripts/easy_gantt/dhtml_modif.js b/easy_gantt/assets/javascripts/easy_gantt/dhtml_modif.js new file mode 100644 index 0000000..ddc4e2d --- /dev/null +++ b/easy_gantt/assets/javascripts/easy_gantt/dhtml_modif.js @@ -0,0 +1,696 @@ +/* dhtml_modif.js */ +/* global ysy */ +window.ysy = window.ysy || {}; +ysy.view = ysy.view || {}; +ysy.view.initGantt = function () { + var toMomentFormat = function (rubyFormat) { + switch (rubyFormat) { + case '%Y-%m-%d': + return 'YYYY-MM-DD'; + case '%Y/%m/%d': + return 'YYYY/MM/DD'; + case '%d/%m/%Y': + return 'DD/MM/YYYY'; + case '%d.%m.%Y': + return 'DD.MM.YYYY'; + case '%d-%m-%Y': + return 'DD-MM-YYYY'; + case '%m/%d/%Y': + return 'MM/DD/YYYY'; + case '%d %b %Y': + return 'DD MMM YYYY'; + case '%d %B %Y': + return 'DD MMMM YYYY'; + case '%b %d, %Y': + return 'MMM DD, YYYY'; + case '%B %d, %Y': + return 'MMMM DD, YYYY'; + default: + return 'D. M. YYYY'; + } + }; + + function getERUISassValue(varName, defaultValue) { + if (window.ERUI && ERUI.sassData !== undefined && ERUI.sassData[varName] !== undefined) { + return parseInt(ERUI.sassData[varName]); + } + return defaultValue; + } + + $.extend(gantt.config, { + //xml_date: "%Y-%m-%d", + //scale_unit: "week", + //date_scale: "Week #%W", + //autosize:"y", + details_on_dblclick: false, + readonly_project: true, + //autofit:true, + drag_empty: true, + work_time: true, + //min_duration:24*60*60*1000, // 1*24*60*60*1000s = 1 day + correct_work_time: true, + //date_grid: "%j %M %Y", + date_format: toMomentFormat(ysy.settings.dateFormat), + date_grid: "%j.%n.%Y", + links: { + finish_to_start: "precedes", + start_to_start: "start_to_start", + start_to_finish: "start_to_finish", + finish_to_finish: "finish_to_finish" + }, + step: 1, + duration_unit: "day", + fit_tasks: true, + row_height: getERUISassValue('gantt-row-height', 25), + task_height: getERUISassValue('gantt-task-height', 20), + min_column_width: 36, + autosize: "y", + link_line_width: 0, + scale_height: 60, + start_on_monday: true, + order_branch: true, + rearrange_branch: true, + grid_resize: true, + grid_width: ysy.data.limits.columnsWidth.grid_width, + task_scroll_offset: 250, + controls_task: {progress: true, resize: true, links: true}, + controls_milestone: {}, + start_date: ysy.data.limits.start_date, + end_date: ysy.data.limits.end_date, + controls_project: {show_progress: true, resize: false}, + allowedParent_task: ["project", "milestone", "empty"], + allowedParent_task_global: ["project", "milestone"], + allowedParent_milestone: ["project"], + allowedParent_project: ["empty"] + }); + gantt.config.columns = ysy.view.leftGrid.constructColumns(ysy.data.columns); + ysy.proManager.fireEvent("ganttConfig", gantt.config); + gantt._pull["empty"] = { + type: "empty", + id: "empty", + $target: [], + $source: [], + columns: {}, + text: "", + start_date: moment() + }; +}; +ysy.view.applyGanttPatch = function () { + ysy.view.leftGrid.patch(); + gantt.locale.date = ysy.settings.labels.date; + $.extend(gantt.templates, { + task_cell_class: function (item, date) { + if (gantt.config.scale_unit === "day") { + //var css=""; + if (moment(date).date() === 1) { + return true; + // css+=" first-date"; + } + //return css; + } + return false; + }, + scale_cell_class: function (date) { + if (gantt.config.scale_unit === "day") { + var css = ""; + if (!gantt._working_time_helper.is_working_day(date)) { + css += " weekend"; + } + //if(date.getDate()===1){ + if (moment(date).date() === 1) { + css += " first-date"; + } + return css; + } + }, + task_text: function (start, end, task) { + return ""; + }, + task_class: function (start, end, task) { + var css = ""; + if (task.css) { + css = task.css; + } + css += " " + (task.type || "task") + "-type"; + if (task.widget && task.widget.model) { + var problems = task.widget.model.getProblems(); + if (problems) { + css += " wrong"; + } + } + return css; + }, + grid_row_class: function (start, end, task) { + var ret = ""; + if (task.css) { + ret = task.css; + } + if (task.widget && task.widget.model) { + var problems = task.widget.model.getProblems(); + if (problems) { + ret += " wrong"; + } + } + ret += " " + (task.type || "task") + "-type"; + return ret;//+'" data-url="/issues/'+task.id+' data-none="'; + //return task.css+" "+task.type+"-type"; + }, + /*grid_file: function (item) { + return ""; + //return "
"; + },*/ + link_class: function (link) { + var css = "type-" + link.type; + if (link.widget) { + var relation = link.widget.model; + if (relation) { + if (!relation.checkDelay()) { + css += " wrong"; + } + if (relation.isSimple) { + css += " gantt-relation-simple"; + } + if (link.unlocked) { + css += " gantt-relation-unlocked"; + } + } + } + return css; + }, + drag_link: function (from, from_start, to, to_start) { + var labels = ysy.view.getLabel("links"); + if (!gantt._get_link_type(from_start, to_start)) { + var reason = "unsupported_link_type"; + } else if (from === to) { + reason = "loop_link"; + } else if (to && to.length > 12) { + reason = "link_target_new"; + } else if (to && gantt.getTask(to).readonly) { + reason = "link_target_readonly"; + } else { + reason = "other"; + } + var obj = { + errorReason: ysy.view.getLabel("errors2")[reason], + from: gantt.getTask(from).text + }; + if (to) { + obj.to = gantt.getTask(to).text; + var ganttLinkType = (from_start ? "start" : "finish") + "_to_" + (to_start ? "start" : "finish"); + obj.type = labels[gantt.config.links[ganttLinkType]]; + } + return Mustache.render(ysy.view.templates.linkDragModal, obj); + }, + scale_row_class: function (scale) { + return scale.className || ""; + } + }); + + gantt.attachEvent("onRowDragStart", function (id /*, elem*/) { + //$(".gantt_grid_data").addClass("dragging"); + var task = gantt.getTask(id); + $(".gantt_row").each(function () { + var target = gantt._pull[$(this).attr("task_id")]; + if (!target) return; + if (gantt.allowedParent(task, target)) { + $(this).addClass("gantt_drag_to_allowed"); + } + }); + return true; + }); + gantt.attachEvent("onRowDragEnd", function (id, elem) { + //$(".gantt_grid_data").removeClass("dragging"); + $(".gantt_drag_to_allowed").removeClass("gantt_drag_to_allowed"); + }); + + // Funkce pro vytvoření a posunování Today markeru + function initTodayMarker() { + var date_to_str = gantt.date.date_to_str(gantt.config.task_date); + var id = gantt.addMarker({start_date: new Date(), css: "today", title: date_to_str(new Date())}); + setInterval(function () { + var today = gantt.getMarker(id); + today.start_date = new Date(); + today.title = date_to_str(today.start_date); + gantt.updateMarker(id); + }, 1000 * 60 * 60); + } + + initTodayMarker(); + + //gantt.initProjectMarker=function initProjectMarker(start,end) { + // if(start&&start.isValid()){ + // var startMarker = gantt.addMarker({start_date: start.toDate(), css: "start", title: "Project start"}); + // } + // if(end&&end.isValid()){ + // var endMarker = gantt.addMarker({start_date: end.toDate(), css: "end", title: "Project due time"}); + // } + //}; + //initProjectMarker(); + +//################################################################################## + if (!ysy.settings.fixedRelations) { + gantt.attachEvent("onLinkClick", function (id, mouseEvent) { + if (!gantt.config.drag_links) return; + ysy.log.debug("LinkClick on " + id, "link_config"); + var link = gantt.getLink(id); + if (gantt._is_readonly(link)) return; + var source = gantt._pull[link.source]; + if (!source) return; + var target = gantt._pull[link.target]; + if (!target) return; + if (source.readonly && target.readonly) return; + + var linkConfigWidget = new ysy.view.LinkPopup().init(link.widget.model, link); + linkConfigWidget.$target = $("#ajax-modal");//$dialog; + linkConfigWidget.repaint(); + showModal("ajax-modal", "auto"); + return false; + }); + } + gantt.attachEvent("onAfterLinkDelete", function (id, elem) { + if (elem.deleted) return; + if (!elem.widget.model._deleted) { + elem.widget.model.remove(); + } + }); + gantt.attachEvent("onBeforeLinkAdd", function (id, link) { + if (link.widget) return true; + var relations = ysy.data.relations; + var data; + data = { + id: id, + source_id: parseInt(link.source), + target_id: parseInt(link.target), + delay: 0, + unlocked: true, + permissions: { + editable: true + }, + type: link.type + }; + var relArray = relations.getArray(); + for (var i = 0; i < relArray.length; i++) { + var relation = relArray[i]; + if (relation.source_id === data.source_id && relation.target_id === data.target_id) { + dhtmlx.message(ysy.view.getLabel("errors2", "duplicate_link"), "error"); + return false; + } + } + var rel = new ysy.data.Relation(); + rel.init(data, relations); + //rel.delay=rel.getActDelay(); // created link have maximal delay + if (!gantt.checkLoopedLink(rel.getTarget(), rel.source_id)) { + dhtmlx.message(ysy.view.getLabel("errors2", "loop_link"), "error"); + return false; + } + + ysy.history.openBrack(); + relations.push(rel); + var allRequests = {}; + rel.sendMoveRequest(allRequests); + gantt.applyMoveRequests(allRequests); + ysy.history.closeBrack(); + return false; + }); + + ysy.view.taskTooltip.taskTooltipInit(); + + dhtmlx.message = function (msg, type, delay) { + if (!type) { + type = msg.type; + msg = msg.text; + delay = msg.delay; + } + window.showFlashMessage(type, msg, delay && delay > 0 ? delay : undefined); + //if (type !== "notice") { + // var flashElement = $("#content").children(".flash")[0]; + // var adjust = -10; + // if (ysy.settings.easyRedmine) { + // $(document).scrollTop(flashElement.offsetTop + adjust + "px"); + // //window.scrollTo(".flash",adjust); + // } else { + // window.scrollTo(0, flashElement.offsetTop + adjust); + // } + //} + }; + + if (!window.showFlashMessage) { + window.showFlashMessage = function (type, message) { + var $content = $("#content"); + $content.find(".flash").remove(); + var template = '
{{{message}}}
'; + var closeFunction = function (event) { + $(this) + .closest('.flash') + .fadeOut(500, function () { + $(this).remove(); + }) + }; + var rendered = Mustache.render(template, {message: message, type: type}); + $content.prepend($(rendered)); + $content.find(".close_button").click(closeFunction); + } + } + if (!dhtmlx.dragScroll) { + dhtmlx.dragScroll = function () { + var $background = $(".gantt_task_bg"); + if (!$background.hasClass("inited")) { + $background.addClass("inited"); + var dnd = new dhtmlxDnD($background[0], {}); + var lastScroll = null; + dnd.attachEvent("onDragStart", function () { + lastScroll = gantt.getCachedScroll(); + }); + dnd.attachEvent("onDragMove", function () { + var diff = dnd.getDiff(); + gantt.scrollTo(lastScroll.x - diff.x, undefined); + }); + } + }; + } + gantt.attachEvent("onTaskOpened", function (id) { + ysy.data.limits.openings[id] = true; + var task = gantt._pull[id]; + if (!task || !task.widget) return true; + var entity = task.widget.model; + if (entity.needLoad) { + entity.needLoad = false; + ysy.data.loader.loadSubEntity(task.type, entity.id); + } + }); + gantt.attachEvent("onTaskClosed", function (id) { + ysy.data.limits.openings[id] = false; + }); + gantt.attachEvent("onTaskSelected", function (id) { + var data = gantt._get_tasks_data(); + gantt._backgroundRenderer.render_items(data); + }); + gantt.attachEvent("onTaskUnselected", function (id, ignore) { + if (ignore) return; + var data = gantt._get_tasks_data(); + gantt._backgroundRenderer.render_items(data); + }); + gantt.attachEvent("onLinkValidation", function (link) { + if (link.source.length > 12) return false; + if (link.target.length > 12) return false; + var source = gantt.getTask(link.source); + var target = gantt.getTask(link.target); + if (source.readonly && target.readonly) return false; + var parentId = source.id; + while (parentId !== 0) { + var parent = gantt.getTask(parentId); + if (parent === target) return false; + parentId = parent.parent; + } + parentId = target.id; + while (parentId !== 0) { + parent = gantt.getTask(parentId); + if (parent === source) return false; + parentId = parent.parent; + } + return true; + }); + gantt.attachEvent("onAfterTaskMove", function (sid, parent, tindex) { + this.open(parent); + return true; + }); + gantt._filter_task = function (id, task) { + // commented out because pushing task out of bounds removed the task and its project + //var min = null, max = null; + //if(this.config.start_date && this.config.end_date){ + // min = this.config.start_date.valueOf(); + // max = this.config.end_date.valueOf(); + // + // if(+task.start_date > max || +task.end_date < +min) + // return false; + //} + return ysy.proManager.eventFilterTask(id, task); + }; + //var oldPosFromDate = gantt.posFromDate; + //gantt.posFromDate = function(date){ + // ysy.log.debug("old: "+oldPosFromDate.call(gantt,date)+" new: "+gantt.posFromDate2(date)); + // return gantt.posFromDate2(date); + //}; + gantt.posFromDate = function (date) { + var scale = this._tasks; + if (typeof date === "string") { + date = moment(date); + } + + var tdate = date.valueOf(); + var units = { + day: 86400000, // 24 * 60 * 60 * 1000 + week: 604800000, // 7 * 24 * 60 * 60 * 1000 + //month: 2592000000 // 30 * 24 * 60 * 60 * 1000 + month: 2629800000, // 30.4375 * 24 * 60 * 60 * 1000 + quarter: 7889400000, // 3 * 30.4375 * 24 * 60 * 60 * 1000 + year: 31557600000 // 12 * 30.4375 * 24 * 60 * 60 * 1000 + }; + if (date._isEndDate) { + tdate += units.day; + } + + if (tdate <= this._min_date) + return 0; + + if (tdate >= this._max_date) + return scale.full_width; + + var unitRatio = (tdate - scale.trace_x[0]) / units[scale.unit]; + var index = Math.floor(unitRatio); + index = Math.min(scale.count - 1, Math.max(0, index)); + if (scale.count === index + 1) { + return scale.left[index] + + scale.width[index] + * (tdate - scale.trace_x[index]) + / (gantt._max_date - scale.trace_x[index]); + } + if (tdate > scale.trace_x[index + 1]) { + index++; + if (scale.count === index + 1) { + return scale.left[index] + + scale.width[index] + * (tdate - scale.trace_x[index]) + / (gantt._max_date - scale.trace_x[index]); + } + } else { + while (index !== 0 && tdate < scale.trace_x[index]) index--; + } + var restRatio = (tdate - scale.trace_x[index]) / (scale.trace_x[index + 1] - scale.trace_x[index]); + return scale.left[index] + scale.width[index] * restRatio; + }; + gantt.dateFromPos2 = function (x) { + // TODO tasks ends + var scale = this._tasks; + if (x < 0 || x > scale.full_width || !scale.full_width) { + scale.needRescale = true; + ysy.log.debug("needRescale", "outer"); + } + if (!scale.trace_x.length) { + return 0; + } + // var units = { + // day: 86400000, // 24 * 60 * 60 * 1000 + // week: 604800000, // 7 * 24 * 60 * 60 * 1000 + // //month: 2592000000 // 30 * 24 * 60 * 60 * 1000 + // month: 2629800000 // 30.4375 * 24 * 60 * 60 * 1000 + // }; + var unitRatio = x / (scale.full_width / scale.count); + var index = Math.floor(unitRatio); + index = Math.min(scale.count - 1, Math.max(0, index)); + if (index === scale.count - 1) { + return gantt.date.Date( + scale.trace_x[index].valueOf() + + (gantt._max_date - scale.trace_x[index]) + * (x - scale.left[index]) + / scale.width[index]); + } + if (x > scale.left[index + 1]) { + index++; + if (index === scale.count - 1) { + return gantt.date.Date( + scale.trace_x[index].valueOf() + + (gantt._max_date - scale.trace_x[index]) + * (x - scale.left[index]) + / scale.width[index]); + } + } + return gantt.date.Date( + scale.trace_x[index].valueOf() + + (scale.trace_x[index + 1] - scale.trace_x[index]) + * (x - scale.left[index]) + / scale.width[index]); + }; + ysy.view.bars.registerRenderer("task", function (task, next) { + var $div = $(next().call(this, task, next)); + if (this.hasChild(task.id) || $div.hasClass("parent")) { + $div.addClass("gantt_parent_task-subtype"); + var $ticks = $("
"); + var width = $div.width(); + if (width < 20) { + $ticks.css({borderLeftWidth: width / 2, borderRightWidth: width / 2}); + } + $div.append($ticks); + + + } + if (task.latest_due) { + var stop_x = this.posFromDate(task.widget.model.latest_due); + var issue_x = this.posFromDate(task.start_date); + var pos_x = stop_x - issue_x; + var $preStop = $(ysy.view.templates.endBlocker.replace("{{pos_x}}", pos_x.toString())); + $div.prepend($preStop); + } + if (task.soonest_start) { + var stop_x = this.posFromDate(task.widget.model.soonest_start); + var issue_x = this.posFromDate(task.start_date); + var pos_x = stop_x - issue_x; + var $preStop = $(ysy.view.templates.preBlocker.replace("{{pos_x}}", pos_x.toString())); + $div.prepend($preStop); + } + return $div[0]; + }); + // var progressRenderer = gantt._render_task_progress; + // gantt._render_task_progress = function (task, element, maxWidth) { + // var width = progressRenderer.call(this, task, element, maxWidth); + // if (task.type !== "project") return width; + // var pos = gantt.posFromDate(task.start_date); + // var todayPos = gantt.posFromDate(moment()); + // if (task.progress < 1 && pos + width < todayPos) { + // element.className += " gantt-project-overdue"; + // } + // return width; + // }; + gantt._default_task_date = function (item, parent_id) { + return moment(); + }; + gantt.attachEvent("onScrollTo", function (x, y) { + var renderer = gantt._backgroundRenderer; + var needRender = renderer.isScrolledOut(x, y); + if (needRender) { + //ysy.log.debug("render_one_canvas on [" + x + "," + y + "]", "scrollRender"); + renderer.render_items(); + } + }); + var ganttOffsetTop; + $(document).add("#content").on("scroll", function (e) { + if (!ganttOffsetTop) { + if (!gantt.$task) return; + ganttOffsetTop = $(gantt.$task).offset().top; + } + var scroll = $(this).scrollTop(); + gantt.scrollTo(undefined, scroll - ganttOffsetTop); + }); + gantt.showTask = function (id) { + var el = this.getTaskNode(id); + if (!el) return; + var left = Math.max(el.offsetLeft - this.config.task_scroll_offset, 0); + var top = $(el).offset().top - 200; + $(window).scrollTop(top); + this.scrollTo(left, top); + }; + gantt.getScrollState = function () { + if (!this.$task || !this.$task_data) return null; + return {x: this.$task.scrollLeft, y: $(window).scrollTop()}; + }; + gantt._path_builder.get_endpoint = function (link) { + var types = gantt.config.links; + var from_start = false, to_start = false; + + if (link.type == types.start_to_start) { + from_start = to_start = true; + } else if (link.type == types.finish_to_finish) { + from_start = to_start = false; + } else if (link.type == types.finish_to_start) { + from_start = false; + to_start = true; + } else if (link.type == types.start_to_finish) { + from_start = true; + to_start = false; + } else { + dhtmlx.assert(false, "Invalid link type"); + } + var source = gantt._pull[link.source]; + var target = gantt._pull[link.target]; + var sourceShift = 0, targetShift = 0; + var fromMount = from_start ? source.$startMount : source.$endMount; + var toMount = to_start ? target.$startMount : target.$endMount; + if (fromMount.length > 1) { + for (var i = 0; i < fromMount.length; i++) { + if (link.id == fromMount[i]) break; + } + sourceShift = (i * 2) / (fromMount.length - 1) * 6 - 6; + } + if (toMount.length > 1) { + for (i = 0; i < toMount.length; i++) { + if (link.id == toMount[i]) break; + } + targetShift = (i * 2) / (toMount.length - 1) * 6 - 6; + } + var from = gantt._get_task_visible_pos(gantt._pull[link.source], from_start); + var to = gantt._get_task_visible_pos(gantt._pull[link.target], to_start); + + return { + x: from.x, + e_x: to.x, + y: from.y + sourceShift, + e_y: to.y + targetShift + }; + }; + gantt._sync_links = function () { + for (var id in this._pull) { + if (!this._pull.hasOwnProperty(id)) continue; + var task = this._pull[id]; + task.$source = []; + task.$target = []; + task.$startMount = []; + task.$endMount = []; + } + for (id in this._lpull) { + if (!this._lpull.hasOwnProperty(id)) continue; + var link = this._lpull[id]; + var types = gantt.config.links; + if (this._pull[link.source]) { + this._pull[link.source].$source.push(id); + if (link.type == types.start_to_start || link.type == types.start_to_finish) { + this._pull[link.source].$startMount.push(id); + } else { + this._pull[link.source].$endMount.push(id); + } + } + if (this._pull[link.target]) { + this._pull[link.target].$target.push(id); + if (link.type == types.start_to_start || link.type == types.finish_to_start) { + this._pull[link.target].$startMount.push(id); + } else { + this._pull[link.target].$endMount.push(id); + } + } + } + }; + gantt._has_children = function (id) { + var item = gantt._pull[id]; + if (item.widget && item.widget.model && item.widget.model.needLoad) { + return true; + } + return this.getChildren(id).length > 0; + }; + gantt.setParent = function (task, new_pid) { + if (ysy.settings.parentIssueDates) { + var parentTask = this._pull[new_pid]; + if (parentTask && gantt._get_safe_type(parentTask.type) === "task") { + parentTask.$no_start = true; + parentTask.$no_end = true; + } + } + task.parent = new_pid; + }; + gantt.attachEvent("onAfterTaskMove", function (taskId) { + var task = gantt._pull[taskId]; + if (!task) return; + task._parentChanged = true; + }); + $("#main").on("resize", function () { + gantt.render(); + }); +}; diff --git a/easy_gantt/assets/javascripts/easy_gantt/dhtml_relations.js b/easy_gantt/assets/javascripts/easy_gantt/dhtml_relations.js new file mode 100644 index 0000000..8e5e5e8 --- /dev/null +++ b/easy_gantt/assets/javascripts/easy_gantt/dhtml_relations.js @@ -0,0 +1,623 @@ +/* dhtml_relations.js */ +/* global ysy */ +window.ysy = window.ysy || {}; +ysy.pro = ysy.pro || {}; +ysy.pro.relations = { + patch: function () { + if (ysy.settings.fixedRelations) { + ysy.pro.relations = null; + return; + } + var patch = function () { + gantt.multiStop = function (task, start_date, end_date, previous) { + var diff; + var sumMove = 0; + if (!previous) { + previous = { + visitedLinks: [], + visitedParents: [], + alreadyMoved: [] + }; + // ysy.log.debug("previous created"); + } + if (task.soonest_start && start_date && start_date.isBefore(task.soonest_start)) { + diff = task.soonest_start.diff(start_date, "seconds"); + start_date.add(diff, "seconds"); + if (end_date) end_date.add(diff, "seconds"); + sumMove += diff; + ysy.log.debug("AscStop(): soonest_start task=" + task.text + " diff=" + diff, "asc"); + } + if (task.latest_due && end_date && end_date.isAfter(task.latest_due)) { + diff = task.latest_due.diff(end_date, "seconds"); + end_date.add(diff, "seconds"); + if (start_date) start_date.add(diff, "seconds"); + sumMove += diff; + ysy.log.debug("AscStop(): latest_end task=" + task.text + " diff=" + diff, "asc"); + } + if (end_date) { + if (ysy.settings.milestonePush) { + var newStartDate = gantt.descStartByMilestone(task, +start_date, +end_date); + if (newStartDate) { + ysy.log.debug("AscStop(): milePush task=" + task.text + " start_date=" + newStartDate.format("YYYY-MM-DD"), "asc"); + var newEndDate = gantt._working_time_helper.add_worktime(newStartDate, task.duration, "day", true); + var mileDiff = (newEndDate - end_date) / 1000; + end_date.add(mileDiff, "seconds"); + if (start_date) { + mileDiff = (newStartDate - start_date) / 1000; + start_date.add(mileDiff, "seconds"); + } + ysy.log.debug("task=" + task.text + " mileDiff=" + (mileDiff / 86400), "task_drag_milestone"); + sumMove += mileDiff; + } + } + } + // #################### + sumMove += gantt.ascStop(task, start_date, end_date, previous); + var parentId = gantt.getParent(task.id); + var parent = gantt._pull[parentId]; + if (parent && gantt._get_safe_type(parent.type) === "task") { + // if(parent.start_date.isAfter(start_date) || parent.end_date.isAfter(end_date)){ + var branch = gantt._branches[parent.id]; + if (branch && branch.length > 0) { + var minDate = start_date; + var maxDate = end_date; + for (var i = 0; i < branch.length; i++) { + var childId = branch[i]; + var child = gantt.getTask(childId); + if (child.id === task.id) continue; + + if (!minDate || minDate.isAfter(child.start_date)) { + minDate = child.start_date; + } + if (!maxDate || maxDate.isBefore(child.end_date)) { + maxDate = child.end_date; + } + } + } + var parentSumMove = gantt.multiStop(parent, moment(minDate), moment(maxDate)); + if (start_date) { + start_date.add(parentSumMove, "seconds"); + if (end_date) { + var new_end = gantt._working_time_helper.add_worktime(start_date, task.duration, "day", true); //< HOSEKP + end_date.add(new_end - end_date, "milliseconds"); + } + } else { + end_date.add(parentSumMove, "seconds"); + } + if (parentSumMove > sumMove) { + ysy.log.debug("AscStop(): parent task=" + task.text + " diff=" + parentSumMove, "asc"); + sumMove = parentSumMove; + } + // } + } + return sumMove; + }; + gantt.ascStop = function (task, start_date, end_date, previous) { + var shortestDiff; + var diff, sumMove = 0; + for (var i = 0; i < task.$target.length; i++) { + var lid = task.$target[i]; + if (previous.visitedLinks.indexOf(lid) > -1) continue; // skip link sourcing moved parent + var link = gantt._lpull[lid]; + if (link.isSimple) continue; + var targetDate; + if (link.type === "precedes" || link.type === "start_to_start") { + if (!start_date) continue; + targetDate = start_date; + } else if (link.type === "finish_to_finish" || link.type === "start_to_finish") { + if (!end_date) continue; + targetDate = end_date; + } else continue; + var source = gantt._pull[link.source]; + var sourceDate = gantt.getLinkSourceDate(source, link.type); + diff = -targetDate.diff(sourceDate, "seconds") + gantt.getLinkCorrection(link.type) * 24 * 60 * 60; + if (!link.unlocked) { + diff += link.delay * 24 * 60 * 60; + if (diff <= 0) { + if (shortestDiff === undefined || shortestDiff < diff) { + shortestDiff = diff; + } + } + } + if (diff > 0) { + sumMove += diff; + ysy.log.debug("AscStop(): relDiff<0 task=" + task.text + " diff=" + diff, "asc"); + if (start_date) { + start_date.add(diff, "seconds"); + } + if (end_date) { + var new_end = gantt._working_time_helper.add_worktime(start_date, task.duration, "day", true); //< HOSEKP + end_date.add(new_end - end_date, "milliseconds"); + // end_date.add(diff, "seconds"); + } + shortestDiff = 0; + } + } + if (shortestDiff) { + sumMove += shortestDiff; + ysy.log.debug("AscStop(): shortest_diff task=" + task.text + " diff=" + shortestDiff, "asc"); + if (start_date) { + start_date.add(shortestDiff, "seconds"); + } + if (end_date) { + new_end = gantt._working_time_helper.add_worktime(start_date, task.duration, "day", true); //< HOSEKP + end_date.add(new_end - end_date, "milliseconds"); + // end_date.add(shortestDiff, "seconds"); + } + } + return sumMove; + }; + /** + * + * @param task + * @param {{[old_start]:{},[old_end]:{},[fromParent]:boolean,[fromChild]:boolean,[asc]:boolean}} options + * @param previous + * @return {number} + */ + gantt.moveDesc = function (task, options, previous/*, resizing*/) { + // runs after task dates are already modified. + if (!previous) { + previous = { + visitedLinks: [], + visitedParents: [], + alreadyMovedFrom: {}, + central: task, + parentsToRecalculate: [] + }; + previous.alreadyMovedFrom[task.id] = options.old_start; + // ysy.log.debug("previous created"); + } + if (!options.old_start) { + options.old_start = previous.alreadyMovedFrom[task.id]; + } + var shouldMove = gantt.shouldMoveChildren(task); + if (!options.fromChild && shouldMove.move) { + var children = gantt.moveChildren(task, $.extend({}, options, {fromParent: true}), previous); + } else if (shouldMove.milestoneMove) { + this.moveMilestoneChildren(task, {old_start: options.old_start}, previous); + } + if (shouldMove.adjust) { + if (!children) { + var branch = gantt._branches[task.id]; + if (branch && branch.length !== 0) { + children = []; + for (i = 0; i < branch.length; i++) { + children.push(gantt.getTask(branch[i])); + } + } + } + if (children && children.length) { + var dates = {start_date: null, end_date: null}; + for (i = 0; i < children.length; i++) { + var child = children[i]; + if (dates.start_date === null || dates.start_date.isAfter(child.start_date)) { + dates.start_date = child.start_date; + } + if (dates.end_date === null || dates.end_date.isBefore(child.end_date)) { + dates.end_date = child.end_date; + } + } + task.start_date.add(dates.start_date - task.start_date, "milliseconds"); + task.end_date.add(dates.end_date - task.end_date, "milliseconds"); + } + } + + var start_date = +task.start_date; + var end_date = +task.end_date; + if (!options.asc) { + /** DESCENDANTS */ + for (var i = 0; i < task.$source.length; i++) { + var lid = task.$source[i]; + var link = gantt._lpull[lid]; + if (previous.visitedLinks.indexOf(lid) > -1) continue; + previous.visitedLinks.push(lid); + if (link.isSimple) continue; + var desc = gantt._pull[link.target]; + // var isTargetingEnd = link.type === "finish_to_finish" || link.type === "start_to_finish"; + if (!link.unlocked) { + var descDates = this.getDescDates(link, start_date, end_date); + // var diff = gantt.getFreeDelay(link, desc, start_date, end_date); + if (descDates.end_date) { + debugger + } else { + gantt.safeMoveToStartDate(desc, descDates.start_date, previous); + } + //ysy.log.debug("pushing "+desc.text+" by freeDelay "+diff); + gantt.moveDesc(desc, {}, previous); + } else { + descDates = this.getDescDates(link, start_date, end_date); + // var descDiff = gantt.getFreeDelay(link, desc, start_date, end_date); + // if (descDiff <= 0) continue; + // console.log("OVER"); + if (descDates.start_date && descDates.start_date.isAfter(desc.start_date)) { + gantt.safeMoveToStartDate(desc, descDates.start_date, previous); + } + if (descDates.end_date && descDates.end_date.isAfter(desc.end_date)) { + debugger; + gantt.safeMoveToStartDate(desc, descDates.start_date, previous); + } + // ysy.log.debug("desc=" + desc.text +" diff="+diff+" descDiff="+descDiff); + gantt.moveDesc(desc, {}, previous); + } + } + } else { + /** ASCENDANTS */ + for (i = 0; i < task.$target.length; i++) { + lid = task.$target[i]; + link = gantt._lpull[lid]; + if (previous.visitedLinks.indexOf(lid) > -1) continue; + previous.visitedLinks.push(lid); + if (link.isSimple) continue; + var asc = gantt._pull[link.source]; + // var isTargetingEnd = link.type === "finish_to_finish" || link.type === "start_to_finish"; + if (!link.unlocked) { + var ascDates = this.getAscDates(link, start_date, end_date); + // var diff = gantt.getFreeDelay(link, desc, start_date, end_date); + if (ascDates.end_date) { + debugger + } else { + gantt.safeMoveToStartDate(asc, ascDates.start_date, previous); + } + //ysy.log.debug("pushing "+desc.text+" by freeDelay "+diff); + gantt.moveDesc(asc, {asc: true}, previous); + } else { + var ascDiff = gantt.getFreeDelay(link, task, asc.start_date, asc.end_date); + if (ascDiff <= 0) continue; + // ysy.log.debug("desc=" + desc.text +" diff="+diff+" descDiff="+descDiff); + gantt.moveDesc(asc, {asc: true}, previous); + } + } + } + if (!options.fromParent) { + gantt.moveParent(task, {}, previous); + } + }; + + gantt.moveChildren = function (task, options, previous) { + var branch = gantt._branches[task.id]; + if (!branch || branch.length === 0) return null; + var children = []; + for (var i = 0; i < branch.length; i++) { + var childId = branch[i]; + //if(gantt.isTaskVisible(childId)){continue;} + var child = gantt.getTask(childId); + if (!child._parent_offset) { + child._parent_offset = gantt._working_time_helper.get_work_units_between(options.old_start, child.start_date, "day"); + } + children.push(child); + if (previous.alreadyMovedFrom[childId]) continue; + var childOldStart = previous.alreadyMovedFrom[childId] || child.start_date; + var childNewStart = gantt._working_time_helper.add_worktime(task.start_date, child._parent_offset, "day", false); + gantt.safeMoveToStartDate(child, childNewStart, previous); + // child.start_date.add(childNewStart - child.start_date, "milliseconds"); + child._changed = gantt.config.drag_mode.move; + gantt.moveDesc(child, {old_start: childOldStart, fromParent: options.fromParent}, previous); + gantt.refreshTask(childId); + } + // gantt._update_parents(task.id); + return children; + }; + gantt.shouldMoveChildren = function (task) { + if (task.type === "milestone") return {milestoneMove: true}; + if (gantt._get_safe_type(task.type) === "task" && ysy.settings.parentIssueDates) return { + move: true, + adjust: true + }; + if (task.$open && gantt.isTaskVisible(task.id)) return {move: true}; + return false; + }; + gantt.safeMoveToStartDate = function (task, start_date, previous) { + if (task.start_date.isSame(start_date)) { + previous.alreadyMovedFrom[task.id] = moment(start_date); + return 0; + } + previous.alreadyMovedFrom[task.id] = moment(task.start_date); + if (task.start_date.isAfter(start_date)) { + // moved forward + var end_date = gantt._working_time_helper.add_worktime(start_date, task.duration, "day", true); + task.start_date.add(start_date - task.start_date, "milliseconds"); + task.end_date.add(end_date - task.end_date, "milliseconds"); + task._changed = gantt.config.drag_mode.move; + gantt.refreshTask(task.id); + return 0; + } else { + // moved backward + end_date = gantt._working_time_helper.add_worktime(start_date, task.duration, "day", true); + task.start_date.add(start_date - task.start_date, "milliseconds"); + task.end_date.add(end_date - task.end_date, "milliseconds"); + task._changed = gantt.config.drag_mode.move; + gantt.refreshTask(task.id); + var ascDiff = gantt.ascStop(task, task.start_date, task.end_date, previous); + if (ascDiff !== 0) { + // ysy.log.debug("diff=" + diff + " ascStopDiff=" + ascDiff); + return ascDiff; + } + } + }; + gantt.moveMilestoneChildren = function (milestone, options, previous) { + var branch = gantt._branches[milestone.id]; + if (!branch || branch.length === 0) return 0; + // ysy.log.debug("Shift children of \"" + milestone.text + "\" by " + shift + " seconds", "parent"); + ysy.log.debug("moveChildren(): of \"" + milestone.text + "\"(" + milestone.end_date.toISOString() + ") from " + options.old_start.toISOString(), "task_drag"); + // var milestoneEndDate=moment(milestone.end_date).add(shift,"seconds"); + // milestoneEndDate._isEndDate=true; + for (var i = 0; i < branch.length; i++) { + var childId = branch[i]; + var child = gantt.getTask(childId); + if (child.end_date.isAfter(milestone.end_date)) { + // var shift = milestone.end_date.diff(child.end_date, "seconds"); + // child.start_date.add(shift, 'seconds'); + var startDateValue = +child.start_date; + // child.end_date = moment(milestone.end_date); + // child.end_date._isEndDate = true; + child.end_date.add(milestone.end_date - child.end_date, "milliseconds"); + var childNewStart = gantt._working_time_helper.add_worktime(child.end_date, -child.duration, "day", false); + var childOldStart = moment(child.start_date); + child.start_date.add(childNewStart - child.start_date, "milliseconds"); + // ysy.log.debug(child.start_date.toISOString()+" "+child.end_date.toISOString()); + child._changed = gantt.config.drag_mode.move; + gantt.refreshTask(child.id); + previous.alreadyMovedFrom[child.id] = previous.alreadyMovedFrom[child.id] || childOldStart; + gantt.moveDesc(child, {old_date: moment(startDateValue), asc: true}, previous); + } + } + return 0; + }; + gantt.moveParent = function (child, option, previous) { + if (!ysy.settings.parentIssueDates) return; + var parentId = gantt.getParent(child.id); + if (previous.central.id === parentId) { + parent = previous.central; + } else { + var parent = gantt._pull[parentId]; + } + if (gantt._get_safe_type(parent.type) !== "task") return 0; + // previous.visitedParents.push(parent.id); + // if (parent.end_date.isBefore(child.end_date)) { + // parent.end_date.add(child.end_date - parent.end_date, "milliseconds"); + // parent._changed = gantt.config.drag_mode.move; + // gantt.refreshTask(parent.id); + // // if(option.skipParent) + // } + gantt.moveDesc(parent, {fromChild: true/*old_start:moment(parent.start_date)*/}, previous); + }; + gantt.startByMilestone = function (task, end_date) { + var issue = task.widget && task.widget.model; + if (!issue) return 0; + var milestone = ysy.data.milestones.getByID(issue.fixed_version_id); + if (!milestone) return 0; + var ganttMilestone = gantt._pull[milestone.getID()]; + if (ganttMilestone) { + var milestoneDate = ganttMilestone.end_date; + } else { + milestoneDate = milestone.start_date; + } + if (milestoneDate.isBefore(end_date)) { + ysy.log.debug("milestoneStop() for " + task.text + + " at " + milestone.start_date.format("YYYY-MM-DD"), "task_drag_milestone"); + return gantt._working_time_helper.toMoment( + gantt._working_time_helper.add_worktime(milestoneDate, -task.duration, "day", false) + ); + } + return null; + }; + gantt.descStartByMilestone = function (task, start_date, end_date) { + var newStartDate = gantt.startByMilestone(task, end_date); + // var start_date = +task.start_date / 1000 + diff; + // var end_date = +task.end_date / 1000 + diff; + for (var i = 0; i < task.$source.length; i++) { + var lid = task.$source[i]; + var link = gantt._lpull[lid]; + if (link.isSimple) continue; + var desc = gantt._pull[link.target]; + var descDiff = gantt.getFreeDelay(link, desc, start_date, end_date); + if (descDiff <= 0) continue; + var descEndDate = gantt._working_time_helper.add_worktime(desc.start_date + descDiff * 1000, desc.duration, "day", true); + var correctedDescStartDate = gantt.descStartByMilestone(desc, desc.start_date + descDiff * 1000, descEndDate); + if (!correctedDescStartDate) continue; + switch (link.type) { + case "precedes": + if (!link.unlocked) { + correctedDescStartDate.subtract(link.delay, "days"); + } + var correctedStartDate = gantt._working_time_helper.add_worktime(correctedDescStartDate, -task.duration, "day", false); + break; + default: + debugger; + } + if (!newStartDate || (correctedStartDate && correctedStartDate.isBefore(newStartDate))) { + newStartDate = correctedStartDate; + } + } + var childrenStartDate = gantt.childrenStartByMilestone(task, (newStartDate ? newStartDate : start_date) - task.start_date); + if (childrenStartDate) { + newStartDate = childrenStartDate; + } + if (newStartDate) { + ysy.log.debug("milestoneDescStop(): task " + task.text + " start set to " + newStartDate.format("YYYY-MM-DD"), "task_drag_milestone"); + } + return newStartDate; + }; + gantt.childrenStartByMilestone = function (task, diff) { + if (task.type === "milestone") return null; + var branch = gantt._branches[task.id]; + if (!branch || branch.length === 0) return 0; + var newStartDate; + for (var i = 0; i < branch.length; i++) { + var childId = branch[i]; + //if(gantt.isTaskVisible(childId)){continue;} + var child = gantt.getTask(childId); + var childEndDate = gantt._working_time_helper.add_worktime(child.start_date + diff, child.duration, "day", true); + var correctedStartDate = gantt.descStartByMilestone(child, child.start_date + diff, childEndDate); + if (!newStartDate || (correctedStartDate && correctedStartDate.isBefore(newStartDate))) { + newStartDate = correctedStartDate; + } + } + if (newStartDate) { + ysy.log.debug("milestoneChildrenStop(): \"" + task.text + "\" start_date set to " + newStartDate.format("YYYY-MM-DD"), "task_drag"); + } + return newStartDate; + }; + gantt.getLinkSourceDate = function (source, type) { + if (type === "precedes") return source.end_date; + if (type === "finish_to_finish") return source.end_date; + if (type === "start_to_start") return source.start_date; + if (type === "start_to_finish") return source.start_date; + return null; + }; + gantt.getLinkTargetDate = function (target, type) { + if (type === "precedes") return target.start_date; + if (type === "finish_to_finish") return target.end_date; + if (type === "start_to_start") return target.start_date; + if (type === "start_to_finish") return target.end_date; + return null; + }; + gantt.getLinkCorrection = function (type) { + if (type === "precedes") return 1; + if (type === "start_to_finish") return -1; + return 0; + }; + /** + * How much can be distance between two task shortened + * @param link - gantt link + * @param desc - gantt task + * @param {number} ascStartDate - number of milliseconds since epoch + * @param {number} ascEndDate - number of milliseconds since epoch + * @return {number} - number of seconds + */ + gantt.getFreeDelay = function (link, desc, ascStartDate, ascEndDate) { + if (!desc) desc = gantt._pull[link.target]; + var sourceDate; + var daysToSeconds = 60 * 60 * 24; + if (!ascStartDate) { + var asc = gantt._pull[link.source]; + ascStartDate = +asc.start_date; + ascEndDate = +asc.end_date; + } + if (link.type === "precedes" || link.type === "finish_to_finish") { + if (!ascEndDate) return 0; + sourceDate = ascEndDate; + } else if (link.type === "start_to_finish" || link.type === "start_to_start") { + if (!ascStartDate) return 0; + sourceDate = ascStartDate; + } else return 0; + var targetDate = +gantt.getLinkTargetDate(desc, link.type); + var correction = gantt.getLinkCorrection(link.type); + var delay = (targetDate - sourceDate) / daysToSeconds / 1000; + return ((link.unlocked ? 0 : link.delay) + correction - delay) * daysToSeconds; + }; + /** + * + * @param {{type:String,delay:int,source:int,target:int,unlocked:boolean}} link + * @param {int} ascStartDate + * @param {int} ascEndDate + * @return {{[start_date]:Object,[end_date]:Object}} + */ + gantt.getDescDates = function (link, ascStartDate, ascEndDate) { + //if (!desc) desc = gantt._pull[link.target]; + var sourceDate; + if (!ascStartDate) { + var asc = gantt._pull[link.source]; + ascStartDate = +asc.start_date; + ascEndDate = +asc.end_date; + } + if (link.type === "precedes" || link.type === "finish_to_finish") { + if (!ascEndDate) return {}; + sourceDate = ascEndDate; + } else if (link.type === "start_to_finish" || link.type === "start_to_start") { + if (!ascStartDate) return {}; + sourceDate = ascStartDate; + } else return {}; + var correction = gantt.getLinkCorrection(link.type); + if (link.type === "finish_to_finish" || link.type === "start_to_finish") { + var descEndDate = moment(sourceDate).add((link.unlocked ? 0 : link.delay) + correction, "days"); + descEndDate._isEndDate = true; + var descSafeEnd = gantt._working_time_helper.get_closest_worktime({ + dir: "future", + date: descEndDate + }); + return {end_date: descSafeEnd}; + } else { + var descStartDate = moment(sourceDate).add((link.unlocked ? 0 : link.delay) + correction, "days"); + var descSafeStart = gantt._working_time_helper.get_closest_worktime({ + dir: "future", + date: descStartDate + }); + return {start_date: descSafeStart}; + } + }; + /** + * + * @param {{type:String,delay:int,source:int,target:int,unlocked:boolean}} link + * @param {int} descStartDate + * @param {int} descEndDate + * @return {{[start_date]:moment,[end_date]:moment}} + */ + gantt.getAscDates = function (link, descStartDate, descEndDate) { + var targetDate; + if (!descStartDate) { + var desc = gantt._pull[link.target]; + descStartDate = +desc.start_date; + descEndDate = +desc.end_date; + } + if (link.type === "start_to_finish" || link.type === "finish_to_finish") { + if (!descEndDate) return {}; + targetDate = descEndDate; + } else if (link.type === "precedes" || link.type === "start_to_start") { + if (!descStartDate) return {}; + targetDate = descStartDate; + } else return {}; + var correction = gantt.getLinkCorrection(link.type); + if (link.type === "finish_to_finish" || link.type === "start_to_finish") { + var ascEndDate = moment(targetDate).subtract((link.unlocked ? 0 : link.delay) + correction, "days"); + ascEndDate._isEndDate = true; + var ascSafeEnd = gantt._working_time_helper.get_closest_worktime({ + dir: "past", + date: ascEndDate + }); + return {end_date: ascSafeEnd}; + } else { + var ascStartDate = moment(targetDate).subtract((link.unlocked ? 0 : link.delay) + correction, "days"); + var ascSafeStart = gantt._working_time_helper.get_closest_worktime({ + dir: "past", + date: ascStartDate + }); + return {start_date: ascSafeStart}; + } + }; + //################################################################################################################## + gantt.attachEvent("onLinkClick", function (id, mouseEvent) { + // if (!gantt.config.drag_links) return; + ysy.log.debug("LinkClick on " + id, "link_config"); + var link = gantt.getLink(id); + if (gantt._is_readonly(link)) return; + var source = gantt._pull[link.source]; + if (!source) return; + var target = gantt._pull[link.target]; + if (!target) return; + if (source.readonly && target.readonly) return; + var relation = link.widget.model; + var isUnlocked = relation.unlocked; + relation.set({unlocked: !isUnlocked, delay: isUnlocked ? relation.getActDelay() : 0}); + return false; + }); + gantt.attachEvent("onContextMenu", function (taskId, id /*, mouseEvent*/) { + if (taskId) return; + if (!gantt.config.drag_links) return; + ysy.log.debug("LinkClick on " + id, "link_config"); + var link = gantt.getLink(id); + if (gantt._is_readonly(link)) return; + var source = gantt._pull[link.source]; + if (!source) return; + var target = gantt._pull[link.target]; + if (!target) return; + if (source.readonly && target.readonly) return; + var linkConfigWidget = new ysy.view.LinkPopup().init(link.widget.model, link); + linkConfigWidget.$target = $("#ajax-modal");//$dialog; + linkConfigWidget.repaint(); + showModal("ajax-modal", "auto"); + }); + }; + patch(); + } +}; diff --git a/easy_gantt/assets/javascripts/easy_gantt/dhtml_rewrite.js b/easy_gantt/assets/javascripts/easy_gantt/dhtml_rewrite.js new file mode 100644 index 0000000..33cd6bc --- /dev/null +++ b/easy_gantt/assets/javascripts/easy_gantt/dhtml_rewrite.js @@ -0,0 +1,703 @@ +/* dhtml_rewrite.js */ +/* global ysy */ +/** + * @external Moment + */ +// noinspection JSCommentMatchesSignature +/** + * @function moment + * @param {*} [arg1] + * @return {Moment} + */ +window.ysy = window.ysy || {}; +ysy.view = ysy.view || {}; +ysy.view.applyGanttRewritePatch = function () { + ysy.view.initNonworkingDays = function () { + var work_helper = gantt._working_time_helper; + work_helper.defHours = ysy.settings.hoursPerDay; + var i; + // Now we specify working days + var nonWorking = ysy.settings.nonWorkingWeekDays; + for (i in nonWorking) { + work_helper.set_time({day: nonWorking[i] % 7, hours: false}); + } + work_helper._cache = {}; + }; + gantt._working_time_helper = { + defHours: 8, + timeformat: "YYYY-MM-DD", + days: {0: true, 1: true, 2: true, 3: true, 4: true, 5: true, 6: true}, + dates: {}, + _cache: {}, + oneDaySeconds: 1000 * 60 * 60 * 24, + oneHourSeconds: 1000 * 60 * 60, + //formatDate: function (date) { + // if (date._isAMomentObject) { + // return date.format(this.timeformat); + // } + // return moment(date).format(this.timeformat); + //}, + toMoment: function (date) { + if (date._isAMomentObject) { + return date; + } + return moment(date); + }, + set_time: function (settings) { + var hours = settings.hours !== undefined ? settings.hours : true; + if (settings.day !== undefined) { + this.days[settings.day] = hours; + } else if (settings.date !== undefined) { + this.dates[+settings.date] = hours; + } + + }, + is_weekend: function (date) { + date = this.toMoment(date); + return !this.days[date.day()]; + }, + is_working_day: function (date) { + date = this.toMoment(date); + if (!this.days[date.day()]) { + return false;//week day + } + var val = this.get_working_hours(date); + return val > 0; + }, + get_working_hours: function (date) { + //return 8; + //console.log("get_working_hours: "+date.toString()); + //var t = this._timestamp({date: date}); + date = this.toMoment(date); + var t = +date; + var hours = true; + //date = moment(date); + if (this.dates[t] !== undefined) { + hours = this.dates[t];//custom day + } else if (this.days[date.day()] !== undefined) { + hours = this.days[date.day()];//week day + } + if (hours === true) { + return this.defHours; + } else if (hours) { + return hours; + } + return 0; + }, + is_working_unit: function (date, unit, order) { + if (!gantt.config.work_time) + return true; + return this.is_working_day(date); + }, + save_working_unit: function (first_date, end_date, unit) { + var weekID = first_date.isoWeek(); + var workDays = this.get_work_units_between(first_date, end_date, "day"); + var workHours = this.get_work_units_between(first_date, end_date, "hour"); + this.weeks[weekID] = {days: workDays, hours: workHours}; + }, + _work_times_between: function (from, to) { + var spanID = from.valueOf() + "_" + to.valueOf(); + if (this._cache[spanID]) { + this._cache[spanID].cached = true; + return this._cache[spanID]; + } + if (from.isBefore(to)) { + var ret = this._work_times_forward(from, to); + } else { + ret = this._work_times_backward(from, to); + } + this._cache[spanID] = ret; + return ret; + }, + _work_times_forward: function (from, to) { + var sumHours = 0; + var sumDays = 0; + + if ((+from % this.oneHourSeconds) !== 0 || from.hours()!==0) { + var mover = moment(from).startOf("day"); + var first = true; + } else { + mover = from; + } + var count = 0; + while (mover.isBefore(to)) { + if ((count++) > 3000) { + ysy.log.error("_work_times_forward: Probably task with start_date=" + from.format("YYYY-MM-DD") + + " and end_date=" + to.format("YYYY-MM-DD") + " is too long"); + break; + } + var hours = this.get_working_hours(mover); + if (hours > 0) { + if (first) { + var rest = 1 - (from - mover) / this.oneDaySeconds; + sumDays += rest; + sumHours += hours * rest; + } else { + sumDays++; + sumHours += hours; + } + } + first = false; + mover.add(1, "days"); + } + if (hours > 0 && mover.isAfter(to)) { + rest = (mover - to) / this.oneDaySeconds; + sumDays -= rest; + sumHours -= hours * rest; + } + return {days: sumDays, hours: sumHours}; + }, + _work_times_backward: function (from, to) { + var sumHours = 0; + var sumDays = 0; + if ((+from % this.oneHourSeconds) !== 0 || from.hours()!==0) { + var mover = moment(from).startOf("day"); + var first = true; + mover.add(1, "day"); + } else { + mover = from; + } + var count = 0; + while (mover.isAfter(to)) { + mover.subtract(1, "days"); + if ((count++) > 3000) { + ysy.log.error("_work_times_backward: Probably task with start_date=" + to.format("YYYY-MM-DD") + + " and end_date=" + from.format("YYYY-MM-DD") + " is too long"); + break; + } + var hours = this.get_working_hours(mover); + if (hours > 0) { + if (first) { + var rest = (from - mover) / this.oneDaySeconds; + sumDays += rest; + sumHours += hours * rest; + } else { + sumDays++; + sumHours += hours; + } + } + first = false; + } + if (hours > 0 && mover.isBefore(to)) { + var rest2 = (to - mover) / this.oneDaySeconds; + sumDays -= rest2; + sumHours -= hours * rest2; + } + //ysy.log.debug(mover.format() + " hours=" + hours + " rest=" + rest + " rest2=" + rest2); + return {days: -1 * sumDays, hours: -1 * sumHours}; + }, + get_work_units_between: function (from, to, unit) { + if (!unit) { + ysy.log.error("missing unit"); + return 0; + } + var safeFrom = moment(from); + if (to._isEndDate) { + var safeTo = moment(to).add(1, "days"); + }else{ + safeTo = moment(to) + } + if (from._isEndDate) { + safeFrom = safeFrom.add(1, "days"); + } + var workSums = this._work_times_between(safeFrom, safeTo); + ysy.log.debug("get_work_units_between: " + this.toMoment(from).format("DD.MM.YYYY") + "(" + from._isEndDate + ") " + this.toMoment(to).format("DD.MM.YYYY") + "(" + to._isEndDate + ") " + unit + " " + (workSums.cached ? "(cached)" : ""), "date_helper"); + if (unit === "day") { + return workSums.days; + } else if (unit === "all") { + return workSums; + } else { + return workSums.hours; + } + }, + is_work_units_between: function (from, to, unit) { + if (!unit) { + return false; + } + return this.get_work_units_between(from, to, unit) > 0; + }, + /** + * + * @param {Moment|Date} from + * @param {boolean} from._isEndDate + * @param {int} duration + * @param {string} unit + * @param {boolean} toIsEndDate + * @return {Moment|Date} + */ + add_worktime: function (from, duration, unit, toIsEndDate) { + var hours; + ysy.log.debug("add_worktime: " + from.toString() + " " + duration + " " + unit, "date_helper"); + if (!unit) { + throw "Missing unit"; + } else { + unit = unit + "s"; + } + var added = 0; + var safeFrom = moment(from); + if (from._isEndDate) { + safeFrom.add(1, "days"); + } + if ((+from % this.oneHourSeconds) !== 0 || safeFrom.hours()!==0) { + var mover = moment(safeFrom).startOf("day"); + var diff = safeFrom - mover; + var first = true; + } else { + mover = safeFrom; + } + //if (!gantt.config.work_time||unit==="weeks") { + // console.log(mover.format("YYYY-MM-DD HH:mm")+" mover before"); + if (gantt.config.work_time && (unit === "days" || unit === "hours")) { + var count = 0; + if (duration === 0) { + while (true) { + if (count++ > 3000) { + ysy.log.error("_add_worktime: Probably task with start_date=" + moment(from).format("YYYY-MM-DD") + + " and duration " + duration + " is too long"); + break; + } + hours = this.get_working_hours(mover); + if (hours) { + if (first) { + mover.add(diff, "milliseconds"); + } + break; + } + first = false; + mover.add(1, "days"); + } + } else if (duration > 0) { + while (added < duration) { + if (count++ > 3000) { + ysy.log.error("_add_worktime: Probably task with start_date=" + moment(from).format("YYYY-MM-DD") + + " and duration " + duration + " is too long"); + break; + } + hours = this.get_working_hours(mover); + if (hours > 0) { + var rest = 1; + if (first) { + rest = 1 - diff / this.oneDaySeconds; + } + if (unit === "hours") { + added += rest * hours; + } else { + added += rest; + } + } + first = false; + mover.add(1, "days"); + } + if (hours && added > duration) { + mover.subtract((added - duration) * (unit === "hours" ? 3600 : 3600 * 24), "seconds"); + } + } else { + while (added < -duration) { + if (count++ > 3000) { + ysy.log.error("_add_worktime: Probably task with end_date=" + moment(from).format("YYYY-MM-DD") + + " and duration " + duration + " is too long"); + break; + } + if (!first || !diff) { + mover.add(-1, "days"); + } + hours = this.get_working_hours(mover); + if (hours > 0) { + rest = 1; + if (first) { + rest = diff / this.oneDaySeconds; + } + if (unit === "hours") { + added += rest * hours; + } else { + added += rest; + } + // console.log(mover.format("YYYY-MM-DD HH:mm")+" moved rest="+rest+" added="+added); + } + first = false; + } + if (hours && added > -duration) { + mover.add((added + duration) * (unit === "hours" ? 3600 : 3600 * 24), "seconds"); + } + } + } else { + mover.add(duration, unit); + } + // console.log(mover.format("YYYY-MM-DD HH:mm")+" mover after"); + if (toIsEndDate || !from._isEndDate && toIsEndDate === undefined) { + mover.add(-1, "days"); + mover._isEndDate = true; + } + // console.log(mover.format("YYYY-MM-DD HH:mm")+" mover endDated"); + if (moment.isMoment(from)) { + return mover; + } + return mover.toDate(); + }, + round_date: function (date, direction) { + ysy.log.debug("round_date: " + date.toString(), "date_helper"); + if (date._isAMomentObject) { + var momentDate = date; + } else { + momentDate = moment(date); + } + momentDate.add(12, "hours").startOf("day"); + var count = 0; + if (gantt.config.work_time) { + if (direction === 'past') { + while (!this.is_working_day(momentDate) && (count++) < 1000) { + momentDate.subtract(1, "days"); + } + } else { + while (!this.is_working_day(momentDate) && (count++) < 1000) { + momentDate.add(1, "days"); + } + } + } + if (!date._isAMomentObject) { + date.setTime(momentDate.valueOf()); + } + + //end.add(12,"hours").startOf("day"); + + }, + /** + * @param {String} settings.dir + * @param {Moment|int} settings.date + * @param {String} [settings.unit] + * @param {int} [settings.length] - minimal size of working span + * @return {*} + */ + get_closest_worktime: function (settings) { + ysy.log.debug("get_closest_worktime: " + settings.date.toString(), "date_helper"); + + var isMoment = moment.isMoment(settings.date); + var unit = settings.unit || "day"; + var dayStart = moment(settings.date).startOf(unit); + var diff = settings.date - dayStart; // in milliseconds + + + var goFuture = settings.dir === 'any' || settings.dir === 'future', + goPast = settings.dir === 'any' || settings.dir === 'past'; + + if (!settings.length) { + if (this.is_working_day(dayStart)) { + dayStart.add(diff, "milliseconds"); + if (isMoment) return dayStart; + return dayStart.toDate(); + } + } + if (goPast) { + var past_target = moment(dayStart); + if (settings.length) { + past_target.add(settings.length, "days"); + } + } + if (goFuture) { + var future_target = dayStart; + if (diff !== 0) { + future_target.add(1, unit); + } + } + if (goPast) { + // ysy.log.debug("checking past " + past_target.format("YYYY-MM-DD")); + if (this.is_working_day(past_target)) { + if (!settings.length) { + past_target.add(diff, "milliseconds"); + if (isMoment) return past_target; + return past_target.toDate(); + } else { + past_target.subtract(1, "days"); + if (this.is_working_day(past_target)) { + past_target.add(diff, "milliseconds"); + if (isMoment) return past_target; + return past_target.toDate(); + } + } + } + past_target.add(-1, "days"); + } + //will seek closest working hour in future or in past, one step in both direction per iteration + for (var count = 0; count < 3000; count++) { //be extra sure we won't fall into infinite loop, 3k seems big enough + if (goPast) { + // ysy.log.debug("checking past " + past_target.format("YYYY-MM-DD")); + if (this.is_working_day(past_target)) { + if (isMoment) return past_target; + return past_target.toDate(); + } + past_target.add(-1, "days"); + } + if (goFuture) { + // ysy.log.debug("checking future " + future_target.format("YYYY-MM-DD")); + if (this.is_working_day(future_target)) { + if (isMoment) return future_target; + return future_target.toDate(); + } + future_target.add(1, "days"); + } + } + dhtmlx.assert(false, "Invalid working time check"); + return false; + } + + + }; +//####################################################################### + gantt.date = { + now: function () { + return moment(); + }, + Date: function (date, isEndDate) { + if (!date) { + //return moment(); + return new Date(); + } + if (date._isAMomentObject/*||!isNaN(date)*/) { + var ndate = moment(date); + if (isEndDate === undefined) { + isEndDate = date._isEndDate; + } + } else { + //ysy.log.warning("date created as new Date(), not as moment()"); + ndate = moment(date).toDate(); + } + ndate._isEndDate = isEndDate; + return ndate; + }, + toMoment: function (date) { + if (!date) { + ysy.log.error("No date to convert to Moment"); + return; + } + if (date._isAMomentObject) { + return date; + } + return moment(date); + }, + fromMoment: function (date, momentDate) { + if (date._isAMomentObject) { + return date; + } + date.setTime(momentDate.valueOf()); + return date; + }, + init: function () { + return; + var s = gantt.locale.date.month_short; + var t = gantt.locale.date.month_short_hash = {}; + for (var i = 0; i < s.length; i++) + t[s[i]] = i; + + var s = gantt.locale.date.month_full; + var t = gantt.locale.date.month_full_hash = {}; + for (var i = 0; i < s.length; i++) + t[s[i]] = i; + }, + _startOf: function (date, unit) { + var momentDate = this.toMoment(date); + momentDate.startOf(unit); + return this.fromMoment(date, momentDate); + }, + date_part: function (date) { + return this._startOf(date, "day"); + }, + time_part: function (date) { + alert("Forbidden function"); + return (date.valueOf() / 1000 - date.getTimezoneOffset() * 60) % 86400; + }, + week_start: function (date) { + return this._startOf(date, "isoweek"); + }, + month_start: function (date) { + return this._startOf(date, "month"); + }, + quarter_start: function (date) { + return this._startOf(date, "quarter"); + }, + year_start: function (date) { + return this._startOf(date, "year"); + }, + day_start: function (date) { + return this._startOf(date, "day"); + }, + hour_start: function (date) { + alert("Forbidden function"); + if (date.getMinutes()) + date.setMinutes(0); + this.minute_start(date); + + return date; + }, + minute_start: function (date) { + alert("Forbidden function"); + if (date.getSeconds()) + date.setSeconds(0); + if (date.getMilliseconds()) + date.setMilliseconds(0); + return date; + }, + /*_add_days: function (date, inc) { + var ndate = new Date(date.valueOf()); + + ndate.setDate(ndate.getDate() + inc); + if (inc >= 0 && (!date.getHours() && ndate.getHours()) && //shift to yesterday on dst + (ndate.getDate() < date.getDate() || ndate.getMonth() < date.getMonth() || ndate.getFullYear() < date.getFullYear())) + ndate.setTime(ndate.getTime() + 60 * 60 * 1000 * (24 - ndate.getHours())); + return ndate; + },*/ + add: function (date, inc, mode) { + var momentDate = this.toMoment(date); + momentDate.add(inc, mode + "s"); + return momentDate.toDate(); + }, + to_fixed: function (num) { + if (num < 10) + return "0" + num; + return num; + }, + copy: function (date) { + var momentDate = moment(date); + if (date._isAMomentObject) { + return momentDate; + } + return momentDate.toDate(); + //return new Date(date.valueOf()); + }, + date_to_str: function (format, utc) { + ysy.log.debug("date_to_str " + format, "date_format"); + format = format.replace(/%[a-zA-Z]/g, function (a) { + switch (a) { + case "%d": + return "\"+gantt.date.to_fixed(date.getDate())+\""; + case "%m": + return "\"+gantt.date.to_fixed((date.getMonth()+1))+\""; + case "%j": + return "\"+date.getDate()+\""; + case "%n": + return "\"+(date.getMonth()+1)+\""; + case "%y": + return "\"+gantt.date.to_fixed(date.getFullYear()%100)+\""; + case "%Y": + return "\"+date.getFullYear()+\""; + case "%D": + return "\"+gantt.locale.date.day_short[date.getDay()]+\""; + case "%l": + return "\"+gantt.locale.date.day_full[date.getDay()]+\""; + case "%M": + return "\"+gantt.locale.date.month_short[date.getMonth()]+\""; + case "%F": + return "\"+gantt.locale.date.month_full[date.getMonth()]+\""; + case "%h": + return "\"+gantt.date.to_fixed((date.getHours()+11)%12+1)+\""; + case "%g": + return "\"+((date.getHours()+11)%12+1)+\""; + case "%G": + return "\"+date.getHours()+\""; + case "%Q": + return "\"+((date.getMonth() / 3) + 1)+\""; + case "%H": + return "\"+gantt.date.to_fixed(date.getHours())+\""; + case "%i": + return "\"+gantt.date.to_fixed(date.getMinutes())+\""; + case "%a": + return "\"+(date.getHours()>11?\"pm\":\"am\")+\""; + case "%A": + return "\"+(date.getHours()>11?\"PM\":\"AM\")+\""; + case "%s": + return "\"+gantt.date.to_fixed(date.getSeconds())+\""; + case "%W": + return "\"+gantt.date.to_fixed(gantt.date.getISOWeek(date))+\""; + default: + return a; + } + }); + if (utc) + format = format.replace(/date\.get/g, "date.getUTC"); + return new Function("date", "return \"" + format + "\";"); + }, + str_to_date: function (format, utc) { + ysy.log.debug("str_to_date " + format, "date_format"); + var splt = "var temp=date.match(/[a-zA-Z]+|[0-9]+/g);"; + var mask = format.match(/%[a-zA-Z]/g); + for (var i = 0; i < mask.length; i++) { + switch (mask[i]) { + case "%j": + case "%d": + splt += "set[2]=temp[" + i + "]||1;"; + break; + case "%n": + case "%m": + splt += "set[1]=(temp[" + i + "]||1)-1;"; + break; + case "%y": + splt += "set[0]=temp[" + i + "]*1+(temp[" + i + "]>50?1900:2000);"; + break; + case "%g": + case "%G": + case "%h": + case "%H": + splt += "set[3]=temp[" + i + "]||0;"; + break; + case "%i": + splt += "set[4]=temp[" + i + "]||0;"; + break; + case "%Y": + splt += "set[0]=temp[" + i + "]||0;"; + break; + case "%a": + case "%A": + splt += "set[3]=set[3]%12+((temp[" + i + "]||'').toLowerCase()=='am'?0:12);"; + break; + case "%s": + splt += "set[5]=temp[" + i + "]||0;"; + break; + case "%M": + splt += "set[1]=gantt.locale.date.month_short_hash[temp[" + i + "]]||0;"; + break; + case "%F": + splt += "set[1]=gantt.locale.date.month_full_hash[temp[" + i + "]]||0;"; + break; + default: + break; + } + } + var code = "set[0],set[1],set[2],set[3],set[4],set[5]"; + if (utc) + code = " Date.UTC(" + code + ")"; + return new Function("date", "var set=[0,0,1,0,0,0]; " + splt + " return new Date(" + code + ");"); + }, + getISOWeek: function (ndate) { + if (!ndate) + return false; + var mom = this.toMoment(ndate); + return mom.isoWeek(); + }, + getUTCISOWeek: function (ndate) { + return this.getISOWeek(ndate); + }, + convert_to_utc: function (date) { + var mom = this.toMoment(date); + mom.utc(); + return this.fromMoment(date, mom); + + //return new Date(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate(), date.getUTCHours(), date.getUTCMinutes(), date.getUTCSeconds()); + }, + parseDate: function (date, format) { + if (typeof (date) == "string") { + ysy.log.debug("parseDate() " + date + " " + format, "date_format"); + if (dhtmlx.defined(format)) { + if (typeof (format) == "string") + format = dhtmlx.defined(gantt.templates[format]) ? gantt.templates[format] : gantt.date.str_to_date(format); + else + format = gantt.templates.xml_date; + } + if (date) + date = format(date); + else + date = null; + } + return this.toMoment(date); + } + }; + ysy.view.initNonworkingDays(); +}; diff --git a/easy_gantt/assets/javascripts/easy_gantt/dhtmlxgantt.js b/easy_gantt/assets/javascripts/easy_gantt/dhtmlxgantt.js new file mode 100644 index 0000000..c8929d4 --- /dev/null +++ b/easy_gantt/assets/javascripts/easy_gantt/dhtmlxgantt.js @@ -0,0 +1,9439 @@ +/* +@license + +dhtmlxGantt v.3.2.1 Stardard +This software is covered by GPL license. You also can obtain Commercial or Enterprise license to use it in non-GPL project - please contact sales@dhtmlx.com. Usage without proper license is prohibited. + +(c) Dinamenta, UAB. +*/ +if (typeof(window.dhx4) == "undefined") { + + window.dhx4 = { + + version: "4.1.3", + + skin: null, // allow to be set by user + + skinDetect: function(comp) { + return {10:"dhx_skyblue",20:"dhx_web",30:"dhx_terrace"}[this.readFromCss(comp+"_skin_detect")]||null; + }, + + // read value from css + readFromCss: function(className, property) { + var t = document.createElement("DIV"); + t.className = className; + if (document.body.firstChild != null) document.body.insertBefore(t, document.body.firstChild); else document.body.appendChild(t); + var w = t[property||"offsetWidth"]; + t.parentNode.removeChild(t); + t = null; + return w; + }, + + // id manager + lastId: 1, + newId: function() { + return this.lastId++; + }, + + // z-index manager + zim: { + data: {}, + step: 5, + first: function() { + return 100; + }, + last: function() { + var t = this.first(); + for (var a in this.data) t = Math.max(t, this.data[a]); + return t; + }, + reserve: function(id) { + this.data[id] = this.last()+this.step; + return this.data[id]; + }, + clear: function(id) { + if (this.data[id] != null) { + this.data[id] = null; + delete this.data[id]; + } + } + }, + + // string to boolean + s2b: function(r) { + if (typeof(r) == "string") r = r.toLowerCase(); + return (r == true || r == 1 || r == "true" || r == "1" || r == "yes" || r == "y"); + }, + + // string to json + s2j: function(s) { + var obj = null; + dhx4.temp = null; + try { eval("dhx4.temp="+s); } catch(e) { dhx4.temp = null; } + obj = dhx4.temp; + dhx4.temp = null; + return obj; + }, + + // absolute top/left position on screen + absLeft: function(obj) { + if (typeof(obj) == "string") obj = document.getElementById(obj); + return this.getOffset(obj).left; + }, + absTop: function(obj) { + if (typeof(obj) == "string") obj = document.getElementById(obj); + return this.getOffset(obj).top; + }, + _aOfs: function(elem) { + var top = 0, left = 0; + while (elem) { + top = top + parseInt(elem.offsetTop); + left = left + parseInt(elem.offsetLeft); + elem = elem.offsetParent; + } + return {top: top, left: left}; + }, + _aOfsRect: function(elem) { + var box = elem.getBoundingClientRect(); + var body = document.body; + var docElem = document.documentElement; + var scrollTop = window.pageYOffset || docElem.scrollTop || body.scrollTop; + var scrollLeft = window.pageXOffset || docElem.scrollLeft || body.scrollLeft; + var clientTop = docElem.clientTop || body.clientTop || 0; + var clientLeft = docElem.clientLeft || body.clientLeft || 0; + var top = box.top + scrollTop - clientTop; + var left = box.left + scrollLeft - clientLeft; + return { top: Math.round(top), left: Math.round(left) }; + }, + getOffset: function(elem) { + if (elem.getBoundingClientRect) { + return this._aOfsRect(elem); + } else { + return this._aOfs(elem); + } + }, + + // copy obj + _isObj: function(k) { + return (k != null && typeof(k) == "object" && typeof(k.length) == "undefined"); + }, + _copyObj: function(r) { + if (this._isObj(r)) { + var t = {}; + for (var a in r) { + if (typeof(r[a]) == "object" && r[a] != null) t[a] = this._copyObj(r[a]); else t[a] = r[a]; + } + } else { + var t = []; + for (var a=0; a= 0); + var dim = {}; + dim.left = document.body.scrollLeft; + dim.right = dim.left+(window.innerWidth||document.body.clientWidth); + dim.top = Math.max((isIE?document.documentElement:document.getElementsByTagName("html")[0]).scrollTop, document.body.scrollTop); + dim.bottom = dim.top+(isIE?Math.max(document.documentElement.clientHeight||0,document.documentElement.offsetHeight||0):window.innerHeight); + return dim; + }, + + // input/textarea range selection + selectTextRange: function(inp, start, end) { + + inp = (typeof(inp)=="string"?document.getElementById(inp):inp); + + var len = inp.value.length; + start = Math.max(Math.min(start, len), 0); + end = Math.min(end, len); + + if (inp.setSelectionRange) { + try {inp.setSelectionRange(start, end);} catch(e){}; // combo in grid under IE requires try/catch + } else if (inp.createTextRange) { + var range = inp.createTextRange(); + range.moveStart("character", start); + range.moveEnd("character", end-len); + try {range.select();} catch(e){}; + } + }, + // transition + transData: null, + transDetect: function() { + + if (this.transData == null) { + + this.transData = {transProp: false, transEv: null}; + + // transition, MozTransition, WebkitTransition, msTransition, OTransition + var k = { + "MozTransition": "transitionend", + "WebkitTransition": "webkitTransitionEnd", + "OTransition": "oTransitionEnd", + "msTransition": "transitionend", + "transition": "transitionend" + }; + + for (var a in k) { + if (this.transData.transProp == false && document.documentElement.style[a] != null) { + this.transData.transProp = a; + this.transData.transEv = k[a]; + } + } + k = null; + } + + return this.transData; + + }, + + // xml parser + _xmlNodeValue: function(node) { + var value = ""; + for (var q=0; q= 0 || navigator.userAgent.indexOf("Trident") >= 0); + window.dhx4.isIE6 = (window.XMLHttpRequest == null && navigator.userAgent.indexOf("MSIE") >= 0); + window.dhx4.isIE7 = (navigator.userAgent.indexOf("MSIE 7.0") >= 0 && navigator.userAgent.indexOf("Trident") < 0); + window.dhx4.isIE8 = (navigator.userAgent.indexOf("MSIE 8.0") >= 0 && navigator.userAgent.indexOf("Trident") >= 0); + window.dhx4.isOpera = (navigator.userAgent.indexOf("Opera") >= 0); + window.dhx4.isChrome = (navigator.userAgent.indexOf("Chrome") >= 0); + window.dhx4.isKHTML = (navigator.userAgent.indexOf("Safari") >= 0 || navigator.userAgent.indexOf("Konqueror") >= 0); + window.dhx4.isFF = (navigator.userAgent.indexOf("Firefox") >= 0); + window.dhx4.isIPad = (navigator.userAgent.search(/iPad/gi) >= 0); +}; + + + +/* +if (typeof(window.dhx4.ajax) == "undefined") { + + window.dhx4.ajax = { + + // if false - dhxr param will added to prevent caching on client side (default), + // if true - do not add extra params + cache: false, + + // default method for load/loadStruct, post/get allowed + // get - since 4.1.1, this should fix 412 error for macos safari + method: "get", + + parse: function(data) { + if (typeof data !== "string") return data; + + data = data.replace(/^[\s]+/,""); + if (window.DOMParser && !dhx4.isIE) { // ff,ie9 + var obj = (new window.DOMParser()).parseFromString(data, "text/xml"); + } else if (window.ActiveXObject !== window.undefined) { + var obj = new window.ActiveXObject("Microsoft.XMLDOM"); + obj.async = "false"; + obj.loadXML(data); + } + return obj; + }, + xmltop: function(tagname, xhr, obj) { + if (typeof xhr.status == "undefined" || xhr.status < 400) { + var xml = (!xhr.responseXML) ? dhx4.ajax.parse(xhr.responseText || xhr) : (xhr.responseXML || xhr); + if (xml && xml.documentElement !== null && !xml.getElementsByTagName("parsererror").length) { + return xml.getElementsByTagName(tagname)[0]; + } + } + if (obj !== -1) dhx4.callEvent("onLoadXMLError",["Incorrect XML", arguments[1], obj]); + return document.createElement("DIV"); + }, + xpath: function(xpathExp, docObj) { + if (!docObj.nodeName) docObj = docObj.responseXML || docObj; + if (dhx4.isIE) { + return docObj.selectNodes(xpathExp)||[]; + } else { + var rows = []; + var first; + var col = (docObj.ownerDocument||docObj).evaluate(xpathExp, docObj, null, XPathResult.ANY_TYPE, null); + while (first = col.iterateNext()) rows.push(first); + return rows; + } + }, + query: function(config) { + dhx4.ajax._call( + (config.method || "GET"), + config.url, + config.data || "", + (config.async || true), + config.callback, + null, + config.headers + ); + }, + get: function(url, onLoad) { + this._call("GET", url, null, true, onLoad); + }, + getSync: function(url) { + return this._call("GET", url, null, false); + }, + put: function(url, postData, onLoad) { + this._call("PUT", url, postData, true, onLoad); + }, + del: function(url, postData, onLoad) { + this._call("DELETE", url, postData, true, onLoad); + }, + post: function(url, postData, onLoad) { + if (arguments.length == 1) { + postData = ""; + } else if (arguments.length == 2 && (typeof(postData) == "function" || typeof(window[postData]) == "function")) { + onLoad = postData; + postData = ""; + } else { + postData = String(postData); + } + this._call("POST", url, postData, true, onLoad); + }, + postSync: function(url, postData) { + postData = (postData == null ? "" : String(postData)); + return this._call("POST", url, postData, false); + }, + getLong: function(url, onLoad) { + this._call("GET", url, null, true, onLoad, {url:url}); + }, + postLong: function(url, postData, onLoad) { + if (arguments.length == 2 && (typeof(postData) == "function" || typeof(window[postData]))) { + onLoad = postData; + postData = ""; + } + this._call("POST", url, postData, true, onLoad, {url:url, postData:postData}); + }, + _call: function(method, url, postData, async, onLoad, longParams, headers) { + + var t = (window.XMLHttpRequest && !dhx4.isIE ? new XMLHttpRequest() : new ActiveXObject("Microsoft.XMLHTTP")); + var isQt = (navigator.userAgent.match(/AppleWebKit/) != null && navigator.userAgent.match(/Qt/) != null && navigator.userAgent.match(/Safari/) != null); + + if (async == true) { + t.onreadystatechange = function() { + if ((t.readyState == 4) || (isQt == true && t.readyState == 3)) { // what for long response and status 404? + if (t.status != 200 || t.responseText == "") + if (!dhx4.callEvent("onAjaxError", [t])) return; + + window.setTimeout(function(){ + if (typeof(onLoad) == "function") { + onLoad.apply(window, [{xmlDoc:t}]); // dhtmlx-compat, response.xmlDoc.responseXML/responseText + } + if (longParams != null) { + if (typeof(longParams.postData) != "undefined") { + dhx4.ajax.postLong(longParams.url, longParams.postData, onLoad); + } else { + dhx4.ajax.getLong(longParams.url, onLoad); + } + } + onLoad = null; + t = null; + },1); + } + } + } + + if (method == "GET" && this.cache != true) { + url += (url.indexOf("?")>=0?"&":"?")+"dhxr"+new Date().getTime()+"=1"; + } + + t.open(method, url, async); + + if (headers){ + for (var key in headers) + t.setRequestHeader(key, headers[key]); + } else if (method.toUpperCase() == "POST" || method == "PUT" || method == "DELETE") { + t.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); + } else if (method == "GET") { + postData = null; + } + + t.setRequestHeader("X-Requested-With", "XMLHttpRequest"); + + t.send(postData); + + if (!async) return {xmlDoc:t}; // dhtmlx-compat, response.xmlDoc.responseXML/responseText + + } + }; + +}; +*/ + +if (typeof(window.dhx4._eventable) == "undefined") { + + window.dhx4._eventable = function(obj, mode) { + + if (mode == "clear") { + + obj.detachAllEvents(); + + obj.dhxevs = null; + + obj.attachEvent = null; + obj.detachEvent = null; + obj.checkEvent = null; + obj.callEvent = null; + obj.detachAllEvents = null; + + obj = null; + + return; + + } + + obj.dhxevs = { data: {} }; + + obj.attachEvent = function(name, func) { + name = String(name).toLowerCase(); + if (!this.dhxevs.data[name]) this.dhxevs.data[name] = {}; + var eventId = window.dhx4.newId(); + this.dhxevs.data[name][eventId] = func; + return eventId; + } + + obj.detachEvent = function(eventId) { + for (var a in this.dhxevs.data) { + var k = 0; + for (var b in this.dhxevs.data[a]) { + if (b == eventId) { + this.dhxevs.data[a][b] = null; + delete this.dhxevs.data[a][b]; + } else { + k++; + } + } + if (k == 0) { + this.dhxevs.data[a] = null; + delete this.dhxevs.data[a]; + } + } + } + + obj.checkEvent = function(name) { + name = String(name).toLowerCase(); + return (this.dhxevs.data[name] != null); + } + + obj.callEvent = function(name, params) { + name = String(name).toLowerCase(); + if (this.dhxevs.data[name] == null) return true; + var r = true; + for (var a in this.dhxevs.data[name]) { + r = this.dhxevs.data[name][a].apply(this, params) && r; + } + return r; + } + + obj.detachAllEvents = function() { + for (var a in this.dhxevs.data) { + for (var b in this.dhxevs.data[a]) { + this.dhxevs.data[a][b] = null; + delete this.dhxevs.data[a][b]; + } + this.dhxevs.data[a] = null; + delete this.dhxevs.data[a]; + } + } + + obj = null; + }; + + dhx4._eventable(dhx4); + +}; + +if (typeof(window.dhtmlx) == "undefined") { + window.dhtmlx={ + extend:function(a, b){ + for (var key in b) + if (!a[key]) + a[key]=b[key]; + return a; + }, + extend_api:function(name,map,ext){ + var t = window[name]; + if (!t) return; //component not defined + window[name]=function(obj){ + if (obj && typeof obj == "object" && !obj.tagName){ + var that = t.apply(this,(map._init?map._init(obj):arguments)); + //global settings + for (var a in dhtmlx) + if (map[a]) this[map[a]](dhtmlx[a]); + //local settings + for (var a in obj){ + if (map[a]) this[map[a]](obj[a]); + else if (a.indexOf("on")===0){ + this.attachEvent(a,obj[a]); + } + } + } else + var that = t.apply(this,arguments); + if (map._patch) map._patch(this); + return that||this; + }; + window[name].prototype=t.prototype; + if (ext) + dhtmlx.extend(window[name].prototype,ext); + }, + url:function(str){ + if (str.indexOf("?") != -1) + return "&"; + else + return "?"; + } + }; +}; + + _isFF = false; + _isIE = false; + _isOpera = false; + _isKHTML = false; + _isMacOS = false; + _isChrome = false; + _FFrv = false; + _KHTMLrv = false; + _OperaRv = false; + +if (navigator.userAgent.indexOf('Macintosh') != -1) + _isMacOS=true; + + +if (navigator.userAgent.toLowerCase().indexOf('chrome')>-1) + _isChrome=true; + +if ((navigator.userAgent.indexOf('Safari') != -1)||(navigator.userAgent.indexOf('Konqueror') != -1)){ + _KHTMLrv = parseFloat(navigator.userAgent.substr(navigator.userAgent.indexOf('Safari')+7, 5)); + + if (_KHTMLrv > 525){ //mimic FF behavior for Safari 3.1+ + _isFF=true; + _FFrv = 1.9; + } else + _isKHTML=true; +} else if (navigator.userAgent.indexOf('Opera') != -1){ + _isOpera=true; + _OperaRv=parseFloat(navigator.userAgent.substr(navigator.userAgent.indexOf('Opera')+6, 3)); +} + + +else if (navigator.appName.indexOf("Microsoft") != -1){ + _isIE=true; + if ((navigator.appVersion.indexOf("MSIE 8.0")!= -1 || + navigator.appVersion.indexOf("MSIE 9.0")!= -1 || + navigator.appVersion.indexOf("MSIE 10.0")!= -1 || + document.documentMode > 7) && + document.compatMode != "BackCompat"){ + _isIE=8; + } +} else if (navigator.appName == 'Netscape' && navigator.userAgent.indexOf("Trident") != -1){ + //ie11 + _isIE=8; +} else { + _isFF=true; + _FFrv = parseFloat(navigator.userAgent.split("rv:")[1]) +} + +if (typeof(window.dhtmlxEvent) == "undefined") { + + function dhtmlxEvent(el, event, handler){ + if (el.addEventListener) + el.addEventListener(event, handler, false); + + else if (el.attachEvent) + el.attachEvent("on"+event, handler); + } +}; + +if (dhtmlxEvent.touchDelay == null) { + dhtmlxEvent.touchDelay = 2000; +}; + +if (typeof(dhtmlxEvent.initTouch) == "undefined") { + + dhtmlxEvent.initTouch = function(){ + var longtouch; + var target; + var tx, ty; + + dhtmlxEvent(document.body, "touchstart", function(ev){ + target = ev.touches[0].target; + tx = ev.touches[0].clientX; + ty = ev.touches[0].clientY; + longtouch = window.setTimeout(touch_event, dhtmlxEvent.touchDelay); + }); + function touch_event(){ + if (target){ + var ev = document.createEvent("HTMLEvents"); // for chrome and firefox + ev.initEvent("dblclick", true, true); + target.dispatchEvent(ev); + longtouch = target = null; + } + }; + dhtmlxEvent(document.body, "touchmove", function(ev){ + if (longtouch){ + if (Math.abs(ev.touches[0].clientX - tx) > 50 || Math.abs(ev.touches[0].clientY - ty) > 50 ){ + window.clearTimeout(longtouch); + longtouch = target = false; + } + } + }); + dhtmlxEvent(document.body, "touchend", function(ev){ + if (longtouch){ + window.clearTimeout(longtouch); + longtouch = target = false; + } + }); + + dhtmlxEvent.initTouch = function(){}; + }; +}; + +if(!window.dhtmlx) + window.dhtmlx = {}; + +(function(){ + var _dhx_msg_cfg = null; + function callback(config, result){ + var usercall = config.callback; + modality(false); + config.box.parentNode.removeChild(config.box); + _dhx_msg_cfg = config.box = null; + if (usercall) + usercall(result); + } + function modal_key(e){ + if (_dhx_msg_cfg){ + e = e||event; + var code = e.which||event.keyCode; + if (dhtmlx.message.keyboard){ + if (code == 13 || code == 32) + callback(_dhx_msg_cfg, true); + if (code == 27) + callback(_dhx_msg_cfg, false); + } + if (e.preventDefault) + e.preventDefault(); + return !(e.cancelBubble = true); + } + } + if (document.attachEvent) + document.attachEvent("onkeydown", modal_key); + else + document.addEventListener("keydown", modal_key, true); + + function modality(mode){ + if(!modality.cover){ + modality.cover = document.createElement("DIV"); + //necessary for IE only + modality.cover.onkeydown = modal_key; + modality.cover.className = "dhx_modal_cover"; + document.body.appendChild(modality.cover); + } + var height = document.body.scrollHeight; + modality.cover.style.display = mode?"inline-block":"none"; + } + + function button(text, result){ + var button_css = "dhtmlx_"+text.toLowerCase().replace(/ /g, "_")+"_button"; // dhtmlx_ok_button, dhtmlx_click_me_button + return "
"+text+"
"; + } + + function info(text){ + if (!t.area){ + t.area = document.createElement("DIV"); + t.area.className = "dhtmlx_message_area"; + t.area.style[t.position]="5px"; + document.body.appendChild(t.area); + } + + t.hide(text.id); + var message = document.createElement("DIV"); + message.innerHTML = "
"+text.text+"
"; + //message.className = "dhtmlx-info dhtmlx-" + text.type; + message.className = "dhtmlx-message dhtmlx-" + text.type; // HOSEK + message.onclick = function(){ + t.hide(text.id); + text = null; + }; + + if (t.position == "bottom" && t.area.firstChild) + t.area.insertBefore(message,t.area.firstChild); + else + t.area.appendChild(message); + + if (text.expire > 0) + t.timers[text.id]=window.setTimeout(function(){ + t.hide(text.id); + }, text.expire); + + t.pull[text.id] = message; + message = null; + + return text.id; + } + function _boxStructure(config, ok, cancel){ + var box = document.createElement("DIV"); + box.className = " dhtmlx_modal_box dhtmlx-"+config.type; + box.setAttribute("dhxbox", 1); + + var inner = ''; + + if (config.width) + box.style.width = config.width; + if (config.height) + box.style.height = config.height; + if (config.title) + inner+='
'+config.title+'
'; + inner+='
'+(config.content?'':config.text)+'
'; + if (ok) + inner += button(config.ok || "OK", true); + if (cancel) + inner += button(config.cancel || "Cancel", false); + if (config.buttons){ + for (var i=0; i
")); + //} + dhtmlxEventable(this); + dhtmlxEvent(obj, "mousedown", dhtmlx.bind(function(e) { + config.original_target = {target : e.target || e.srcElement}; + this.dragStart(obj, e); + }, this)); + +} +dhtmlxDnD.prototype = { + dragStart: function(obj, e) { + this.config = { + obj: obj, + marker: null, + started: false, + pos: this.getPosition(e), + sensitivity: 4, + offset:$(obj).offset() + }; + if(this._settings) + dhtmlx.mixin(this.config, this._settings, true); + e.preventDefault(); + + var mousemove = dhtmlx.bind(function(e) { return this.dragMove(obj, e); }, this); + var scroll = dhtmlx.bind(function(e) { return this.dragScroll(obj, e); }, this); + var limitation = false; + var limited_mousemove = dhtmlx.bind(function(e) { + //if(dhtmlx.defined(this.config.updates_per_second)){ + // if(!gantt._checkTimeout(this, this.config.updates_per_second)) + // return true; + //} + if(limitation) return; + limitation = true; + var res = mousemove(e); + limitation = false; + return res; + }, this); + + var mouseup = dhtmlx.bind(function(e) { + dhtmlxDetachEvent(document.body, "mousemove", limited_mousemove); + dhtmlxDetachEvent(document.body, "mouseup", mouseup); + return this.dragEnd(obj); + }, this); + // initialize dnd marker + if(this.config.marker){ + var marker = this.config.marker = document.createElement("div"); + marker.className = "gantt_drag_marker"; + //marker.innerHTML = "Dragging object"; + document.body.appendChild(marker); + //$("#gantt_markers")[0].appendChild(marker); + //obj.appendChild(marker); + } + e.pos = this.getPosition(e); + this.actPos= e.pos; // HOSEK + dhtmlxEvent(document.body, "mousemove", limited_mousemove); + dhtmlxEvent(document.body, "mouseup", mouseup); + // document.body.className += " gantt_noselect"; + this.callEvent("onDragStart", [obj,e]); + }, + dragMove: function(obj, e) { + if (/*!this.config.marker &&*/ !this.config.started) { + var pos = this.getPosition(e); + var diff_x = pos.x - this.config.pos.x; + var diff_y = pos.y - this.config.pos.y; + var distance = Math.sqrt(Math.pow(Math.abs(diff_x), 2) + Math.pow(Math.abs(diff_y), 2)); + + if (distance > this.config.sensitivity) { + // real drag starts here, + // when user moves mouse at first time after onmousedown + /*if(this.config.started){ + return; + }*/ + this.config.started = true; + this.config.ignore = false; + if (this.callEvent("onBeforeDragStart", [obj, this.config.original_target]) === false) { + this.config.ignore = true; + return true; + } + this.callEvent("onAfterDragStart", [obj, this.config.original_target]); + } else + this.config.ignore = true; + } + if (!this.config.ignore) { + e.pos = this.getPosition(e); + this.actPos= e.pos; // HOSEK + if(this.config.marker){ + this.config.marker.style.left = e.pos.x + "px"; + this.config.marker.style.top = e.pos.y + "px"; + } + this.callEvent("onDragMove", [obj,e]); + } + }, + + dragEnd: function(obj) { + if (this.config.marker) { + this.destroyMarker(); + } + if(this.config.started&&!this.config.ignore){ + this.callEvent("onDragEnd", []); + } + // document.body.className = document.body.className.replace(" gantt_noselect", ""); + }, + getDiff: function(e){ + //var pos=this.getPosition(e); + return {x:(this.actPos.x-this.config.pos.x),y:(this.actPos.y-this.config.pos.y)}; + }, + getPosition: function(e) { + var x = 0, y = 0; + e = e || window.event; + if (e.pageX || e.pageY) { + x = e.pageX; + y = e.pageY; + } else if (e.clientX || e.clientY) { + x = e.clientX + document.body.scrollLeft + document.documentElement.scrollLeft; + y = e.clientY + document.body.scrollTop + document.documentElement.scrollTop; + } + return { x:x, y:y }; + }, + destroyMarker:function(){ + this.config.marker.parentNode.removeChild(this.config.marker); + this.config.marker = null; + }, + getRelativePos:function(){ + return {x:(this.actPos.x-this.config.offset.left),y:(this.actPos.y-this.config.offset.top)} + } +}; +gantt._init_grid = function () { + this._click.gantt_close = dhtmlx.bind(function (e, id, trg) { + this.close(id); + return false; + }, this); + this._click.gantt_open = dhtmlx.bind(function (e, id, trg) { + this.open(id); + return false; + }, this); + + + this._click.gantt_row = dhtmlx.bind(function (e, id, trg) { + if (id !== null) { + var task = this.getTask(id); + //if(this.config.scroll_on_click) + // this.showDate(task.start_date); + this.callEvent("onTaskRowClick", [id, trg]); + } + }, this); + + this._click.gantt_grid_head_cell = dhtmlx.bind(function (e, id, trg) { + var column = trg.getAttribute("column_id"); + + if (!this.callEvent("onGridHeaderClick", [column, e])) + return; + + if (column == "add") { + this._click.gantt_add(e, this.config.root_id); + } else if (this.config.sort) { + var sort = (this._sort && this._sort.direction && this._sort.name == column) ? this._sort.direction : "desc"; + // invert sort direction + sort = (sort == "desc") ? "asc" : "desc"; + this._sort = { + name: column, + direction: sort + }; + this.sort(column, sort == "desc"); + } + }, this); + + if (!this.config.sort && (this.config.order_branch || this.config.rearrange_branch)) { + this._init_dnd(this.config.rearrange_branch); + } + + this._click.gantt_add = dhtmlx.bind(function (e, id, trg) { + if (this.config.readonly) return; + + var item = { }; + this.createTask(item, id ? id : this.config.root_id); + + return false; + }, this); + + if(this._init_resize){ + this._init_resize(); + } + +}; + +gantt._render_grid = function () { + if (this._is_grid_visible()) { + this._calc_grid_width(); + this._render_grid_header(); + } +}; + +gantt._calc_grid_width = function () { + var columns = this.getGridColumns(); + var cols_width = 0; + var unknown = []; + var width = []; + + for (var i = 0; i < columns.length; i++) { + var v = parseInt(columns[i].width, 10); + if (window.isNaN(v)) { + v = 50; + unknown.push(i); + } + width[i] = v; + cols_width += v; + } + + if (this.config.autofit || unknown.length) { + var diff = this._get_grid_width() - cols_width; + // TODO: logic may be improved for proportional changing of width + var step = diff / (unknown.length > 0 ? unknown.length : (width.length > 0 ? width.length : 1)); + if (unknown.length > 0) { + // there are several columns with undefined width + var delta = diff / (unknown.length ? unknown.length : 1); + for (var i = 0; i < unknown.length; i++) { + var index = unknown[i]; + width[index] += delta; + } + } else { + // delta must be added for all columns + var delta = diff / (width.length ? width.length : 1); + for (var i = 0; i < width.length; i++) + width[i] += delta; + } + + for (var i = 0; i < width.length; i++) { + columns[i].width = width[i]; + } + }else{ + this.config.grid_width = cols_width; + } +}; + +gantt._render_grid_header = function () { + var columns = this.getGridColumns(); + var cells = []; + var width = 0, + labels = this.locale.labels; + + var lineHeigth = this.config.scale_height - 2; + + for (var i = 0; i < columns.length; i++) { + var last = i == columns.length - 1; + var col = columns[i]; + if (last && this._get_grid_width() > width + col.width) + col.width = this._get_grid_width() - width; + width += col.width; + var sort = (this._sort && col.name == this._sort.name) ? ("
") : ""; + var cssClass = ["gantt_grid_head_cell", + ("gantt_grid_head_" + col.name), + (last ? "gantt_last_cell" : ""), + this.templates.grid_header_class(col.name, col)].join(" "); + + var style = "width:" + (col.width - (last ? 1 : 0)) + "px;"; + var label = (col.label || labels["column_" + col.name]); + label = label || ""; + var cell = "
" + label + sort + "
"; + cells.push(cell); + } + this.$grid_scale.style.height = (this.config.scale_height - 1) + "px"; + this.$grid_scale.style.lineHeight = lineHeigth + "px"; + this.$grid_scale.style.width = (width - 1) + "px"; + this.$grid_scale.innerHTML = cells.join(""); +}; + + +gantt._render_grid_item = function (item) { + if (!gantt._is_grid_visible()) + return null; + if(!item.columns){ + return gantt._render_grid_superitem(item); + } + var columns = this.getGridColumns(); + var cells = []; + var width = 0; + for (var i = 0; i < columns.length; i++) { + var last = i == columns.length - 1; + var col = columns[i]; + var cell; + + var value; + if (col.name == "add") { + value = "
"; + } else { + if (col.template) + value = col.template(item); + else + value = item[col.name]; + + if (value instanceof Date) + value = this.templates.date_grid(value); + value = "
" + value + "
"; + } + var css = "gantt_cell" +(col.css?" "+col.css:"")+ (last ? " gantt_last_cell" : ""); + + var tree = ""; + if (col.tree) { + for (var j = 0; j < item.$level; j++) + tree += this.templates.grid_indent(item); + + var has_child = this._has_children(item.id); + if (has_child) { + tree += this.templates.grid_open(item); + tree += this.templates.grid_folder(item); + } else { + tree += this.templates.grid_blank(item); + tree += this.templates.grid_file(item); + } + } + var style = "width:" + (col.width - (last ? 1 : 0)) + "px;"; + if (dhtmlx.defined(col.align)) + style += "text-align:" + col.align + ";"; + cell = "
" + tree + value + "
"; + cells.push(cell); + } + var css = item.$index % 2 === 0 ? " odd" : ""; + css += (item.$transparent) ? " gantt_transparent" : ""; + if (this.templates.grid_row_class) { + var css_template = this.templates.grid_row_class.call(this, item.start_date, item.end_date, item); + if (css_template) + css += " " + css_template; + } + + if (this.getState().selected_task == item.id) { + css += " gantt_selected"; + } + var el = document.createElement("div"); + el.className = "gantt_row" + css; + el.setAttribute("data-url","/issues/"+item.id+".json"); // HOSEK + el.style.height = this.config.row_height + "px"; + el.style.lineHeight = (gantt.config.row_height) + "px"; + el.setAttribute(this.config.task_attribute, item.id); + el.innerHTML = cells.join(""); + return el; +}; + + +gantt.open = function (id) { + if (this.callEvent("onBeforeTaskOpened", [id]) === false) return; + gantt._set_item_state(id, true); + this.callEvent("onTaskOpened", [id]); +}; +gantt.close = function (id) { + gantt._set_item_state(id, false); + this.callEvent("onTaskClosed", [id]); +}; +gantt._set_item_state = function (id, state) { + if (id && this._pull[id]) { + this._pull[id].$open = state; + this.refreshData(); + } +}; + +gantt._is_grid_visible = function () { + return (this.config.grid_width && this.config.show_grid); +}; +gantt._get_grid_width = function () { + if (this._is_grid_visible()) { + if (this._is_chart_visible()) { + return this.config.grid_width; + } else { + return this._x; + } + } else { + return 0; + } +}; +gantt.getTaskIndex = function (id) { + var branch = this.getChildren(this.getParent(id)); + for (var i = 0; i < branch.length; i++) + if (branch[i] == id) + return i; + + return -1; +}; +gantt.getGlobalTaskIndex = function (id) { + var branch = this._order; + for (var i = 0; i < branch.length; i++) + if (branch[i] == id) + return i; + + return -1; +}; +gantt.moveTask = function (sid, tindex, parent) { + ysy.log.debug("moveTask","move_task"); + //target id as 4th parameter + var id = arguments[3]; + if (id) { + if (id === sid) return; + + parent = this.getParent(id); + tindex = this.getTaskIndex(id); + } + if(sid == parent){ + return; + } + parent = parent || this.config.root_id; + var source = this.getTask(sid); + var source_pid = this.getParent(source.id); + var sbranch = this.getChildren(this.getParent(source.id)); + + var tbranch = this.getChildren(parent); + if (tindex == -1) + tindex = tbranch.length + 1; + if (source_pid == parent) { + var sindex = this.getTaskIndex(sid); + if (sindex == tindex) return; + } + + + /* + prevent moving to another sub-branch: + + gantt.attachEvent("onBeforeTaskMove", function(id, parent, tindex){ + var task = gantt.getTask(id); + if(task.parent != parent) + return false; + return true; + }); + */ + if(this.callEvent("onBeforeTaskMove", [sid, parent, tindex]) === false) + return; + + this._replace_branch_child(source_pid, sid); + tbranch = this.getChildren(parent); + + var tid = tbranch[tindex]; + if (!tid) //adding as last element + tbranch.push(sid); + else + tbranch = tbranch.slice(0, tindex).concat([ sid ]).concat(tbranch.slice(tindex)); + + this.setParent(source, parent); + this._branches[parent] = tbranch; + + var childTree = this._getTaskTree(sid); + for(var i = 0; i < childTree.length; i++){ + var item = this._pull[childTree[i]]; + if(item) + item.$level = this.calculateTaskLevel(item); + } + + if(tindex*1 > 0){ + if(id){ + source.$drop_target = (this.getTaskIndex(sid) > this.getTaskIndex(id) ? "next:" : '') + id; + }else{ + source.$drop_target = "next:" + gantt.getPrevSibling(sid); + } + }else if(tbranch[tindex*1 + 1]){ + source.$drop_target = tbranch[tindex*1 + 1]; + }else{ + source.$drop_target = parent; + } + + if(!this.callEvent("onAfterTaskMove", [sid, parent, tindex])) + return; + source.$level = gantt.calculateTaskLevel(source); + this.refreshData(); + +}; + +gantt._init_dnd = function (rearrange) { + var dnd = new dhtmlxDnD(this.$grid_data, {marker:true,updates_per_second: 60}); + if (dhtmlx.defined(this.config.dnd_sensitivity)) + dnd.config.sensitivity = this.config.dnd_sensitivity; + + dnd.attachEvent("onBeforeDragStart", dhtmlx.bind(function (obj, e) { + var target = e.target; + ysy.log.debug("Grid onBeforeDragStart "+target.className,"move_task"); + if(!$(target).hasClass("gantt_drag_handle")){ + return false; + } + var el = this._locateHTML(e); + if (!el) return false; + if (this.hideQuickInfo) this._hideQuickInfo(); + + var id = this.locate(e); + + var task = gantt.getTask(id); + + if(gantt._is_readonly(task)) + return false; + if(task.type===gantt.config.types.milestone){ + return false; + } + + dnd.config.initial_open_state = task.$open; + if (!this.callEvent("onRowDragStart", [id, e.target || e.srcElement, e])) { + return false; + } + + }, this)); + + dnd.attachEvent("onAfterDragStart", dhtmlx.bind(function (obj, e) { + var el = this._locateHTML(e); + ysy.log.debug("Grid onAfterDragStart "+e.target.className,"move_task"); + dnd.config.marker.innerHTML = el.outerHTML; + dnd.config.id = this.locate(e); + var task = this.getTask(dnd.config.id); + dnd.config.index = this.getTaskIndex(dnd.config.id); + dnd.config.parent = task.parent; + task.$open = false; + task.$transparent = true; + //this.refreshData(); + }, this)); + + + dnd.lastTaskOfLevel = function (level) { + var ids = gantt._order, + pull = gantt._pull, + last_item = null; + for (var i = 0, len = ids.length; i < len; i++) { + if (pull[ids[i]].$level == level) { + last_item = pull[ids[i]]; + } + } + return last_item ? last_item.id : null; + }; + dnd._getGridPos = dhtmlx.bind( function(e){ + var pos = this._get_position(this.$grid_data); + + // row offset + var x = pos.x; + var y = e.pos.y - 10; + + // prevent moving row out of grid_data container + if (y < pos.y) y = pos.y; + if (y > pos.y + this.$grid_data.offsetHeight - this.config.row_height) y = pos.y + this.$grid_data.offsetHeight - this.config.row_height; + + pos.x = x; + pos.y = y; + return pos; + }, this); + dnd.attachEvent("onDragMove", dhtmlx.bind(function (obj, e) { + ysy.log.debug("Grid onDragMove","move_task"); + var dd = dnd.config; + var pos = dnd._getGridPos(e); + + // setting position of row + dd.marker.style.left = pos.x + 10 + "px"; + dd.marker.style.top = pos.y + "px"; + + //previous action might cause page scroll appear thus change position of the gantt, need to recalculate + pos = dnd._getGridPos(e); + + var x = pos.x, + y = pos.y; + + // highlight row when mouseover + var $window = $(window); + //var target = document.elementFromPoint(pos.x - document.body.scrollLeft + 1, y - document.body.scrollTop+9); + var target = document.elementFromPoint(pos.x - $window.scrollLeft() + 1, y - $window.scrollTop()+9); + var el = this.locate(target); + + var item = this.getTask(dnd.config.id); + if (!this.isTaskExists(el)) { + el = dnd.lastTaskOfLevel(item.$level); + if (el == dnd.config.id) { + el = null; + } + } + + if (this.isTaskExists(el)) { + var box = gantt._get_position(target); + var over = this.getTask(el); + + if(rearrange){ // HOSEK + ysy.log.debug("over="+over.id+" "+over.text,"sort_over"); + if(dnd.config.over!==over){ + dnd.config.over=over; + $(target).closest(".gantt_grid_data").find(".gantt_drag_hover").removeClass("gantt_drag_hover"); + $(target).closest(".gantt_row").addClass("gantt_drag_hover"); + } + return true; + } // HOSEK + + if (box.y + target.offsetHeight / 2 < y) { + //hovering over bottom part of item, check can be drop to bottom + var index = this.getGlobalTaskIndex(over.id); + var next = this._pull[this._order[index + 1]]; //adds +1 when hovering over placeholder + if (next) { + if (next.id != item.id) + over = next; //there is a valid target + else + return; + } else { + //we at end of the list, check and drop at the end of list + next = this._pull[this._order[index]]; + if (next.$level == item.$level && next.id != item.id) { + this.moveTask(item.id, -1, this.getParent(next.id)); + + return; + } + } + } + //if item is on different level, check the one before it + var index = this.getGlobalTaskIndex(over.id), + prev = this._pull[this._order[index-1]]; + + var shift = 1; + while((!prev || prev.id == over.id) && index - shift >= 0){ + prev = this._pull[this._order[index-shift]]; + shift++; + } + + if (item.id == over.id) return; + //replacing item under cursor + if (over.$level == item.$level && item.id != over.id) { + this.moveTask(item.id, 0, 0, over.id); + + }else if(over.$level == item.$level - 1 && !gantt.getChildren(over.id).length){ + this.moveTask(item.id, 0, over.id); + + } else if(prev && (prev.$level == item.$level) && (item.id != prev.id)){ + this.moveTask(item.id, -1, this.getParent(prev.id)); + + } + } + return true; + }, this)); + + + dnd.attachEvent("onDragEnd", dhtmlx.bind(function () { + ysy.log.debug("Grid onDragEnd","move_task"); + //if(!dnd.config.started){return;} + var task = this.getTask(dnd.config.id); + if(this.callEvent("onBeforeRowDragEnd",[dnd.config.id, dnd.config.parent, dnd.config.index]) === false) { + this.moveTask(dnd.config.id, dnd.config.index, dnd.config.parent); + task.$drop_target = null; + }else{ + if(rearrange){ // HOSEK + var over=dnd.config.over; + var allowed = gantt.allowedParent(task,over); + while(!allowed){ + if(over.real_id>1000000000000){ + dhtmlx.message(ysy.view.getLabel("errors2","unsaved_parent"),"error"); + over = null; + break; + } + var parentID = over.parent; + if(parentID === 0){ + over = null; + break; + } + over = gantt.getTask(parentID); + allowed = gantt.allowedParent(task,over); + } + if(over){ + if(this.callEvent("onBeforeTaskMove", [task.id, over.id, 0]) === false) + return; + //this.moveTask(task.id, -1, over.id); + task.$drop_target = null; + //task.widget.update(task,["fixed_version_id"]); + gantt.silentMoveTask(task, over.id); + this.callEvent("onAfterTaskMove", [task.id, over.id, 0]); + task.widget.update(task); + } + } // HOSEK + this.callEvent("onRowDragEnd", [dnd.config.id, task.$drop_target]); + } + + task.$transparent = false; + task.$open = dnd.config.initial_open_state; + this.refreshData(); + + }, this)); +}; + +/* will be overwriten in order to provide hide/show column functionality in some editions */ +gantt.getGridColumns = function () { + return this.config.columns; +}; + + +gantt._has_children = function(id){ + return this.getChildren(id).length > 0; +}; +// --#include core/grid_resize.js +// --#include core/dynamic_loading.js +// --#include core/grid_column_api.js + +gantt._scale_helpers = { + getSum : function(sizes, from, to){ + if(to === undefined) + to = sizes.length - 1; + if(from === undefined) + from = 0; + + var summ = 0; + for(var i=from; i <= to; i++) + summ += sizes[i]; + + return summ; + }, + setSumWidth : function(sum_width, scale, from, to){ + var parts = scale.width; + + if(to === undefined) + to = parts.length - 1; + if(from === undefined) + from = 0; + var length = to - from + 1; + + if(from > parts.length - 1 || length <= 0 || to > parts.length - 1) + return; + + var oldWidth = this.getSum(parts, from, to); + + var diff = sum_width - oldWidth; + + this.adjustSize(diff, parts, from, to); + this.adjustSize(- diff, parts, to + 1); + + scale.full_width = this.getSum(parts); + }, + splitSize : function(width, count){ + var arr = []; + for(var i=0; i < count; i++) arr[i] = 0; + + this.adjustSize(width, arr); + return arr; + + }, + adjustSize : function(width, parts, from, to){ + if(!from) + from = 0; + if(to === undefined) + to = parts.length - 1; + + var length = to - from + 1; + + var full = this.getSum(parts, from, to); + + var shared = 0; + + for(var i = from; i <= to; i++){ + var share = Math.floor(width*(full ? (parts[i]/full) : (1/length))); + + full -= parts[i]; + width -= share; + length--; + + parts[i] += share; + shared += share; + } + parts[parts.length - 1] += width; + //parts[parts.length - 1] += width - shared; + }, + // HOSEK V + sortScales : function(scales){ + scales.sort(function(a, b){ + var durA=moment.duration(a.step,a.unit+"s"); + var durB=moment.duration(b.step,b.unit+"s") + if(durA < durB){ + return 1; + }else if(durA > durB){ + return -1; + }else{ + return 0; + } + }); + }, + primaryScale : function(){ + + gantt._init_template("date_scale"); + + return { + unit: gantt.config.scale_unit, + step: gantt.config.step, + template : gantt.templates.date_scale, + date : gantt.config.date_scale, + css: gantt.templates.scale_cell_class + }; + }, + + prepareConfigs : function(scales, min_coll_width, container_width, scale_height){ + var heights = this.splitSize(scale_height, scales.length); + var full_width = container_width; + + var configs = []; + for(var i=scales.length-1; i >= 0; i--){ + var main_scale = (i == scales.length - 1); + var cfg = this.initScaleConfig(scales[i]); + if(main_scale){ + this.processIgnores(cfg); + } + + this.initColSizes(cfg, min_coll_width, full_width, heights[i]); + this.limitVisibleRange(cfg); + + if(main_scale){ + full_width = cfg.full_width; + } + + configs.unshift(cfg); + } + + + for( var i =0; i < configs.length-1; i++){ + this.alineScaleColumns(configs[configs.length-1], configs[i]); + } + for(var i = 0; i < configs.length; i++){ + this.setPosSettings(configs[i]); + } + return configs; + + }, + setPosSettings: function(config){ + for(var i = 0, len = config.trace_x.length; i < len; i++){ + config.left.push((config.width[i - 1] || 0) + (config.left[i - 1] || 0)); + } + }, + + _ignore_time_config : function(date){ + if(this.config.skip_off_time){ + return !this.isWorkTime(date); + } + return false; + }, + //defined in an extension + processIgnores : function(config){ + config.ignore_x = {}; + config.display_count = config.count; + }, + initColSizes : function(config, min_col_width, full_width, line_height){ + var cont_width = full_width; + + config.height = line_height; + + var column_count = config.display_count === undefined ? config.count : config.display_count; + + if(!column_count) + column_count = 1; + + config.col_width = Math.floor(cont_width/column_count); + + if(min_col_width){ + if (config.col_width < min_col_width){ + config.col_width = min_col_width; + cont_width = config.col_width * column_count; + } + } + config.width = []; + var ignores = config.ignore_x || {}; + for(var i =0; i < config.trace_x.length; i++){ + if(ignores[config.trace_x[i].valueOf()] || (config.display_count == config.count)){ + config.width[i] = 0; + }else{ + config.width[i] = 1; + } + } + + this.adjustSize(cont_width - this.getSum(config.width)/* 1 width per column from the code above */, config.width); + config.full_width = this.getSum(config.width); + }, + initScaleConfig : function(config){ + var cfg = dhtmlx.mixin({ + count:0, + col_width:0, + full_width:0, + height:0, + width:[], + left:[], + trace_x:[], + date_cache:{} + }, config); + + this.eachColumn(config.unit, config.step, function(date){ + cfg.count++; + var dateObj=gantt.date.Date(date); + cfg.trace_x.push(dateObj); + }); + + return cfg; + }, + iterateScales : function(lower_scale, upper_scale, from, to, callback){ + var upper_dates = upper_scale.trace_x; + var lower_dates = lower_scale.trace_x; + + var prev = from || 0; + var end = to || (lower_dates.length - 1); + var prevUpper = 0; + for(var up=1; up < upper_dates.length; up++){ + for(var next=prev; next <= end; next++){ + if(+lower_dates[next] == +upper_dates[up]){ + if(callback){ + callback.apply(this, [prevUpper, up, prev, next]); + } + prev = next; + prevUpper = up; + continue; + } + } + } + }, + alineScaleColumns : function(lower_scale, upper_scale, from, to){ + this.iterateScales(lower_scale, upper_scale, from, to, function(upper_start, upper_end, lower_start, lower_end){ + var targetWidth = this.getSum(lower_scale.width, lower_start, lower_end - 1); + var actualWidth = this.getSum(upper_scale.width, upper_start, upper_end - 1); + if(actualWidth != targetWidth){ + this.setSumWidth(targetWidth, upper_scale, upper_start, upper_end - 1); + } + + }); + }, + + eachColumn : function(unit, step, callback){ + var start = gantt.date.Date(gantt._min_date), + end = gantt.date.Date(gantt._max_date); + if(gantt.date[unit + "_start"]){ + start = gantt.date[unit + "_start"](start); + } + + var curr = moment(start).toDate(); + if(+curr >= +end){ + end = gantt.date.add(curr, step, unit); + } + while(+curr < +end){ + callback.call(this, gantt.date.Date(curr)); + var tzOffset = curr.getTimezoneOffset(); + curr = gantt.date.add(curr, step, unit); + curr = gantt._correct_dst_change(curr, tzOffset, step, unit); + if(gantt.date[unit + '_start']) + curr = gantt.date[unit + "_start"](curr); + } + }, + limitVisibleRange : function(cfg){ + var dates = cfg.trace_x; + + var left = 0, right = cfg.width.length-1; + var diff = 0; + if(+dates[0] < +gantt._min_date && left != right){ + var width = Math.floor(cfg.width[0] * ((dates[1] - gantt._min_date)/ (dates[1] - dates[0]))); + diff += cfg.width[0] - width; + cfg.width[0] = width; + + dates[0] = moment(gantt._min_date).toDate(); + } + + var last = dates.length - 1; + var lastDate = dates[last]; + var outDate = gantt.date.add(lastDate, cfg.step, cfg.unit); + if(+outDate > +gantt._max_date && last > 0){ + var width = cfg.width[last] - Math.floor(cfg.width[last] * ((outDate - gantt._max_date)/(outDate - lastDate))); + diff += cfg.width[last] - width; + cfg.width[last] = width; + } + + if(diff){ + var full = this.getSum(cfg.width); + var shared = 0; + for(var i =0; i < cfg.width.length; i++){ + var share = Math.floor(diff*(cfg.width[i]/full)); + cfg.width[i] += share; + shared += share; + } + this.adjustSize(diff - shared, cfg.width); + } + + } +}; +// --#include core/scales_ignore.js +gantt._tasks_dnd = { + drag : null, + _events:{ + before_start:{}, + before_finish:{}, + after_finish:{} + }, + _handlers:{}, + init:function(){ + this.clear_drag_state(); + var drag = gantt.config.drag_mode; + this.set_actions(); + + var evs = { + "before_start":"onBeforeTaskDrag", + "before_finish":"onBeforeTaskChanged", + "after_finish":"onAfterTaskDrag" + }; + //for now, all drag operations will trigger the same events + for(var stage in this._events){ + for(var mode in drag){ + this._events[stage][mode] = evs[stage]; + } + } + + this._handlers[drag.move] = this._handlers[drag.move] || this._move; + this._handlers[drag.resize] = this._handlers[drag.resize] || this._resize; + this._handlers[drag.progress] = this._handlers[drag.progress] || this._resize_progress; + + }, + set_actions:function(){ + var data = gantt.$task_data; + dhtmlxEvent(data, "mousemove", dhtmlx.bind(function(e){ + this.on_mouse_move(e||event); + }, this)); + dhtmlxEvent(data, "mousedown", dhtmlx.bind(function(e){ + this.on_mouse_down(e||event); + }, this)); + dhtmlxEvent(data, "mouseup", dhtmlx.bind(function(e){ + this.on_mouse_up(e||event); + }, this)); + }, + + clear_drag_state : function(){ + this.drag = { + id:null, + mode:null, + pos:null, + start_x:null, + start_y:null, + obj:null, + left:null, + last_event:null + }; + }, + _resize : function(ev, shift, drag){ + var cfg = gantt.config; + var coords_x = this._drag_task_coords(ev, drag); + if(drag.left){ + var old_start = ev.start_date; + var start_date = moment(gantt.dateFromPos(coords_x.start + shift.x)); + gantt.multiStop(ev, start_date); + ev.start_date = start_date; + if(!ev.start_date){ + ev.start_date = gantt.date.Date(gantt.getState().min_date); + } + }else{ + var old_end = ev.end_date; + var end_date = moment(gantt.dateFromPos(coords_x.end + shift.x)).subtract(1, "days"); + end_date._isEndDate = true; + gantt.multiStop(ev, undefined, end_date); + ev.end_date = end_date; + if(!ev.end_date){ + ev.end_date = gantt.date.Date(gantt.getState().max_date); + } + ev.end_date._isEndDate = true; // HOSEK < A + } + if (ev.end_date - ev.start_date < cfg.min_duration){ + if(drag.left) + ev.start_date = gantt.calculateEndDate(ev.end_date, -1); + else + ev.end_date = gantt.calculateEndDate(ev.start_date, 1); + } + if(drag.left){ + gantt.moveDesc(ev, ev.start_date.diff(old_start, "seconds"),null, 'left'); + }else{ + gantt.moveDesc(ev, ev.end_date.diff(old_end, "seconds"), null, 'right'); + } + //gantt.moveChildren(ev,ev.start_date.diff(old_start,"seconds")); + return; // HOSEK + gantt._init_task_timing(ev); + }, + _resize_progress:function(ev, shift, drag){ + var coords_x = this._drag_task_coords(ev, drag); + + var diff = Math.max(0, drag.pos.x - coords_x.start); + ev.progress = Math.min(1, diff / (coords_x.end - coords_x.start)); + }, + _move : function(ev, shift, drag){ + // TODO konce tasku + var coords_x = this._drag_task_coords(ev, drag); + if (ev.type==="milestone"){ + new_end = moment(gantt.dateFromPos(coords_x.start + shift.x)); + new_end = gantt._working_time_helper.get_closest_worktime({date:new_end, unit:"day", dir: 'any', length:1}); + new_end._isEndDate = true; + new_start = moment(new_end); + } else { + var new_start = gantt._working_time_helper.get_closest_worktime({ + date:moment(gantt.dateFromPos(coords_x.start + shift.x)), + dir:"future" + }); + var new_end = gantt._working_time_helper.add_worktime(new_start, ev.duration, "day", true); //< HOSEKP + } + if (!new_start.isValid() || !new_end.isValid()) { + debugger; + } + // console.log(new_start.format("DD.MM.YYYY HH:mm")+" "+new_end.format("DD.MM.YYYY HH:mm")); + gantt.multiStop(ev, new_start, new_end); + var old_start = ev.start_date; + if(!new_start){ + ev.start_date = gantt.date.Date(gantt.getState().min_date); + ev.end_date = gantt.dateFromPos(gantt.posFromDate(ev.start_date) + (coords_x.end - coords_x.start)); + }else if(!new_end){ + ev.end_date = gantt.date.Date(gantt.getState().max_date); + ev.start_date = gantt.dateFromPos(gantt.posFromDate(ev.end_date) - (coords_x.end - coords_x.start)); + }else{ + ev.start_date = new_start; + ev.end_date = new_end; + } + if (ev.type === "project") { + ev.maximal_start = null; + ev.minimal_end = null; + } else { + gantt.moveDesc(ev, {old_start: old_start}); + } + //console.log(new_start.toString()+" "+new_end.toString()); + }, + _drag_task_coords : function(t, drag){ + // TODO konce tasku + var start = drag.obj_s_x = drag.obj_s_x || gantt.posFromDate(t.start_date); + var end = drag.obj_e_x = drag.obj_e_x || gantt.posFromDate(t.end_date); + return { + start : start, + end : end + }; + }, + on_mouse_move : function(e){ + if(this.drag.start_drag) + this._start_dnd(e); + + var drag = this.drag; + + if (drag.mode){ + //if(!e.movementX && !e.movementY) return; + if(!drag.last_event){ + setTimeout($.proxy(this._update_on_move,this),5); + } + drag.last_event=e; + } + }, + _update_on_move : function(){ + var drag = this.drag; + + if (drag&&drag.mode){ + var e=drag.last_event; + if(!e) return; + drag.last_event = null; + var pos = gantt._get_mouse_pos(e); + if (drag.pos) { + if (drag.pos.x == pos.x + && drag.pos.y == pos.y) return; + } else { + if (Math.abs(drag.start_x - pos.x) < 10 + && Math.abs(drag.start_y - pos.y) < 10) return; + } + + drag.pos = pos; + // TODO konce tasku + //ysy.log.debug("pos.x="+pos.x,"task_drag"); + var curr_date = gantt.dateFromPos(pos.x); + if(!curr_date || isNaN( curr_date.getTime() )) + return; + + //ysy.log.debug(curr_date,"task_drag"); + var shift = {x:pos.x - drag.start_x,y:pos.y - drag.start_y}; + var ev = gantt.getTask(drag.id); + + + if(this._handlers[drag.mode]){ + var original = dhtmlx.mixin({}, ev); + var copy = dhtmlx.mixin({}, ev); + this._handlers[drag.mode].apply(this, [copy, shift, drag]); + dhtmlx.mixin(ev, copy, true); + //return; + //gantt._update_parents(gantt.getParent(drag.id),false); + gantt.callEvent("onTaskDrag", [ev.id, drag.mode, copy, original, e]); + //gantt.moveDesc(ev, ev.end_date); + //gantt.moveChildren(ev,ev.start_date.diff(original.start_date,"seconds")); + //gantt._update_parents(); + ev._changed = drag.mode; + + gantt.refreshTask(drag.id); + } + } + }, + + on_mouse_down : function(e, src){ + // on Mac we do not get onmouseup event when clicking right mouse button leaving us in dnd state + // let's ignore right mouse button then + if (e.button == 2) + return false; + + var id =gantt.locate(e); + var task = null; + if(gantt.isTaskExists(id)){ + task = gantt.getTask(id); + } + if(task==null){ // HOSEK < V + ysy.log.debug("No task under click","empty_field"); + this.clear_drag_state(); + return; + } // HOSEK A + if (gantt._is_readonly(task) || this.drag.mode) return false; + + this.clear_drag_state(); + + src = src||(e.target||e.srcElement); + + var className = gantt._trim(src.className || ""); + if(!className || !this._get_drag_mode(className)){ + if(src.parentNode) + return this.on_mouse_down(e, src.parentNode); + else + return false; + } + + var drag = this._get_drag_mode(className); + ysy.log.debug("drag.mode="+(drag?drag.mode:"null"),"empty_field"); + if(!drag){ + if (gantt.checkEvent("onMouseDown") && gantt.callEvent("onMouseDown", [className.split(" ")[0]])) { + if (src.parentNode) + return this.on_mouse_down(e,src.parentNode); + + } + }else{ + if (drag.mode && drag.mode != gantt.config.drag_mode.ignore && gantt.config["drag_" + drag.mode]){ + id = gantt.locate(src); + if(id!=null){ + task = dhtmlx.copy(gantt.getTask(id) || {}); + + if(gantt._is_readonly(task)){ + this.clear_drag_state(); + return false; + } + + /*if(gantt._is_flex_task(task) && drag.mode != gantt.config.drag_mode.progress){//only progress drag is allowed for tasks with flexible duration + this.clear_drag_state(); + return; + }*/ // HOSEK + drag.id = id; + drag.obj = task; + } + var pos = gantt._get_mouse_pos(e); + drag.start_x = pos.x; + drag.start_y = pos.y; + this.drag.start_drag = drag; + }else + this.clear_drag_state(); + } + return false; + }, + _fix_dnd_scale_time:function(task, drag){ + alert("Forbidden function _fix_dnd_scale_time"); + var unit = gantt._tasks.unit, + step = gantt._tasks.step; + if(!gantt.config.round_dnd_dates){ + unit = 'minute'; + step = gantt.config.time_step; + } + unit="day"; // HOSEK + + function fixStart(task){ + if(!gantt.isWorkTime(task.start_date)) + task.start_date = gantt.calculateEndDate(task.start_date, 0, gantt.config.duration_unit); // HOSEK (originally -1) + } + function fixEnd(task){ + if(!gantt.isWorkTime(gantt.date.Date(task.end_date - 1))) + task.end_date = gantt.calculateEndDate(task.end_date, 1, gantt.config.duration_unit); + } + if(drag.mode == gantt.config.drag_mode.resize){ + if(drag.left){ + task.start_date = gantt.roundDate({date:task.start_date, unit:unit, step:step}); + fixStart(task); + }else{ + task.end_date = gantt.roundDate({date:task.end_date, unit:unit, step:step}); + fixEnd(task); + } + task.duration=gantt.calculateDuration(task.start_date,task.end_date); + }else if(drag.mode == gantt.config.drag_mode.move){ + task.start_date = gantt.roundDate({date:task.start_date, unit:unit, step:step}); + fixStart(task); + + task.end_date = gantt.calculateEndDate(task.start_date, task.duration, gantt.config.duration_unit); + } + }, + _fix_working_times:function(task, drag){ + //console.log("_fix_working_times REWRITED"); + gantt._working_time_helper.round_date(task.start_date); + if(drag.mode == gantt.config.drag_mode.resize){ + gantt._working_time_helper.round_date(task.end_date, "past"); + task.duration=gantt._working_time_helper.get_work_units_between(task.start_date,task.end_date,"day"); + } + task.end_date=gantt._working_time_helper.add_worktime(task.start_date,task.duration,"day"); + /*return; + var drag = drag || {mode : gantt.config.drag_mode.move}; + if(gantt.config.work_time && gantt.config.correct_work_time){ + if(drag.mode == gantt.config.drag_mode.resize){ + if(drag.left){ + task.start_date = gantt.getClosestWorkTime({date:task.start_date, dir:'future'}); + }else{ + task.end_date = gantt.getClosestWorkTime({date:task.end_date, dir:'past'}); + } + }else if(drag.mode == gantt.config.drag_mode.move){ + gantt.correctTaskWorkTime(task); + } + }*/ + }, + on_mouse_up : function(e){ + var drag = this.drag; + if (drag.mode && drag.id) { + //drop + var ev = gantt.getTask(drag.id); + + //if(gantt.config.work_time && gantt.config.correct_work_time){ + // this._fix_working_times(ev, drag); + //} + //gantt.resetProjectDates(ev); + //this._fix_dnd_scale_time(ev, drag); + + //gantt._init_task_timing(ev); + // HOSEK + this._fireEvent("before_finish", drag.mode, [drag.id, drag.mode, e]); + gantt.updateAllTask(ev); + + if (gantt._tasks.needRescale) { + gantt._tasks.needRescale = false; + //gantt._adjust_scales(); + //gantt.refreshData(); + gantt.render(); + ysy.log.debug("Scale rescaled", "outer"); + } + this.clear_drag_state(); + return; + // HOSEK + //if (!this._fireEvent("before_finish", drag.mode, [drag.id, drag.mode, dhtmlx.copy(drag.obj), e])) { + // drag.obj._dhx_changed = false; + // dhtmlx.mixin(ev, drag.obj, true); + // + // gantt.updateTask(ev.id); + //} else { + // var drag_id = drag.id; + // + // gantt._init_task_timing(ev); + // this._fireEvent("after_finish", drag.mode, [drag_id, drag.mode, e]); + // this.clear_drag_state(); + // gantt.updateTask(ev.id); + //} + } + this.clear_drag_state(); + }, + _get_drag_mode : function(className){ + var modes = gantt.config.drag_mode; + var classes = (className || "").split(" "); + var classname = classes[0]; + var drag = {mode:null, left:null}; + switch (classname) { + case "gantt_task_line": + case "gantt_task_content": + drag.mode = modes.move; + break; + case "gantt_task_drag": + drag.mode = modes.resize; + if(classes[1] && classes[1].indexOf("left", classes[1].length - "left".length) !== -1){ + drag.left = true; + }else{ + drag.left = false; + } + break; + case "gantt_task_progress_drag": + drag.mode = modes.progress; + break; + case "gantt_link_control": + case "gantt_link_point": + drag.mode = modes.ignore; + break; + case "gantt_task_cell": + drag.mode = "empty"; // HOSEK < A V + break; + default: + drag = null; + break; + } + return drag; + + }, + + _start_dnd : function(e){ + var drag = this.drag = this.drag.start_drag; + delete drag.start_drag; + + var cfg = gantt.config; + var id = drag.id; + if (!cfg["drag_"+drag.mode] || !gantt.callEvent("onBeforeDrag",[id, drag.mode, e]) || !this._fireEvent("before_start", drag.mode, [id, drag.mode, e])){ + this.clear_drag_state(); + }else { + delete drag.start_drag; + } + + }, + _fireEvent:function(stage, mode, params){ + dhtmlx.assert(this._events[stage], "Invalid stage:{" + stage + "}"); + + var trigger = this._events[stage][mode]; + + dhtmlx.assert(trigger, "Unknown after drop mode:{" + mode + "}"); + dhtmlx.assert(params, "Invalid event arguments"); + + + if(!gantt.checkEvent(trigger)) + return true; + + return gantt.callEvent(trigger, params); + } +}; + +gantt.roundTaskDates = function(task){ + alert("Forbidden function roundTaskDates"); + var drag_state = gantt._tasks_dnd.drag; + + if(!drag_state){ + drag_state = {mode:gantt.config.drag_mode.move}; + } + gantt._tasks_dnd._fix_dnd_scale_time(task, drag_state); +}; + + + + + + + +gantt._render_link = function(id){ + var link = this.getLink(id); + var renders = gantt._get_link_renderers(); + //ysy.log.debug("_render_link() id="+id+" rend="+renders.length,"link_render"); + for(var i = 0; i < renders.length; i++) + renders[i].render_item(link); +}; + +gantt._get_link_type = function(from_start, to_start){ + var type = null; + if(from_start && to_start){ + type = gantt.config.links.start_to_start; + }else if(!from_start && to_start){ + type = gantt.config.links.finish_to_start; + }else if(!from_start && !to_start){ + type = gantt.config.links.finish_to_finish; + }else if(from_start && !to_start){ + type = gantt.config.links.start_to_finish; + } + return type; +}; + +gantt.isLinkAllowed = function(from, to, from_start, to_start){ + var link = null; + if(typeof(from) == "object"){ + link = from; + }else{ + link = {source:from, target:to, type: this._get_link_type(from_start, to_start)}; + } + + if(!link) return false; + if(!(link.source && link.target && link.type)) return false; + if(link.source == link.target) return false; + + var res = true; + //any custom rules + if(this.checkEvent("onLinkValidation")) + res = this.callEvent("onLinkValidation", [link]); + + return res; +}; + +gantt._render_link_element = function(link){ + //ysy.log.debug("render_link_element() link="+link.id,"link_render"); + var dots = this._path_builder.get_points(link); + var drawer = gantt._drawer; + var lines = drawer.get_lines(dots); + + var div = document.createElement("div"); + + + var css = "gantt_task_link"; + + if(link.color){ + css += " gantt_link_inline_color"; + } + var cssTemplate = this.templates.link_class ? this.templates.link_class(link) : ""; + if(cssTemplate){ + css += " " + cssTemplate; + } + + if(this.config.highlight_critical_path && this.isCriticalLink){ + if(this.isCriticalLink(link)) + css += " gantt_critical_link"; + } + + div.className = css; + div.setAttribute(gantt.config.link_attribute, link.id); + for(var i=0; i < lines.length; i++){ + if(i == lines.length - 1){ + lines[i].size -= gantt.config.link_arrow_size; + } + var el = drawer.render_line(lines[i], lines[i+1]); + if(link.color){ + el.firstChild.style.backgroundColor = link.color; + } + div.appendChild(el); + } + + var direction = lines[lines.length - 1].direction; + var endpoint = gantt._render_link_arrow(dots[dots.length - 1], direction); + if(link.color){ + endpoint.style.borderColor = link.color; + } + div.appendChild(endpoint); + // HOSEK + // if (link.delay !== 0) { + var delaypos = { + x: (dots[dots.length - 2].x + dots[dots.length - 1].x) / 2 - 5, + y: dots[dots.length - 2].y - 2 + }; + var delay_element = gantt.render_delay_element(link, delaypos); + if(delay_element) + div.appendChild(delay_element); + // } + // HOSEK + return div; +}; + +gantt._render_link_arrow = function(point, direction){ + var div = document.createElement("div"); + var drawer = gantt._drawer; + var top = point.y; + var left = point.x; + + var size = gantt.config.link_arrow_size; + var line_width = gantt.config.row_height; + var className = "gantt_link_arrow gantt_link_arrow_" + direction; + switch (direction){ + case drawer.dirs.right: + top -= (size - line_width)/2; + left -= size; + break; + case drawer.dirs.left: + top -= (size - line_width)/2; + break; + case drawer.dirs.up: + left -= (size - line_width)/2; + break; + case drawer.dirs.down: + top -= size; + left -= (size - line_width)/2; + break; + default: + break; + } + div.style.cssText = [ + "top:"+top + "px", + "left:"+left+'px'].join(';'); + div.className = className; + + return div; +}; + + +gantt._drawer = { + current_pos:null, + dirs:{"left":'left',"right":'right',"up":'up', "down":'down'}, + path:[], + clear:function(){ + this.current_pos = null; + this.path = []; + }, + point:function(pos){ + this.current_pos = dhtmlx.copy(pos); + }, + get_lines:function(dots){ + this.clear(); + this.point(dots[0]); + for(var i=1; i from.x){ + direction = this.dirs.right; + }else if (to.y > from.y){ + direction = this.dirs.down; + }else { + direction = this.dirs.up; + } + return direction; + } + +}; +gantt._y_from_ind = function(index){ + return (index)*gantt.config.row_height; +}; +gantt._path_builder = { + + path:[], + clear:function(){ + this.path = []; + }, + current:function(){ + return this.path[this.path.length - 1]; + }, + point:function(next){ + if(!next) + return this.current(); + + this.path.push(dhtmlx.copy(next)); + return next; + }, + point_to:function(direction, diff, point){ + if(!point) + point = dhtmlx.copy(this.point()); + else + point = {x:point.x, y:point.y}; + var dir = gantt._drawer.dirs; + switch (direction){ + case (dir.left): + point.x -= diff; + break; + case (dir.right): + point.x += diff; + break; + case (dir.up): + point.y -= diff; + break; + case (dir.down): + point.y += diff; + break; + default: + break; + } + return this.point(point); + }, + get_points:function(link){ + var pt = this.get_endpoint(link); + var xy = gantt.config; + + + var dy = pt.e_y - pt.y; + var dx = pt.e_x - pt.x; + + var dir = gantt._drawer.dirs; + + this.clear(); + this.point({x: pt.x, y : pt.y}); + + var shiftX = ((link.id % 3) / 3 + 1.5) * xy.link_arrow_size;//just random size for first line + //var shiftX = Math.max((link.delay-1)*gantt._tasks.col_width/gantt._get_line(gantt.config.scale_unit)*gantt._get_line("day"),2*xy.link_arrow_size); + + + var forward = (pt.e_x > pt.x); + if(link.type == gantt.config.links.start_to_start){ + this.point_to(dir.left, shiftX); + if(forward){ + this.point_to(dir.down, dy); + this.point_to(dir.right, dx); + }else{ + this.point_to(dir.right, dx); + this.point_to(dir.down, dy); + } + this.point_to(dir.right, shiftX); + + }else if(link.type == gantt.config.links.finish_to_start){ + forward = (pt.e_x > (pt.x + 2*shiftX)); + this.point_to(dir.right, shiftX); + if(forward){ + dx -= shiftX; + this.point_to(dir.down, dy); + this.point_to(dir.right, dx); + }else{ + dx -= 2*shiftX; + var sign = dy > 0 ? 1 : -1; + + this.point_to(dir.down, sign * (xy.row_height/2)); + this.point_to(dir.right, dx); + this.point_to(dir.down, sign * ( Math.abs(dy) - (xy.row_height/2))); + this.point_to(dir.right, shiftX); + } + + }else if(link.type == gantt.config.links.finish_to_finish){ + this.point_to(dir.right, shiftX); + if(forward){ + this.point_to(dir.right, dx); + this.point_to(dir.down, dy); + }else{ + this.point_to(dir.down, dy); + this.point_to(dir.right, dx); + } + this.point_to(dir.left, shiftX); + }else if(link.type == gantt.config.links.start_to_finish){ + + forward = (pt.e_x > (pt.x - 2*shiftX)); + this.point_to(dir.left, shiftX); + + if(!forward){ + dx += shiftX; + this.point_to(dir.down, dy); + this.point_to(dir.right, dx); + }else{ + dx += 2*shiftX; + var sign = dy > 0 ? 1 : -1; + this.point_to(dir.down, sign * (xy.row_height/2)); + this.point_to(dir.right, dx); + this.point_to(dir.down, sign * ( Math.abs(dy) - (xy.row_height/2))); + this.point_to(dir.left, shiftX); + } + + } + + return this.path; + }, + get_endpoint : function(link){ + var types = gantt.config.links; + var from_start = false, to_start = false; + + if(link.type == types.start_to_start){ + from_start = to_start = true; + }else if(link.type == types.finish_to_finish){ + from_start = to_start = false; + }else if(link.type == types.finish_to_start){ + from_start = false; + to_start = true; + }else if(link.type == types.start_to_finish){ + from_start = true; + to_start = false; + }else{ + dhtmlx.assert(false, "Invalid link type"); + } + + var from = gantt._get_task_visible_pos(gantt._pull[link.source], from_start); + var to = gantt._get_task_visible_pos(gantt._pull[link.target], to_start); + + return { + x : from.x, + e_x : to.x, + y : from.y , + e_y : to.y + }; + } +}; + +gantt._init_links_dnd = function() { + var dnd = new dhtmlxDnD(this.$task_bars, { marker:true,sensitivity : 0, updates_per_second : 60 }), + start_marker = "task_left", + end_marker = "task_right", + link_edge_marker = "gantt_link_point", + link_landing_hover_area = "gantt_link_control"; + + dnd.attachEvent("onBeforeDragStart", dhtmlx.bind(function(obj,e) { +//console.log("init_links_dnd"); + var target = (e.target||e.srcElement); + resetDndState(); + if(gantt.getState().drag_id) + return false; + + + if(gantt._locate_css(target, link_edge_marker)){ + if(gantt._locate_css(target, start_marker)) + gantt._link_source_task_start = true; + + var sid = gantt._link_source_task = this.locate(e); + + + var t = gantt.getTask(sid); + if(gantt._is_readonly(t)){ + resetDndState(); + return false; + } + + var shift = 0; + if(gantt._get_safe_type(t.type) == gantt.config.types.milestone){ + shift = (gantt._get_visible_milestone_width() - gantt._get_milestone_width())/2; + } + + this._dir_start = getLinePos(t, !!gantt._link_source_task_start, shift); + return true; + }else{ + return false; + } + + }, this)); + + dnd.attachEvent("onAfterDragStart", dhtmlx.bind(function(obj,e) { + updateMarkedHtml(dnd.config.marker); + }, this)); + + function getLinePos(task, to_start, shift){ + var pos = gantt._get_task_pos(task, !!to_start); + pos.y += gantt._get_task_height()/2; + + shift = shift || 0; + pos.x += (to_start ? -1 : 1)*shift; + return pos; + } + + dnd.attachEvent("onDragMove", dhtmlx.bind(function(obj,e) { + var dd = dnd.config; + var pos = dnd.getPosition(e); + //advanceMarker(dd.marker, pos); + var landing = false;// = gantt._is_link_drop_area(e); + + var prevTarget = gantt._link_target_task; + var prevLanding = gantt._link_landing; + var prevToStart = gantt._link_target_task_start; + var to_start = true; + if(gantt._locate_css(e,"gantt_task_line")){ + var targ = gantt.locate(e); + }else{ + targ = null; + } + ysy.log.debug("landing=" + landing + " target=" + targ, "link_drag"); + if (targ){ + //refreshTask + to_start = !gantt._locate_css(e, end_marker); + var link = getDndState(); + var landing = gantt.isLinkAllowed(link.from, link.to, link.from_start, link.to_start); + } + + gantt._link_target_task = targ; + gantt._link_landing = landing; + gantt._link_target_task_start = to_start; + + if(landing){ + var t = gantt.getTask(targ); + + var node = gantt._locate_css(e, link_landing_hover_area); + var shift = 0; + if(node){ + shift = Math.floor(node.offsetWidth / 2); + } + + this._dir_end = getLinePos(t, !!gantt._link_target_task_start,shift); + }else{ + this._dir_end = gantt._get_mouse_pos(e); + } + + var targetChanged = !(prevLanding == landing && prevTarget == targ && prevToStart == to_start); + if(targetChanged){ + if(prevTarget) + gantt.refreshTask(prevTarget, false); + if(targ) + gantt.refreshTask(targ, false); + } + + if(targetChanged){ + updateMarkedHtml(dd.marker); + } + + + + showDirectingLine(this._dir_start.x, this._dir_start.y, this._dir_end.x, this._dir_end.y); + + return true; + }, this)); + + + dnd.attachEvent("onDragEnd", dhtmlx.bind(function() { + var drag = getDndState(); + + if(drag.from && drag.to && drag.from != drag.to){ + var type = gantt._get_link_type(drag.from_start, drag.to_start); + + var link = {source : drag.from, target: drag.to, type:type}; + if(link.type && gantt.isLinkAllowed(link)) + gantt.addLink(link); + } + + resetDndState(); + + if(drag.from) + gantt.refreshTask(drag.from, false); + if(drag.to) + gantt.refreshTask(drag.to, false); + removeDirectionLine(); + }, this)); + + function updateMarkedHtml(marker){ + var link = getDndState(); + + var css = ["gantt_link_tooltip"]; + var allowed = gantt.isLinkAllowed(link.from, link.to, link.from_start, link.to_start); + if(link.from && link.to){ + if (allowed){ + css.push("gantt_allowed_link"); + } else { + css.push("gantt_invalid_link"); + } + } + + var className = gantt.templates.drag_link_class(link.from, link.from_start, link.to, link.to_start); + if(className) + css.push(className); + + var html = "
" + + gantt.templates.drag_link(link.from, link.from_start, link.to, link.to_start) + + "
"; + marker.innerHTML = html; + } + + function advanceMarker(marker, pos){ + marker.style.left = pos.x + 5 + "px"; + marker.style.top = pos.y + 5 + "px"; + } + function getDndState(){ + return { from : gantt._link_source_task, + to : gantt._link_target_task, + from_start : gantt._link_source_task_start, + to_start : gantt._link_target_task_start}; + } + function resetDndState(){ + gantt._link_source_task = + gantt._link_source_task_start = + gantt._link_target_task = null; + gantt._link_target_task_start = true; + } + function showDirectingLine(s_x, s_y, e_x, e_y){ + var div = getDirectionLine(); + + var link = getDndState(); + + var css = ["gantt_link_direction"]; + if(gantt.templates.link_direction_class){ + css.push(gantt.templates.link_direction_class(link.from, link.from_start, link.to, link.to_start)); + } + + var dist =Math.sqrt( (Math.pow(e_x - s_x, 2)) + (Math.pow(e_y - s_y, 2)) ); + dist = Math.max(0, dist - 3); + if(!dist) + return; + + div.className = css.join(" "); + var tan = (e_y - s_y)/(e_x - s_x), + angle = Math.atan(tan); + + if(coordinateCircleQuarter(s_x, e_x, s_y, e_y) == 2){ + angle += Math.PI; + }else if(coordinateCircleQuarter(s_x, e_x, s_y, e_y) == 3){ + angle -= Math.PI; + } + + + + var sin = Math.sin(angle), + cos = Math.cos(angle), + top = Math.round(s_y), + left = Math.round(s_x); + + + var style = [ + "-webkit-transform: rotate("+angle+"rad)", + "-moz-transform: rotate("+angle+"rad)", + "-ms-transform: rotate("+angle+"rad)", + "-o-transform: rotate("+angle+"rad)", + "transform: rotate("+angle+"rad)", + "width:" + Math.round(dist) + "px" + ]; + + if(window.navigator.userAgent.indexOf("MSIE 8.0") != -1){ + //ms-filter breaks styles in ie9, so add it only for 8th + style.push("-ms-filter: \"" + ieTransform(sin, cos) + "\""); + + var shiftLeft = Math.abs(Math.round(s_x - e_x)), + shiftTop = Math.abs(Math.round(e_y - s_y)); + //fix rotation axis + switch(coordinateCircleQuarter(s_x, e_x, s_y, e_y)){ + case 1: + top -= shiftTop; + break; + case 2: + left -= shiftLeft; + top -= shiftTop; + break; + case 3: + left -= shiftLeft; + break; + default: + break; + } + + } + + style.push("top:" + top + "px"); + style.push("left:" + left + "px"); + + div.style.cssText = style.join(";"); + } + + function ieTransform(sin, cos){ + return "progid:DXImageTransform.Microsoft.Matrix("+ + "M11 = "+cos+","+ + "M12 = -"+sin+","+ + "M21 = "+sin+","+ + "M22 = "+cos+","+ + "SizingMethod = 'auto expand'"+ + ")"; + } + function coordinateCircleQuarter(sX, eX, sY, eY){ + if(eX >= sX){ + if(eY <= sY){ + return 1; + }else{ + return 4; + } + }else{ + if(eY <= sY){ + return 2; + }else{ + return 3; + } + } + + } + function getDirectionLine(){ + if(!dnd._direction){ + dnd._direction = document.createElement("div"); + gantt.$task_links.appendChild(dnd._direction); + } + return dnd._direction; + } + function removeDirectionLine(){ + if(dnd._direction){ + if (dnd._direction.parentNode) //the event line can be detached because of data refresh + dnd._direction.parentNode.removeChild(dnd._direction); + + dnd._direction = null; + } + } + + gantt._is_link_drop_area = function(e){ + return !!gantt._locate_css(e, link_landing_hover_area); + }; +}; +gantt._get_link_state = function(){ + return { + link_landing_area : this._link_landing, + link_target_id : this._link_target_task, + link_target_start : this._link_target_task_start, + link_source_id : this._link_source_task, + link_source_start : this._link_source_task_start + }; +}; + + +gantt._init_tasks = function(){ + //store temporary configs + this._tasks = { + col_width:this.config.columnWidth, + width: [], // width of each column + full_width: 0, // width of all columns + trace_x:[], + rendered:{} + }; + + + this._click.gantt_task_link = dhtmlx.bind(function(e, trg){ + var id = this.locate(e, gantt.config.link_attribute); + if(id){ + this.callEvent("onLinkClick", [id, e]); + } + }, this); + + this._click.gantt_scale_cell = dhtmlx.bind(function(e, trg){ + var pos = gantt._get_mouse_pos(e); + var date = gantt.dateFromPos(pos.x); + var coll = Math.floor(gantt._day_index_by_date(date)); + + var coll_date = gantt._tasks.trace_x[coll]; + + gantt.callEvent("onScaleClick", [e, coll_date]); + }, this); + + /*this._dbl_click.gantt_task_link = dhtmlx.bind(function(e, id, trg){ // HOSEK + var id = this.locate(e, gantt.config.link_attribute); + this._delete_link_handler(id, e); + }, this); + + this._dbl_click.gantt_link_point = dhtmlx.bind(function(e, id, trg){ + var id = this.locate(e), + task = this.getTask(id); + + + var link = null; + if(trg.parentNode && trg.parentNode.className){ + if(trg.parentNode.className.indexOf("_left") > -1){ + link = task.$target[0]; + }else{ + link = task.$source[0]; + } + } + if(link) + this._delete_link_handler(link, e); + return false; + }, this);*/ + + this._tasks_dnd.init(); + this._init_links_dnd(); + + this._link_layers.clear(); + + var links_layer = this.addLinkLayer({ + renderer: this._render_link_element, + container: this.$task_links, + filter: gantt._create_filter(['_filter_link', '_is_chart_visible']) + }); + gantt._link_layers.process(); + this._linkRenderer = this._link_layers.getRenderer(links_layer); + + this._task_layers.clear(); + var bar_layer = this.addTaskLayer({ + renderer: this._render_task_element, + container: this.$task_bars, + filter: gantt._create_filter(['_filter_task', '_is_chart_visible']) + }); + + var grid_layer = this.addTaskLayer({ + renderer: this._render_grid_item, + container: this.$grid_data, + filter: gantt._create_filter(['_filter_task', '_is_grid_visible']) + }); + //var background_layer = this.addTaskLayer({ + // renderer: this._render_bg_line, + // container: this.$task_bg, + // filter: gantt._create_filter(['_filter_task', '_is_chart_visible', '_is_std_background']) + //}); + var background_layer = this.addTaskLayer(ysy.view.getGanttBackground()); + gantt._task_layers.process(); + this._taskRenderer = this._task_layers.getRenderer(bar_layer); + this._gridRenderer = this._task_layers.getRenderer(grid_layer); + this._backgroundRenderer = this._task_layers.getRenderer(background_layer); + + + //function refreshId(renders, oldId, newId, item){ + // for(var i =0; i < renders.length; i++){ + // renders[i].change_id(oldId, newId); + // renders[i].render_item(item); + // } + //} + //if(this._onTaskIdChange) + // this.detachEvent(this._onTaskIdChange); + // + //this._onTaskIdChange = this.attachEvent("onTaskIdChange", function(oldId, newId){ + // var render = this._get_task_renderers(); + // refreshId(render, oldId, newId, this.getTask(newId)); + //}); + // + //if(this._onLinkIdChange) + // this.detachEvent(this._onLinkIdChange); + // + //this._onLinkIdChange = this.attachEvent("onLinkIdChange", function(oldId, newId){ + // var render = this._get_link_renderers(); + // refreshId(render, oldId, newId, this.getLink(newId)); + //}); +}; + +gantt._create_filter = function(filter_methods){ + if(!(filter_methods instanceof Array)){ + filter_methods = Array.prototype.slice.call(arguments, 0); + } + + return function(obj){ + var res = true; + for(var i = 0, len = filter_methods.length; i < len; i++){ + var filter_method = filter_methods[i]; + if(gantt[filter_method]){ + res = res && (gantt[filter_method].apply(gantt, [obj.id, obj]) !== false); + } + } + + return res; + }; +}; + +gantt._is_chart_visible = function(){ + return !!this.config.show_chart; +}; + +gantt._filter_task = function(id, task){ + var min = null, max = null; + if(this.config.start_date && this.config.end_date){ + min = this.config.start_date.valueOf(); + max = this.config.end_date.valueOf(); + + if(+task.start_date > max || +task.end_date < +min) + return false; + } + return true; +}; +gantt._filter_link = function(id, link){ + if(!this.config.show_links){ + return false; + } + // TODO enable rendering task even out of chart + if(!(gantt.isTaskVisible(link.source) && gantt.isTaskVisible(link.target))) + return false; + + return this.callEvent("onBeforeLinkDisplay", [id, link]); +}; +gantt._is_std_background = function(){ + return !this.config.static_background; +}; + +/*gantt._delete_link_handler = function(id, e){ // HOSEK + if(id && this.callEvent("onLinkDblClick", [id, e])){ + var link = gantt.getLink(id); + if(gantt._is_readonly(link)) return; + + var title = ""; + var question = gantt.locale.labels.link + " " +this.templates.link_description(this.getLink(id)) + " " + gantt.locale.labels.confirm_link_deleting; + + window.setTimeout(function(){ + gantt._dhtmlx_confirm(question, title, function(){ + gantt.deleteLink(id); + }); + },(gantt.config.touch ? 300 : 1)); + } +};*/ +gantt.getTaskNode = function(id){ + return this._taskRenderer.rendered[id]; +}; +gantt.getLinkNode = function(id){ + return this._linkRenderer.rendered[id]; +}; + + + + + +gantt._get_tasks_data = function(){ + var rows = []; + for(var i=0; i < this._order.length; i++){ + var item = this._pull[this._order[i]]; + item.$index = i; + //this._update_parents(item.id, true); + this.resetProjectDates(item); + rows.push(item); + } + return rows; +}; +gantt._get_links_data = function(){ + var links = []; + for(var i in this._lpull) + links.push(this._lpull[i]); + + return links; +}; +gantt._render_data = function(){ + this.callEvent("onBeforeDataRender", []); + //if(!this._is_render_active()) + // return; + + this._sync_order(); + this._update_layout_sizes(); + + if(this.config.static_background) + this._render_bg_canvas(); + + var data = this._get_tasks_data(); + + var renderers = this._get_task_renderers(); + for(var i=0; i < renderers.length; i++){ + renderers[i].render_items(data); + } + + var links = gantt._get_links_data(); + renderers = this._get_link_renderers(); + for(var i=0; i < renderers.length; i++) + renderers[i].render_items(links); + + this.callEvent("onDataRender", []); +}; + +gantt._update_layout_sizes = function(){ + var cfg = this._tasks; + + cfg.bar_height = this._get_task_height(); + + //task bars layer + this.$task_data.style.height = Math.max(this.$task.offsetHeight - this.config.scale_height, 0) + 'px'; + this.$task_bg.style.height = ""; + this.$task_bg.style.backgroundImage = ""; + + //timeline area layers + var data_els = this.$task_data.childNodes; + for(var i= 0, len = data_els.length; i < len; i++){ + var el = data_els[i]; + if(this._is_layer(el) && el.style) + el.style.width = cfg.full_width + "px"; + } + + //grid area + if(this._is_grid_visible()){ + var columns = this.getGridColumns(); + var width = 0; + for (var i = 0; i < columns.length; i++) + width += columns[i].width; + this.$grid_data.style.width = Math.max(width-1, 0) + "px"; + } +}; + +gantt._scale_range_unit = function(){ + var unit = this.config.scale_unit; + if(this.config.scale_offset_minimal){ + var scales = this._get_scales(); + unit = scales[scales.length - 1].unit; + } + return unit; +}; + +gantt._init_tasks_range = function(){ + var unit = this._scale_range_unit(); + + //reset project timing + this._sync_order(); // HOSEK + this._get_tasks_data(); + + var range = this.getSubtaskDates(); + this._min_date = range.start_date || moment(); + this._max_date = range.end_date || moment(); + + if(this.config.start_date && +this.config.start_date < +this._min_date){ + this._min_date = this.date[unit + "_start"]( this.date.Date(this.config.start_date)); + } + if(this.config.end_date && +this.config.end_date > +this._max_date){ + this._max_date = this.date[unit + "_start"]( this.date.Date(this.config.end_date)); + } + if(!(this._max_date && this._max_date)){ + this._min_date = gantt.date.Date(); + this._max_date = gantt.date.Date(this._min_date); + } + var coef=1; // HOSEK + if(unit==="day"){coef=3;} + this._min_date = this.date[unit + "_start"](this._min_date); + //this._min_date = this.calculateEndDate(this.date[unit + "_start"](this._min_date), -1, unit);//one free column before first task + this._min_date = this.date.add(this._min_date,-coef,unit); // HOSEK + + this._max_date = this.date[unit + "_start"](this._max_date); + //this._max_date = this.calculateEndDate(this._max_date, 2, unit);//one free column after last task + this._max_date = this.date.add(this._max_date,3*coef,unit); // HOSEK +}; + + + +gantt._prepare_scale_html = function(config){ + var cells = []; + var date = null, content = null, css = null; + + if(config.template || config.date){ + content = config.template || this.date.date_to_str(config.date); + } + + + css = config.css || function(){}; + if(!config.css && this.config.inherit_scale_class){ + css = gantt.templates.scale_cell_class; + } + + for (var i = 0; i < config.count; i++) { + date = gantt.date.Date(config.trace_x[i]); + var value = content.call(this, date), + width = config.width[i], + style = "", + template = "", + cssclass = ""; + + if(width){ + style = "width:"+(width)+"px;"; + cssclass = "gantt_scale_cell" + (i == config.count-1 ? " gantt_last_cell" : ""); + + template = css.call(this, date); + if(template) cssclass += " " + template; + var cell = "
" + value + "
"; + cells.push(cell); + }else{ + //do not render ignored cells + } + + } + return cells.join(""); +}; +gantt._get_scales = function(){ + var helpers = this._scale_helpers; + var scales = [helpers.primaryScale()].concat(this.config.subscales); + + helpers.sortScales(scales); + return scales; +}; + +gantt._render_tasks_scales = function() { + this._init_tasks_range(); + this._scroll_resize(); + this._set_sizes(); + + var scales_html = "", + outer_width = 0, + data_width = 0, + scale_height = 0; + + if(this._is_chart_visible()){ + var helpers = this._scale_helpers; + var scales = this._get_scales(); + scale_height = (this.config.scale_height-1); + var resize = this._get_resize_options(); + var avail_width = resize.x ? Math.max(this.config.autosize_min_width, 0) : this.$task.offsetWidth; + + var cfgs = helpers.prepareConfigs(scales,this.config.min_column_width, avail_width, scale_height); + var cfg = this._tasks = cfgs[cfgs.length - 1]; + + var html = []; + + var css = this.templates.scale_row_class; + for(var i=0; i < cfgs.length; i++){ + var cssClass = "gantt_scale_line"; + var tplClass = css(cfgs[i]); + if(tplClass){ + cssClass += " " + tplClass; + } + + html.push("
" + this._prepare_scale_html(cfgs[i]) + "
"); + } + + scales_html = html.join(""); + outer_width = cfg.full_width + this.$scroll_ver.offsetWidth + "px"; + data_width = cfg.full_width + "px"; + scale_height += "px"; + } + + if(this._is_chart_visible()){ + this.$task.style.display = ""; + }else{ + this.$task.style.display = "none"; + } + + this.$task_scale.style.height = scale_height; + + this.$task_data.style.width = + this.$task_scale.style.width = outer_width; + + this.$task_scale.innerHTML = scales_html; + +}; + +gantt._render_bg_line = function(item){ + var cfg = gantt._tasks; + var count = cfg.count; + var row = document.createElement("div"); + if(gantt.config.show_task_cells){ + for (var j = 0; j < count; j++) { + var width = cfg.width[j], + cssclass = ""; + + if(width > 0){//do not render skipped columns + var cell = document.createElement("div"); + cell.style.width = (width)+"px"; + + cssclass = "gantt_task_cell" + (j == count-1 ? " gantt_last_cell" : ""); + cssTemplate = this.templates.task_cell_class(item, cfg.trace_x[j]); + if(cssTemplate) + cssclass += " " + cssTemplate; + cell.className = cssclass; + + row.appendChild(cell); + } + + } + } + var odd = item.$index%2 !== 0; + var cssTemplate = gantt.templates.task_row_class(item.start_date, item.end_date, item); + var css = "gantt_task_row" + (odd ? " odd" : "") + (cssTemplate ? ' '+cssTemplate : ''); + + if(this.getState().selected_task == item.id){ + css += " gantt_selected"; + } + + //var row = "
" + cells.join("") + "
"; + + row.className = css; + row.style.height = (gantt.config.row_height)+"px"; + row.setAttribute(this.config.task_attribute, item.id); + return row; +}; + +//defined in an extension +gantt._render_bg_canvas = function(){}; + + +gantt._adjust_scales = function(){ + if(this.config.fit_tasks){ + var old_min = +this._min_date, + old_max = +this._max_date; + this._init_tasks_range(); + if(+this._min_date != old_min || +this._max_date != old_max){ + this.render(); + + this.callEvent("onScaleAdjusted", []); + return true; + } + } + return false; +}; + +//refresh task and related links +gantt.refreshTask = function(taskId, refresh_links){ + gantt._update_parents(gantt.getParent(taskId)); + this.refresher.refreshTask(taskId); +}; +gantt._refreshTask = function(taskId){ + var i; + var renders = this._get_task_renderers(); + + var task = this.getTask(taskId); + //if(task && this.isTaskVisible(taskId)){ + if(!task){ + // nothing + }else + if(+this._min_date < +task.start_date && +this._max_date > +task.end_date){ + for(i =0; i < renders.length; i++) + renders[i].render_item(task); + + for(i=0; i < task.$source.length; i++){ + gantt.refreshLink(task.$source[i]); + } + for(i=0; i < task.$target.length; i++){ + gantt.refreshLink(task.$target[i]); + } + }else{ + this.render(); // HOSEK + } +}; +gantt.refreshLink = function(linkId){ + this.refresher.refreshLink(linkId); +}; +gantt._refreshLink = function(linkId){ + //if(!this._is_render_active()) + // return; + //ysy.log.debug("Link "+linkId+" is being refreshed","link_render"); + + if(this.isLinkExists(linkId)){ + this._render_link(linkId); + }else{ + var renders = this._get_link_renderers(); + for(var i =0; i < renders.length; i++) + renders[i].remove_item(linkId); + } +}; + + + +gantt._combine_item_class = function(basic, template, itemId){ + var css = [basic]; + if(template) + css.push(template); + + var state = gantt.getState(); + + var task = this.getTask(itemId); + + + if(task.type){css.push("gantt_"+task.type+"-type");} + //if(this._get_safe_type(task.type) == this.config.types.milestone){ + // css.push("gantt_milestone"); + //} + // + //if(this._get_safe_type(task.type) == this.config.types.project){ + // css.push("gantt_project"); + //} + + if(this._is_flex_task(task)) + css.push("gantt_dependent_task"); + + if(this.config.select_task && itemId == state.selected_task) + css.push("gantt_selected"); + + if(itemId == state.drag_id){ + css.push("gantt_drag_" + state.drag_mode); + if(state.touch_drag){ + css.push("gantt_touch_" + state.drag_mode); + } + } + var links = gantt._get_link_state(); + if(links.link_source_id == itemId) + css.push("gantt_link_source"); + + if(links.link_target_id == itemId) + css.push("gantt_link_target"); + + + if(this.config.highlight_critical_path && this.isCriticalTask){ + if(this.isCriticalTask(task)) + css.push("gantt_critical_task"); + } + + if(links.link_landing_area && + (links.link_target_id && links.link_source_id) && + (links.link_target_id != links.link_source_id)){ + + var from_id = links.link_source_id; + var from_start = links.link_source_start; + var to_start = links.link_target_start; + + var allowDrag = gantt.isLinkAllowed(from_id, itemId, from_start, to_start); + + var dragClass = ""; + if(allowDrag){ + if(to_start) + dragClass = "link_start_allow"; + else + dragClass = "link_finish_allow"; + }else{ + if(to_start) + dragClass = "link_start_deny"; + else + dragClass = "link_finish_deny"; + } + css.push(dragClass); + } + return css.join(" "); +}; + +gantt._render_pair = function(parent, css, task, content){ + var state = gantt.getState(); + + if (+task.start_date >= +state.min_date) + parent.appendChild(content(css + " task_left")); + + if(+task.end_date <= +state.max_date) { + parent.appendChild(content(css + " task_right")); + } +}; + +gantt._get_task_height = function(){ + // height of the bar item + var height = this.config.task_height; + if(height == "full") + height = this.config.row_height - 5; + //item height cannot be bigger than row height + height = Math.min(height, this.config.row_height); + return Math.max(height, 0); +}; + +gantt._get_milestone_width = function(){ + return this._get_task_height(); +}; +gantt._get_visible_milestone_width = function(){ + var origWidth = gantt._get_task_height();//m-s have square shape + return Math.sqrt(2*origWidth*origWidth); +}; + +// TODO: remove reduntant methods for task positioning +gantt.getTaskPosition = function(task, start_date, end_date){ + var x = this.posFromDate(start_date || task.start_date); + var x2 = this.posFromDate(end_date || task.end_date); + x2 = Math.max(x, x2); + var y = this.getTaskTop(task.id); + var height = this.config.task_height; + return { + left:x, + top:y, + height : height, + width: Math.max((x2 - x), 0) + }; +}; + +gantt._get_task_width = function(task, start, end ){ + return Math.round(this._get_task_pos(task, false).x - this._get_task_pos(task, true).x); +}; + +gantt._is_readonly = function(item){ + if(item && item[this.config.editable_property]){ + return false; + }else{ + return (item && item[this.config.readonly_property]) || this.config.readonly || this.config["readonly_"+gantt._get_safe_type(item.type)]; + } +}; +gantt._task_default_render = function(task){ + var pos = this._get_task_pos(task); + + var cfg = this.config; + var height = this._get_task_height(); + var type = this._get_safe_type(task.type); + var controls = cfg["controls_"+type] || {}; + + var padd = Math.floor((this.config.row_height - height)/2); + if(type == cfg.types.milestone && cfg.link_line_width > 1){ + //little adjust milestone position, so horisontal corners would match link arrow when thickness of link line is more than 1px + padd += 1; + } + + var div = document.createElement("div"); + var width = gantt._get_task_width(task); + + + div.setAttribute(this.config.task_attribute, task.id); + + if(cfg.show_progress && (controls.show_progress || controls.progress)){ + var progress = this._render_task_progress(task,div, width); + if(!this._is_readonly(task) && controls.progress){ + this._render_task_progress_drag(div,progress); + } + } + + //use separate div to display content above progress bar + var content = gantt._render_task_content(task, width); + if(task.textColor){ + content.style.color = task.textColor; + } + div.appendChild(content); + + var css = this._combine_item_class("gantt_task_line", + this.templates.task_class(task.start_date, task.end_date, task), + task.id); + if(task.color || task.progressColor || task.textColor){ + css += " gantt_task_inline_color"; + } + div.className = css; + + var styles = [ + "left:" + pos.x + "px", + "top:" + (padd + pos.y) + 'px', + "height:" + height + 'px', + "line-height:" + height + 'px', + "width:" + width + 'px' + ]; + if(task.color){ + styles.push("background-color:" + task.color); + } + if(task.textColor){ + styles.push("color:" + task.textColor); + } + + div.style.cssText = styles.join(";"); + var side = this._render_leftside_content(task); + if(side) div.appendChild(side); + + side = this._render_rightside_content(task); + if(side) div.appendChild(side); + + if(!this._is_readonly(task)){ + if(cfg.drag_resize && !this._is_flex_task(task) && controls.resize){ + gantt._render_pair(div, "gantt_task_drag", task, function(css){ + var el = document.createElement("div"); + el.className = css; + return el; + }); + } + if(cfg.drag_links && this.config.show_links && controls.links){ + gantt._render_pair(div, "gantt_link_control", task, function(css){ + var outer = document.createElement("div"); + outer.className = css; + outer.style.cssText = [ + "height:" + height + 'px', + "line-height:" + height + 'px' + ].join(";"); + var inner = document.createElement("div"); + inner.className = "gantt_link_point"; + outer.appendChild(inner); + return outer; + }); + } + } + return div; +}; + +gantt._render_task_element = function(task){ + var painters = this.config.type_renderers; + var renderer = painters[this._get_safe_type(task.type)], + defaultRenderer = this._task_default_render; + + if(!renderer){ + renderer = defaultRenderer; + } + return renderer.call(this, task, dhtmlx.bind(defaultRenderer, this)); +}; + +gantt._render_side_content = function(task, template, cssClass){ + if(!template) return null; + + var text = template(task.start_date, task.end_date, task); + if(!text) return null; + var content = document.createElement("div"); + content.className = "gantt_side_content " + cssClass; + content.innerHTML = text; + return content; +}; + + + +gantt._render_leftside_content = function(task){ + var css = "gantt_left " + gantt._get_link_crossing_css(true, task); + return gantt._render_side_content(task, this.templates.leftside_text, css); +}; +gantt._render_rightside_content = function(task){ + var css = "gantt_right " + gantt._get_link_crossing_css(false, task); + return gantt._render_side_content(task, this.templates.rightside_text, css); +}; + +gantt._get_conditions = function(leftside){ + if(leftside){ + return { + $source : [ + gantt.config.links.start_to_start + ], + $target : [ + gantt.config.links.start_to_start, + gantt.config.links.finish_to_start + ] + }; + }else{ + return { + $source : [ + gantt.config.links.finish_to_start, + gantt.config.links.finish_to_finish + ], + $target : [ + gantt.config.links.finish_to_finish + ] + }; + } +}; + +gantt._get_link_crossing_css = function(left, task){ + var cond = gantt._get_conditions(left); + + for(var i in cond){ + var links = task[i]; + for(var ln =0; ln < links.length; ln++){ + var link = gantt.getLink(links[ln]); + + for(var tp =0; tp < cond[i].length; tp++){ + if(link.type == cond[i][tp]){ + return "gantt_link_crossing"; + } + } + } + } + return ""; +}; + + + +gantt._render_task_content = function(task, width){ + var content = document.createElement("div"); + if(this._get_safe_type(task.type) != this.config.types.milestone) + content.innerHTML = this.templates.task_text(task.start_date, task.end_date, task); + content.className = "gantt_task_content"; + //content.style.width = width + 'px'; + return content; +}; +gantt._render_task_progress = function(task, element, maxWidth){ + var done = task.progress*1 || 0; + + maxWidth = Math.max(maxWidth - 2, 0);//2px for borders + var pr = document.createElement("div"); + var width = Math.round(maxWidth*done); + + width = Math.min(maxWidth, width); + if(task.progressColor){ + pr.style.backgroundColor = task.progressColor; + pr.style.opacity = 1; + } + pr.style.width = width + 'px'; + pr.className = "gantt_task_progress"; + pr.innerHTML = this.templates.progress_text(task.start_date, task.end_date, task); + element.appendChild(pr); + return width; + //if(this.config.drag_progress && !gantt._is_readonly(task)){ + //} +}; +gantt._render_task_progress_drag = function(element,width){ + var drag = document.createElement("div"); + drag.style.left = width + 'px'; + drag.className = "gantt_task_progress_drag"; + //pr.appendChild(drag); + element.appendChild(drag); +} +gantt._get_line = function(step) { + var steps = { + "second": 1, + "minute": 60, + "hour": 60*60, + "day": 60*60*24, + "week": 60*60*24*7, + "month": 60*60*24*30, + "year": 60*60*24*365 + }; + return steps[step] || 0; +}; + + +gantt.dateFromPos = function(x){ + return gantt.dateFromPos2(x); + var scale = this._tasks; + if(x < 0 || x > scale.full_width || !scale.full_width){ + return null; + } + + var ind = this._findBinary(this._tasks.left, x); + var summ = this._tasks.left[ind]; + //ysy.log.debug("ind="+ind+" summ="+summ,"task_drag"); + var col_width = scale.width[ind] || scale.col_width; + var part = 0; + if(col_width) + part = (x - summ)/col_width; + + var unit = 0; + if(part){ + unit = gantt._get_coll_duration(scale, scale.trace_x[ind]); + } + //ysy.log.debug("trace="+scale.trace_x[ind].valueOf()+" part="+part+" unit="+unit,"task_drag"); + var date = gantt.date.Date(scale.trace_x[ind].valueOf() + Math.round(part*unit)); + //ysy.log.debug("old:"+date.toString()+" new:"+gantt.dateFromPos2(x).toString(),"task_drag"); + return date; +}; + +gantt.posFromDate = function(date){ + var ind = gantt._day_index_by_date(date); + dhtmlx.assert(ind >= 0, "Invalid day index"); + + var wholeCells = Math.floor(ind); + var partCell = ind % 1; + + var pos = gantt._tasks.left[Math.min(wholeCells, gantt._tasks.width.length - 1)]; + if(wholeCells == gantt._tasks.width.length) + pos += gantt._tasks.width[gantt._tasks.width.length - 1]; + //for(var i=1; i <= wholeCells; i++) + // pos += gantt._tasks.width[i-1]; + + if(partCell){ + if(wholeCells < gantt._tasks.width.length){ + pos += gantt._tasks.width[wholeCells]*(partCell % 1); + }else{ + pos += 1; + } + + } + return pos; +}; +gantt.posFromDateCached = function(date){ + if (typeof date !== "string") { + var momentDate = date; + date = date.toISOString(); + } + var pos = gantt._tasks.date_cache[date]; + if(!pos){ + pos = gantt.posFromDate(momentDate || date); + gantt._tasks.date_cache[date] = pos; + } + return pos; +}; + +gantt._day_index_by_date = function(date){ + //ysy.log.debug(date,"task_drag"); + /*if(date.isValid&&!date.isValid()){ + debugger; + }*/ + var tdate=gantt.date.Date(date);// HOSEK + if(date._isEndDate){tdate=moment(tdate).add(1,"days");} + var pos = tdate.valueOf(); + var days = gantt._tasks.trace_x, + ignores = gantt._tasks.ignore_x || {}; + + if(pos <= this._min_date) + return 0; + + if(pos >= this._max_date) + return days.length; + + /*var day = null; + for (var xind = 0, length = days.length-1; xind < length; xind++) { + // | 8:00, 8:30 | 8:15 should be checked against 8:30 + // clicking at the most left part of the cell, say 8:30 should create event in that cell, not previous one + day = +days[xind+1]; + if (pos < day && !ignores[day]) + break; + }*/ + + var day_ind = gantt._findBinary(days, pos); + var day = +gantt._tasks.trace_x[day_ind]; + while(ignores[day]){ + day = gantt._tasks.trace_x[++day_ind]; + } + + if(!day) return 0; + + return day_ind + ((tdate - days[day_ind]) / gantt._get_coll_duration(gantt._tasks, days[day_ind])); + + +}; +gantt._findBinary = function(array, target) { + // modified binary search, target value not exactly match array elements, looking for closest one + + var low = 0, high = array.length - 1, i, item, prev; + while (low <= high) { + + i = Math.floor((low + high) / 2); + item = +array[i]; + prev = +array[i - 1]; + if (item < target){ + low = i + 1; continue; + } + if (item > target){ + if(!(!isNaN(prev) && prev < target)) { + high = i - 1; continue; + }else{ + // if target is between 'i' and 'i-1' return 'i - 1' + return i - 1; + } + + } + + return i; + } + return array.length - 1; +}; +gantt._get_coll_duration = function(scale, date){ + return gantt.date.add(date, scale.step, scale.unit) - date; +}; + +gantt._get_x_pos = function(task, to_start){ + to_start = to_start !== false; + var x = gantt.posFromDate(to_start ? task.start_date : task.end_date); +}; + +gantt.getTaskTop = function(task_id){ + return this._y_from_ind(this._get_visible_order(task_id)); +}; + +gantt._get_task_coord = function(task, to_start, x_correction){ + to_start = to_start !== false; + x_correction = x_correction || 0; + var isMilestone = (this._get_safe_type(task.type) == this.config.types.milestone); + + var date = null; + + if(to_start && !isMilestone){ + date = (task.start_date || this._default_task_date(task)); + }else{ + date = (task.end_date || this.calculateEndDate(this._default_task_date(task))); + } + var x = this.posFromDate(date), + y = this.getTaskTop(task.id); + + if(isMilestone){ + if(to_start){ + x -= x_correction; + }else{ + x += x_correction; + } + } + return {x:x, y:y}; +}; +gantt._get_task_pos = function(task, to_start){ + to_start = to_start !== false; + var mstoneCorrection = gantt._get_milestone_width()/2; + return this._get_task_coord(task, to_start, mstoneCorrection); +}; + +gantt._get_task_visible_pos = function(task, to_start){ + to_start = to_start !== false; + var mstoneCorrection = gantt._get_visible_milestone_width()/2; + return this._get_task_coord(task, to_start, mstoneCorrection); +}; + + +gantt._correct_shift=function(start, back){ + return start-=((gantt.date.Date(gantt._min_date)).getTimezoneOffset()-(gantt.date.Date(start)).getTimezoneOffset())*60000*(back?-1:1); +}; + + + +gantt._get_mouse_pos = function(ev){ + if (ev.pageX || ev.pageY) { + var pos = {x: ev.pageX, y: ev.pageY}; + } else { + var d = _isIE ? document.documentElement : document.body; + pos = { + x:ev.clientX + d.scrollLeft - d.clientLeft, + y:ev.clientY + d.scrollTop - d.clientTop + }; + } + + var box = gantt._get_position(gantt.$task_data); + pos.x = pos.x - box.x + gantt.$task_data.scrollLeft; + pos.y = pos.y - box.y + gantt.$task_data.scrollTop; + //ysy.log.debug("POS=["+pos.x+","+pos.y+"]"); + return pos; +}; + +gantt._is_layer = function(dom_element){ + return (dom_element && dom_element.hasAttribute && dom_element.hasAttribute(this.config.layer_attribute)); +}; +//helper for rendering bars and links +gantt._task_renderer = function(layer){ +//gantt._task_renderer = function(id, render_one, node, filter){ + //hash of dom elements is needed to redraw single bar/link + if(typeof layer !== "object"){ + var id=layer; + }else{ + var id = layer.id; + var render_one = layer.renderer; + var node = layer.container; + var filter = layer.filter; + } + if(!this._task_area_pulls) + this._task_area_pulls = {}; + + if(!this._task_area_renderers) + this._task_area_renderers = {}; + + if(this._task_area_renderers[id]) + return this._task_area_renderers[id]; + + if(!render_one) + dhtmlx.assert(false, "Invalid renderer call"); + + if(node) + node.setAttribute(this.config.layer_attribute, true); + + this._task_area_renderers[id] = $.extend({ + render_item : function(item, container){ + var pull = gantt._task_area_pulls[id]; + container = container || node; + + + if(filter){ + if(!filter(item)){ + ysy.log.debug("_task_area_renderers() filtered id="+item.id,"link_render"); + this.remove_item(item.id); + return; + } + } + var dom = render_one.call(gantt, item, pull[item.id]); + if(!dom) return; + if(pull[item.id]){ + this.replace_item(item.id, dom); + }else{ + pull[item.id] = dom; + container.appendChild(dom); + } + }, + clear : function(container){ + this.rendered = gantt._task_area_pulls[id] = {}; + container = container || node; + if(container) + container.innerHTML = ""; + }, + render_items : function(items, container){ + container = container || node; + this.clear(container); + var buffer = document.createDocumentFragment(); + for(var i= 0, vis = items.length; i < vis; i++){ + this.render_item(items[i], buffer); + } + container.appendChild(buffer); + }, + replace_item: function(item_id, newNode){ + var item = this.rendered[item_id]; + if(item && item.parentNode){ + item.parentNode.replaceChild(newNode, item); + } + this.rendered[item_id] = newNode; + }, + remove_item:function(item_id){ + var item = this.rendered[item_id]; + if(item && item.parentNode){ + item.parentNode.removeChild(item); + } + delete this.rendered[item_id]; + }, + change_id: function(oldid, newid) { + this.rendered[newid] = this.rendered[oldid]; + delete this.rendered[oldid]; + }, + rendered : this._task_area_pulls[id] || {}, + node: node, + unload : function(){ + this.clear(); + delete gantt._task_area_renderers[id]; + delete gantt._task_area_pulls[id]; + } + },layer); + + return this._task_area_renderers[id]; +}; + +gantt._clear_renderers = function(){ + for(var i in this._task_area_renderers){ + this._task_renderer(i).unload(); + } +}; + + + +// --#include core/tasks_canvas_render.js +gantt.attachEvent("onGanttReady", function(){ + gantt._task_layers.add(); + gantt._link_layers.add(); +}); + +gantt._layers = { + prepareConfig: function(config){ + if(typeof config == "function"){ + config = {renderer: config}; + } + + var id = config.id = dhtmlx.uid(); + + if(!config.container) + config.container = document.createElement("div"); + + return config; + }, + create: function(get_container, rel_root){ + return { + tempCollection:[], + renderers:{}, + container: get_container, + getRenderers: function(){ + var res = []; + for (var i in this.renderers){ + res.push(this.renderers[i]); + } + return res; + }, + getRenderer: function(id){ + return this.renderers[id]; + }, + add: function(layer){ + if(layer) + this.tempCollection.push(layer); + //this.process(); + }, + // TODO put process to all layer creators + process: function(){ + if(!this.container()) return; + var container = this.container(); + var pending = this.tempCollection; + for(var i =0; i < pending.length; i++){ + var layer = pending[i]; + var node = layer.container, + id = layer.id, + topmost = layer.topmost; + if(!node.parentNode){ + //insert on top or below the tasks + if(topmost){ + container.appendChild(node); + }else{ + var rel = rel_root ? rel_root() : container.firstChild; + if(rel) + container.insertBefore(node, rel); + else + container.appendChild(node); + } + } + this.renderers[id] = gantt._task_renderer(layer); + //this.renderers[id] = gantt._task_renderer(id, layer.renderer, node, layer.filter); + this.tempCollection.splice(i,1); + i--; + } + }, + remove: function(id){ + this.renderers[id].unload(); + delete this.renderers[id]; + }, + clear: function(){ + for(var i in this.renderers){ + this.renderers[i].unload(); + } + this.renderers = {}; + } + }; + } +}; + +gantt._create_filter = function(filter_methods){ + if(!(filter_methods instanceof Array)){ + filter_methods = Array.prototype.slice.call(arguments, 0); + } + + return function(obj){ + var res = true; + for(var i = 0, len = filter_methods.length; i < len; i++){ + var filter_method = filter_methods[i]; + if(gantt[filter_method]){ + res = res && (gantt[filter_method].call(gantt, obj.id, obj) !== false); + } + } + + return res; + }; +}; + +gantt._add_generic_layer = function(layersManager, filters){ + return function(config){ + if(config.filter === undefined){ + config.filter = gantt._create_filter(filters); + } + config = gantt._layers.prepareConfig(config); + layersManager.add(config); + return config.id; + }; +}; + +gantt._task_layers = gantt._layers.create(function(){return gantt.$task_data; }, function(){return gantt.$task_links;}); + +gantt._link_layers = gantt._layers.create(function(){return gantt.$task_data; }); + +gantt.addTaskLayer = gantt._add_generic_layer(gantt._task_layers, ['_filter_task', '_is_chart_visible']); + +gantt.removeTaskLayer = function(id){ + gantt._task_layers.remove(id); +}; + +gantt.addLinkLayer = gantt._add_generic_layer(gantt._link_layers, ['_filter_link', '_is_chart_visible']); +gantt.removeLinkLayer = function(id){ + gantt._link_layers.remove(id); +}; + +gantt._get_task_renderers = function(){ + return this._task_layers.getRenderers(); +}; +gantt._get_link_renderers = function(){ + return this._link_layers.getRenderers(); +}; + +gantt._pull = {}; +gantt._branches = {}; +gantt._order = []; +gantt._lpull = {}; + +gantt.load = function(url, type, callback){ + this._load_url = url; + dhtmlx.assert(arguments.length, "Invalid load arguments"); + this.callEvent("onLoadStart", []); + var tp = 'json', cl = null; + if(arguments.length >= 3){ + tp = type; + cl = callback; + }else{ + if(typeof arguments[1] == "string") + tp = arguments[1]; + else if(typeof arguments[1] == "function") + cl = arguments[1]; + } + + this._load_type = tp; + + dhx4.ajax.get(url, dhtmlx.bind(function(l) { + this.on_load(l, tp); + this.callEvent("onLoadEnd", []); + if(typeof cl == "function") + cl.call(this); + }, this)); +}; +gantt.parse = function(data, type) { + this.on_load({xmlDoc: {responseText: data}}, type); +}; + +gantt.serialize = function(type){ + type = type || "json"; + return this[type].serialize(); +}; + +/* +tasks and relations +{ +data:[ + { + "id":"string", + "text":"...", + "start_date":"Date or string", + "end_date":"Date or string", + "duration":"number", + "progress":"0..1", + "parent_id":"string", + "order":"number" + },...], +links:[ + { + id:"string", + source:"string", + target:"string", + type:"string" + },...], +collections:{ + collectionName:[ + {key:, label:, optional:...},... + ],... + } +} + + gantt._pull - id to object hash + gantt._branch - array of per branch arrays of objects|ids + gantt._order - array of visible elements + gantt._order_full - array of all elements + + gantt._links +* */ + +gantt.on_load = function(resp, type){ + this.callEvent("onBeforeParse", []); + if(!type) + type = "json"; + dhtmlx.assert(this[type], "Invalid data type:'" + type + "'"); + + var raw = resp.xmlDoc.responseText; + + var data = this[type].parse(raw, resp); + this._process_loading(data); +}; + + + +gantt._process_loading = function(data){ + if(data.collections) + this._load_collections(data.collections); + + var tasks = data.data; + var task; + for (var i = 0; i < tasks.length; i++) { + task = tasks[i]; + this._init_task(task); + if (!this.callEvent("onTaskLoading", [task])) continue; + this._pull[task.id] = task; + } + + for (var i in this._pull){ + task = this._pull[i]; + this.setParent(task, this.getParent(task) || this.config.root_id); + } + + // calculating $level for each item + for (var i in this._pull){ + task = this._pull[i]; + this._add_branch(task, true); + task.$level = this.calculateTaskLevel(task); + } + this._sync_order(); + this._init_links(data.links || (data.collections ? data.collections.links : [])); + this.callEvent("onParse", []); + this.render(); + if(this.config.initial_scroll){ + var id = (this._order[0] || this.config.root_id); + if(id) + this.showTask(id); + } +}; + + +gantt._init_links = function(links){ + if (links) + for(var i=0; i < links.length; i++){ + if(links[i]){ + var link = this._init_link(links[i]); + this._lpull[link.id] = link; + } + } + this._sync_links(); +}; + + +gantt._load_collections = function(collections){ + var collections_loaded = false; + for (var key in collections) { + if (collections.hasOwnProperty(key)) { + collections_loaded = true; + var collection = collections[key]; + var arr = this.serverList[key]; + if (!arr) continue; + arr.splice(0, arr.length); //clear old options + for (var j = 0; j < collection.length; j++) { + var option = collection[j]; + var obj = dhtmlx.copy(option); + obj.key = obj.value;// resulting option object + + for (var option_key in option) { + if (option.hasOwnProperty(option_key)) { + if (option_key == "value" || option_key == "label") + continue; + obj[option_key] = option[option_key]; // obj['value'] = option['value'] + } + } + arr.push(obj); + } + } + } + if (collections_loaded) + this.callEvent("onOptionsLoad", []); +}; + +gantt._sync_order = function(silent) { + this._order = []; + this._sync_order_item({parent:this.config.root_id, $open:true, $ignore:true, id:this.config.root_id}); + this._order.push("empty"); + + if(!silent){ + this._scroll_resize(); + this._set_sizes(); + } +}; +gantt.attachEvent("onBeforeTaskDisplay", function(id, task){ + return !task.$ignore; +}); +gantt._sync_order_item = function(item) { + if(!item){ + ysy.log.debug("error: no item"); + } + if (!item.id + || this._filter_task(item.id, item) + && this.callEvent("onBeforeTaskDisplay", [item.id, item])) { + if (item.id) { //do not trigger event for virtual root + this._order.push(item.id); + } + if (item.$open) { + var children = this.getChildren(item.id); + if (children) + for (var i = 0; i < children.length; i++) + this._sync_order_item(this._pull[children[i]]); + } + } +}; + +gantt._get_visible_order = function(id){ + dhtmlx.assert(id, "Invalid argument"); + var ord = this._order; + for(var i= 0, count = ord.length; i < count; i++) + if(ord[i] == id) return i; + + return -1; +}; + + + +gantt.eachTask = function(code, parent, master){ + parent = parent || this.config.root_id; + master = master || this; + + var branch = this.getChildren(parent); + if (branch) + for (var i=0; i + + My task 1 + 16.08.2013 + 22.08.2013 + + + + + + + +*/ + +gantt.xml = { + _xmlNodeToJSON:function(node, attrs_only){ + var t = {}; + for (var i = 0; i < node.attributes.length; i++) + t[node.attributes[i].name] = node.attributes[i].value; + + if (!attrs_only){ + for (var i = 0; i < node.childNodes.length; i++) { + var child = node.childNodes[i]; + if (child.nodeType == 1) + t[child.tagName] = child.firstChild ? child.firstChild.nodeValue : ""; + } + + if (!t.text) t.text = node.firstChild ? node.firstChild.nodeValue : ""; + } + + return t; + }, + _getCollections:function(loader){ + var collection = {}; + var opts = dhx4.ajax.xpath("//coll_options", loader); + for (var i = 0; i < opts.length; i++) { + var bind = opts[i].getAttribute("for"); + var arr = collection[bind] = []; + var itms = dhx4.ajax.xpath(".//item", opts[i]); + for (var j = 0; j < itms.length; j++) { + var itm = itms[j]; + var attrs = itm.attributes; + var obj = { key: itms[j].getAttribute("value"), label: itms[j].getAttribute("label")}; + for (var k = 0; k < attrs.length; k++) { + var attr = attrs[k]; + if (attr.nodeName == "value" || attr.nodeName == "label") + continue; + obj[attr.nodeName] = attr.nodeValue; + } + arr.push(obj); + } + } + return collection; + }, + _getXML:function(text, loader, toptag){ + toptag = toptag || "data"; + if (!loader.getXMLTopNode){ + loader = dhx4.ajax.parse(loader); + } + + var xml = dhx4.ajax.xmltop(toptag, loader.xmlDoc); + if (xml.tagName != toptag) throw "Invalid XML data"; + + var skey = xml.getAttribute("dhx_security"); + if (skey) + dhtmlx.security_key = skey; + + return xml; + }, + parse:function(text, loader){ + loader = this._getXML(text, loader); + var data = { }; + + var evs = data.data = []; + var xml = dhx4.ajax.xpath("//task", loader); + + for (var i = 0; i < xml.length; i++) + evs[i] = this._xmlNodeToJSON(xml[i]); + + data.collections = this._getCollections(loader); + return data; + }, + _copyLink:function(obj){ + return ""; + }, + _copyObject:function(obj){ + return ""; + }, + serialize:function(){ + var tasks = []; + var links = []; + + var json = gantt.json.serialize(); + for(var i= 0, len = json.data.length; i < len; i++){ + tasks.push(this._copyObject(json.data[i])); + } + for(var i= 0, len = json.links.length; i < len; i++){ + links.push(this._copyLink(json.links[i])); + } + return ""+tasks.join("")+""+links.join("")+""; + } +}; + + +gantt.oldxml = { + parse:function(text, loader){ + loader = gantt.xml._getXML(text, loader, "projects"); + var data = { collections:{ links:[] } }; + + var evs = data.data = []; + var xml = dhx4.ajax.xpath("//task", loader); + + for (var i = 0; i < xml.length; i++){ + evs[i] = gantt.xml._xmlNodeToJSON(xml[i]); + var parent = xml[i].parentNode; + + if (parent.tagName == "project") + evs[i].parent = "project-"+parent.getAttribute("id"); + else + evs[i].parent = parent.parentNode.getAttribute("id"); + } + + xml = dhx4.ajax.xpath("//project", loader); + for (var i = 0; i < xml.length; i++){ + var ev = gantt.xml._xmlNodeToJSON(xml[i], true); + ev.id ="project-"+ev.id; + evs.push(ev); + } + + for (var i=0; i +item.start_date || +this._max_date < +item.end_date){ + this.render(); + } + + return item.id; +}; + +gantt._default_task_date = function(item, parent_id){ + var parent = (parent_id && parent_id != this.config.root_id) ? this.getTask(parent_id) : false, + startDate = ''; + if(parent){ + startDate = parent.start_date; + }else{ + var first = this._order[0]; + startDate = first ? this.getTask(first).start_date : (this.config.start_date || this.getState().min_date); + } + return gantt.date.Date(startDate); +}; + +gantt._set_default_task_timing = function(task){ + task.start_date = task.start_date || gantt._default_task_date(task, this.getParent(task)); + task.duration = task.duration || this.config.duration_step; + task.end_date = task.end_date || this.calculateEndDate(task.start_date, task.duration); +}; + +/*gantt.createTask = function(item, parent){ + item = item || {}; + item.id = dhtmlx.uid(); + if(!item.start_date){ + item.start_date = gantt._default_task_date(item, parent); + } + if(item.text === undefined){ + item.text = gantt.locale.labels.new_task; + } + if(item.duration === undefined){ + item.duration = 1; + } + + if(parent){ + this.setParent(item, parent); + parent = this.getTask(parent); + parent.$open = true; + } + + if(!this.callEvent("onTaskCreated", [item])){ + return null; + } + if (this.config.details_on_create){ + item.$new = true; + this._pull[item.id] = this._init_task(item); + + this._add_branch(item); + item.$level = this.calculateTaskLevel(item); + this.selectTask(item.id); + this.refreshData(); + this.showLightbox(item.id); + }else{ + if (this.addTask(item)){ + this.showTask(item.id); + this.selectTask(item.id); + } + } + return item.id; +};*/ + +gantt.deleteTask = function(id) { + return this._deleteTask(id); +}; + +//TODO: do something with overcomplicated dataprocessor logic +gantt._getChildLinks = function(id){ + var item = this.getTask(id); + if(!item){ + return []; + } + + var links = item.$source.concat(item.$target); + + var branches = this.getChildren(item.id); + for (var i = 0; i < branches.length; i++) { + links = links.concat(this._getChildLinks(branches[i])); + } + + var res = {}; + for(var i=0; i < links.length; i++){ + res[links[i]] = true; + } + links = []; + for(var i in res){ + links.push(i); + } + + return links; +}; +gantt._getTaskTree = function(id){ + var item = this.getTask(id); + if(!item){ + return []; + } + + var items = []; + var branches = this.getChildren(item.id); + for (var i = 0; i < branches.length; i++) { + items.push(branches[i]); + items = items.concat(this._getTaskTree(branches[i])); + } + return items; +}; +gantt._deleteRelatedLinks = function(links, silent){ + var use_dp = (this._dp && !silent); + var prev_mode = ''; + var send_changes = use_dp ? this._dp.updateMode != 'off' : false; + if (use_dp){ + prev_mode = this._dp.updateMode; + this._dp.setUpdateMode("off"); + } + for(var i =0; i < links.length; i++){ + if (use_dp) { + this._dp.setGanttMode("links"); + this._dp.setUpdated(links[i],true,"deleted"); + } + this._deleteLink(links[i], true); + } + + if(use_dp){ + this._dp.setUpdateMode(prev_mode); + if(send_changes) + this._dp.sendAllData(); + } +}; +gantt._deleteRelatedTasks = function(id, silent){ + var use_dp = (this._dp && !silent); + var prev_mode = ''; + + if (use_dp) { + prev_mode = this._dp.updateMode; + this._dp.setGanttMode("tasks"); + this._dp.setUpdateMode("off"); + } + var tree = this._getTaskTree(id); + for (var i = 0; i < tree.length; i++) { + // add deleted subrow into dataprocessor update list manually + // because silent mode is on + var t_id = tree[i]; + this._unset_task(t_id); + if(use_dp){ + this._dp.setUpdated(t_id,true,"deleted"); + } + } + if(use_dp){ + + this._dp.setUpdateMode(prev_mode); + } +}; +gantt._unset_task = function(id){ + var item = this.getTask(id); + this._update_flags(id, null); + delete this._pull[id]; + this._move_branch(item, this.getParent(item), null); +}; +gantt._deleteTask = function(id, silent) { + var item = this.getTask(id); + if (!silent && this.callEvent("onBeforeTaskDelete", [id, item])===false) return false; + + var links = gantt._getChildLinks(id); + this._deleteRelatedTasks(id, silent); + this._deleteRelatedLinks(links, silent); + this._unset_task(id); + if (!silent) { + this.callEvent("onAfterTaskDelete", [id, item]); + this.refreshData(); + } + return true; +}; + +gantt.clearAll = function() { + this._clear_data(); + this.callEvent("onClear", []); + this.refreshData(); +}; +gantt._clear_data = function(){ + this._pull = {}; + this._branches = {}; + this._order = []; + this._order_full = []; + this._lpull = {}; + this._update_flags(); + this.userdata = {}; +}; + +gantt._update_flags = function(oldid, newid){ + // TODO: need a proper way to update all possible flags + if(oldid === undefined){ + this._lightbox_id = this._selected_task = null; + if (this._tasks_dnd.drag){ + this._tasks_dnd.drag.id = null; + } + }else{ + if (this._lightbox_id == oldid) + this._lightbox_id = newid; + if (this._selected_task == oldid){ + this._selected_task = newid; + } + if (this._tasks_dnd.drag && this._tasks_dnd.drag.id == oldid){ + this._tasks_dnd.drag.id = newid; + } + } +}; +//gantt.changeTaskId = function(oldid, newid) { +// var item = this._pull[newid] = this._pull[oldid]; +// this._pull[newid].id = newid; +// delete this._pull[oldid]; +// for (var id in this._pull) { +// var task = this._pull[id]; +// if(this.getParent(task) == oldid) +// this.setParent(task, newid); +// } +// this._update_flags(oldid, newid); +// this._replace_branch_child(this.getParent(item), oldid, newid); +// +// this.callEvent("onTaskIdChange", [oldid, newid]); +//}; + +gantt._get_duration_unit = function(){ + return (gantt._get_line(this.config.duration_unit)*1000) || this.config.duration_unit; +}; + +gantt._get_safe_type = function(type){ + return type || "task"; +}; +gantt._get_type_name = function(type_value){ + for(var i in this.config.types){ + if(this.config.types[i] == type_value){ + return i; + } + } + return "task"; +}; +gantt.getWorkHours = function(date){ + return this._working_time_helper.get_working_hours(date); +}; + +gantt.setWorkTime = function(config){ + this._working_time_helper.set_time(config); +}; + +gantt.isWorkTime = function(date, unit){ + var helper = this._working_time_helper; + return helper.is_working_unit(date, unit || this.config.duration_unit); +}; + +gantt.correctTaskWorkTime = function(task){ + alert("Forbidden function correctTaskWorkTime"); + if(gantt.config.work_time && gantt.config.correct_work_time){ + if(!gantt.isWorkTime(task.start_date)){ + task.start_date = gantt.getClosestWorkTime({date:task.start_date, dir:'future'}); + task.end_date = gantt.calculateEndDate(task.start_date, task.duration); + }else if(!gantt.isWorkTime(new Date(+task.end_date - 1))){ + task.end_date = gantt.calculateEndDate(task.start_date, task.duration); + } + } +}; + +gantt.getClosestWorkTime = function(config){ + var helper = this._working_time_helper; + if(config instanceof Date){ + config = { + date:config + }; + } + config.dir = config.dir || 'any'; + config.unit = config.unit || this.config.duration_unit; + return helper.get_closest_worktime(config); +}; + +gantt.calculateDuration = function(start_date, end_date){ + var helper = this._working_time_helper; + return helper.get_work_units_between(start_date, end_date, this.config.duration_unit, this.config.duration_step); +}; +gantt._hasDuration = function(start_date, end_date){ + var helper = this._working_time_helper; + return helper.is_work_units_between(start_date, end_date, this.config.duration_unit, this.config.duration_step); +}; + +gantt.calculateEndDate = function(start, duration, unit){ + var helper = this._working_time_helper; + return helper.add_worktime(start, duration, unit || this.config.duration_unit); +}; + +gantt._init_task = function(task){ + if (!dhtmlx.defined(task.id)) + task.id = dhtmlx.uid(); + + if(task.start_date) + task.start_date = gantt.date.parseDate(task.start_date, "xml_date"); + if(task.end_date) + task.end_date = gantt.date.parseDate(task.end_date, "xml_date"); + + + + if(task.start_date){ + if(!task.end_date && task.duration){ + task.end_date = this.calculateEndDate(task.start_date, task.duration); + } + } + + gantt._init_task_timing(task); + if(task.start_date && task.end_date){ + //gantt.correctTaskWorkTime(task); // HOSEK neopravovat počátky a konce dat ze serveru + } + + task.$source = []; + task.$target = []; + if(task.parent === undefined){ + this.setParent(task, this.config.root_id); + } + task.$open = dhtmlx.defined(task.open) ? task.open : this.config.open_tree_initially; + task.$level = this.calculateTaskLevel(task); + return task; +}; + +gantt._init_task_timing = function(task){ + var task_type = this._get_safe_type(task.type); + + if(task.$rendered_type === undefined){ + task.$rendered_type = task_type; + }else if(task.$rendered_type != task_type){ + delete task.$no_end; + delete task.$no_start; + task.$rendered_type = task_type; + } + + if((task.$no_end === undefined || task.$no_start === undefined) && task_type != this.config.types.milestone){ + if(task_type == this.config.types.project){ + //project duration is always defined by children duration + task.$no_end = task.$no_start = true; + this._set_default_task_timing(task); + }else{ + //tasks can have fixed duration, children duration(as projects), or one date fixed, and other defined by nested items + task.$no_end = !(task.end_date || task.duration); + task.$no_start = !task.start_date; + } + } + + if (task.start_date && task.end_date){ + task.duration = Math.max(this.calculateDuration(task.start_date, task.end_date),1); // HOSEK - because of weekend issues + } + task.duration = task.duration || 0; +}; +gantt._is_flex_task = function(task){ + return !!(task.$no_end || task.$no_start); +}; + +// downward calculation of project duration +gantt.resetProjectDates = function(task){ + if(task.$no_end || task.$no_start){ + var dates = this.getSubtaskDates(task.id); + if (task.minimal_end && task.minimal_end > dates.end_date) { + dates.end_date = moment(task.minimal_end); + dates.end_date._isEndDate = true; + } + if (task.maximal_start && task.maximal_start < dates.start_date) { + dates.start_date = moment(task.maximal_start); + } + ysy.log.debug("resetProjectDates to start="+dates.start_date+" end="+dates.end_date,"parent"); + this._assign_project_dates(task, dates.start_date, dates.end_date); + } +}; + +gantt.getSubtaskDates = function(task_id){ + var min = null, + max = null, + root = task_id !== undefined ? task_id : gantt.config.root_id; + + this.eachTask(function(child){ + //if(this._get_safe_type(child.type) == gantt.config.types.project) + // return; + //var dates=gantt.getSubtaskDates(child.id); + if(child.start_date){ + if(!min || min > child.start_date.valueOf()){ + min=child.start_date.valueOf(); + } + } + if(child.end_date){ + if(!max || max < child.end_date.valueOf()){ + max=child.end_date.valueOf(); + } + } + //ysy.log.debug("subtaskDates: root: "+task_id+" child: "+child.id+" min:"+moment(min).format("YYYY-MM-DD")); + //if((child.start_date && !child.$no_start) && (!min || min > child.start_date.valueOf())) + // min = child.start_date.valueOf(); + //if((child.end_date && !child.$no_end) && (!max || max < child.end_date.valueOf())) + // max = child.end_date.valueOf(); + }, root); + if((!min || !max) && task_id){ + var task = gantt.getTask(task_id); + min = min || task.start_date; + max = max || task.end_date; + } + var start_date = moment(min); + var end_date = moment(max); + end_date._isEndDate=true; + return { + start_date:start_date.isValid()?start_date:null, + end_date:end_date.isValid()?end_date:null + }; + return { + start_date: min ? gantt.date.Date(min) : null, + end_date: max ? gantt.date.Date(max): null + }; +}; + +gantt._assign_project_dates = function(task, from, to){ + if(task.$no_start){ + if(from && from != Infinity){ + task.start_date = gantt.date.Date(from); + }else{ + task.start_date = this._default_task_date(task, this.getParent(task)); + } + } + + if(task.$no_end){ + if(to && to != -Infinity){ + task.end_date = gantt.date.Date(to); + }else{ + task.end_date = this.calculateEndDate(task.start_date, this.config.duration_step); + } + } + if(task.$no_start || task.$no_end){ + this._init_task_timing(task); + } +}; + +// upward calculation of project duration +gantt._update_parents = function(taskId, silent){ + ysy.log.debug("_update_parents on ID="+taskId,"parent"); + if(!taskId) return; + + var task = this.getTask(taskId); + var pid = this.getParent(task); + + while(!(task.$no_end || task.$no_start) && pid && this.isTaskExists(pid)){ + task = this.getTask(pid); + pid = this.getParent(task); + } + + if(task.$no_start || task.$no_end){ + gantt.resetProjectDates(task); + + if(!silent) + this.refreshTask(task.id, true); + } + + if(pid && this.isTaskExists(pid)){ + this._update_parents(pid, silent); + } +}; +gantt.isChildOf = function(child_id, parent_id){ + if(!this.isTaskExists(child_id)) + return false; + if(parent_id === this.config.root_id) + return this.isTaskExists(child_id); + + var task = this.getTask(child_id); + var pid = this.getParent(child_id); + + while(task && this.isTaskExists(pid)){ + task = this.getTask(pid); + + if(task && task.id == parent_id) + return true; + pid = this.getParent(task); + } + return false; +}; + +gantt.roundDate = function(config){ + alert("Forbidden function roundDate"); + if(config instanceof Date){ + config = { + date: config, + unit: gantt._tasks.unit, + step: gantt._tasks.step + }; + } + var date = config.date, + steps = config.step, + unit = config.unit; + var rounding=this._get_line(unit)*1000; + var tzOffset = date.getTimezoneOffset(); // HOSEK + return moment(Math.round((+date) / rounding) * rounding).add(tzOffset,"m").toDate(); + /*var upper, lower; + if(unit == gantt._tasks.unit && steps == gantt._tasks.step && + +date >= +gantt._min_date && +date <= +gantt._max_date){ + //find date in time scale config + var colIndex = Math.floor(gantt._day_index_by_date(date)); + lower = new Date(gantt._tasks.trace_x[colIndex]); + upper = new Date(lower); + if(.trace_x[colIndex + 1]) + upper = new Date(gantt._tasks.trace_x[colIndex + 1]); + }else{ + upper = gantt.date[unit + "_start"](new Date(this._min_date)); + while(+upper < +date){ + upper = gantt.date[unit + "_start"](gantt.date.add(upper, steps, unit)); + + var tzOffset = upper.getTimezoneOffset(); + upper = gantt.date.add(upper, steps, unit); + upper = gantt._correct_dst_change(upper, tzOffset, upper, unit); + if(gantt.date[unit + '_start']) + upper = gantt.date[unit + '_start'](upper); + } + + lower = gantt.date.add(upper, -1*steps, unit); + + } + if(config.dir && config.dir == 'future') + return upper; + if(config.dir && config.dir == 'past') + return lower; + + if(Math.abs(date - lower) < Math.abs(upper - date)){ + return lower; + }else{ + return upper; + }*/ + +}; + + +gantt.attachEvent("onBeforeTaskUpdate", function(id, task){ + gantt._init_task_timing(task); + return true; +}); +gantt.attachEvent("onBeforeTaskAdd", function(id, task){ + gantt._init_task_timing(task); + return true; +}); + +gantt.calculateTaskLevel = function (item) { + var level = 0; + while (this.getParent(item)) { + if (!this.isTaskExists(this.getParent(item))) break; + item = this.getTask(this.getParent(item)); + level++; + } + return level; +}; + + +gantt.sort = function(field, desc, parent, silent) { + var render = !silent;//4th argument to cancel redraw after sorting + + if (!this.isTaskExists(parent)) { + parent = this.config.root_id; + } + + if (!field) field = "order"; + var criteria = (typeof(field) == "string") ? (function(a, b) { + if(a[field] == b[field]){ + return 0; + } + + var result = a[field] > b[field]; + if (desc) result = !result; + return result ? 1 : -1; + }) : field; + + + var els = this.getChildren(parent); + if (els){ + var temp = []; + for (var i = els.length - 1; i >= 0; i--) + temp[i] = this._pull[els[i]]; + + temp.sort(criteria); + + for (var i = 0; i < temp.length; i++) { + els[i] = temp[i].id; + this.sort(field, desc, els[i], true); + } + } + + if (render) { + this.render(); + } +}; + +gantt.getNext = function(id) { + for (var i = 0; i < this._order.length-1; i++) { + if (this._order[i] == id) + return this._order[i+1]; + } + return null; +}; +gantt.getPrev = function(id) { + for (var i = 1; i < this._order.length; i++) { + if (this._order[i] == id) + return this._order[i-1]; + } + return null; +}; + +gantt._get_parent_id = function(task){ + var parent = this.config.root_id; + if(task){ + parent = task.parent; + } + return parent; +}; + +gantt.getParent = function(id){ + var task = null; + if(id.id){ + task = id; + }else if(this.isTaskExists(id)){ + task = gantt.getTask(id); + } + + return this._get_parent_id(task); +}; + + + +gantt.setParent = function(task, new_pid){ + task.parent = new_pid; +}; + +gantt.getSiblings = function(id){ + if(!this.isTaskExists(id)){ + return []; + } + var parent = this.getParent(id); + return this.getChildren(parent); +}; +gantt.getNextSibling = function(id){ + var siblings = this.getSiblings(id); + for(var i= 0, len = siblings.length; i < len; i++){ + if(siblings[i] == id) + return siblings[i+1] || null; + } + return null; +}; +gantt.getPrevSibling = function(id){ + var siblings = this.getSiblings(id); + for(var i= 0, len = siblings.length; i < len; i++){ + if(siblings[i] == id) + return siblings[i-1] || null; + } + return null; +}; + +/*gantt._dp_init = function(dp) { + dp.setTransactionMode("POST", true); + dp.serverProcessor += (dp.serverProcessor.indexOf("?") != -1 ? "&" : "?") + "editing=true"; + dp._serverProcessor = dp.serverProcessor; + + dp.styles = { + updated:"gantt_updated", + inserted:"gantt_inserted", + deleted:"gantt_deleted", + invalid:"gantt_invalid", + error:"gantt_error", + clear:"" + }; + + dp._methods=["_row_style","setCellTextStyle","_change_id","_delete_task"]; + + dp.setGanttMode = function(mode){ + var modes = dp.modes || {}; + if(dp._ganttMode){ + modes[dp._ganttMode] = { + _in_progress : dp._in_progress, + _invalid : dp._invalid, + updatedRows : dp.updatedRows + }; + } + + var newState = modes[mode]; + if(!newState){ + newState = modes[mode] = { + _in_progress : {}, + _invalid : {}, + updatedRows : [] + }; + } + dp._in_progress = newState._in_progress; + dp._invalid = newState._invalid; + dp.updatedRows = newState.updatedRows; + dp.modes = modes; + dp._ganttMode = mode; + }; + + this._sendTaskOrder = function(id, item){ + if(item.$drop_target){ + dp.setGanttMode("tasks"); + this.getTask(id).target = item.$drop_target; + dp.setUpdated(id, true,"order"); + delete this.getTask(id).$drop_target; + } + }; + this.attachEvent("onAfterTaskAdd", function(id, item) { + dp.setGanttMode("tasks"); + dp.setUpdated(id,true,"inserted"); + }); + this.attachEvent("onAfterTaskUpdate", function(id, item) { + dp.setGanttMode("tasks"); + dp.setUpdated(id,true); + + gantt._sendTaskOrder(id, item); + }); + this.attachEvent("onAfterTaskDelete", function(id, item) { + dp.setGanttMode("tasks"); + dp.setUpdated(id,true,"deleted"); + + if(dp.updateMode != 'off' && !dp._tSend){ + dp.sendAllData(); + } + + }); + + this.attachEvent("onAfterLinkUpdate", function(id, item) { + dp.setGanttMode("links"); + dp.setUpdated(id, true); + }); + this.attachEvent("onAfterLinkAdd", function(id, item) { + dp.setGanttMode("links"); + dp.setUpdated(id, true,"inserted"); + }); + this.attachEvent("onAfterLinkDelete", function(id, item) { + dp.setGanttMode("links"); + dp.setUpdated(id, true,"deleted"); + }); + this.attachEvent("onRowDragEnd", function(id, target) { + gantt._sendTaskOrder(id, gantt.getTask(id)); + }); + + dp.attachEvent("onBeforeDataSending", function() { + var url = this._serverProcessor; + if(this._tMode == "REST"){ + var mode = this._ganttMode.substr(0, this._ganttMode.length - 1);// links, tasks -> /link/id, /task/id + + url = url.substring(0, url.indexOf("?") > -1 ? url.indexOf("?") : url.length); + //editing=true& + this.serverProcessor = url + (url.slice(-1) == "/" ? "" : "/") + mode; + }else{ + this.serverProcessor = url + window.dhtmlx.url(url) + "gantt_mode=" + this._ganttMode; + } + + return true; + }); + + + var afterUpdate = dp.afterUpdate; + dp.afterUpdate = function(){ + var xml; + if(arguments.length == 3){ + xml = arguments[1]; + }else{ + // old dataprocessor + xml = arguments[4]; + } + var mode = dp._ganttMode; + var reqUrl = xml.filePath; + + if(this._tMode != "REST"){ + if (reqUrl.indexOf("gantt_mode=links") != -1) { + mode = "links"; + }else{ + mode = "tasks"; + } + }else{ + if(reqUrl.indexOf("/link") > reqUrl.indexOf("/task")){ + mode = "links"; + }else{ + mode = "tasks"; + } + } + dp.setGanttMode(mode); + + var res = afterUpdate.apply(dp, arguments); + dp.setGanttMode(mode); + return res; + }; + + dp._getRowData=dhtmlx.bind(function(id, pref) { + var task; + if (dp._ganttMode == "tasks") + task = this.isTaskExists(id) ? this.getTask(id) : { id: id }; + else + task = this.isLinkExists(id) ? this.getLink(id) : { id: id }; + + task = dhtmlx.copy(task); + + var data = {}; + for (var key in task) { + if (key.substr(0, 1) == "$") continue; + var value = task[key]; + if (value instanceof Date) + data[key] = this.templates.xml_format(value); + else if(value === null) + data[key] = ""; + else + data[key] = value; + } + if(task.$no_start){ + task.start_date = ""; + task.duration = ""; + } + if(task.$no_end){ + task.end_date = ""; + task.duration = ""; + } + data[dp.action_param] = this.getUserData(id, dp.action_param); + return data; + }, this); + + this._change_id = dhtmlx.bind(function(oldid, newid) { + if (dp._ganttMode != "tasks") + this.changeLinkId(oldid, newid); + else + this.changeTaskId(oldid, newid); + }, this); + + this._row_style = function(row_id, classname){ + if (dp._ganttMode != "tasks") return; + var el = gantt.getTaskRowNode(row_id); + if (!el) return; + if (!classname) { + var regexp = / (gantt_updated|gantt_inserted|gantt_deleted|gantt_invalid|gantt_error)/g; + el.className = el.className.replace(regexp, ""); + } else + el.className += " " + classname; + }; + + // fake method for dataprocessor + this._delete_task = function(row_id, node){}; + + this._dp = dp; + + gantt._patch_dhx4_ajax(); +}; +*//* +gantt._patch_dhx4_ajax = function(){ + var dhxVersion = parseInt((dhx4.version || "").split(".").join(""), 10); + + // dhtmlx 4.0.0 - 4.1.3 does not provide filePath parameter to callback, patch won't be needed for future versions + if(dhxVersion <= 413){ + window.dhx4.ajax._call = function(method, url, postData, async, onLoad, longParams, headers) { + + var t = (window.XMLHttpRequest && !dhx4.isIE ? new XMLHttpRequest() : new ActiveXObject("Microsoft.XMLHTTP")); + var isQt = (navigator.userAgent.match(/AppleWebKit/) != null && navigator.userAgent.match(/Qt/) != null && navigator.userAgent.match(/Safari/) != null); + + if (async == true) { + t.onreadystatechange = function() { + if ((t.readyState == 4) || (isQt == true && t.readyState == 3)) { // what for long response and status 404? + if (t.status != 200 || t.responseText == "") + if (!dhx4.callEvent("onAjaxError", [t])) return; + + window.setTimeout(function(){ + if (typeof(onLoad) == "function") { + onLoad.apply(window, [{xmlDoc:t, filePath:url}]); // dhtmlx-compat, response.xmlDoc.responseXML/responseText + } + if (longParams != null) { + if (typeof(longParams.postData) != "undefined") { + dhx4.ajax.postLong(longParams.url, longParams.postData, onLoad); + } else { + dhx4.ajax.getLong(longParams.url, onLoad); + } + } + onLoad = null; + t = null; + },1); + } + } + } + + if (method == "GET" && this.cache != true) { + url += (url.indexOf("?")>=0?"&":"?")+"dhxr"+new Date().getTime()+"=1"; + } + + t.open(method, url, async); + + if (headers){ + for (var key in headers) + t.setRequestHeader(key, headers[key]); + } else if (method.toUpperCase() == "POST" || method == "PUT" || method == "DELETE") { + t.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); + } else if (method == "GET") { + postData = null; + } + + t.setRequestHeader("X-Requested-With", "XMLHttpRequest"); + + t.send(postData); + + if (!async) return {xmlDoc:t, filePath:url}; // dhtmlx-compat, response.xmlDoc.responseXML/responseText + + }; + } + + gantt._patch_dhx4_ajax = function(){}; +}; + +gantt.getUserData = function(id, name) { + if (!this.userdata) this.userdata = {}; + if (this.userdata[id] && this.userdata[id][name]) return this.userdata[id][name]; + return ""; +}; +gantt.setUserData = function(id, name, value) { + if (!this.userdata) this.userdata = {}; + if (!this.userdata[id]) this.userdata[id] = {}; + this.userdata[id][name] = value; +}; +*/ + +gantt._init_link = function(link) { + if (!dhtmlx.defined(link.id)) + link.id = dhtmlx.uid(); + return link; +}; + +gantt._sync_links = function() { + for (var id in this._pull) { + this._pull[id].$source = []; + this._pull[id].$target = []; + } + for (var id in this._lpull) { + var link = this._lpull[id]; + if(this._pull[link.source]) + this._pull[link.source].$source.push(id); + if(this._pull[link.target]) + this._pull[link.target].$target.push(id); + } +}; + +gantt.getLink = function(id) { + dhtmlx.assert(this._lpull[id], "Link doesn't exist"); + return this._lpull[id]; +}; + +gantt.getLinks = function(){ + var links = []; + for (var key in gantt._lpull) + links.push(gantt._lpull[key]); + return links; +}; + +gantt.isLinkExists = function(id) { + return dhtmlx.defined(this._lpull[id]); +}; + +gantt.addLink = function(link) { + link = this._init_link(link); + + if (this.callEvent("onBeforeLinkAdd", [link.id, link])===false) return false; + + this._lpull[link.id] = link; + this._sync_links(); + //this._render_link(link.id); + this.refreshLink(link.id); + this.callEvent("onAfterLinkAdd", [link.id, link]); + return link.id; +}; + +gantt.updateLink = function (id, data) { + if (!dhtmlx.defined(data)) + data = this.getLink(id); + + if (this.callEvent("onBeforeLinkUpdate", [id, data]) === false) + return false; + + this._lpull[id] = data; + data.widget.update(data); + this._sync_links(); + //this._render_link(id); + this.refreshLink(id); + this.callEvent("onAfterLinkUpdate", [id, data]); + return true; +}; + +gantt.deleteLink = function(id) { + return this._deleteLink(id); +}; + +gantt._deleteLink = function(id, silent) { + var link = this.getLink(id); + if (!silent && this.callEvent("onBeforeLinkDelete", [id, link])===false) return false; + + delete this._lpull[id]; + this._sync_links(); + this.refreshLink(id); + if (!silent) this.callEvent("onAfterLinkDelete", [id, link]); + return true; +}; + +//gantt.changeLinkId = function(oldid, newid) { +// this._lpull[newid] = this._lpull[oldid]; +// this._lpull[newid].id = newid; +// delete this._lpull[oldid]; +// +// this._sync_links(); +// this.callEvent("onLinkIdChange", [oldid, newid]); +//}; + + +gantt.getChildren = function(id) { + return dhtmlx.defined(this._branches[id]) ? this._branches[id] : []; +}; +gantt.hasChild = function(id) { + return (dhtmlx.defined(this._branches[id]) && this._branches[id].length); +}; + + +gantt.refreshData = function(){ + this.refresher.renderData(); + //this._render_data(); +}; + + +gantt._configure = function(col, data, force){ + for (var key in data) + if (typeof col[key] == "undefined" || force) + col[key] = data[key]; +}; +gantt._init_skin = function(){ + gantt._get_skin(false); + gantt._init_skin = function(){}; +}; +gantt._get_skin = function(force){ + if (!gantt.skin || force){ + var links = document.getElementsByTagName("link"); + for (var i = 0; i < links.length; i++) { + var res = links[i].href.match("dhtmlxgantt_([a-z]+).css"); + if (res){ + gantt.skin = res[1]; + break; + } + } + } + + if (!gantt.skin) gantt.skin = "terrace"; + var skinset = gantt.skins[gantt.skin]; + + //apply skin related settings + this._configure(gantt.config, skinset.config, force); + + var config = gantt.getGridColumns(); + if (config[1] && typeof config[1].width == "undefined") + config[1].width = skinset._second_column_width; + if (config[2] && typeof config[2].width == "undefined") + config[2].width = skinset._third_column_width; + + if (skinset._lightbox_template) + gantt._lightbox_template = skinset._lightbox_template; + + //gantt.resetLightbox(); //HOSEK +}; +gantt.resetSkin = function(){ + this.skin = ""; + this._get_skin(true); +}; +gantt.skins = {}; + +/* +gantt._lightbox_methods = {}; +gantt._lightbox_template="
 
"; + +gantt.showLightbox=function(id){ + if (!id || gantt._is_readonly(this.getTask(id))) return; + if (!this.callEvent("onBeforeLightbox",[id])) return; + + var task = this.getTask(id); + + var box = this.getLightbox(this._get_safe_type(task.type)); + this._center_lightbox(box); + this.showCover(); + this._fill_lightbox(id,box); + this.callEvent("onLightbox",[id]); +}; +gantt._get_timepicker_step = function(){ + if(this.config.round_dnd_dates){ + var scale = gantt._tasks, + step = (this._get_line(scale.unit) * scale.step)/60;//timepicker step is measured in minutes + if(step >= 60*24 || !this._is_chart_visible()){ + step = this.config.time_step; + } + return step; + } + return this.config.time_step; +}; +gantt.getLabel = function(property, key) { + var sections = this._get_typed_lightbox_config(); + for (var i=0; i
"+this.locale.labels[button]+"
"; + + } + buttons = this.config.buttons_right; + for (var i = 0; i < buttons.length; i++){ + var button = this.config._migrate_buttons[buttons[i]] ? this.config._migrate_buttons[buttons[i]] : buttons[i]; + html+="
"+this.locale.labels[button]+"
"; + + } + html+=""; + d.innerHTML=html; + + if (gantt.config.drag_lightbox){ + d.firstChild.onmousedown = gantt._ready_to_dnd; + d.firstChild.onselectstart = function(){ return false; }; + d.firstChild.style.cursor = "pointer"; + gantt._init_dnd_events(); + + } + + document.body.insertBefore(d,document.body.firstChild); + this._lightbox=d; + + var sns = this._get_typed_lightbox_config(type); + html = this._render_sections(sns); + + var ds=d.getElementsByTagName("div"); + for (var i=0; i
"+this.locale.labels["button_"+sns[i].button]+"
"; + } + if (this.config.wide_form){ + html+="
"; + } + html+="
"+button+this.locale.labels["section_"+sns[i].name]+"
"+block.render.call(this,sns[i]); + html+="
"; + } + return html; +}; + + +gantt.resizeLightbox=function(){ + var d = this._lightbox; + if (!d) return; + + var con = d.childNodes[1]; + con.style.height="0px"; + con.style.height=con.scrollHeight+"px"; + d.style.height=con.scrollHeight+this.config.lightbox_additional_height+"px"; + con.style.height=con.scrollHeight+"px"; //it is incredible , how ugly IE can be + + +}; + +gantt._center_lightbox = function(box) { + if (box){ + box.style.display="block"; + + var scroll_top = window.pageYOffset||document.body.scrollTop||document.documentElement.scrollTop; + var scroll_left = window.pageXOffset||document.body.scrollLeft||document.documentElement.scrollLeft; + + var view_height = window.innerHeight||document.documentElement.clientHeight; + + if(scroll_top) // if vertical scroll on window + box.style.top=Math.round(scroll_top+Math.max((view_height-box.offsetHeight)/2, 0))+"px"; + else // vertical scroll on body + box.style.top=Math.round(Math.max(((view_height-box.offsetHeight)/2), 0) + 9)+"px"; // +9 for compatibility with auto tests + + // not quite accurate but used for compatibility reasons + if(document.documentElement.scrollWidth > document.body.offsetWidth) // if horizontal scroll on the window + box.style.left=Math.round(scroll_left+(document.body.offsetWidth-box.offsetWidth)/2)+"px"; + else // horizontal scroll on the body + box.style.left=Math.round((document.body.offsetWidth-box.offsetWidth)/2)+"px"; + } +}; +gantt.showCover = function(){ + if(this._cover) return; + + this._cover=document.createElement("DIV"); + this._cover.className="gantt_cal_cover"; + var _document_height = ((document.height !== undefined) ? document.height : document.body.offsetHeight); + var _scroll_height = ((document.documentElement) ? document.documentElement.scrollHeight : 0); + this._cover.style.height = Math.max(_document_height, _scroll_height) + 'px'; + document.body.appendChild(this._cover); +}; + + +gantt._init_lightbox_events = function(){ + gantt.lightbox_events = {}; + + + gantt.lightbox_events["gantt_save_btn"] = function(e) { + gantt._save_lightbox(); + }; + + + gantt.lightbox_events["gantt_delete_btn"] = function(e) { + if(!gantt.callEvent("onLightboxDelete", [gantt._lightbox_id])) + return; + + if(gantt.isTaskExists(gantt._lightbox_id)){ + gantt.$click.buttons["delete"](gantt._lightbox_id); + }else{ + gantt.hideLightbox(); + } + + }; + + + gantt.lightbox_events["gantt_cancel_btn"] = function(e) { + gantt._cancel_lightbox(); + }; + + + gantt.lightbox_events["default"] = function(e, src) { + if (src.getAttribute("dhx_button")) { + gantt.callEvent("onLightboxButton", [src.className, src, e]); + } else { + var index, block, sec; + if (src.className.indexOf("gantt_custom_button") != -1) { + if (src.className.indexOf("gantt_custom_button_") != -1) { + index = src.parentNode.getAttribute("index"); + sec = src.parentNode.parentNode; + } else { + index = src.getAttribute("index"); + sec = src.parentNode; + src = src.firstChild; + } + } + + var sections = gantt._get_typed_lightbox_config(); + + if (index) { + block = gantt.form_blocks[sections[index].type]; + block.button_click(index, src, sec, sec.nextSibling); + } + } + }; + dhtmlxEvent(gantt.getLightbox(), "click", function(e) { + e = e || window.event; + var src = e.target ? e.target : e.srcElement; + + if (!src.className) + src = src.previousSibling; + if (src && src.className && src.className.indexOf("gantt_btn_set") === 0) + src = src.firstChild; + if (src && src.className) { + var func = dhtmlx.defined(gantt.lightbox_events[src.className]) ? gantt.lightbox_events[src.className] : gantt.lightbox_events["default"]; + return func(e, src); + } + return false; + }); + + gantt.getLightbox().onkeydown=function(e){ + switch((e||event).keyCode){ + case gantt.keys.edit_save: + if ((e||event).shiftKey) return; + gantt._save_lightbox(); + break; + case gantt.keys.edit_cancel: + gantt._cancel_lightbox(); + break; + default: + break; + } + }; +}; + +gantt._cancel_lightbox=function(){ + var task = this.getLightboxValues(); + this.callEvent("onLightboxCancel",[this._lightbox_id, task.$new]); + if(gantt.isTaskExists(task.id) && task.$new){ + this._deleteTask(task.id, true); + } + + this.refreshData(); + this.hideLightbox(); +}; + +gantt._save_lightbox=function(){ + var task = this.getLightboxValues(); + if(!this.callEvent("onLightboxSave", [this._lightbox_id, task, !!task.$new])) + return; + + if (task.$new){ + delete task.$new; + this.addTask(task); + }else if(this.isTaskExists(task.id)){ + dhtmlx.mixin(this.getTask(task.id), task, true); + this.updateTask(task.id); + } + this.refreshData(); + + // TODO: do we need any blockable events here to prevent closing lightbox? + this.hideLightbox(); +}; + +gantt._resolve_default_mapping = function(section) { + var mapping = section.map_to; + var time_controls = {"time":true, "duration":true}; + if(time_controls[section.type]){ + if(section.map_to == 'auto'){ + mapping = {start_date: "start_date", end_date: "end_date", duration: "duration"}; + }else if(typeof(section.map_to) === "string"){ + mapping = {start_date: section.map_to}; + } + } + + return mapping; +}; + +gantt.getLightboxValues=function(){ + var task = {}; + + if(gantt.isTaskExists(this._lightbox_id)) { + task = dhtmlx.mixin({}, this.getTask(this._lightbox_id)); + } + + var sns = this._get_typed_lightbox_config(); + for (var i=0; i < sns.length; i++) { + var node = document.getElementById(sns[i].id); + node=(node?node.nextSibling:node); + var block=this.form_blocks[sns[i].type]; + if(!block) continue; + var res=block.get_value.call(this,node,task, sns[i]); + var map_to = gantt._resolve_default_mapping(sns[i]); + if (typeof map_to == "string" && map_to != "auto") { + task[map_to] = res; + } else if(typeof map_to == "object") { + for(var property in map_to) { + if(map_to[property]) + task[map_to[property]] = res[property]; + } + } + } + return task; +}; + + +gantt.hideLightbox=function(){ + var box = this.getLightbox(); + if (box) box.style.display="none"; + this._lightbox_id=null; + + this.hideCover(); + this.callEvent("onAfterLightbox",[]); +}; +gantt.hideCover=function(){ + if (this._cover) + this._cover.parentNode.removeChild(this._cover); + this._cover=null; +}; + +gantt.resetLightbox = function(){ + if (gantt._lightbox && !gantt._custom_lightbox) + gantt._lightbox.parentNode.removeChild(gantt._lightbox); + gantt._lightbox = null; +}; +gantt._set_lightbox_values = function(data, box){ + var task = data; + var s = box.getElementsByTagName("span"); + if (gantt.templates.lightbox_header) { + s[1].innerHTML = ""; + s[2].innerHTML = gantt.templates.lightbox_header(task.start_date, task.end_date, task); + } else { + s[1].innerHTML = this.templates.task_time(task.start_date, task.end_date, task); + s[2].innerHTML = (this.templates.task_text(task.start_date, task.end_date, task) || "").substr(0, 70); //IE6 fix + } + + + var sns = this._get_typed_lightbox_config(this.getLightboxType()); + for (var i = 0; i < sns.length; i++) { + var section = sns[i]; + + if(!this.form_blocks[section.type]){ + continue;//skip incorrect sections, same check is done during rendering + } + + + var node = document.getElementById(section.id).nextSibling; + var block = this.form_blocks[section.type]; + var map_to = gantt._resolve_default_mapping(sns[i]); + var value = dhtmlx.defined(task[map_to]) ? task[map_to] : section.default_value; + block.set_value.call(gantt, node, value, task, section); + + if (section.focus) + block.focus.call(gantt, node); + } + if(data.id) + gantt._lightbox_id = data.id; +}; +gantt._fill_lightbox = function(id, box) { + var task = this.getTask(id); + this._set_lightbox_values(task, box); +}; + + +gantt.getLightboxSection = function(name){ + var config = this._get_typed_lightbox_config(); + var i =0; + for (i; i < config.length; i++) + if (config[i].name == name) + break; + var section = config[i]; + if(!section) + return null; + + if (!this._lightbox) + this.getLightbox(); + var header = document.getElementById(section.id); + var node = header.nextSibling; + + var result = { + section: section, + header: header, + node: node, + getValue:function(ev){ + return gantt.form_blocks[section.type].get_value.call(gantt, node, (ev||{}), section); + }, + setValue:function(value, ev){ + return gantt.form_blocks[section.type].set_value.call(gantt, node, value, (ev||{}), section); + } + }; + + var handler = this._lightbox_methods["get_"+section.type+"_control"]; + return handler?handler(result):result; +}; + +gantt._lightbox_methods.get_template_control = function(result) { + result.control = result.node; + return result; +}; +gantt._lightbox_methods.get_select_control = function(result) { + result.control = result.node.getElementsByTagName('select')[0]; + return result; +}; +gantt._lightbox_methods.get_textarea_control = function(result) { + result.control = result.node.getElementsByTagName('textarea')[0]; + return result; +}; +gantt._lightbox_methods.get_time_control = function(result) { + result.control = result.node.getElementsByTagName('select'); // array + return result; +}; + + + +*/ + +gantt._init_dnd_events = function(){ + dhtmlxEvent(document.body, "mousemove", gantt._move_while_dnd); + dhtmlxEvent(document.body, "mouseup", gantt._finish_dnd); + gantt._init_dnd_events = function(){}; +}; +gantt._move_while_dnd = function(e){ + if (gantt._dnd_start_lb){ + if (!document.gantt_unselectable){ + document.body.className += " gantt_unselectable"; + document.gantt_unselectable = true; + } + var lb = gantt.getLightbox(); + var now = (e&&e.target)?[e.pageX, e.pageY]:[event.clientX, event.clientY]; + lb.style.top = gantt._lb_start[1]+now[1]-gantt._dnd_start_lb[1]+"px"; + lb.style.left = gantt._lb_start[0]+now[0]-gantt._dnd_start_lb[0]+"px"; + } +}; +gantt._ready_to_dnd = function(e){ + var lb = gantt.getLightbox(); + gantt._lb_start = [parseInt(lb.style.left,10), parseInt(lb.style.top,10)]; + gantt._dnd_start_lb = (e&&e.target)?[e.pageX, e.pageY]:[event.clientX, event.clientY]; +}; +gantt._finish_dnd = function(){ + if (gantt._lb_start){ + gantt._lb_start = gantt._dnd_start_lb = false; + document.body.className = document.body.className.replace(" gantt_unselectable",""); + document.gantt_unselectable = false; + } +}; + + + + +gantt._focus = function(node, select){ + if (node && node.focus){ + if (gantt.config.touch){ + //do not focus editor, to prevent auto-zoom + } else { + try { + if (select && node.select) node.select(); + node.focus(); + }catch(e){ } + } + } +}; + + +/*gantt.form_blocks={ + getTimePicker: function(sns, hidden) { + var time_format = sns.time_format; + if (!time_format) { + // default order + var time_format = ["%d", "%m", "%Y"]; + if(gantt._get_line(gantt._tasks.unit) < gantt._get_line("day")){ + time_format.push("%H:%i"); + } + } + // map: default order => real one + sns._time_format_order = { size:0 }; + + + var cfg = this.config; + var dt = this.date.date_part(gantt.date.Date(gantt._min_date)); + var last = 24*60, first = 0; + if(gantt.config.limit_time_select){ + last = 60*cfg.last_hour+1; + first = 60*cfg.first_hour; + dt.setHours(cfg.first_hour); + } + var html = ""; + + for (var p = 0; p < time_format.length; p++) { + var time_option = time_format[p]; + + // adding spaces between selects + if (p > 0) { + html += " "; + } + + var options = ''; + switch (time_option) { + case "%Y": + sns._time_format_order[2] = p; + sns._time_format_order.size++; + //year + + var range, offset, start_year, end_year; + + if(sns.year_range){ + if(!isNaN(sns.year_range)){ + range = sns.year_range; + }else if(sns.year_range.push){ + // if + start_year = sns.year_range[0]; + end_year = sns.year_range[1]; + } + } + + range = range || 10; + offset = offset || Math.floor(range/2); + start_year = start_year || dt.getFullYear() - offset; + end_year = end_year || start_year + range; + + + for (var i=start_year; i < end_year; i++) + options+=""; + break; + case "%m": + sns._time_format_order[1] = p; + sns._time_format_order.size++; + //month + for (var i=0; i < 12; i++) + options+=""; + break; + case "%d": + sns._time_format_order[0] = p; + sns._time_format_order.size++; + //days + for (var i=1; i < 32; i++) + options+=""; + break; + case "%H:%i": + // var last = 24*60, first = 0; + sns._time_format_order[3] = p; + sns._time_format_order.size++; + //hours + var i = first; + var tdate = dt.getDate(); + sns._time_values = []; + + while(i"+time+""; + sns._time_values.push(i); + dt.setTime(dt.valueOf()+this._get_timepicker_step()*60*1000); + var diff = (dt.getDate()!=tdate)?1:0; // moved or not to the next day + i=diff*24*60+dt.getHours()*60+dt.getMinutes(); + } + break; + default: + break; + } + + if(options){ + var readonly = sns.readonly ? "disabled='disabled'" : ""; + var display = hidden ? " style='display:none'" : ""; + html += ""; + } + } + return html; + }, + _fill_lightbox_select: function (s,i,d,map,cfg) { + s[i+map[0]].value=d.getDate(); + s[i+map[1]].value=d.getMonth(); + s[i+map[2]].value=d.getFullYear(); + if (dhtmlx.defined(map[3])) { + var v = d.getHours()*60+ d.getMinutes(); + v = Math.round(v/gantt._get_timepicker_step())*gantt._get_timepicker_step(); + var input = s[i+map[3]]; + input.value= v; + //in case option not shown + input.setAttribute('data-value', v); + } + }, + template:{ + render: function(sns){ + var height=(sns.height||"30")+"px"; + return "
"; + }, + set_value:function(node,value,ev,config){ + node.innerHTML = value||""; + }, + get_value:function(node,ev,config){ + return node.innerHTML||""; + }, + focus: function(node){ + } + }, + textarea:{ + render:function(sns){ + var height=(sns.height||"130")+"px"; + return "
"; + }, + set_value:function(node,value,ev){ + node.firstChild.value=value||""; + }, + get_value:function(node,ev){ + return node.firstChild.value; + }, + focus:function(node){ + var a=node.firstChild; gantt._focus(a, true); + } + }, + select:{ + render:function(sns){ + var height=(sns.height||"23")+"px"; + var html="
"; + return html; + }, + set_value:function(node,value,ev,sns){ + var select = node.firstChild; + if (!select._dhx_onchange && sns.onchange) { + select.onchange = sns.onchange; + select._dhx_onchange = true; + } + if (typeof value == "undefined") + value = (select.options[0]||{}).value; + select.value=value||""; + }, + get_value:function(node,ev){ + return node.firstChild.value; + }, + focus:function(node){ + var a=node.firstChild; gantt._focus(a, true); + } + }, + time:{ + render:function(sns) { + var time = this.form_blocks.getTimePicker.call(this, sns); + var parts = ["
"]; + parts.push(time); + + if(sns.single_date){ + time = this.form_blocks.getTimePicker.call(this, sns, true); + parts.push(""); + }else{ + parts.push("  –  "); + } + + parts.push(time); + parts.push("
"); + return parts.join(''); + }, + set_value:function(node,value,ev,config){ + var cfg = config; + var s=node.getElementsByTagName("select"); + + var map = config._time_format_order; + var map_size = config._time_format_size; + + if(cfg.auto_end_date) { + var _update_lightbox_select = function() { + start_date = new Date(s[map[2]].value,s[map[1]].value,s[map[0]].value,0,0); + end_date = gantt.calculateEndDate(start_date, 1); + this.form_blocks._fill_lightbox_select(s,map.size, end_date,map,cfg); + }; + for(var i=0; i<4; i++) { + s[i].onchange = _update_lightbox_select; + } + } + + var mapping = gantt._resolve_default_mapping(config); + + if(typeof(mapping) === "string") mapping = {start_date: mapping}; + + var start_date = ev[mapping.start_date] || new Date(); + var end_date = ev[mapping.end_date] || gantt.calculateEndDate(start_date, 1); + + this.form_blocks._fill_lightbox_select(s,0,start_date,map,cfg); + this.form_blocks._fill_lightbox_select(s,map.size,end_date,map,cfg); + }, + + get_value:function(node, ev, config) { + var s=node.getElementsByTagName("select"); + var map = config._time_format_order; + + var hours = 0, minutes = 0; + if (dhtmlx.defined(map[3])) { + var time = parseInt(s[map[3]].value, 10); + hours = Math.floor(time/60); + minutes = time%60; + } + var start_date=new Date(s[map[2]].value,s[map[1]].value,s[map[0]].value,hours,minutes); + + hours = minutes = 0; + if (dhtmlx.defined(map[3])) { + var time = parseInt(s[map.size+map[3]].value, 10); + hours = Math.floor(time/60); + minutes = time%60; + } + var end_date=new Date(s[map[2]+map.size].value,s[map[1]+map.size].value,s[map[0]+map.size].value,hours,minutes); + + if (end_date <= start_date) + end_date = gantt.date.add(start_date, gantt._get_timepicker_step(),"minute"); + + var mapped_fields = gantt._resolve_default_mapping(config); + + var res = { + start_date: new Date(start_date), + end_date: new Date(end_date) + }; + if(typeof mapped_fields == "string"){ + return res.start_date; + }else{ + return res; + } + }, + focus:function(node){ + gantt._focus(node.getElementsByTagName("select")[0]); + } + }, + duration:{ + render:function(sns) { + var time = this.form_blocks.getTimePicker.call(this, sns); + time = "
"+time+"
"; + var label = this.locale.labels[this.config.duration_unit + "s"]; + + var singleDate = sns.single_date ? ' style="display:none"' : ""; + var readonly = sns.readonly ? " disabled='disabled'" : ""; + + var duration = "
" + + "" + + "" + + " " + label + " " + + "
"; + var html = "
"+time+" "+duration+"
"; + return html; + }, + set_value:function(node,value,ev,config){ + var cfg = config; + var s=node.getElementsByTagName("select"); + var inps = node.getElementsByTagName("input"); + + var duration = inps[1]; + var btns=[inps[0],inps[2]]; + var endspan = node.getElementsByTagName("span")[0]; + + var map = config._time_format_order; + + function _calc_date() { + var start_date = gantt.form_blocks.duration._get_start_date.call(gantt, node ,config); + var duration = gantt.form_blocks.duration._get_duration.call(gantt, node ,config); + var end_date = gantt.calculateEndDate(start_date, duration); + + endspan.innerHTML = gantt.templates.task_date(end_date); + } + + function _change_duration(step) { + var value = duration.value; + value = parseInt(value, 10); + if (window.isNaN(value)) + value = 0; + value+=step; + if (value < 1) value = 1; + duration.value = value; + _calc_date(); + } + + btns[0].onclick = dhtmlx.bind(function() { _change_duration(-1*this.config.duration_step); }, this); + btns[1].onclick = dhtmlx.bind(function() { _change_duration(1*this.config.duration_step); }, this); + s[0].onchange = _calc_date; + s[1].onchange = _calc_date; + s[2].onchange = _calc_date; + if (s[3]) s[3].onchange = _calc_date; + duration.onkeydown = dhtmlx.bind(function(e) { + e = e || window.event; + // up + var code = (e.charCode || e.keyCode || e.which); + + if (code == 40) { + _change_duration(-1*this.config.duration_step); + return false; + } + // down + if (code == 38) { + _change_duration(1*this.config.duration_step); + return false; + } + window.setTimeout(function(e) { + _calc_date(); + }, 1); + }, this); + + duration.onchange = dhtmlx.bind(function(e) { _calc_date(); }, this); + + var mapping = gantt._resolve_default_mapping(config); + if(typeof(mapping) === "string") mapping = {start_date: mapping}; + + var start_date = ev[mapping.start_date] || new Date(); + var end_date = ev[mapping.end_date] || gantt.calculateEndDate(start_date, 1); + var duration_val = Math.round(ev[mapping.duration]) || gantt.calculateDuration(start_date, end_date); + + gantt.form_blocks._fill_lightbox_select(s, 0, start_date, map, cfg); + duration.value = duration_val; + _calc_date(); + }, + + _get_start_date: function(node, config) { + var s=node.getElementsByTagName("select"); + var map = config._time_format_order; + var hours = 0; + var minutes = 0; + if (dhtmlx.defined(map[3])) { + var input = s[map[3]]; + var time = parseInt(input.value, 10); + if(isNaN(time) && input.hasAttribute("data-value")){ + time = parseInt(input.getAttribute("data-value"), 10); + } + + hours = Math.floor(time/60); + minutes = time%60; + } + return new Date(s[map[2]].value,s[map[1]].value,s[map[0]].value,hours,minutes); + }, + _get_duration: function(node, config) { + var duration = node.getElementsByTagName("input")[1]; + duration = parseInt(duration.value, 10); + if (!duration || window.isNaN(duration)) duration = 1; + if (duration < 0) duration *= -1; + return duration; + }, + + get_value:function(node, ev, config) { + var start_date = gantt.form_blocks.duration._get_start_date(node, config); + var duration = gantt.form_blocks.duration._get_duration(node, config); + + var end_date = gantt.calculateEndDate(start_date, duration); + var mapped_fields = gantt._resolve_default_mapping(config); + var res = { + start_date: new Date(start_date), + end_date: new Date(end_date), + duration: duration + }; + if(typeof mapped_fields == "string"){ + return res.start_date; + }else{ + return res; + } + }, + focus:function(node){ + gantt._focus(node.getElementsByTagName("select")[0]); + } + }, + parent: { + _filter : function(options, config, item_id){ + var filter = config.filter || function(){ return true;}; + + options = options.slice(0); + + for(var i=0; i < options.length; i++){ + var task = options[i]; + if(task.id == item_id || gantt.isChildOf(task.id, item_id) || filter(task.id, task) === false){ + options.splice(i, 1); + i--; + } + } + return options; + }, + + _display : function(config, item_id){ + var tasks = [], + options = []; + if(item_id){ + tasks = gantt.getTaskByTime(); + if(config.allow_root){ + tasks.unshift({id:gantt.config.root_id, text:config.root_label || ""}); + } + tasks = this._filter(tasks, config, item_id); + if(config.sort){ + tasks.sort(config.sort); + } + } + var text = config.template || gantt.templates.task_text; + for(var i = 0; i < tasks.length; i++){ + var label = text.apply(gantt, [tasks[i].start_date, tasks[i].end_date, tasks[i]]); + if(label === undefined){ + label = ""; + } + options.push({ + key: tasks[i].id, + label: label + }); + } + config.options = options; + config.map_to = config.map_to || "parent"; + return gantt.form_blocks.select.render.apply(this, arguments); + }, + render : function(sns){ + return gantt.form_blocks.parent._display(sns, false); + }, + set_value:function(node,value,ev,config){ + var tmpDom = document.createElement("div"); + tmpDom.innerHTML = gantt.form_blocks.parent._display(config, ev.id); + var newOptions = tmpDom.removeChild(tmpDom.firstChild); + node.onselect = null; + node.parentNode.replaceChild(newOptions, node); + + return gantt.form_blocks.select.set_value.apply(gantt, [newOptions,value,ev,config]); + }, + get_value:function(){ + return gantt.form_blocks.select.get_value.apply(gantt, arguments); + }, + focus:function(){ + return gantt.form_blocks.select.focus.apply(gantt, arguments); + } + } +}; + +gantt._is_lightbox_timepicker = function() { + var s = this._get_typed_lightbox_config(); + for (var i = 0; i < s.length; i++) + if (s[i].name == "time" && s[i].type == "time") + return true; + return false; +}; + +gantt._dhtmlx_confirm = function(message, title, callback, ok) { + if (!message) + return callback(); + var opts = { text: message }; + if (title) + opts.title = title; + if(ok){ + opts.ok = ok; + } + if (callback) { + opts.callback = function(result) { + if (result) + callback(); + }; + } + dhtmlx.confirm(opts); +}; + +gantt._get_typed_lightbox_config = function(type){ + if(type === undefined){ + type = this.getLightboxType(); + } + + var field = this._get_type_name(type); + + if(gantt.config.lightbox[field+"_sections"]){ + return gantt.config.lightbox[field+"_sections"]; + }else{ + return gantt.config.lightbox.sections; + } +}; + +gantt._silent_redraw_lightbox = function(type){ + var oldType = this.getLightboxType(); + + if(this.getState().lightbox){ + var taskId = this.getState().lightbox; + var formData = this.getLightboxValues(), + task = dhtmlx.copy(this.getTask(taskId)); + + this.resetLightbox(); + + var updTask = dhtmlx.mixin(task, formData, true); + var box = this.getLightbox(type ? type : undefined); + this._center_lightbox(this.getLightbox()); + this._set_lightbox_values(updTask, box); + }else{ + this.resetLightbox(); + this.getLightbox(type ? type : undefined); + } + this.callEvent("onLightboxChange", [oldType, this.getLightboxType()]); +}; +gantt._extend_to_optional = function(lightbox_block){ + + var duration = lightbox_block; + var optional_time = { + render : duration.render, + focus : duration.focus, + set_value: function (node, value, task, section){ + var mapping = gantt._resolve_default_mapping(section); + if(!task[mapping.start_date]){ + optional_time.disable(node, section); + var val = {}; + + for(var i in mapping){ + //take default values from the time control from task start/end dates + val[mapping[i]] = task[i]; + } + + return duration.set_value.call(gantt, node, value, val, section);//set default value + }else{ + optional_time.enable(node, section); + return duration.set_value.call(gantt, node, value, task, section); + } + }, + get_value: function (node, task, section){ + if(section.disabled){ + return {start_date: null}; + }else{ + return duration.get_value.call(gantt, node, task, section); + } + }, + update_block : function(node, section){ + gantt.callEvent("onSectionToggle", [gantt._lightbox_id, section]); + node.style.display = section.disabled ? "none" : "block"; + + if(section.button){ + var button = node.previousSibling.firstChild.firstChild, + labels = gantt.locale.labels; + + var button_text = section.disabled ? labels[section.name + "_enable_button"] : labels[section.name + "_disable_button"]; + + button.nextSibling.innerHTML = button_text; + } + gantt.resizeLightbox(); + }, + disable: function(node, section){ + section.disabled = true; + optional_time.update_block(node, section); + + }, + enable:function(node, section){ + section.disabled = false; + optional_time.update_block(node, section); + }, + button_click: function(index, el, section, container){ + if(gantt.callEvent("onSectionButton", [gantt._lightbox_id, section]) === false){ + return; + } + var config = gantt._get_typed_lightbox_config()[index]; + if(config.disabled){ + optional_time.enable(container, config); + }else{ + optional_time.disable(container, config); + } + } + }; + return optional_time; +}; + +gantt.form_blocks.duration_optional = gantt._extend_to_optional(gantt.form_blocks.duration); +gantt.form_blocks.time_optional = gantt._extend_to_optional(gantt.form_blocks.time); +*/ +/** + * @desc: constructor, data processor object + * @param: serverProcessorURL - url used for update + * @type: public + */ +function dataProcessor(serverProcessorURL){ + this.serverProcessor = serverProcessorURL; + this.action_param="!nativeeditor_status"; + + this.object = null; + this.updatedRows = []; //ids of updated rows + + this.autoUpdate = true; + this.updateMode = "cell"; + this._tMode="GET"; + this._headers = null; + this._payload = null; + this.post_delim = "_"; + + this._waitMode=0; + this._in_progress={};//? + this._invalid={}; + this.mandatoryFields=[]; + this.messages=[]; + + this.styles={ + updated:"font-weight:bold;", + inserted:"font-weight:bold;", + deleted:"text-decoration : line-through;", + invalid:"background-color:FFE0E0;", + invalid_cell:"border-bottom:2px solid red;", + error:"color:red;", + clear:"font-weight:normal;text-decoration:none;" + }; + + this.enableUTFencoding(true); + dhx4._eventable(this); + + return this; + } + +dataProcessor.prototype={ + setTransactionMode:function(mode,total){ + if (typeof mode == "object"){ + this._tMode = mode.mode || this._tMode; + this._headers = this._headers || mode.headers; + this._payload = this._payload || mode.payload; + } else { + this._tMode=mode; + this._tSend=total; + } + + if (this._tMode == "REST"){ + this._tSend = false; + this._endnm = true; + } + }, + escape:function(data){ + if (this._utf) + return encodeURIComponent(data); + else + return escape(data); + }, + /** + * @desc: allows to set escaping mode + * @param: true - utf based escaping, simple - use current page encoding + * @type: public + */ + enableUTFencoding:function(mode){ + this._utf=dhx4.s2b(mode); + }, + /** + * @desc: allows to define, which column may trigger update + * @param: val - array or list of true/false values + * @type: public + */ + setDataColumns:function(val){ + this._columns=(typeof val == "string")?val.split(","):val; + }, + /** + * @desc: get state of updating + * @returns: true - all in sync with server, false - some items not updated yet. + * @type: public + */ + getSyncState:function(){ + return !this.updatedRows.length; + }, + /** + * @desc: enable/disable named field for data syncing, will use column ids for grid + * @param: mode - true/false + * @type: public + */ + enableDataNames:function(mode){ + this._endnm=dhx4.s2b(mode); + }, + /** + * @desc: enable/disable mode , when only changed fields and row id send to the server side, instead of all fields in default mode + * @param: mode - true/false + * @type: public + */ + enablePartialDataSend:function(mode){ + this._changed=dhx4.s2b(mode); + }, + /** + * @desc: set if rows should be send to server automaticaly + * @param: mode - "row" - based on row selection changed, "cell" - based on cell editing finished, "off" - manual data sending + * @type: public + */ + setUpdateMode:function(mode,dnd){ + this.autoUpdate = (mode=="cell"); + this.updateMode = mode; + this.dnd=dnd; + }, + ignore:function(code,master){ + this._silent_mode=true; + code.call(master||window); + this._silent_mode=false; + }, + /** + * @desc: mark row as updated/normal. check mandatory fields,initiate autoupdate (if turned on) + * @param: rowId - id of row to set update-status for + * @param: state - true for "updated", false for "not updated" + * @param: mode - update mode name + * @type: public + */ + setUpdated:function(rowId,state,mode){ + if (this._silent_mode) return; + var ind=this.findRow(rowId); + + mode=mode||"updated"; + var existing = this.obj.getUserData(rowId,this.action_param); + if (existing && mode == "updated") mode=existing; + if (state){ + this.set_invalid(rowId,false); //clear previous error flag + this.updatedRows[ind]=rowId; + this.obj.setUserData(rowId,this.action_param,mode); + if (this._in_progress[rowId]) + this._in_progress[rowId]="wait"; + } else{ + if (!this.is_invalid(rowId)){ + this.updatedRows.splice(ind,1); + this.obj.setUserData(rowId,this.action_param,""); + } + } + + //clear changed flag + if (!state) + this._clearUpdateFlag(rowId); + + this.markRow(rowId,state,mode); + if (state && this.autoUpdate) this.sendData(rowId); + }, + _clearUpdateFlag:function(id){}, + markRow:function(id,state,mode){ + var str=""; + var invalid=this.is_invalid(id); + if (invalid){ + str=this.styles[invalid]; + state=true; + } + if (this.callEvent("onRowMark",[id,state,mode,invalid])){ + //default logic + str=this.styles[state?mode:"clear"]+str; + + this.obj[this._methods[0]](id,str); + + if (invalid && invalid.details){ + str+=this.styles[invalid+"_cell"]; + for (var i=0; i < invalid.details.length; i++) + if (invalid.details[i]) + this.obj[this._methods[1]](id,i,str); + } + } + }, + getState:function(id){ + return this.obj.getUserData(id,this.action_param); + }, + is_invalid:function(id){ + return this._invalid[id]; + }, + set_invalid:function(id,mode,details){ + if (details) mode={value:mode, details:details, toString:function(){ return this.value.toString(); }}; + this._invalid[id]=mode; + }, + /** + * @desc: check mandatory fields and varify values of cells, initiate update (if specified) + * @param: rowId - id of row to set update-status for + * @type: public + */ + checkBeforeUpdate:function(rowId){ + return true; + }, + /** + * @desc: send row(s) values to server + * @param: rowId - id of row which data to send. If not specified, then all "updated" rows will be send + * @type: public + */ + sendData:function(rowId){ + if (this._waitMode && (this.obj.mytype=="tree" || this.obj._h2)) return; + if (this.obj.editStop) this.obj.editStop(); + + + if(typeof rowId == "undefined" || this._tSend) return this.sendAllData(); + if (this._in_progress[rowId]) return false; + + this.messages=[]; + if (!this.checkBeforeUpdate(rowId) && this.callEvent("onValidationError",[rowId,this.messages])) return false; + this._beforeSendData(this._getRowData(rowId),rowId); + }, + _beforeSendData:function(data,rowId){ + if (!this.callEvent("onBeforeUpdate",[rowId,this.getState(rowId),data])) return false; + this._sendData(data,rowId); + }, + serialize:function(data, id){ + if (typeof data == "string") + return data; + if (typeof id != "undefined") + return this.serialize_one(data,""); + else{ + var stack = []; + var keys = []; + for (var key in data) + if (data.hasOwnProperty(key)){ + stack.push(this.serialize_one(data[key],key+this.post_delim)); + keys.push(key); + } + stack.push("ids="+this.escape(keys.join(","))); + if (dhtmlx.security_key) + stack.push("dhx_security="+dhtmlx.security_key); + return stack.join("&"); + } + }, + serialize_one:function(data, pref){ + if (typeof data == "string") + return data; + var stack = []; + for (var key in data) + if (data.hasOwnProperty(key)){ + if ((key == "id" || key == this.action_param) && this._tMode == "REST") continue; + stack.push(this.escape((pref||"")+key)+"="+this.escape(data[key])); + } + return stack.join("&"); + }, + _sendData:function(a1,rowId){ + if (!a1) return; //nothing to send + if (!this.callEvent("onBeforeDataSending",rowId?[rowId,this.getState(rowId),a1]:[null, null, a1])) return false; + + if (rowId) + this._in_progress[rowId]=(new Date()).valueOf(); + + var that = this; + var back = function(xml){ + var ids = []; + if (rowId) + ids.push(rowId); + else if (a1) + for (var key in a1) + ids.push(key); + + return that.afterUpdate(that,xml,ids); + }; + + var a3 = this.serverProcessor+(this._user?(dhtmlx.url(this.serverProcessor)+["dhx_user="+this._user,"dhx_version="+this.obj.getUserData(0,"version")].join("&")):""); + + if (this._tMode=="GET") + dhx4.ajax.get(a3+((a3.indexOf("?")!=-1)?"&":"?")+this.serialize(a1,rowId), back); + else if (this._tMode == "POST") + dhx4.ajax.post(a3,this.serialize(a1,rowId), back); + else if (this._tMode == "REST"){ + var state = this.getState(rowId); + var url = a3.replace(/(\&|\?)editing\=true/,""); + var data = ""; + var method = "post"; + + if (state == "inserted"){ + data = this.serialize(a1, rowId); + } else if (state == "deleted"){ + method = "DELETE"; + url = url + (url.slice(-1) == "/" ? "" : "/") + rowId; + } else { + method = "PUT"; + data = this.serialize(a1, rowId); + url = url + (url.slice(-1) == "/" ? "" : "/") + rowId; + } + + + if (this._payload) + for (var key in this._payload) + url = url + dhtmlx.url(url) + this.escape(key) + "=" + this.escape(this._payload[key]); + + dhx4.ajax.query({ + url:url, + method:method, + headers:this._headers, + data:data, + callback:back + }); + } + + this._waitMode++; + }, + sendAllData:function(){ + if (!this.updatedRows.length) return; + + this.messages=[]; var valid=true; + for (var i=0; i Math.abs(wy)){ + if(res.x) return true;//no horisontal scroll, must not block scrolling + + var dir = wx/-40; + var left = gantt.$task.scrollLeft+dir*30; + gantt.scrollTo(left, null); + gantt.$scroll_hor.scrollTop = top; + } else { + if(res.y) return true;//no vertical scroll, must not block scrolling + + var dir = wy/-40; + if (typeof wy == "undefined") + dir = e.detail; + + var top = gantt.$scroll_ver.scrollTop+dir*30; + if(!gantt.config.prevent_default_scroll && gantt._cached_scroll_pos && gantt._cached_scroll_pos.y == top) return true; + + gantt.scrollTo(null, top); + gantt.$scroll_ver.scrollTop = top; + } + + if (e.preventDefault) + e.preventDefault(); + e.cancelBubble=true; + return false; + } + + if (ff) + dhtmlxEvent(gantt.$container, "wheel", onMouseWheel); + else + dhtmlxEvent(gantt.$container, "mousewheel", onMouseWheel); + +}; + + +gantt._scroll_resize = function() { + if (this._x < 20 || this._y < 20) return; + + var grid_width = this._get_grid_width(); + + var task_width = Math.max(this._x - grid_width, 0); + var task_height = Math.max(this._y - this.config.scale_height, 0); + + var scroll_size = this.config.scroll_size + 1;//1px for inner content + + var task_data_width = Math.max(this.$task_data.offsetWidth - scroll_size, 0); + var task_data_height = this.config.row_height*this._order.length; + + var resize = this._get_resize_options(); + var scroll_hor = this._scroll_hor = resize.x ? false : (task_data_width > task_width); + var scroll_ver = this._scroll_ver = resize.y ? false : (task_data_height > task_height); + + this.$scroll_hor.style.display = scroll_hor ? "block" : "none"; + this.$scroll_hor.style.height = (scroll_hor ? scroll_size : 0) + "px"; + this.$scroll_hor.style.width = Math.max((this._x - (scroll_ver ? scroll_size : 2)), 0) + "px"; + this.$scroll_hor.firstChild.style.width = (task_data_width + grid_width + scroll_size + 2) + "px"; + + this.$scroll_ver.style.display = scroll_ver ? "block" : "none"; + this.$scroll_ver.style.width = (scroll_ver ? scroll_size : 0) + "px"; + this.$scroll_ver.style.height = Math.max((this._y - (scroll_hor ? scroll_size : 0) - this.config.scale_height), 0) + "px"; + this.$scroll_ver.style.top = this.config.scale_height + "px"; + this.$scroll_ver.firstChild.style.height = (this.config.scale_height + task_data_height) + "px"; +}; + +gantt.locate = function(e) { + var trg = gantt._get_target_node(e); + + //ignore empty cells + var className = trg.className || ""; + if(!className.indexOf){ + //'className' exist but not a string - IE svg element in DOM + className = ''; + } + if ((className || "").indexOf("gantt_task_cell") >= 0) return null; + + var attribute = arguments[1] || this.config.task_attribute; + + while (trg){ + if (trg.getAttribute){ //text nodes has not getAttribute + var test = trg.getAttribute(attribute); + if (test) return test; + } + trg=trg.parentNode; + } + return null; +}; +gantt._get_target_node = function(e){ + var trg; + if (e.tagName) + trg = e; + else { + e=e||window.event; + trg=e.target||e.srcElement; + } + return trg; +}; +gantt._trim = function(str){ + var func = String.prototype.trim || function(){ return this.replace(/^\s+|\s+$/g, ""); }; + return func.apply(str); +}; + +gantt._locate_css = function(e, classname, strict){ + if(strict === undefined) + strict = true; + + var trg = gantt._get_target_node(e); + var css = ''; + var test = false; + while (trg){ + css = trg.className; + if(css && !css.indexOf){ + //'className' exist but not a string - IE svg element in DOM + css = ''; + } + + if(css){ + var ind = css.indexOf(classname); + if (ind >= 0){ + if (!strict) + return trg; + + //check that we have exact match + var left = (ind === 0) || (!gantt._trim(css.charAt(ind - 1))); + var right = ((ind + classname.length >= css.length)) || (!gantt._trim(css.charAt(ind + classname.length))); + + if (left && right) + return trg; + } + } + + trg=trg.parentNode; + } + return null; +}; +gantt._locateHTML = function(e, attribute) { + var trg = gantt._get_target_node(e); + attribute = attribute || this.config.task_attribute; + + while (trg){ + if (trg.getAttribute){ //text nodes has not getAttribute + var test = trg.getAttribute(attribute); + if (test) return trg; + } + trg=trg.parentNode; + } + return null; +}; + +gantt.getTaskRowNode = function(id) { + var els = this.$grid_data.childNodes; + var attribute = this.config.task_attribute; + for (var i = 0; i < els.length; i++) { + if (els[i].getAttribute) { + var value = els[i].getAttribute(attribute); + if (value == id) return els[i]; + } + } + return null; +}; + +gantt.getState = function(){ + return { + drag_id : this._tasks_dnd.drag.id, + drag_mode : this._tasks_dnd.drag.mode, + drag_from_start : this._tasks_dnd.drag.left, + selected_task : this._selected_task, + min_date : gantt.date.Date(this._min_date), + max_date : gantt.date.Date(this._max_date), + lightbox : this._lightbox_id, + touch_drag : this._touch_drag + + }; + +}; + + +gantt._checkTimeout = function(host, updPerSecond){ + if(!updPerSecond) + return true; + var timeout = 1000/updPerSecond; + if(timeout < 1) return true; + + if(host._on_timeout) + return false; + + setTimeout(function(){ + delete host._on_timeout; + }, timeout); + + host._on_timeout = true; + return true; +}; + +gantt.selectTask = function(id){ + if(!this.config.select_task) + return false; + if (id){ + + if(this._selected_task == id) + //return false; + return this._selected_task; + + if(!this.callEvent("onBeforeTaskSelected", [id])){ + return false; + } + + this.unselectTask(true); + this._selected_task = id; + + this.refreshTask(id); + if(this.config.scroll_on_click){ + var task=gantt.getTask(id); + this.showDate(task.start_date); + } + this.callEvent("onTaskSelected", [id]); + } + return this._selected_task; +}; +gantt.unselectTask = function(ignore){ + var id = this._selected_task; + if(!id) + return; + this._selected_task = null; + this.refreshTask(id); + this.callEvent("onTaskUnselected", [id,ignore]); +}; +gantt.getSelectedId = function() { + return dhtmlx.defined(this._selected_task) ? this._selected_task : null; +}; + +gantt.changeLightboxType = function(type){ + if(this.getLightboxType() == type) + return true; + gantt._silent_redraw_lightbox(type); +}; + +gantt._is_render_active = function(){ + return !this._skip_render; +}; + +gantt._correct_dst_change = function(date, prevOffset, step, unit){ + var time_unit = gantt._get_line(unit) * step; + if(time_unit > 60*60 && time_unit < 60*60*24){ + //correct dst change only if current unit is more than one hour and less than day (days have own checking), e.g. 12h + var offsetChanged = date.getTimezoneOffset() - prevOffset; + if(offsetChanged){ + date = gantt.date.add(date, offsetChanged, "minute"); + } + } + return date; +}; + +gantt.batchUpdate = function (callback) { + var call_dp = (this._dp && this._dp.updateMode != "off"); + var dp_mode; + if (call_dp){ + dp_mode = this._dp.updateMode; + this._dp.setUpdateMode("off"); + } + + this._skip_render = true; + + try{ + callback(); + }catch(e){ + + } + + this._skip_render = false; + this.render(); + if (call_dp) { + this._dp.setUpdateMode(dp_mode); + this._dp.sendData(); + } +}; + +/*gantt.date.quarter_start = function(date){ + gantt.date.month_start(date); + var m = date.getMonth(), + res_month; + + if(m >= 9){ + res_month = 9; + }else if(m >= 6){ + res_month = 6; + }else if(m >= 3){ + res_month = 3; + }else{ + res_month = 0; + } + + date.setMonth(res_month); + return date; +}; +gantt.date.add_quarter = function(date, inc){ + return gantt.date.add(date, inc*3, "month"); +};*/ +/* + %d - the day as a number with a leading zero ( 01 to 31 ); + %j - the day as a number without a leading zero ( 1 to 31 ); + %D - the day as an abbreviation ( Sun to Sat ); + %l - the day as a full name ( Sunday to Saturday ); + %W - the ISO-8601 week number of the year. Weeks start on Monday; 1) + %m - the month as a number without a leading zero ( 1 to 12 ); + %n - the month as a number with a leading zero ( 01 to 12); + %M - the month as an abbreviation ( Jan to Dec ); + %F - the month as a full name ( January to December ); + %y - the year as a two-digit number ( 00 to 99 ); + %Y - the year as a four-digit number ( 1900–9999 ); + %h - the hour based on the 12-hour clock ( 00 to 11 ); + %H - the hour based on the 24-hour clock ( 00 to 23 ); + %i - the minute as a number with a leading zero ( 00 to 59 ); + %s - the second as a number without a leading zero ( 00 to 59 ); 2) + %a - displays am (for times from midnight until noon) and pm (for times from noon until midnight); + %A - displays AM (for times from midnight until noon) and PM (for times from noon until midnight). + + */ + +if(!gantt.config) gantt.config = {}; +if(!gantt.config) gantt.config = {}; +if(!gantt.templates) gantt.templates = {}; + +(function(){ + +dhtmlx.mixin(gantt.config, + {links : { + "finish_to_start":"0", + "start_to_start":"1", + "finish_to_finish":"2", + "start_to_finish":"3" + }, + types : { + 'task':'task', + 'project':'project', + 'milestone':'milestone' + }, + duration_unit : "day", + work_time:false, + correct_work_time:false, + skip_off_time:false, + + autosize:false, + autosize_min_width: 0, + + show_links : true, + show_task_cells : true, + // replace backgroung of the task area with a canvas img + static_background: false, + branch_loading: false, + show_loading: false, + show_chart : true, + show_grid : true, + min_duration : 60*60*1000, + xml_date : "%d-%m-%Y %H:%i", + api_date : "%d-%m-%Y %H:%i", + start_on_monday: true, + server_utc : false, + show_progress:true, + fit_tasks : false, + select_task:true, + scroll_on_click: true, + preserve_scroll: true, + readonly:false, + + /*grid */ + date_grid: "%Y-%m-%d", + + drag_links : true, + drag_progress:true, + drag_resize:true, + drag_move:true, + drag_mode:{ + "resize":"resize", + "progress":"progress", + "move":"move", + "ignore":"ignore", + "empty":"empty" // HOSEK + }, + round_dnd_dates:true, + link_wrapper_width:20, + root_id:0, + + autofit: false, // grid column automatic fit grid_width config + columns: [ + {name:"text", tree:true, width:'*', resize:true }, + {name:"start_date", align: "center", resize:true }, + {name:"duration", align: "center" }, + {name:"add", width:'44' } + ], + + /*scale*/ + step: 1, + scale_unit: "day", + scale_offset_minimal:true, + subscales : [ + + ], + + inherit_scale_class:false, + + time_step: 60, + duration_step: 1, + date_scale: "%d %M", + task_date: "%d %F %Y", + time_picker: "%H:%i", + task_attribute: "task_id", + link_attribute: "link_id", + layer_attribute: "data-layer", + buttons_left: [ + "gantt_save_btn", + "gantt_cancel_btn" + ], + _migrate_buttons: { + "dhx_save_btn":"gantt_save_btn", + "dhx_cancel_btn":"gantt_cancel_btn", + "dhx_delete_btn":"gantt_delete_btn" + }, + buttons_right: [ + "gantt_delete_btn" + ], + lightbox: { + sections: [ + {name: "description", height: 70, map_to: "text", type: "textarea", focus: true}, + {name: "time", type: "duration", map_to: "auto"} + ], + project_sections: [ + {name: "description", height: 70, map_to: "text", type: "textarea", focus: true}, + {name: "type", type: "typeselect", map_to: "type"}, + {name: "time", type: "duration", readonly:true, map_to: "auto"} + ], + milestone_sections: [ + {name: "description", height: 70, map_to: "text", type: "textarea", focus: true}, + {name: "type", type: "typeselect", map_to: "type"}, + {name: "time", type: "duration", single_date:true, map_to: "auto"} + ] + }, + drag_lightbox: true, + sort: false, + details_on_create: true, + details_on_dblclick:true, + initial_scroll : true, + task_scroll_offset : 100, + + task_height: "full",//number px of 'full' for row height + min_column_width:70, + + // min width for grid column (when resizing) + min_grid_column_width:70, + // name of the attribute with column index for resize element + grid_resizer_column_attribute: "column_index", + // name of the attribute with column index for resize element + grid_resizer_attribute: "grid_resizer", + + // grid width can be increased after the column has been resized + keep_grid_width:false, + + // grid width can be adjusted + grid_resize:false, + + // + readonly_property: "readonly", + editable_property: "editable", + type_renderers:{}, + + open_tree_initially: false, + optimize_render: 'auto', + prevent_default_scroll: false + +}); +gantt.keys={ + edit_save:13, + edit_cancel:27 +}; + +gantt._init_template = function(name, initial){ + var registeredTemplates = this._reg_templates || {}; + + if(this.config[name] && registeredTemplates[name] != this.config[name]){ + if(!(initial && this.templates[name])){ + this.templates[name] = this.date.date_to_str(this.config[name]); + registeredTemplates[name] = this.config[name]; + } + } + this._reg_templates = registeredTemplates; +}; +gantt._init_templates = function(){ + var labels = gantt.locale.labels; + labels.gantt_save_btn = labels.icon_save; + labels.gantt_cancel_btn = labels.icon_cancel; + labels.gantt_delete_btn = labels.icon_delete; + + + + //build configuration based templates + var d = this.date.date_to_str; + var c = this.config; + gantt._init_template("date_scale", true); + gantt._init_template("date_grid", true); + gantt._init_template("task_date", true); + + + + dhtmlx.mixin(this.templates,{ + xml_date:this.date.str_to_date(c.xml_date,c.server_utc), + xml_format:d(c.xml_date,c.server_utc), + api_date:this.date.str_to_date(c.api_date), + progress_text:function(start, end, task){return "";}, + grid_header_class : function(column, config){ + return ""; + }, + + task_text:function(start, end, task){ + return task.text; + }, + task_class:function(start, end, task){return "";}, + grid_row_class:function(start, end, task){ + return ""; + }, + task_row_class:function(start, end, task){ + return ""; + }, + task_cell_class:function(item, date){return "";}, + scale_cell_class:function(date){return "";}, + scale_row_class:function(date){return "";}, + + grid_indent:function(item) { + return "
"; + }, + grid_folder:function(item) { + if(item.$open||gantt._get_safe_type(item.type)!==gantt.config.types.task){ // HOSEK + return "
"; + }else{ + return "
"; // HOSEK + } + }, + grid_file:function(item) { + if(gantt._get_safe_type(item.type)===gantt.config.types.task) + return "
"; // HOSEK + return "
"; + //return "
"; + }, + grid_open:function(item) { + return "
"; + }, + grid_blank:function(item) { + return "
"; + }, + + task_time:function(start,end,ev){ + return gantt.templates.task_date(start)+" - "+gantt.templates.task_date(end); + }, + time_picker:d(c.time_picker), + link_class : function(link){ + return ""; + }, + link_description : function(link){ + var from = gantt.getTask(link.source), + to = gantt.getTask(link.target); + + return "" + from.text + "" + to.text+""; + }, + + drag_link : function(from, from_start, to, to_start) { + from = gantt.getTask(from); + var labels = gantt.locale.labels; + + var text = "" + from.text + " " + (from_start ? labels.link_start : labels.link_end)+"
"; + if(to){ + to = gantt.getTask(to); + text += " " + to.text + " "+ (to_start ? labels.link_start : labels.link_end)+"
"; + } + return text; + }, + drag_link_class: function(from, from_start, to, to_start) { + var add = ""; + + if(from && to){ + var allowed = gantt.isLinkAllowed(from, to, from_start, to_start); + add = " " + (allowed ? "gantt_link_allow" : "gantt_link_deny"); + } + + return "gantt_link_tooltip" + add; + } + }); + + this.callEvent("onTemplatesReady",[]); +}; + +})(); +if (window.jQuery){ + +(function( $ ){ + + var methods = []; + $.fn.dhx_gantt = function(config){ + config = config || {}; + if (typeof(config) === 'string') { + if (methods[config] ) { + return methods[config].apply(this, []); + }else { + $.error('Method ' + config + ' does not exist on jQuery.dhx_gantt'); + } + } else { + var views = []; + this.each(function() { + if (this && this.getAttribute){ + if (!this.getAttribute("dhxgantt")){ + for (var key in config) + if (key!="data") + gantt.config[key] = config[key]; + + gantt.init(this); + if (config.data) + gantt.parse(config.data); + + views.push(gantt); + } + } + }); + + + if (views.length === 1) return views[0]; + return views; + } + }; + +})(jQuery); + +} + +if (window.dhtmlx){ + + if (!dhtmlx.attaches) + dhtmlx.attaches = {}; + + dhtmlx.attaches.attachGantt=function(start, end){ + var obj = document.createElement("DIV"); + obj.id = "gantt_"+dhtmlx.uid(); + obj.style.width = "100%"; + obj.style.height = "100%"; + obj.cmp = "grid"; + + document.body.appendChild(obj); + this.attachObject(obj.id); + + var that = this.vs[this.av]; + that.grid = gantt; + + gantt.init(obj.id, start, end); + obj.firstChild.style.border = "none"; + + that.gridId = obj.id; + that.gridObj = obj; + + var method_name="_viewRestore"; + return this.vs[this[method_name]()].grid; + }; + +} +gantt.locale = { + //date:{ + // month_full:["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"], + // month_short:["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"], + // day_full:["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"], + // day_short:["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"] + //}, + labels:{ + //new_task:"New task", + //icon_save:"Save", + //icon_cancel:"Cancel", + //icon_details:"Details", + //icon_edit:"Edit", + //icon_delete:"Delete", + //confirm_closing:"",//Your changes will be lost, are your sure ? + //confirm_deleting:"Task will be deleted permanently, are you sure?", + // section_description:"Description", + // section_time:"Time period", + //section_type:"Type", + // + // /* grid columns */ + // + // column_text : "Task name", + // column_start_date : "Start time", + // column_duration : "Duration", + // column_add : "", + // + ///* link confirmation */ + //link: "Link", + //confirm_link_deleting:"will be deleted", + //link_start: " (start)", + //link_end: " (end)", + // + //type_task: "Task", + //type_project: "Project", + //type_milestone: "Milestone", + // + // minutes: "Minutes", + // hours: "Hours", + // days: "Days", + // weeks: "Week", + // months: "Months", + // years: "Years" + } +}; + + + + +gantt.skins.skyblue = { + config:{ + grid_width:350, + row_height: 27, + scale_height: 27, + link_line_width:1, + link_arrow_size:8, + lightbox_additional_height:75 + }, + _second_column_width:95, + _third_column_width:80 +}; +gantt.skins.meadow = { + config:{ + grid_width:350, + row_height: 27, + scale_height: 30, + link_line_width:2, + link_arrow_size:6, + lightbox_additional_height:72 + }, + _second_column_width:95, + _third_column_width:80 +}; + +gantt.skins.terrace = { + config:{ + grid_width:360, + row_height: 35, + scale_height: 35, + link_line_width:2, + link_arrow_size:6, + lightbox_additional_height:75 + }, + _second_column_width:90, + _third_column_width:70 +}; +gantt.skins.broadway = { + config:{ + grid_width:360, + row_height: 35, + scale_height: 35, + link_line_width:1, + link_arrow_size:7, + lightbox_additional_height:86 + }, + _second_column_width:90, + _third_column_width:80, + + _lightbox_template:"
 
", + _config_buttons_left: {}, + _config_buttons_right: { + "gantt_delete_btn": "icon_delete", + "gantt_save_btn": "icon_save" + } +}; + + +gantt.config.touch_drag = 500; //nearly immediate dnd +gantt.config.touch = true; +gantt.config.touch_feedback = true; + + +gantt._touch_feedback = function(){ + if(gantt.config.touch_feedback){ + if(navigator.vibrate) + navigator.vibrate(1); + } +}; + +gantt._init_touch_events = function(){ + if (this.config.touch != "force") + this.config.touch = this.config.touch && + ((navigator.userAgent.indexOf("Mobile")!=-1) || + (navigator.userAgent.indexOf("iPad")!=-1) || + (navigator.userAgent.indexOf("Android")!=-1) || + (navigator.userAgent.indexOf("Touch")!=-1)); + + if (this.config.touch){ + if (window.navigator.msPointerEnabled){ + this._touch_events(["MSPointerMove", "MSPointerDown", "MSPointerUp"], function(ev){ + if (ev.pointerType == ev.MSPOINTER_TYPE_MOUSE ) return null; + return ev; + }, function(ev){ + return (!ev || ev.pointerType == ev.MSPOINTER_TYPE_MOUSE); + }); + } else + this._touch_events(["touchmove", "touchstart", "touchend"], function(ev){ + if (ev.touches && ev.touches.length > 1) return null; + if (ev.touches[0]) + return { + target: ev.target, + pageX: ev.touches[0].pageX, + pageY: ev.touches[0].pageY, + clientX:ev.touches[0].clientX, + clientY:ev.touches[0].clientY + }; + else + return ev; + }, function(){ return false; }); + } +}; + + +//we can't use native scrolling, as we need to sync momentum between different parts +//so we will block native scroll and use the custom one +//in future we can add custom momentum +gantt._touch_events = function(names, accessor, ignore){ + //webkit on android need to be handled separately + var dblclicktime = 0; + var action_mode = false; + var scroll_mode = false; + var dblclick_timer = 0; + var action_start = null; + var scroll_state; + var long_tap_timer = null; + var current_target = null; + + //touch move + if (!this._gantt_touch_event_ready){ + this._gantt_touch_event_ready = 1; + dhtmlxEvent(gantt.$container, names[0], function(e){ + if (ignore(e)) return; + + //ignore common and scrolling moves + if (!action_mode) return; + + if (long_tap_timer) clearTimeout(long_tap_timer); + + var source = accessor(e); + if (gantt._tasks_dnd.drag.id || gantt._tasks_dnd.drag.start_drag) { + gantt._tasks_dnd.on_mouse_move(source); + if (e.preventDefault) + e.preventDefault(); + e.cancelBubble = true; + return false; + } + if (source && action_start){ + var dx = action_start.pageX - source.pageX; + var dy = action_start.pageY - source.pageY; + if (!scroll_mode && (Math.abs(dx) > 5 || Math.abs(dy) > 5)){ + gantt._touch_scroll_active = scroll_mode = true; + dblclicktime = 0; + scroll_state = gantt.getScrollState(); + } + + if (scroll_mode){ + gantt.scrollTo(scroll_state.x + dx, scroll_state.y + dy); + var new_scroll_state = gantt.getScrollState(); + + if((scroll_state.x != new_scroll_state.x && dy > 2 * dx) || + (scroll_state.y != new_scroll_state.y && dx > 2 * dy )) + { + return block_action(e); + } + } + } + return block_action(e); + }); + } + + //block touch context menu in IE10 + dhtmlxEvent(this.$container, "contextmenu", function(e){ + if (action_mode) + return block_action(e); + }); + + //touch start + dhtmlxEvent(this.$container, names[1], function(e){ + if (ignore(e)) return; + if (e.touches && e.touches.length > 1){ + action_mode = false; + return; + } + + action_mode = true; + action_start = accessor(e); + + + + //dbl-tap handling + if (action_start && dblclicktime){ + var now = gantt.date.Date(); + if ((now - dblclicktime) < 500 ){ + gantt._on_dblclick(action_start); + block_action(e); + } else + dblclicktime = now; + } else { + dblclicktime = gantt.date.Date(); + } + + //long tap + long_tap_timer = setTimeout(function(){ + var taskId = gantt.locate(action_start); + if(taskId && !gantt._locate_css(action_start, "gantt_link_control") && !gantt._locate_css(action_start, "gantt_grid_data")) { + gantt._tasks_dnd.on_mouse_down(action_start); + gantt._tasks_dnd._start_dnd(action_start); + gantt._touch_drag = true; + cloneTaskRendered(taskId); + + gantt.refreshTask(taskId); + + gantt._touch_feedback(); + } + + long_tap_timer = null; + }, gantt.config.touch_drag); + }); + + //touch end + dhtmlxEvent(this.$container, names[2], function(e){ + if (ignore(e)) return; + if (long_tap_timer) clearTimeout(long_tap_timer); + gantt._touch_drag = false; + action_mode = false; + var source = accessor(e); + gantt._tasks_dnd.on_mouse_up(source); + + if(current_target) { + gantt.refreshTask(gantt.locate(current_target)); + current_target.parentNode.removeChild(current_target); + gantt._touch_feedback(); + } + + gantt._touch_scroll_active = action_mode = scroll_mode = false; + current_target = null; + }); + + + //common helper, prevents event + function block_action(e){ + if (e && e.preventDefault) + e.preventDefault(); + (e||event).cancelBubble = true; + return false; + } + + function cloneTaskRendered(taskId) { + var renders = gantt._task_area_pulls; + var task = gantt.getTask(taskId); + if(task && gantt.isTaskVisible(taskId)){ + for(var i in renders) { + task = renders[i][taskId]; + if(task && task.getAttribute("task_id") && task.getAttribute("task_id") == taskId) { + var copy = task.cloneNode(true); + current_target = task; + renders[i][taskId] = copy; + task.style.display="none"; + copy.className += " gantt_drag_move "; + task.parentNode.appendChild(copy); + return copy; + } + } + } + } +}; diff --git a/easy_gantt/assets/javascripts/easy_gantt/dhtmlxgantt_marker.js b/easy_gantt/assets/javascripts/easy_gantt/dhtmlxgantt_marker.js new file mode 100644 index 0000000..5688374 --- /dev/null +++ b/easy_gantt/assets/javascripts/easy_gantt/dhtmlxgantt_marker.js @@ -0,0 +1,127 @@ +/* +@license + +dhtmlxGantt v.3.2.1 Stardard +This software is covered by GPL license. You also can obtain Commercial or Enterprise license to use it in non-GPL project - please contact sales@dhtmlx.com. Usage without proper license is prohibited. + +(c) Dinamenta, UAB. +*/ + +if(!gantt._markers) + gantt._markers = {}; + +gantt.config.show_markers = true; + +gantt.attachEvent("onClear", function(){ + gantt._markers = {}; +}); + +gantt.attachEvent("onGanttReady", function(){ + /*if(!gantt.$marker_area){ // HOSEK + var markerArea=$(".gantt_marker_area"); + if(markerArea.length===0){ + markerArea = document.createElement("div"); + markerArea.className = "gantt_marker_area"; + gantt.$task_data.appendChild(markerArea); + }else{ + markerArea=markerArea[0]; + } + gantt.$marker_area = markerArea; + }*/ // HOSEK + + //gantt._markerRenderer = gantt._task_renderer("markers", render_marker, gantt.$marker_area, null); + gantt._markerRenderer = gantt._task_renderer({id:"markers",renderer: render_marker,container: gantt.$marker_area,filter: null}); + + function render_marker(marker){ + if(!gantt.config.show_markers) + return false; + + if(!marker.start_date) + return false; + + var state = gantt.getState(); + if(+marker.start_date > +state.max_date) + return; + if(+marker.end_date && +marker.end_date < +state.min_date || +marker.start_date < +state.min_date) + return; + + var div = document.createElement("div"); + + div.setAttribute("marker_id", marker.id); + + var css = "gantt_marker"; + if(gantt.templates.marker_class) + css += " " + gantt.templates.marker_class(marker); + + if(marker.css){ + css += " " + marker.css; + } + + if(marker.title){ + div.title = marker.title; + } + div.className = css; + + var start = gantt.posFromDate(marker.start_date); + div.style.left = start + "px"; + div.style.height = Math.max(gantt._y_from_ind(gantt._order.length), 0) + "px"; + if(marker.end_date){ + var end = gantt.posFromDate(marker.end_date); + div.style.width = Math.max((end - start), 0) + "px"; + + } + + if(marker.text){ + div.innerHTML = "
" + marker.text + "
"; + } + + return div; + } +}); + + +gantt.attachEvent("onDataRender", function(){ + gantt.renderMarkers(); +}); + +gantt.getMarker = function(id){ + if(!this._markers) return null; + + return this._markers[id]; +}; + +gantt.addMarker = function(marker){ + marker.id = marker.id || dhtmlx.uid(); + + this._markers[marker.id] = marker; + + return marker.id; +}; + +gantt.deleteMarker = function(id){ + if(!this._markers || !this._markers[id]) + return false; + + delete this._markers[id]; + return true; +}; +gantt.updateMarker = function(id){ + if(this._markerRenderer) + this._markerRenderer.render_item(id); +}; +gantt.renderMarkers = function(){ + if(!this._markers) + return false; + + if(!this._markerRenderer) + return false; + + var to_render = []; + + for(var id in this._markers) + to_render.push(this._markers[id]); + + this._markerRenderer.render_items(to_render); + + return true; +}; \ No newline at end of file diff --git a/easy_gantt/assets/javascripts/easy_gantt/easy_gantt.js b/easy_gantt/assets/javascripts/easy_gantt/easy_gantt.js new file mode 100644 index 0000000..e198612 --- /dev/null +++ b/easy_gantt/assets/javascripts/easy_gantt/easy_gantt.js @@ -0,0 +1,9 @@ +/* + * = require_directory ./libs + * = require easy_gantt/utils + * = require easy_gantt/data + * = require easy_gantt/widget + * = require_directory . + * = stub easy_gantt/sample + * = stub easy_gantt/libs/moment +*/ \ No newline at end of file diff --git a/easy_gantt/assets/javascripts/easy_gantt/flash_message.js b/easy_gantt/assets/javascripts/easy_gantt/flash_message.js new file mode 100644 index 0000000..9d50049 --- /dev/null +++ b/easy_gantt/assets/javascripts/easy_gantt/flash_message.js @@ -0,0 +1,37 @@ +window.showFlashMessage = (function (type, message, delay) { + var $content = $("#content"); + $content.find(".flash").remove(); + var element = document.createElement("div"); + element.className = 'fixed flash ' + type; + element.style.position = 'fixed'; + element.style.zIndex = '10001'; + element.style.right = '5px'; + element.style.top = '5px'; + element.setAttribute("onclick", "closeFlashMessage($(this))"); + var close = document.createElement("a"); + close.className = 'icon-close close-icon'; + close.setAttribute("href", "javascript:void(0)"); + close.style.float = 'right'; + close.style.marginLeft = '5px'; + // close.setAttribute("onclick", "closeFlashMessage($(this))"); + var span = document.createElement("span"); + span.innerHTML = message; + element.appendChild(close); + element.appendChild(span); + $content.prepend(element); + var $element = $(element); + if (delay) { + setTimeout(function () { + window.requestAnimationFrame(function () { + closeFlashMessage($element); + }); + }, delay); + } + return $element; +}); + +window.closeFlashMessage = (function ($element) { + $element.closest('.flash').fadeOut(500, function () { + $element.remove(); + }); +}); diff --git a/easy_gantt/assets/javascripts/easy_gantt/gantt_widget.js b/easy_gantt/assets/javascripts/easy_gantt/gantt_widget.js new file mode 100644 index 0000000..539858f --- /dev/null +++ b/easy_gantt/assets/javascripts/easy_gantt/gantt_widget.js @@ -0,0 +1,589 @@ +/* gantt_widget.js */ +/* global ysy */ +window.ysy = window.ysy || {}; +ysy.view = ysy.view || {}; +//##################################################################### +ysy.view.Gantt = function () { + ysy.view.Widget.call(this); + this.name = "GanttInitWidget"; + ysy.log.message("widget Gantt created"); +}; +ysy.main.extender(ysy.view.Widget, ysy.view.Gantt, { + _updateChildren: function () { + if (this.children.length > 0) { + return; + } + var renderer = new ysy.view.GanttRender(); + renderer.init( + ysy.data.limits, + ysy.settings.critical, + ysy.settings.zoom, + ysy.settings.sumRow + ); + this.children.push(renderer); + }, + _postInit: function () { + //gantt.initProjectMarker(this.model.start,this.model.end); + }, + _repaintCore: function () { + if (this.$target === null) { + throw "Target is null for " + this.name; + } + if (!ysy.data.columns) { + //ysy.log.log("GanttInitWidget: columns are missing"); + return true; + } + ysy.data.limits.setSilent("zoomDate", gantt.getShowDate() || moment()); + ysy.view.initGantt(); + this.addBaselineLayer(); + gantt.init(this.$target); // REPAINT + ysy.log.debug("gantt.init()", "load"); + this.tideFunctionality(); // TIDE FUNCTIONALITY + for (var i = 0; i < this.children.length; i++) { + var child = this.children[i]; + child.$target = this.$target; // SET CHILD TARGET + child.repaint(true); // CHILD REPAINT + } + if (!ysy.settings.controls.controls) { + $(".gantt_bars_area").addClass("no_task_controls"); + } + dhtmlx.dragScroll(); + }, + addBaselineLayer: function () { + } +}); +//############################################################################## +ysy.view.GanttRender = function () { + ysy.view.Widget.call(this); + this.name = "GanttRenderWidget"; +}; +ysy.main.extender(ysy.view.Widget, ysy.view.GanttRender, { + _updateChildren: function () { + if (!this.tasks) { + this.tasks = new ysy.view.GanttTasks(); + this.tasks.init( + ysy.data.issues, + ysy.data.milestones, + ysy.data.projects, + ysy.settings.resource, + ysy.data.assignees, + ysy.settings.scheme + ); + this.children.push(this.tasks); + } + if (!this.links) { + this.links = new ysy.view.GanttLinks(); + this.links.init( + ysy.data.relations, + ysy.settings.resource + ); + this.children.push(this.links); + } + if (!this.refresher) { + this.refresher = new ysy.view.GanttRefresher(); + this.refresher.init(gantt); + this.children.push(this.refresher); + } + if (!this.sumRow && ysy.settings.sumRow.active) { + this.sumRow = new ysy.view.SumRow(); + this.sumRow.init(); + this.children.push(this.sumRow); + } + }, + zoomTo: function (timespan) { + if (timespan === "year") { + $.extend(gantt.config, { + scale_unit: "year", + date_scale: "%Y", + subscales: [ + ] + }); + } else if (timespan === "quarter") { + $.extend(gantt.config, { + scale_unit: "quarter", + date_scale: "%Q", + subscales: [ + {unit: "year", step: 1, date: "%Y"} + ] + }); + } else if (timespan === "month") { + $.extend(gantt.config, { + scale_unit: "month", + date_scale: "%M", + subscales: [ + {unit: "year", step: 1, date: "%Y"} + ] + }); + } else if (timespan === "week") { + $.extend(gantt.config, { + scale_unit: "week", + date_scale: "%W", + subscales: [ + {unit: "month", step: 1, date: "%F %Y"} + ] + }); + } else if (timespan === "day") { + $.extend(gantt.config, { + scale_unit: "day", + date_scale: "%d", + subscales: [ + {unit: "month", step: 1, date: "%F %Y"} + ] + }); + } + if (this.sumRow && ysy.settings.sumRow.active) { + gantt.config.subscales.push(this.sumRow.getSubScale(timespan)); + } + }, + _repaintCore: function () { + if (this.$target === null) { + throw "Target is null for " + this.name; + } + this.zoomTo(ysy.settings.zoom.zoom); + //this.addBaselineLayer(); + //ysy.data.limits.setSilent("pos", gantt.getScrollState()); + //var pos = ysy.data.limits.pos; + //if (pos)ysy.log.debug("scrollSave pos={x:" + pos.x + ",y:" + pos.y + "}", "scroll"); + // gantt.render(); + this.tideFunctionality(); // TIDE FUNCTIONALITY + for (var i = 0; i < this.children.length; i++) { + var child = this.children[i]; + child.repaint(true); // CHILD REPAINT + } + + }, + addBaselineLayer: function () { + } +}); +//############################################################################## +ysy.view.GanttTasks = function () { + ysy.view.Widget.call(this); + this.name = "GanttTasksWidget"; + ysy.view.ganttTasks = this; +}; +ysy.main.extender(ysy.view.Widget, ysy.view.GanttTasks, { + _selectChildren: function () { + var issues = this.model.getArray(); + var milestones = ysy.data.milestones.getArray(); + var projects = ysy.data.projects.getArray(); + return issues.concat(milestones,projects); + }, + _updateChildren: function () { + var combined = this._selectChildren(); + //var combined=issues.concat(milestones); + var i, model, task; + if (this.children.length === 0) { + for (i = 0; i < combined.length; i++) { + model = combined[i]; + task = new ysy.view.GanttTask(); + task.init(model); + task.parent = this; + task.order = i + 1; + this.children.push(task); + } + } else { + var narr = []; + var temp = {}; + for (i = 0; i < this.children.length; i++) { + var child = this.children[i]; + temp[child.model.getID()] = child; + } + for (i = 0; i < combined.length; i++) { + model = combined[i]; + task = temp[model.getID()]; + if (!task) { + task = new ysy.view.GanttTask(); + task.init(model); + task.parent = this; + } else { + delete temp[model.getID()]; + } + task.order = i + 1; + narr.push(task); + } + for (var key in temp) { + if (temp.hasOwnProperty(key)) { + temp[key].destroy(true); + } + } + //var narr = []; + this.children = narr; + } + ysy.log.log("-- " + this.children.length + " Children updated in " + this.name); + }, + _repaintCore: function () { + for (var i = 0; i < this.children.length; i++) { + var child = this.children[i]; + //this.setChildTarget(child, i); // SET CHILD TARGET + child.repaint(true); // CHILD REPAINT + } + gantt._sync_links(); + gantt.reconstructTree(); + gantt.sort(gantt._sort && gantt._sort.criteria); + //window.initInlineEditForContainer($("#gantt_cont")); // TODO + } +}); +//############################################################################## +ysy.view.GanttTask = function () { + ysy.view.Widget.call(this); + this.name = "GanttTaskWidget"; +}; +ysy.main.extender(ysy.view.Widget, ysy.view.GanttTask, { + _repaintCore: function () { + var issue = this.model; + var gantt_issue = this._constructDhData(issue); + if (gantt._pull[gantt_issue.id]) { + if (this.dhdata.parent != gantt_issue.realParent) { + gantt.silentMoveTask(this.dhdata, gantt_issue.realParent); + delete gantt_issue.realParent; + } + $.extend(this.dhdata, gantt_issue); + gantt.refreshTask(gantt_issue.id); + } else { + this.destroyDhData(); + this.dhdata = gantt_issue; + ysy.log.debug("addTaskNoDraw()", "load"); + gantt.addTaskFaster(gantt_issue); + } + //window.initInlineEditForContainer($("#gantt_cont")); // TODO + }, + destroyDhData: function (silent) { + if (!this.dhdata) return; + this.dhdata.deleted = true; + if (gantt.isTaskExists(this.dhdata.id)) { + gantt._deleteTask(this.dhdata.id, silent); + } + this.dhdata = null; + ysy.log.debug("Destroy for " + this.name, "widget_destroy"); + }, + destroy: function (silent) { + this.destroyDhData(silent); + this.deleted = true; + }, + _constructDhData: function (issue) { + // var parent = issue.getParent() || 0; + var gantt_issue = { + id: issue.getID(), + real_id: issue.id, + text: issue.name, + css: (issue.css || '') + (issue.closed ? ' closed' : ''), + //model:issue, + widget: this, + order: this.order, + open: issue.isOpened(), + start_date: issue.start_date ? moment(issue.start_date) : undefined, + $ignore: issue._ignore || false, + columns: issue.columns, + readonly: !issue.isEditable(), + realParent: issue.getParent() || 0, + type: issue.ganttType + }; + gantt_issue.$open = gantt_issue.open; + if (issue.isProject) { + // -- PROJECT -- + $.extend(gantt_issue, { + progress: issue.getProgress(), + maximal_start: issue.start_date, + minimal_end: issue.end_date, + start_date: issue.start_date, + end_date: issue.end_date + }); + } else if (issue.isIssue) { + // -- ISSUE -- + var end_date = moment(issue._end_date); + //if (!issue._end_date.isValid()) { + // console.error("_end_date is not valid"); + // end_date = moment(issue._start_date).add(1, "d"); + //} + end_date._isEndDate = true; + $.extend(gantt_issue, { + start_date: moment(issue._start_date), + end_date: end_date, + progress: (issue.done_ratio || 0) / 100.0, + //duration: issue.end_date.diff(issue.start_date, 'days'), + assigned_to: issue.assigned_to, + estimated: issue.estimated_hours || 0, + soonest_start: issue.soonest_start, + latest_due: issue.latest_due + }); + } else if (issue.milestone) { + // -- MILESTONE -- + gantt_issue.end_date = moment(gantt_issue.start_date); + gantt_issue.end_date._isEndDate = true; + } else { + // -- ASSIGNEE -- + } + ysy.proManager.fireEvent("extendGanttTask", issue, gantt_issue); + return gantt_issue; + }, + update: function (item, keys) { + var obj; + if (item.type === "milestone") { + this.model.set({ + name: item.text, + start_date: moment(item.start_date) + }); + } else if (item.type === "project") { + obj = { + start_date: moment(item.start_date), + end_date: moment(item.end_date), + _shift: item.start_date.diff(this.model.start_date, "days") + (this.model._shift || 0) + }; + obj.end_date._isEndDate = true; + this.model.set(obj); + } else { + var fullObj = { + name: item.text, + //assignedto: item.assignee, + estimated_hours: item.estimated, + done_ratio: Math.round(item.progress * 10) * 10, + start_date: moment(item.start_date), + end_date: moment(item.end_date) + }; + fullObj.end_date._isEndDate = true; + if (item._parentChanged) { + $.extend(fullObj, this._constructParentUpdate(item.parent)); + item._parentChanged = false; + } + obj = fullObj; + if (keys !== undefined) { + obj = {}; + for (var i = 0; i < keys.length; i++) { + var key = keys[i]; + if (key === "fixed_version_id") { + if (typeof item.parent === "string") { + obj.fixed_version_id = parseInt(item.parent.substring(1)); + } else { + obj.parent = item.parent; // TODO subtask žížaly musí mít parent nebo něco + } + //this.parent.requestRepaint(); + } else { + obj[key] = fullObj[key]; + } + } + } + this.model.set(obj); + } + this.requestRepaint(); + }, + _constructParentUpdate: function (parentId) { + if (typeof parentId !== "string") { + var parent = gantt._pull[parentId]; + if (!parent) return {}; + var parentModel = parent.widget.model; + if (!parentModel) return {}; + if (parentModel.fixed_version_id) { + return { + parent_issue_id: parentId, + fixed_version_id: parentModel.fixed_version_id, + project_id: parentModel.project_id + }; + } else { + return {parent_issue_id: parentId, project_id: parentModel.project_id}; + } + } else if (ysy.main.startsWith(parentId, "p")) { + return { + parent_issue_id: null, project_id: parseInt(parentId.substring(1)), fixed_version_id: null + }; + } else if (ysy.main.startsWith(parentId, "m")) { + return { + parent_issue_id: null, fixed_version_id: parseInt(parentId.substring(1)) + }; + } else if (parentId === "empty") { + return { + parent_issue_id: null, project_id: ysy.settings.projectID, fixed_version_id: null + }; + } else return null; + } +}); +//############################################################################## +ysy.view.GanttLinks = function () { + ysy.view.Widget.call(this); + this.name = "GanttLinksWidget"; + ysy.view.ganttLinks = this; +}; +ysy.main.extender(ysy.view.Widget, ysy.view.GanttLinks, { + _updateChildren: function () { + var rela, link, i; + var model = this.model.getArray(); + if (this.children.length === 0) { + for (i = 0; i < model.length; i++) { + rela = model[i]; + if (rela.isHalfLink()) continue; + link = new ysy.view.GanttLink(); + link.init(rela); + this.children.push(link); + } + } else { + var narr = []; + var temp = {}; + for (i = 0; i < this.children.length; i++) { + var child = this.children[i]; + temp[child.model.id] = child; + } + for (i = 0; i < model.length; i++) { + rela = model[i]; + if (rela.isHalfLink()) continue; + link = temp[rela.id]; + if (!link) { + link = new ysy.view.GanttLink(); + link.init(rela); + } else { + delete temp[rela.id]; + } + narr.push(link); + } + for (var key in temp) { + if (temp.hasOwnProperty(key)) { + temp[key].destroy(true); + } + } + this.children = narr; + } + ysy.log.log("-- " + this.children.length + " Children updated in " + this.name); + }, + _repaintCore: function () { + //this._updateTaskInGantt(); + for (var i = 0; i < this.children.length; i++) { + var child = this.children[i]; + //this.setChildTarget(child, i); // SET CHILD TARGET + child.repaint(true); // CHILD REPAINT + } + //gantt.refreshData(); + } +}); +//############################################################################## +ysy.view.GanttLink = function () { + ysy.view.Widget.call(this); + this.name = "GanttLinkWidget"; +}; +ysy.main.extender(ysy.view.Widget, ysy.view.GanttLink, { + _repaintCore: function () { + var rela = this.model; + var link = this._constructDhData(rela); + if (gantt._lpull[link.id]) { + $.extend(this.dhdata, link); + gantt.refreshLink(link.id); + gantt.refreshTask(link.source); + gantt.refreshTask(link.target); + } else { + this.dhdata = link; + gantt.addLink(link); + } + //gantt.sort(); + }, + destroy: function (silent) { + if (this.dhdata) { + this.dhdata.deleted = true; + if (gantt.isLinkExists(this.model.id)) { + gantt._deleteLink(this.model.id, silent); + } + this.dhdata = null; + ysy.log.debug("Destroy for " + this.name, "widget_destroy"); + } + this.deleted = true; + }, + _constructDhData: function (model) { + return { + id: model.id, + source: model.source_id, + target: model.target_id, + type: model.isSimple ? "start_to_start" : model.type, + isSimple: model.isSimple, + unlocked: model.unlocked, + delay: model.delay, + readonly: !model.isEditable(), + widget: this + }; + }, + update: function (item) { + ysy.history.openBrack(); + this.model.set({ + // name: item.text, + // source_id: item.source, + // target_id: item.target, + type: this.model.type || item.type, + delay: item.delay + }); + var allRequests = {}; + this.model.sendMoveRequest(allRequests); + gantt.applyMoveRequests(allRequests); + ysy.history.closeBrack(); + } + +}); +//############################################################################## +ysy.view.GanttRefresher = function () { + ysy.view.Widget.call(this); + this.name = "GanttRefresherWidget"; + this.all = false; + this.data = false; + this.tasks = []; + this.links = []; +}; +ysy.main.extender(ysy.view.Widget, ysy.view.GanttRefresher, { + _postInit: function () { + this.model.refresher = this; + }, + _register: function () { + }, + renderAll: function () { + this.all = true; + this.requestRepaint(); + }, + renderData: function () { + this.data = true; + this.requestRepaint(); + }, + refreshTask: function (taskId) { + for (var i = 0; i < this.tasks.length; i++) { + if (this.tasks[i] == taskId) return; + } + this.tasks.push(taskId); + this.requestRepaint(); + }, + refreshLink: function (linkId) { + for (var i = 0; i < this.links.length; i++) { + if (this.links[i] == linkId) return; + } + this.links.push(linkId); + this.requestRepaint(); + }, + _repaintCore: function () { + if (this.all) { + ysy.log.debug("---- Refresher: _renderAll", "refresher"); + var visibleDate = gantt.getShowDate(); + if (!visibleDate) { + visibleDate = ysy.data.limits.zoomDate; + } + gantt._backgroundRenderer.forceRender = false; + this.model._render(); + gantt._backgroundRenderer.forceRender = true; + //ysy.log.debug(moment(visibleDate).toISOString(), "refresher"); + gantt.showDate(visibleDate); + delete gantt._backgroundRenderer.forceRender; + ysy.view.affix.requestRepaint(); + } else if (this.data) { + ysy.log.debug("---- Refresher: _renderData", "refresher"); + this.model._render_data(); + if (this.all) return true; + } else { + ysy.log.debug("---- Refresher: _render for " + this.tasks.length + " tasks and " + this.links.length + " links", "refresher"); + for (var i = 0; i < this.tasks.length; i++) { + var taskId = this.tasks[i]; + this.model._refreshTask(taskId); + if (this.all || this.data) return true; + } + for (i = 0; i < this.links.length; i++) { + var linkId = this.links[i]; + this.model._refreshLink(linkId); + if (this.all || this.data) return true; + } + } + this.all = false; + this.data = false; + this.tasks = []; + this.links = []; + } + +}); diff --git a/easy_gantt/assets/javascripts/easy_gantt/history.js b/easy_gantt/assets/javascripts/easy_gantt/history.js new file mode 100644 index 0000000..2734190 --- /dev/null +++ b/easy_gantt/assets/javascripts/easy_gantt/history.js @@ -0,0 +1,130 @@ +/* history.js */ +/* global ysy */ +window.ysy = window.ysy || {}; +ysy.history = ysy.history || {}; +$.extend(ysy.history, { + _name: "History", + _inbrack: 0, + stack: [], + _binded: false, + add: function (diff, ctx) { + this.stack.push({diff: diff, ctx: ctx}); + this._fireChanges(this, "add"); + this._bind(); + }, + revert: function (who) { + var first = true; + if (this.stack.length === 0) return; + while (this._inbrack || first) { + first = false; + var rev = this.stack.pop(); + if (!rev) return; + if (typeof rev === "string") { + if (rev === "close") { + this._inbrack++; + continue; + } else if (rev === "open") { + this._inbrack--; + break; + } + } + this._revertOne(rev); + } + //console.log("Revert by "+(typeof rev.diff)); + if (this.stack.length === 0) { + this._unbind(); + } + this._fireChanges(who, "revert"); + }, + _revertOne: function (rev) { + if (typeof rev.diff === "array") { + rev.ctx.array = rev.diff; + rev.ctx._fireChanges(this, "revert"); + } else if (typeof rev.diff === "function") { + $.proxy(rev.diff, rev.ctx)(); + rev.ctx._fireChanges(this, "revert"); + } else { + //rev.ctx.set(rev.diff,true); + $.extend(rev.ctx, rev.diff); + rev.ctx._fireChanges(this, "revert"); + } + + }, + clear: function (who) { + if (this.stack.length > 0) { + this._unbind(); + } + this.stack = []; + this._inbrack = 0; + this._fireChanges(who, "clear"); + }, + openBrack: function () { + if (this._inbrack) { + + } else { + this.stack.push("open"); + } + this._inbrack++; + }, + closeBrack: function () { + if (this._inbrack === 0) { + ysy.log.error("History bracket is not opened"); + } + if (this._inbrack === 1) { + if (this.stack[this.stack.length - 1] === "open") { + this.stack.pop(); + } else { + this.stack.push("close"); + } + } + this._inbrack--; + }, + _bind: function () { + if (this._binded)return; + $(window).bind('beforeunload', function (e) { + var message = "Some changes are not saved!"; + e.returnValue = message; + return message; + }); + }, + _unbind: function () { + $(window).unbind('beforeunload'); + this._binded = false; + }, + _onChange: [], + register: function (func, ctx) { + this._onChange.push({func: func, ctx: ctx}); + }, + inBracket: function () { + return this._inbrack > 0 + }, + isEmpty: function () { + return !this.stack.length; + }, + _fireChanges: function (who, reason) { + if (who) { + var rest = ""; + if (reason) { + rest = " because of " + reason; + } + ysy.log.log("* " + who._name + " ordered repaint on " + this._name + "" + rest); + + } + if (this._onChange.length > 0) { + ysy.log.log("- " + this._name + " onChange fired for " + this._onChange.length + " widgets"); + } else { + ysy.log.log("- no changes for " + this._name); + } + for (var i = 0; i < this._onChange.length; i++) { + var ctx = this._onChange[i].ctx; + if (!ctx || ctx.deleted) { + this._onChange.splice(i, 1); + continue; + } + //this.onChangeNew[i].func(); + ysy.log.log("-- changes to " + ctx.name + " widget"); + //console.log(ctx); + $.proxy(this._onChange[i].func, ctx)(); + } + } +}); \ No newline at end of file diff --git a/easy_gantt/assets/javascripts/easy_gantt/left_grid.js b/easy_gantt/assets/javascripts/easy_gantt/left_grid.js new file mode 100644 index 0000000..1dcf53c --- /dev/null +++ b/easy_gantt/assets/javascripts/easy_gantt/left_grid.js @@ -0,0 +1,331 @@ +/* left_grid.js */ +/* global ysy */ +window.ysy = window.ysy || {}; +ysy.view = ysy.view || {}; +ysy.view.leftGrid = ysy.view.leftGrid || {}; +$.extend(ysy.view.leftGrid, { + columnsWidth: { + id: 60, + subject: 200, + name: 200, + project: 140, + other: 70, + updated_on: 85, + assigned_to: 100, + grid_width: 400 + }, + patch: function () { + ysy.data.limits.columnsWidth = $.extend({}, this.columnsWidth); + ysy.view.columnBuilders = ysy.view.columnBuilders || {}; + $.extend(ysy.view.columnBuilders, { + id: function (obj) { + if (obj.id > 1000000000000) return ''; + var path = ysy.settings.paths.rootPath + "issues/"; + return "#" + obj.id + ""; + }, + updated_on: function (obj) { + if (!obj.columns)return ""; + var value = obj.columns.updated_on; + if (value) { + return moment.utc(value, 'YYYY-MM-DD HH:mm:ss ZZ').fromNow(); + } else { + return ""; + } + }, + done_ratio: function (obj) { + if (!obj.columns)return ""; + //return ''+Math.round(obj.progress*10)*10+''; + return '' + Math.round(obj.progress * 10) * 10 + ''; + }, + estimated_hours: function (obj) { + if (!obj.columns)return ""; + return '' + obj.estimated + ''; + }, + subject: function (obj) { + var id = parseInt(obj.real_id); + var text = ysy.main.escapeText(obj.text); + if (isNaN(id) || id > 1000000000000) return text; + var path = ysy.settings.paths.rootPath + "issues/"; + if (obj.type === "milestone") { + path = ysy.settings.paths.rootPath + "versions/" + } else if (obj.type === "project") { + path = ysy.settings.paths.rootPath + "projects/" + } else if (obj.type === "assignee") { + if (obj.subtype === "group") { + path = ysy.settings.paths.rootPath + "groups/" + } else { + path = ysy.settings.paths.rootPath + "users/" + } + } + return "" + text + ""; + }, + _default: function (col) { + return function (obj) { + if (!obj.columns) return ""; + if (col.dont_escape) return obj.columns[col.name]; + return ysy.main.escapeText(obj.columns[col.name] || ""); + }; + } + + }); + gantt._render_grid_superitem = function (item) { + var subjectColumn = ysy.view.columnBuilders.subject; + + var tree = ""; + for (var j = 0; j < item.$level; j++) + tree += this.templates.grid_indent(item); + var has_child = this._has_children(item.id); + if (has_child) { + tree += this.templates.grid_open(item); + tree += this.templates.grid_folder(item); + } else { + tree += this.templates.grid_blank(item); + tree += this.templates.grid_file(item); + } + var afterText = this.templates.superitem_after_text(item, has_child); + + var odd = item.$index % 2 === 0; + var style = "";//"width:" + (col.width - (last ? 1 : 0)) + "px;"; + var cell = "
" + tree + subjectColumn(item) + afterText + "
"; + + var css = odd ? " odd" : ""; + if (this.templates.grid_row_class) { + var css_template = this.templates.grid_row_class.call(this, item.start_date, item.end_date, item); + if (css_template) + css += " " + css_template; + } + + if (this.getState().selected_task == item.id) { + css += " gantt_selected"; + } + var el = document.createElement("div"); + el.className = "gantt_row" + css; + //el.setAttribute("data-url","/issues/"+item.id+".json"); // HOSEK + el.style.height = this.config.row_height + "px"; + el.style.lineHeight = (gantt.config.row_height) + "px"; + el.setAttribute(this.config.task_attribute, item.id); + el.innerHTML = cell; + return el; + }; + $.extend(gantt.templates, { + grid_open:function(item) { + return "
"; + }, + grid_folder: function (item) { + /// = HAS CHILDREN + if (this["grid_bullet_" + gantt._get_safe_type(item.type)]) { + return this["grid_bullet_" + gantt._get_safe_type(item.type)](item, true); + } + // default fallback + if (item.$open || gantt._get_safe_type(item.type) !== gantt.config.types.task) { + return "
"; + } else { + return "
"; + } + }, + grid_file: function (item) { + // = HAS NO CHILDREN + if (this["grid_bullet_" + gantt._get_safe_type(item.type)]) { + return this["grid_bullet_" + gantt._get_safe_type(item.type)](item, false); + } + // default fallback + if (gantt._get_safe_type(item.type) === gantt.config.types.task) + return "
"; + return "
"; + }, + grid_bullet_milestone: function (item, has_children) { + var rearrangable = false; + return "
" + + "
"; + }, + grid_bullet_project: function (item, has_children) { + if (item.$open || !has_children) { + return "
"; + } else { + return "
"; + } + }, + grid_bullet_task: function (item, has_children) { + if (has_children) { + return "
"; + } else { + return "
"; + } + }, + superitem_after_text: function (item, has_children) { + if (this["superitem_after_" + gantt._get_safe_type(item.type)]) { + return this["superitem_after_" + gantt._get_safe_type(item.type)](item, has_children); + } + return ""; + } + }); + gantt._render_grid_header = function () { + var columns = this.getGridColumns(); + var cells = []; + var width = 0, + labels = this.locale.labels; + + var lineHeigth = this.config.scale_height - 2; + var resizes = []; + + for (var i = 0; i < columns.length; i++) { + var last = i === columns.length - 1; + var col = columns[i]; + if (last && this._get_grid_width() > width + col.width) + col.width = this._get_grid_width() - width; + width += col.width; + var sort = (this._sort && col.name === this._sort.name) ? ("
") : ""; + if (col.tree) { + if (!this._sort) sort = '
'; + if (ysy.pro.collapsor) { + sort += ysy.pro.collapsor.templateHtml; + } + } + var cssClass = ["gantt_grid_head_cell", + ("gantt_grid_head_" + col.name), + (last ? "gantt_last_cell" : ""), + this.templates.grid_header_class(col.name, col)].join(" "); + + var style = "width:" + (col.width - (last ? 1 : 0)) + "px;"; + var label = (col.label || labels["column_" + col.name]); + label = label || ""; + var cell = "
" + label + sort + "
"; + if (!last) { + resizes.push("
"); + } + cells.push(cell); + //var resize='
'; + /*var resize = '
\ +
'; + resizes.push(resize);*/ + } + //var resize = '
\ + //
'; + this.$grid_resize.style.left = (this._get_grid_width() - 6) + "px"; + this.$grid_scale.style.height = (this.config.scale_height - 1) + "px"; + this.$grid_scale.style.lineHeight = lineHeigth + "px"; + this.$grid_scale.style.width = (width - 1) + "px"; + this.$grid_scale.style.position = "relative"; + this.$grid_scale.innerHTML = cells.join("") + resizes.join(""); + ysy.view.leftGrid.resizeTable(); + if (ysy.view.collapsors) { + ysy.view.collapsors.requestRepaint(); + } + //resizeColumns(); + }; + gantt._calc_grid_width = function () { + var i; + var columns = this.getGridColumns(); + var cols_width = 0; + var width = []; + + for (i = 0; i < columns.length; i++) { + var v = parseInt(columns[i].min_width, 10); + width[i] = v; + cols_width += v; + } + + var diff = this._get_grid_width() - cols_width; + if (this.config.autofit || diff > 0) { + var delta = Math.ceil(diff / (columns.length ? columns.length : 1)); + //var ratio=1+diff/(cols_width?cols_width:1); + for (i = 0; i < width.length; i++) { + columns[i].width = columns[i].min_width + delta;//*ratio; + } + } else { + for (i = 0; i < columns.length; i++) { + columns[i].width = columns[i].min_width; + } + //this.config.grid_width = cols_width; + } + }; + }, + constructColumns: function (columns) { + var dcolumns = []; + var columnBuilders = ysy.view.columnBuilders; + var getBuilder = function (col) { + if (columnBuilders[col.name]) { + return columnBuilders[col.name]; + } else if (columnBuilders[col.name + "Builder"]) { + return columnBuilders[col.name + "Builder"](col); + } + else return columnBuilders._default(col); + }; + for (var i = 0; i < columns.length; i++) { + var col = columns[i]; + var isMainColumn = col.name === "subject" || col.name === "name"; + if (col.name === "id" && !ysy.settings.easyRedmine) continue; + var css = "gantt_grid_body_" + col.name; + if (col.name !== "") { + var width = ysy.data.limits.columnsWidth[col.name] || ysy.data.limits.columnsWidth["other"]; + var dcolumn = { + name: col.name, + label: col.title, + min_width: width, + width: width, + tree: isMainColumn, + align: isMainColumn ? "left" : "center", + template: getBuilder(col), + css: css + }; + if (isMainColumn) { + dcolumns.unshift(dcolumn); + } else { + dcolumns.push(dcolumn); + } + } + } + return dcolumns; + }, + resizeTable: function () { + var $resizes = $(".gantt_grid_column_resize_wrap:not(inited)"); + var colWidths = ysy.data.limits.columnsWidth; + var $gantt_grid = $(".gantt_grid"); + var $gantt_grid_data = $(".gantt_grid_data"); + var $gantt_grid_scale = $(".gantt_grid_scale"); + $resizes.each(function (index, el) { + var config = {}; + var $el = $(el); + var column = $el.data("column_id"); + var dhtmlxDrag = new dhtmlxDnD(el, config); + var minWidth, + realWidth, + resizePos, + gridWidth; + dhtmlxDrag.attachEvent("onDragStart", function () { + if (this.config.started) return; + minWidth = colWidths[column] || colWidths.other; + realWidth = $gantt_grid.find(".gantt_grid_head_" + column).width(); + gridWidth = $gantt_grid.width(); + resizePos = $el.offset(); + }); + dhtmlxDrag.attachEvent("onDragMove", function (target, event) { + //var diff=Math.floor(event.pageX-lastPos); + var diff = Math.floor(dhtmlxDrag.getDiff().x); + ysy.log.debug("moveDrag diff=" + diff + "px width=" + realWidth + "px", "grid_resize"); + + $gantt_grid.width(gridWidth + diff); + $gantt_grid_data.width(gridWidth + diff); + $gantt_grid_scale.width(gridWidth + diff); + $el.offset({top: resizePos.top, left: resizePos.left + diff}); + colWidths[column] = minWidth + diff; + var columns = gantt.config.columns; + if (index < columns.length - 1) { + gantt.config.columns[index].min_width = minWidth + diff; + gantt.config.columns[index].width = realWidth + diff + 1; + $gantt_grid.find(".gantt_grid_head_" + column + ", .gantt_grid_body_" + column).width(realWidth + diff + "px"); + } + gantt.config.grid_width = gridWidth + diff; + colWidths.grid_width = gridWidth + diff; + }); + dhtmlxDrag.attachEvent("onDragEnd", function (target, event) { + gantt.render(); + //gantt._render_grid(); + //var data = gantt._get_tasks_data(); + //gantt._gridRenderer.render_items(data); + //ysy.view.ganttTasks.requestRepaint(); + }); + }); + $resizes.addClass("inited"); + } +}); diff --git a/easy_gantt/assets/javascripts/easy_gantt/libs/moment.js b/easy_gantt/assets/javascripts/easy_gantt/libs/moment.js new file mode 100644 index 0000000..e606ed7 --- /dev/null +++ b/easy_gantt/assets/javascripts/easy_gantt/libs/moment.js @@ -0,0 +1,12915 @@ +//! moment.js +//! version : 2.17.1 +//! authors : Tim Wood, Iskren Chernev, Moment.js contributors +//! license : MIT +//! momentjs.com +;(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : + typeof define === 'function' && define.amd ? define(factory) : + global.moment = factory() +}(this, (function () { 'use strict'; + +var hookCallback; + +function hooks () { + return hookCallback.apply(null, arguments); +} + +// This is done to register the method called with moment() +// without creating circular dependencies. +function setHookCallback (callback) { + hookCallback = callback; +} + +function isArray(input) { + return input instanceof Array || Object.prototype.toString.call(input) === '[object Array]'; +} + +function isObject(input) { + // IE8 will treat undefined and null as object if it wasn't for + // input != null + return input != null && Object.prototype.toString.call(input) === '[object Object]'; +} + +function isObjectEmpty(obj) { + var k; + for (k in obj) { + // even if its not own property I'd still call it non-empty + return false; + } + return true; +} + +function isNumber(input) { + return typeof input === 'number' || Object.prototype.toString.call(input) === '[object Number]'; +} + +function isDate(input) { + return input instanceof Date || Object.prototype.toString.call(input) === '[object Date]'; +} + +function map(arr, fn) { + var res = [], i; + for (i = 0; i < arr.length; ++i) { + res.push(fn(arr[i], i)); + } + return res; +} + +function hasOwnProp(a, b) { + return Object.prototype.hasOwnProperty.call(a, b); +} + +function extend(a, b) { + for (var i in b) { + if (hasOwnProp(b, i)) { + a[i] = b[i]; + } + } + + if (hasOwnProp(b, 'toString')) { + a.toString = b.toString; + } + + if (hasOwnProp(b, 'valueOf')) { + a.valueOf = b.valueOf; + } + + return a; +} + +function createUTC (input, format, locale, strict) { + return createLocalOrUTC(input, format, locale, strict, true).utc(); +} + +function defaultParsingFlags() { + // We need to deep clone this object. + return { + empty : false, + unusedTokens : [], + unusedInput : [], + overflow : -2, + charsLeftOver : 0, + nullInput : false, + invalidMonth : null, + invalidFormat : false, + userInvalidated : false, + iso : false, + parsedDateParts : [], + meridiem : null + }; +} + +function getParsingFlags(m) { + if (m._pf == null) { + m._pf = defaultParsingFlags(); + } + return m._pf; +} + +var some; +if (Array.prototype.some) { + some = Array.prototype.some; +} else { + some = function (fun) { + var t = Object(this); + var len = t.length >>> 0; + + for (var i = 0; i < len; i++) { + if (i in t && fun.call(this, t[i], i, t)) { + return true; + } + } + + return false; + }; +} + +var some$1 = some; + +function isValid(m) { + if (m._isValid == null) { + var flags = getParsingFlags(m); + var parsedParts = some$1.call(flags.parsedDateParts, function (i) { + return i != null; + }); + var isNowValid = !isNaN(m._d.getTime()) && + flags.overflow < 0 && + !flags.empty && + !flags.invalidMonth && + !flags.invalidWeekday && + !flags.nullInput && + !flags.invalidFormat && + !flags.userInvalidated && + (!flags.meridiem || (flags.meridiem && parsedParts)); + + if (m._strict) { + isNowValid = isNowValid && + flags.charsLeftOver === 0 && + flags.unusedTokens.length === 0 && + flags.bigHour === undefined; + } + + if (Object.isFrozen == null || !Object.isFrozen(m)) { + m._isValid = isNowValid; + } + else { + return isNowValid; + } + } + return m._isValid; +} + +function createInvalid (flags) { + var m = createUTC(NaN); + if (flags != null) { + extend(getParsingFlags(m), flags); + } + else { + getParsingFlags(m).userInvalidated = true; + } + + return m; +} + +function isUndefined(input) { + return input === void 0; +} + +// Plugins that add properties should also add the key here (null value), +// so we can properly clone ourselves. +var momentProperties = hooks.momentProperties = []; + +function copyConfig(to, from) { + var i, prop, val; + + if (!isUndefined(from._isAMomentObject)) { + to._isAMomentObject = from._isAMomentObject; + } + if (!isUndefined(from._i)) { + to._i = from._i; + } + if (!isUndefined(from._f)) { + to._f = from._f; + } + if (!isUndefined(from._l)) { + to._l = from._l; + } + if (!isUndefined(from._strict)) { + to._strict = from._strict; + } + if (!isUndefined(from._tzm)) { + to._tzm = from._tzm; + } + if (!isUndefined(from._isUTC)) { + to._isUTC = from._isUTC; + } + if (!isUndefined(from._offset)) { + to._offset = from._offset; + } + if (!isUndefined(from._pf)) { + to._pf = getParsingFlags(from); + } + if (!isUndefined(from._locale)) { + to._locale = from._locale; + } + + if (momentProperties.length > 0) { + for (i in momentProperties) { + prop = momentProperties[i]; + val = from[prop]; + if (!isUndefined(val)) { + to[prop] = val; + } + } + } + + return to; +} + +var updateInProgress = false; + +// Moment prototype object +function Moment(config) { + copyConfig(this, config); + this._d = new Date(config._d != null ? config._d.getTime() : NaN); + if (!this.isValid()) { + this._d = new Date(NaN); + } + // Prevent infinite loop in case updateOffset creates new moment + // objects. + if (updateInProgress === false) { + updateInProgress = true; + hooks.updateOffset(this); + updateInProgress = false; + } +} + +function isMoment (obj) { + return obj instanceof Moment || (obj != null && obj._isAMomentObject != null); +} + +function absFloor (number) { + if (number < 0) { + // -0 -> 0 + return Math.ceil(number) || 0; + } else { + return Math.floor(number); + } +} + +function toInt(argumentForCoercion) { + var coercedNumber = +argumentForCoercion, + value = 0; + + if (coercedNumber !== 0 && isFinite(coercedNumber)) { + value = absFloor(coercedNumber); + } + + return value; +} + +// compare two arrays, return the number of differences +function compareArrays(array1, array2, dontConvert) { + var len = Math.min(array1.length, array2.length), + lengthDiff = Math.abs(array1.length - array2.length), + diffs = 0, + i; + for (i = 0; i < len; i++) { + if ((dontConvert && array1[i] !== array2[i]) || + (!dontConvert && toInt(array1[i]) !== toInt(array2[i]))) { + diffs++; + } + } + return diffs + lengthDiff; +} + +function warn(msg) { + if (hooks.suppressDeprecationWarnings === false && + (typeof console !== 'undefined') && console.warn) { + console.warn('Deprecation warning: ' + msg); + } +} + +function deprecate(msg, fn) { + var firstTime = true; + + return extend(function () { + if (hooks.deprecationHandler != null) { + hooks.deprecationHandler(null, msg); + } + if (firstTime) { + var args = []; + var arg; + for (var i = 0; i < arguments.length; i++) { + arg = ''; + if (typeof arguments[i] === 'object') { + arg += '\n[' + i + '] '; + for (var key in arguments[0]) { + arg += key + ': ' + arguments[0][key] + ', '; + } + arg = arg.slice(0, -2); // Remove trailing comma and space + } else { + arg = arguments[i]; + } + args.push(arg); + } + warn(msg + '\nArguments: ' + Array.prototype.slice.call(args).join('') + '\n' + (new Error()).stack); + firstTime = false; + } + return fn.apply(this, arguments); + }, fn); +} + +var deprecations = {}; + +function deprecateSimple(name, msg) { + if (hooks.deprecationHandler != null) { + hooks.deprecationHandler(name, msg); + } + if (!deprecations[name]) { + warn(msg); + deprecations[name] = true; + } +} + +hooks.suppressDeprecationWarnings = false; +hooks.deprecationHandler = null; + +function isFunction(input) { + return input instanceof Function || Object.prototype.toString.call(input) === '[object Function]'; +} + +function set (config) { + var prop, i; + for (i in config) { + prop = config[i]; + if (isFunction(prop)) { + this[i] = prop; + } else { + this['_' + i] = prop; + } + } + this._config = config; + // Lenient ordinal parsing accepts just a number in addition to + // number + (possibly) stuff coming from _ordinalParseLenient. + this._ordinalParseLenient = new RegExp(this._ordinalParse.source + '|' + (/\d{1,2}/).source); +} + +function mergeConfigs(parentConfig, childConfig) { + var res = extend({}, parentConfig), prop; + for (prop in childConfig) { + if (hasOwnProp(childConfig, prop)) { + if (isObject(parentConfig[prop]) && isObject(childConfig[prop])) { + res[prop] = {}; + extend(res[prop], parentConfig[prop]); + extend(res[prop], childConfig[prop]); + } else if (childConfig[prop] != null) { + res[prop] = childConfig[prop]; + } else { + delete res[prop]; + } + } + } + for (prop in parentConfig) { + if (hasOwnProp(parentConfig, prop) && + !hasOwnProp(childConfig, prop) && + isObject(parentConfig[prop])) { + // make sure changes to properties don't modify parent config + res[prop] = extend({}, res[prop]); + } + } + return res; +} + +function Locale(config) { + if (config != null) { + this.set(config); + } +} + +var keys; + +if (Object.keys) { + keys = Object.keys; +} else { + keys = function (obj) { + var i, res = []; + for (i in obj) { + if (hasOwnProp(obj, i)) { + res.push(i); + } + } + return res; + }; +} + +var keys$1 = keys; + +var defaultCalendar = { + sameDay : '[Today at] LT', + nextDay : '[Tomorrow at] LT', + nextWeek : 'dddd [at] LT', + lastDay : '[Yesterday at] LT', + lastWeek : '[Last] dddd [at] LT', + sameElse : 'L' +}; + +function calendar (key, mom, now) { + var output = this._calendar[key] || this._calendar['sameElse']; + return isFunction(output) ? output.call(mom, now) : output; +} + +var defaultLongDateFormat = { + LTS : 'h:mm:ss A', + LT : 'h:mm A', + L : 'MM/DD/YYYY', + LL : 'MMMM D, YYYY', + LLL : 'MMMM D, YYYY h:mm A', + LLLL : 'dddd, MMMM D, YYYY h:mm A' +}; + +function longDateFormat (key) { + var format = this._longDateFormat[key], + formatUpper = this._longDateFormat[key.toUpperCase()]; + + if (format || !formatUpper) { + return format; + } + + this._longDateFormat[key] = formatUpper.replace(/MMMM|MM|DD|dddd/g, function (val) { + return val.slice(1); + }); + + return this._longDateFormat[key]; +} + +var defaultInvalidDate = 'Invalid date'; + +function invalidDate () { + return this._invalidDate; +} + +var defaultOrdinal = '%d'; +var defaultOrdinalParse = /\d{1,2}/; + +function ordinal (number) { + return this._ordinal.replace('%d', number); +} + +var defaultRelativeTime = { + future : 'in %s', + past : '%s ago', + s : 'a few seconds', + m : 'a minute', + mm : '%d minutes', + h : 'an hour', + hh : '%d hours', + d : 'a day', + dd : '%d days', + M : 'a month', + MM : '%d months', + y : 'a year', + yy : '%d years' +}; + +function relativeTime (number, withoutSuffix, string, isFuture) { + var output = this._relativeTime[string]; + return (isFunction(output)) ? + output(number, withoutSuffix, string, isFuture) : + output.replace(/%d/i, number); +} + +function pastFuture (diff, output) { + var format = this._relativeTime[diff > 0 ? 'future' : 'past']; + return isFunction(format) ? format(output) : format.replace(/%s/i, output); +} + +var aliases = {}; + +function addUnitAlias (unit, shorthand) { + var lowerCase = unit.toLowerCase(); + aliases[lowerCase] = aliases[lowerCase + 's'] = aliases[shorthand] = unit; +} + +function normalizeUnits(units) { + return typeof units === 'string' ? aliases[units] || aliases[units.toLowerCase()] : undefined; +} + +function normalizeObjectUnits(inputObject) { + var normalizedInput = {}, + normalizedProp, + prop; + + for (prop in inputObject) { + if (hasOwnProp(inputObject, prop)) { + normalizedProp = normalizeUnits(prop); + if (normalizedProp) { + normalizedInput[normalizedProp] = inputObject[prop]; + } + } + } + + return normalizedInput; +} + +var priorities = {}; + +function addUnitPriority(unit, priority) { + priorities[unit] = priority; +} + +function getPrioritizedUnits(unitsObj) { + var units = []; + for (var u in unitsObj) { + units.push({unit: u, priority: priorities[u]}); + } + units.sort(function (a, b) { + return a.priority - b.priority; + }); + return units; +} + +function makeGetSet (unit, keepTime) { + return function (value) { + if (value != null) { + set$1(this, unit, value); + hooks.updateOffset(this, keepTime); + return this; + } else { + return get(this, unit); + } + }; +} + +function get (mom, unit) { + return mom.isValid() ? + mom._d['get' + (mom._isUTC ? 'UTC' : '') + unit]() : NaN; +} + +function set$1 (mom, unit, value) { + if (mom.isValid()) { + mom._d['set' + (mom._isUTC ? 'UTC' : '') + unit](value); + } +} + +// MOMENTS + +function stringGet (units) { + units = normalizeUnits(units); + if (isFunction(this[units])) { + return this[units](); + } + return this; +} + + +function stringSet (units, value) { + if (typeof units === 'object') { + units = normalizeObjectUnits(units); + var prioritized = getPrioritizedUnits(units); + for (var i = 0; i < prioritized.length; i++) { + this[prioritized[i].unit](units[prioritized[i].unit]); + } + } else { + units = normalizeUnits(units); + if (isFunction(this[units])) { + return this[units](value); + } + } + return this; +} + +function zeroFill(number, targetLength, forceSign) { + var absNumber = '' + Math.abs(number), + zerosToFill = targetLength - absNumber.length, + sign = number >= 0; + return (sign ? (forceSign ? '+' : '') : '-') + + Math.pow(10, Math.max(0, zerosToFill)).toString().substr(1) + absNumber; +} + +var formattingTokens = /(\[[^\[]*\])|(\\)?([Hh]mm(ss)?|Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|W[o|W]?|Qo?|YYYYYY|YYYYY|YYYY|YY|gg(ggg?)?|GG(GGG?)?|e|E|a|A|hh?|HH?|kk?|mm?|ss?|S{1,9}|x|X|zz?|ZZ?|.)/g; + +var localFormattingTokens = /(\[[^\[]*\])|(\\)?(LTS|LT|LL?L?L?|l{1,4})/g; + +var formatFunctions = {}; + +var formatTokenFunctions = {}; + +// token: 'M' +// padded: ['MM', 2] +// ordinal: 'Mo' +// callback: function () { this.month() + 1 } +function addFormatToken (token, padded, ordinal, callback) { + var func = callback; + if (typeof callback === 'string') { + func = function () { + return this[callback](); + }; + } + if (token) { + formatTokenFunctions[token] = func; + } + if (padded) { + formatTokenFunctions[padded[0]] = function () { + return zeroFill(func.apply(this, arguments), padded[1], padded[2]); + }; + } + if (ordinal) { + formatTokenFunctions[ordinal] = function () { + return this.localeData().ordinal(func.apply(this, arguments), token); + }; + } +} + +function removeFormattingTokens(input) { + if (input.match(/\[[\s\S]/)) { + return input.replace(/^\[|\]$/g, ''); + } + return input.replace(/\\/g, ''); +} + +function makeFormatFunction(format) { + var array = format.match(formattingTokens), i, length; + + for (i = 0, length = array.length; i < length; i++) { + if (formatTokenFunctions[array[i]]) { + array[i] = formatTokenFunctions[array[i]]; + } else { + array[i] = removeFormattingTokens(array[i]); + } + } + + return function (mom) { + var output = '', i; + for (i = 0; i < length; i++) { + output += array[i] instanceof Function ? array[i].call(mom, format) : array[i]; + } + return output; + }; +} + +// format date using native date object +function formatMoment(m, format) { + if (!m.isValid()) { + return m.localeData().invalidDate(); + } + + format = expandFormat(format, m.localeData()); + formatFunctions[format] = formatFunctions[format] || makeFormatFunction(format); + + return formatFunctions[format](m); +} + +function expandFormat(format, locale) { + var i = 5; + + function replaceLongDateFormatTokens(input) { + return locale.longDateFormat(input) || input; + } + + localFormattingTokens.lastIndex = 0; + while (i >= 0 && localFormattingTokens.test(format)) { + format = format.replace(localFormattingTokens, replaceLongDateFormatTokens); + localFormattingTokens.lastIndex = 0; + i -= 1; + } + + return format; +} + +var match1 = /\d/; // 0 - 9 +var match2 = /\d\d/; // 00 - 99 +var match3 = /\d{3}/; // 000 - 999 +var match4 = /\d{4}/; // 0000 - 9999 +var match6 = /[+-]?\d{6}/; // -999999 - 999999 +var match1to2 = /\d\d?/; // 0 - 99 +var match3to4 = /\d\d\d\d?/; // 999 - 9999 +var match5to6 = /\d\d\d\d\d\d?/; // 99999 - 999999 +var match1to3 = /\d{1,3}/; // 0 - 999 +var match1to4 = /\d{1,4}/; // 0 - 9999 +var match1to6 = /[+-]?\d{1,6}/; // -999999 - 999999 + +var matchUnsigned = /\d+/; // 0 - inf +var matchSigned = /[+-]?\d+/; // -inf - inf + +var matchOffset = /Z|[+-]\d\d:?\d\d/gi; // +00:00 -00:00 +0000 -0000 or Z +var matchShortOffset = /Z|[+-]\d\d(?::?\d\d)?/gi; // +00 -00 +00:00 -00:00 +0000 -0000 or Z + +var matchTimestamp = /[+-]?\d+(\.\d{1,3})?/; // 123456789 123456789.123 + +// any word (or two) characters or numbers including two/three word month in arabic. +// includes scottish gaelic two word and hyphenated months +var matchWord = /[0-9]*['a-z\u00A0-\u05FF\u0700-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+|[\u0600-\u06FF\/]+(\s*?[\u0600-\u06FF]+){1,2}/i; + + +var regexes = {}; + +function addRegexToken (token, regex, strictRegex) { + regexes[token] = isFunction(regex) ? regex : function (isStrict, localeData) { + return (isStrict && strictRegex) ? strictRegex : regex; + }; +} + +function getParseRegexForToken (token, config) { + if (!hasOwnProp(regexes, token)) { + return new RegExp(unescapeFormat(token)); + } + + return regexes[token](config._strict, config._locale); +} + +// Code from http://stackoverflow.com/questions/3561493/is-there-a-regexp-escape-function-in-javascript +function unescapeFormat(s) { + return regexEscape(s.replace('\\', '').replace(/\\(\[)|\\(\])|\[([^\]\[]*)\]|\\(.)/g, function (matched, p1, p2, p3, p4) { + return p1 || p2 || p3 || p4; + })); +} + +function regexEscape(s) { + return s.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&'); +} + +var tokens = {}; + +function addParseToken (token, callback) { + var i, func = callback; + if (typeof token === 'string') { + token = [token]; + } + if (isNumber(callback)) { + func = function (input, array) { + array[callback] = toInt(input); + }; + } + for (i = 0; i < token.length; i++) { + tokens[token[i]] = func; + } +} + +function addWeekParseToken (token, callback) { + addParseToken(token, function (input, array, config, token) { + config._w = config._w || {}; + callback(input, config._w, config, token); + }); +} + +function addTimeToArrayFromToken(token, input, config) { + if (input != null && hasOwnProp(tokens, token)) { + tokens[token](input, config._a, config, token); + } +} + +var YEAR = 0; +var MONTH = 1; +var DATE = 2; +var HOUR = 3; +var MINUTE = 4; +var SECOND = 5; +var MILLISECOND = 6; +var WEEK = 7; +var WEEKDAY = 8; + +var indexOf; + +if (Array.prototype.indexOf) { + indexOf = Array.prototype.indexOf; +} else { + indexOf = function (o) { + // I know + var i; + for (i = 0; i < this.length; ++i) { + if (this[i] === o) { + return i; + } + } + return -1; + }; +} + +var indexOf$1 = indexOf; + +function daysInMonth(year, month) { + return new Date(Date.UTC(year, month + 1, 0)).getUTCDate(); +} + +// FORMATTING + +addFormatToken('M', ['MM', 2], 'Mo', function () { + return this.month() + 1; +}); + +addFormatToken('MMM', 0, 0, function (format) { + return this.localeData().monthsShort(this, format); +}); + +addFormatToken('MMMM', 0, 0, function (format) { + return this.localeData().months(this, format); +}); + +// ALIASES + +addUnitAlias('month', 'M'); + +// PRIORITY + +addUnitPriority('month', 8); + +// PARSING + +addRegexToken('M', match1to2); +addRegexToken('MM', match1to2, match2); +addRegexToken('MMM', function (isStrict, locale) { + return locale.monthsShortRegex(isStrict); +}); +addRegexToken('MMMM', function (isStrict, locale) { + return locale.monthsRegex(isStrict); +}); + +addParseToken(['M', 'MM'], function (input, array) { + array[MONTH] = toInt(input) - 1; +}); + +addParseToken(['MMM', 'MMMM'], function (input, array, config, token) { + var month = config._locale.monthsParse(input, token, config._strict); + // if we didn't find a month name, mark the date as invalid. + if (month != null) { + array[MONTH] = month; + } else { + getParsingFlags(config).invalidMonth = input; + } +}); + +// LOCALES + +var MONTHS_IN_FORMAT = /D[oD]?(\[[^\[\]]*\]|\s)+MMMM?/; +var defaultLocaleMonths = 'January_February_March_April_May_June_July_August_September_October_November_December'.split('_'); +function localeMonths (m, format) { + if (!m) { + return this._months; + } + return isArray(this._months) ? this._months[m.month()] : + this._months[(this._months.isFormat || MONTHS_IN_FORMAT).test(format) ? 'format' : 'standalone'][m.month()]; +} + +var defaultLocaleMonthsShort = 'Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec'.split('_'); +function localeMonthsShort (m, format) { + if (!m) { + return this._monthsShort; + } + return isArray(this._monthsShort) ? this._monthsShort[m.month()] : + this._monthsShort[MONTHS_IN_FORMAT.test(format) ? 'format' : 'standalone'][m.month()]; +} + +function handleStrictParse(monthName, format, strict) { + var i, ii, mom, llc = monthName.toLocaleLowerCase(); + if (!this._monthsParse) { + // this is not used + this._monthsParse = []; + this._longMonthsParse = []; + this._shortMonthsParse = []; + for (i = 0; i < 12; ++i) { + mom = createUTC([2000, i]); + this._shortMonthsParse[i] = this.monthsShort(mom, '').toLocaleLowerCase(); + this._longMonthsParse[i] = this.months(mom, '').toLocaleLowerCase(); + } + } + + if (strict) { + if (format === 'MMM') { + ii = indexOf$1.call(this._shortMonthsParse, llc); + return ii !== -1 ? ii : null; + } else { + ii = indexOf$1.call(this._longMonthsParse, llc); + return ii !== -1 ? ii : null; + } + } else { + if (format === 'MMM') { + ii = indexOf$1.call(this._shortMonthsParse, llc); + if (ii !== -1) { + return ii; + } + ii = indexOf$1.call(this._longMonthsParse, llc); + return ii !== -1 ? ii : null; + } else { + ii = indexOf$1.call(this._longMonthsParse, llc); + if (ii !== -1) { + return ii; + } + ii = indexOf$1.call(this._shortMonthsParse, llc); + return ii !== -1 ? ii : null; + } + } +} + +function localeMonthsParse (monthName, format, strict) { + var i, mom, regex; + + if (this._monthsParseExact) { + return handleStrictParse.call(this, monthName, format, strict); + } + + if (!this._monthsParse) { + this._monthsParse = []; + this._longMonthsParse = []; + this._shortMonthsParse = []; + } + + // TODO: add sorting + // Sorting makes sure if one month (or abbr) is a prefix of another + // see sorting in computeMonthsParse + for (i = 0; i < 12; i++) { + // make the regex if we don't have it already + mom = createUTC([2000, i]); + if (strict && !this._longMonthsParse[i]) { + this._longMonthsParse[i] = new RegExp('^' + this.months(mom, '').replace('.', '') + '$', 'i'); + this._shortMonthsParse[i] = new RegExp('^' + this.monthsShort(mom, '').replace('.', '') + '$', 'i'); + } + if (!strict && !this._monthsParse[i]) { + regex = '^' + this.months(mom, '') + '|^' + this.monthsShort(mom, ''); + this._monthsParse[i] = new RegExp(regex.replace('.', ''), 'i'); + } + // test the regex + if (strict && format === 'MMMM' && this._longMonthsParse[i].test(monthName)) { + return i; + } else if (strict && format === 'MMM' && this._shortMonthsParse[i].test(monthName)) { + return i; + } else if (!strict && this._monthsParse[i].test(monthName)) { + return i; + } + } +} + +// MOMENTS + +function setMonth (mom, value) { + var dayOfMonth; + + if (!mom.isValid()) { + // No op + return mom; + } + + if (typeof value === 'string') { + if (/^\d+$/.test(value)) { + value = toInt(value); + } else { + value = mom.localeData().monthsParse(value); + // TODO: Another silent failure? + if (!isNumber(value)) { + return mom; + } + } + } + + dayOfMonth = Math.min(mom.date(), daysInMonth(mom.year(), value)); + mom._d['set' + (mom._isUTC ? 'UTC' : '') + 'Month'](value, dayOfMonth); + return mom; +} + +function getSetMonth (value) { + if (value != null) { + setMonth(this, value); + hooks.updateOffset(this, true); + return this; + } else { + return get(this, 'Month'); + } +} + +function getDaysInMonth () { + return daysInMonth(this.year(), this.month()); +} + +var defaultMonthsShortRegex = matchWord; +function monthsShortRegex (isStrict) { + if (this._monthsParseExact) { + if (!hasOwnProp(this, '_monthsRegex')) { + computeMonthsParse.call(this); + } + if (isStrict) { + return this._monthsShortStrictRegex; + } else { + return this._monthsShortRegex; + } + } else { + if (!hasOwnProp(this, '_monthsShortRegex')) { + this._monthsShortRegex = defaultMonthsShortRegex; + } + return this._monthsShortStrictRegex && isStrict ? + this._monthsShortStrictRegex : this._monthsShortRegex; + } +} + +var defaultMonthsRegex = matchWord; +function monthsRegex (isStrict) { + if (this._monthsParseExact) { + if (!hasOwnProp(this, '_monthsRegex')) { + computeMonthsParse.call(this); + } + if (isStrict) { + return this._monthsStrictRegex; + } else { + return this._monthsRegex; + } + } else { + if (!hasOwnProp(this, '_monthsRegex')) { + this._monthsRegex = defaultMonthsRegex; + } + return this._monthsStrictRegex && isStrict ? + this._monthsStrictRegex : this._monthsRegex; + } +} + +function computeMonthsParse () { + function cmpLenRev(a, b) { + return b.length - a.length; + } + + var shortPieces = [], longPieces = [], mixedPieces = [], + i, mom; + for (i = 0; i < 12; i++) { + // make the regex if we don't have it already + mom = createUTC([2000, i]); + shortPieces.push(this.monthsShort(mom, '')); + longPieces.push(this.months(mom, '')); + mixedPieces.push(this.months(mom, '')); + mixedPieces.push(this.monthsShort(mom, '')); + } + // Sorting makes sure if one month (or abbr) is a prefix of another it + // will match the longer piece. + shortPieces.sort(cmpLenRev); + longPieces.sort(cmpLenRev); + mixedPieces.sort(cmpLenRev); + for (i = 0; i < 12; i++) { + shortPieces[i] = regexEscape(shortPieces[i]); + longPieces[i] = regexEscape(longPieces[i]); + } + for (i = 0; i < 24; i++) { + mixedPieces[i] = regexEscape(mixedPieces[i]); + } + + this._monthsRegex = new RegExp('^(' + mixedPieces.join('|') + ')', 'i'); + this._monthsShortRegex = this._monthsRegex; + this._monthsStrictRegex = new RegExp('^(' + longPieces.join('|') + ')', 'i'); + this._monthsShortStrictRegex = new RegExp('^(' + shortPieces.join('|') + ')', 'i'); +} + +// FORMATTING + +addFormatToken('Y', 0, 0, function () { + var y = this.year(); + return y <= 9999 ? '' + y : '+' + y; +}); + +addFormatToken(0, ['YY', 2], 0, function () { + return this.year() % 100; +}); + +addFormatToken(0, ['YYYY', 4], 0, 'year'); +addFormatToken(0, ['YYYYY', 5], 0, 'year'); +addFormatToken(0, ['YYYYYY', 6, true], 0, 'year'); + +// ALIASES + +addUnitAlias('year', 'y'); + +// PRIORITIES + +addUnitPriority('year', 1); + +// PARSING + +addRegexToken('Y', matchSigned); +addRegexToken('YY', match1to2, match2); +addRegexToken('YYYY', match1to4, match4); +addRegexToken('YYYYY', match1to6, match6); +addRegexToken('YYYYYY', match1to6, match6); + +addParseToken(['YYYYY', 'YYYYYY'], YEAR); +addParseToken('YYYY', function (input, array) { + array[YEAR] = input.length === 2 ? hooks.parseTwoDigitYear(input) : toInt(input); +}); +addParseToken('YY', function (input, array) { + array[YEAR] = hooks.parseTwoDigitYear(input); +}); +addParseToken('Y', function (input, array) { + array[YEAR] = parseInt(input, 10); +}); + +// HELPERS + +function daysInYear(year) { + return isLeapYear(year) ? 366 : 365; +} + +function isLeapYear(year) { + return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0; +} + +// HOOKS + +hooks.parseTwoDigitYear = function (input) { + return toInt(input) + (toInt(input) > 68 ? 1900 : 2000); +}; + +// MOMENTS + +var getSetYear = makeGetSet('FullYear', true); + +function getIsLeapYear () { + return isLeapYear(this.year()); +} + +function createDate (y, m, d, h, M, s, ms) { + //can't just apply() to create a date: + //http://stackoverflow.com/questions/181348/instantiating-a-javascript-object-by-calling-prototype-constructor-apply + var date = new Date(y, m, d, h, M, s, ms); + + //the date constructor remaps years 0-99 to 1900-1999 + if (y < 100 && y >= 0 && isFinite(date.getFullYear())) { + date.setFullYear(y); + } + return date; +} + +function createUTCDate (y) { + var date = new Date(Date.UTC.apply(null, arguments)); + + //the Date.UTC function remaps years 0-99 to 1900-1999 + if (y < 100 && y >= 0 && isFinite(date.getUTCFullYear())) { + date.setUTCFullYear(y); + } + return date; +} + +// start-of-first-week - start-of-year +function firstWeekOffset(year, dow, doy) { + var // first-week day -- which january is always in the first week (4 for iso, 1 for other) + fwd = 7 + dow - doy, + // first-week day local weekday -- which local weekday is fwd + fwdlw = (7 + createUTCDate(year, 0, fwd).getUTCDay() - dow) % 7; + + return -fwdlw + fwd - 1; +} + +//http://en.wikipedia.org/wiki/ISO_week_date#Calculating_a_date_given_the_year.2C_week_number_and_weekday +function dayOfYearFromWeeks(year, week, weekday, dow, doy) { + var localWeekday = (7 + weekday - dow) % 7, + weekOffset = firstWeekOffset(year, dow, doy), + dayOfYear = 1 + 7 * (week - 1) + localWeekday + weekOffset, + resYear, resDayOfYear; + + if (dayOfYear <= 0) { + resYear = year - 1; + resDayOfYear = daysInYear(resYear) + dayOfYear; + } else if (dayOfYear > daysInYear(year)) { + resYear = year + 1; + resDayOfYear = dayOfYear - daysInYear(year); + } else { + resYear = year; + resDayOfYear = dayOfYear; + } + + return { + year: resYear, + dayOfYear: resDayOfYear + }; +} + +function weekOfYear(mom, dow, doy) { + var weekOffset = firstWeekOffset(mom.year(), dow, doy), + week = Math.floor((mom.dayOfYear() - weekOffset - 1) / 7) + 1, + resWeek, resYear; + + if (week < 1) { + resYear = mom.year() - 1; + resWeek = week + weeksInYear(resYear, dow, doy); + } else if (week > weeksInYear(mom.year(), dow, doy)) { + resWeek = week - weeksInYear(mom.year(), dow, doy); + resYear = mom.year() + 1; + } else { + resYear = mom.year(); + resWeek = week; + } + + return { + week: resWeek, + year: resYear + }; +} + +function weeksInYear(year, dow, doy) { + var weekOffset = firstWeekOffset(year, dow, doy), + weekOffsetNext = firstWeekOffset(year + 1, dow, doy); + return (daysInYear(year) - weekOffset + weekOffsetNext) / 7; +} + +// FORMATTING + +addFormatToken('w', ['ww', 2], 'wo', 'week'); +addFormatToken('W', ['WW', 2], 'Wo', 'isoWeek'); + +// ALIASES + +addUnitAlias('week', 'w'); +addUnitAlias('isoWeek', 'W'); + +// PRIORITIES + +addUnitPriority('week', 5); +addUnitPriority('isoWeek', 5); + +// PARSING + +addRegexToken('w', match1to2); +addRegexToken('ww', match1to2, match2); +addRegexToken('W', match1to2); +addRegexToken('WW', match1to2, match2); + +addWeekParseToken(['w', 'ww', 'W', 'WW'], function (input, week, config, token) { + week[token.substr(0, 1)] = toInt(input); +}); + +// HELPERS + +// LOCALES + +function localeWeek (mom) { + return weekOfYear(mom, this._week.dow, this._week.doy).week; +} + +var defaultLocaleWeek = { + dow : 0, // Sunday is the first day of the week. + doy : 6 // The week that contains Jan 1st is the first week of the year. +}; + +function localeFirstDayOfWeek () { + return this._week.dow; +} + +function localeFirstDayOfYear () { + return this._week.doy; +} + +// MOMENTS + +function getSetWeek (input) { + var week = this.localeData().week(this); + return input == null ? week : this.add((input - week) * 7, 'd'); +} + +function getSetISOWeek (input) { + var week = weekOfYear(this, 1, 4).week; + return input == null ? week : this.add((input - week) * 7, 'd'); +} + +// FORMATTING + +addFormatToken('d', 0, 'do', 'day'); + +addFormatToken('dd', 0, 0, function (format) { + return this.localeData().weekdaysMin(this, format); +}); + +addFormatToken('ddd', 0, 0, function (format) { + return this.localeData().weekdaysShort(this, format); +}); + +addFormatToken('dddd', 0, 0, function (format) { + return this.localeData().weekdays(this, format); +}); + +addFormatToken('e', 0, 0, 'weekday'); +addFormatToken('E', 0, 0, 'isoWeekday'); + +// ALIASES + +addUnitAlias('day', 'd'); +addUnitAlias('weekday', 'e'); +addUnitAlias('isoWeekday', 'E'); + +// PRIORITY +addUnitPriority('day', 11); +addUnitPriority('weekday', 11); +addUnitPriority('isoWeekday', 11); + +// PARSING + +addRegexToken('d', match1to2); +addRegexToken('e', match1to2); +addRegexToken('E', match1to2); +addRegexToken('dd', function (isStrict, locale) { + return locale.weekdaysMinRegex(isStrict); +}); +addRegexToken('ddd', function (isStrict, locale) { + return locale.weekdaysShortRegex(isStrict); +}); +addRegexToken('dddd', function (isStrict, locale) { + return locale.weekdaysRegex(isStrict); +}); + +addWeekParseToken(['dd', 'ddd', 'dddd'], function (input, week, config, token) { + var weekday = config._locale.weekdaysParse(input, token, config._strict); + // if we didn't get a weekday name, mark the date as invalid + if (weekday != null) { + week.d = weekday; + } else { + getParsingFlags(config).invalidWeekday = input; + } +}); + +addWeekParseToken(['d', 'e', 'E'], function (input, week, config, token) { + week[token] = toInt(input); +}); + +// HELPERS + +function parseWeekday(input, locale) { + if (typeof input !== 'string') { + return input; + } + + if (!isNaN(input)) { + return parseInt(input, 10); + } + + input = locale.weekdaysParse(input); + if (typeof input === 'number') { + return input; + } + + return null; +} + +function parseIsoWeekday(input, locale) { + if (typeof input === 'string') { + return locale.weekdaysParse(input) % 7 || 7; + } + return isNaN(input) ? null : input; +} + +// LOCALES + +var defaultLocaleWeekdays = 'Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday'.split('_'); +function localeWeekdays (m, format) { + if (!m) { + return this._weekdays; + } + return isArray(this._weekdays) ? this._weekdays[m.day()] : + this._weekdays[this._weekdays.isFormat.test(format) ? 'format' : 'standalone'][m.day()]; +} + +var defaultLocaleWeekdaysShort = 'Sun_Mon_Tue_Wed_Thu_Fri_Sat'.split('_'); +function localeWeekdaysShort (m) { + return (m) ? this._weekdaysShort[m.day()] : this._weekdaysShort; +} + +var defaultLocaleWeekdaysMin = 'Su_Mo_Tu_We_Th_Fr_Sa'.split('_'); +function localeWeekdaysMin (m) { + return (m) ? this._weekdaysMin[m.day()] : this._weekdaysMin; +} + +function handleStrictParse$1(weekdayName, format, strict) { + var i, ii, mom, llc = weekdayName.toLocaleLowerCase(); + if (!this._weekdaysParse) { + this._weekdaysParse = []; + this._shortWeekdaysParse = []; + this._minWeekdaysParse = []; + + for (i = 0; i < 7; ++i) { + mom = createUTC([2000, 1]).day(i); + this._minWeekdaysParse[i] = this.weekdaysMin(mom, '').toLocaleLowerCase(); + this._shortWeekdaysParse[i] = this.weekdaysShort(mom, '').toLocaleLowerCase(); + this._weekdaysParse[i] = this.weekdays(mom, '').toLocaleLowerCase(); + } + } + + if (strict) { + if (format === 'dddd') { + ii = indexOf$1.call(this._weekdaysParse, llc); + return ii !== -1 ? ii : null; + } else if (format === 'ddd') { + ii = indexOf$1.call(this._shortWeekdaysParse, llc); + return ii !== -1 ? ii : null; + } else { + ii = indexOf$1.call(this._minWeekdaysParse, llc); + return ii !== -1 ? ii : null; + } + } else { + if (format === 'dddd') { + ii = indexOf$1.call(this._weekdaysParse, llc); + if (ii !== -1) { + return ii; + } + ii = indexOf$1.call(this._shortWeekdaysParse, llc); + if (ii !== -1) { + return ii; + } + ii = indexOf$1.call(this._minWeekdaysParse, llc); + return ii !== -1 ? ii : null; + } else if (format === 'ddd') { + ii = indexOf$1.call(this._shortWeekdaysParse, llc); + if (ii !== -1) { + return ii; + } + ii = indexOf$1.call(this._weekdaysParse, llc); + if (ii !== -1) { + return ii; + } + ii = indexOf$1.call(this._minWeekdaysParse, llc); + return ii !== -1 ? ii : null; + } else { + ii = indexOf$1.call(this._minWeekdaysParse, llc); + if (ii !== -1) { + return ii; + } + ii = indexOf$1.call(this._weekdaysParse, llc); + if (ii !== -1) { + return ii; + } + ii = indexOf$1.call(this._shortWeekdaysParse, llc); + return ii !== -1 ? ii : null; + } + } +} + +function localeWeekdaysParse (weekdayName, format, strict) { + var i, mom, regex; + + if (this._weekdaysParseExact) { + return handleStrictParse$1.call(this, weekdayName, format, strict); + } + + if (!this._weekdaysParse) { + this._weekdaysParse = []; + this._minWeekdaysParse = []; + this._shortWeekdaysParse = []; + this._fullWeekdaysParse = []; + } + + for (i = 0; i < 7; i++) { + // make the regex if we don't have it already + + mom = createUTC([2000, 1]).day(i); + if (strict && !this._fullWeekdaysParse[i]) { + this._fullWeekdaysParse[i] = new RegExp('^' + this.weekdays(mom, '').replace('.', '\.?') + '$', 'i'); + this._shortWeekdaysParse[i] = new RegExp('^' + this.weekdaysShort(mom, '').replace('.', '\.?') + '$', 'i'); + this._minWeekdaysParse[i] = new RegExp('^' + this.weekdaysMin(mom, '').replace('.', '\.?') + '$', 'i'); + } + if (!this._weekdaysParse[i]) { + regex = '^' + this.weekdays(mom, '') + '|^' + this.weekdaysShort(mom, '') + '|^' + this.weekdaysMin(mom, ''); + this._weekdaysParse[i] = new RegExp(regex.replace('.', ''), 'i'); + } + // test the regex + if (strict && format === 'dddd' && this._fullWeekdaysParse[i].test(weekdayName)) { + return i; + } else if (strict && format === 'ddd' && this._shortWeekdaysParse[i].test(weekdayName)) { + return i; + } else if (strict && format === 'dd' && this._minWeekdaysParse[i].test(weekdayName)) { + return i; + } else if (!strict && this._weekdaysParse[i].test(weekdayName)) { + return i; + } + } +} + +// MOMENTS + +function getSetDayOfWeek (input) { + if (!this.isValid()) { + return input != null ? this : NaN; + } + var day = this._isUTC ? this._d.getUTCDay() : this._d.getDay(); + if (input != null) { + input = parseWeekday(input, this.localeData()); + return this.add(input - day, 'd'); + } else { + return day; + } +} + +function getSetLocaleDayOfWeek (input) { + if (!this.isValid()) { + return input != null ? this : NaN; + } + var weekday = (this.day() + 7 - this.localeData()._week.dow) % 7; + return input == null ? weekday : this.add(input - weekday, 'd'); +} + +function getSetISODayOfWeek (input) { + if (!this.isValid()) { + return input != null ? this : NaN; + } + + // behaves the same as moment#day except + // as a getter, returns 7 instead of 0 (1-7 range instead of 0-6) + // as a setter, sunday should belong to the previous week. + + if (input != null) { + var weekday = parseIsoWeekday(input, this.localeData()); + return this.day(this.day() % 7 ? weekday : weekday - 7); + } else { + return this.day() || 7; + } +} + +var defaultWeekdaysRegex = matchWord; +function weekdaysRegex (isStrict) { + if (this._weekdaysParseExact) { + if (!hasOwnProp(this, '_weekdaysRegex')) { + computeWeekdaysParse.call(this); + } + if (isStrict) { + return this._weekdaysStrictRegex; + } else { + return this._weekdaysRegex; + } + } else { + if (!hasOwnProp(this, '_weekdaysRegex')) { + this._weekdaysRegex = defaultWeekdaysRegex; + } + return this._weekdaysStrictRegex && isStrict ? + this._weekdaysStrictRegex : this._weekdaysRegex; + } +} + +var defaultWeekdaysShortRegex = matchWord; +function weekdaysShortRegex (isStrict) { + if (this._weekdaysParseExact) { + if (!hasOwnProp(this, '_weekdaysRegex')) { + computeWeekdaysParse.call(this); + } + if (isStrict) { + return this._weekdaysShortStrictRegex; + } else { + return this._weekdaysShortRegex; + } + } else { + if (!hasOwnProp(this, '_weekdaysShortRegex')) { + this._weekdaysShortRegex = defaultWeekdaysShortRegex; + } + return this._weekdaysShortStrictRegex && isStrict ? + this._weekdaysShortStrictRegex : this._weekdaysShortRegex; + } +} + +var defaultWeekdaysMinRegex = matchWord; +function weekdaysMinRegex (isStrict) { + if (this._weekdaysParseExact) { + if (!hasOwnProp(this, '_weekdaysRegex')) { + computeWeekdaysParse.call(this); + } + if (isStrict) { + return this._weekdaysMinStrictRegex; + } else { + return this._weekdaysMinRegex; + } + } else { + if (!hasOwnProp(this, '_weekdaysMinRegex')) { + this._weekdaysMinRegex = defaultWeekdaysMinRegex; + } + return this._weekdaysMinStrictRegex && isStrict ? + this._weekdaysMinStrictRegex : this._weekdaysMinRegex; + } +} + + +function computeWeekdaysParse () { + function cmpLenRev(a, b) { + return b.length - a.length; + } + + var minPieces = [], shortPieces = [], longPieces = [], mixedPieces = [], + i, mom, minp, shortp, longp; + for (i = 0; i < 7; i++) { + // make the regex if we don't have it already + mom = createUTC([2000, 1]).day(i); + minp = this.weekdaysMin(mom, ''); + shortp = this.weekdaysShort(mom, ''); + longp = this.weekdays(mom, ''); + minPieces.push(minp); + shortPieces.push(shortp); + longPieces.push(longp); + mixedPieces.push(minp); + mixedPieces.push(shortp); + mixedPieces.push(longp); + } + // Sorting makes sure if one weekday (or abbr) is a prefix of another it + // will match the longer piece. + minPieces.sort(cmpLenRev); + shortPieces.sort(cmpLenRev); + longPieces.sort(cmpLenRev); + mixedPieces.sort(cmpLenRev); + for (i = 0; i < 7; i++) { + shortPieces[i] = regexEscape(shortPieces[i]); + longPieces[i] = regexEscape(longPieces[i]); + mixedPieces[i] = regexEscape(mixedPieces[i]); + } + + this._weekdaysRegex = new RegExp('^(' + mixedPieces.join('|') + ')', 'i'); + this._weekdaysShortRegex = this._weekdaysRegex; + this._weekdaysMinRegex = this._weekdaysRegex; + + this._weekdaysStrictRegex = new RegExp('^(' + longPieces.join('|') + ')', 'i'); + this._weekdaysShortStrictRegex = new RegExp('^(' + shortPieces.join('|') + ')', 'i'); + this._weekdaysMinStrictRegex = new RegExp('^(' + minPieces.join('|') + ')', 'i'); +} + +// FORMATTING + +function hFormat() { + return this.hours() % 12 || 12; +} + +function kFormat() { + return this.hours() || 24; +} + +addFormatToken('H', ['HH', 2], 0, 'hour'); +addFormatToken('h', ['hh', 2], 0, hFormat); +addFormatToken('k', ['kk', 2], 0, kFormat); + +addFormatToken('hmm', 0, 0, function () { + return '' + hFormat.apply(this) + zeroFill(this.minutes(), 2); +}); + +addFormatToken('hmmss', 0, 0, function () { + return '' + hFormat.apply(this) + zeroFill(this.minutes(), 2) + + zeroFill(this.seconds(), 2); +}); + +addFormatToken('Hmm', 0, 0, function () { + return '' + this.hours() + zeroFill(this.minutes(), 2); +}); + +addFormatToken('Hmmss', 0, 0, function () { + return '' + this.hours() + zeroFill(this.minutes(), 2) + + zeroFill(this.seconds(), 2); +}); + +function meridiem (token, lowercase) { + addFormatToken(token, 0, 0, function () { + return this.localeData().meridiem(this.hours(), this.minutes(), lowercase); + }); +} + +meridiem('a', true); +meridiem('A', false); + +// ALIASES + +addUnitAlias('hour', 'h'); + +// PRIORITY +addUnitPriority('hour', 13); + +// PARSING + +function matchMeridiem (isStrict, locale) { + return locale._meridiemParse; +} + +addRegexToken('a', matchMeridiem); +addRegexToken('A', matchMeridiem); +addRegexToken('H', match1to2); +addRegexToken('h', match1to2); +addRegexToken('HH', match1to2, match2); +addRegexToken('hh', match1to2, match2); + +addRegexToken('hmm', match3to4); +addRegexToken('hmmss', match5to6); +addRegexToken('Hmm', match3to4); +addRegexToken('Hmmss', match5to6); + +addParseToken(['H', 'HH'], HOUR); +addParseToken(['a', 'A'], function (input, array, config) { + config._isPm = config._locale.isPM(input); + config._meridiem = input; +}); +addParseToken(['h', 'hh'], function (input, array, config) { + array[HOUR] = toInt(input); + getParsingFlags(config).bigHour = true; +}); +addParseToken('hmm', function (input, array, config) { + var pos = input.length - 2; + array[HOUR] = toInt(input.substr(0, pos)); + array[MINUTE] = toInt(input.substr(pos)); + getParsingFlags(config).bigHour = true; +}); +addParseToken('hmmss', function (input, array, config) { + var pos1 = input.length - 4; + var pos2 = input.length - 2; + array[HOUR] = toInt(input.substr(0, pos1)); + array[MINUTE] = toInt(input.substr(pos1, 2)); + array[SECOND] = toInt(input.substr(pos2)); + getParsingFlags(config).bigHour = true; +}); +addParseToken('Hmm', function (input, array, config) { + var pos = input.length - 2; + array[HOUR] = toInt(input.substr(0, pos)); + array[MINUTE] = toInt(input.substr(pos)); +}); +addParseToken('Hmmss', function (input, array, config) { + var pos1 = input.length - 4; + var pos2 = input.length - 2; + array[HOUR] = toInt(input.substr(0, pos1)); + array[MINUTE] = toInt(input.substr(pos1, 2)); + array[SECOND] = toInt(input.substr(pos2)); +}); + +// LOCALES + +function localeIsPM (input) { + // IE8 Quirks Mode & IE7 Standards Mode do not allow accessing strings like arrays + // Using charAt should be more compatible. + return ((input + '').toLowerCase().charAt(0) === 'p'); +} + +var defaultLocaleMeridiemParse = /[ap]\.?m?\.?/i; +function localeMeridiem (hours, minutes, isLower) { + if (hours > 11) { + return isLower ? 'pm' : 'PM'; + } else { + return isLower ? 'am' : 'AM'; + } +} + + +// MOMENTS + +// Setting the hour should keep the time, because the user explicitly +// specified which hour he wants. So trying to maintain the same hour (in +// a new timezone) makes sense. Adding/subtracting hours does not follow +// this rule. +var getSetHour = makeGetSet('Hours', true); + +// months +// week +// weekdays +// meridiem +var baseConfig = { + calendar: defaultCalendar, + longDateFormat: defaultLongDateFormat, + invalidDate: defaultInvalidDate, + ordinal: defaultOrdinal, + ordinalParse: defaultOrdinalParse, + relativeTime: defaultRelativeTime, + + months: defaultLocaleMonths, + monthsShort: defaultLocaleMonthsShort, + + week: defaultLocaleWeek, + + weekdays: defaultLocaleWeekdays, + weekdaysMin: defaultLocaleWeekdaysMin, + weekdaysShort: defaultLocaleWeekdaysShort, + + meridiemParse: defaultLocaleMeridiemParse +}; + +// internal storage for locale config files +var locales = {}; +var localeFamilies = {}; +var globalLocale; + +function normalizeLocale(key) { + return key ? key.toLowerCase().replace('_', '-') : key; +} + +// pick the locale from the array +// try ['en-au', 'en-gb'] as 'en-au', 'en-gb', 'en', as in move through the list trying each +// substring from most specific to least, but move to the next array item if it's a more specific variant than the current root +function chooseLocale(names) { + var i = 0, j, next, locale, split; + + while (i < names.length) { + split = normalizeLocale(names[i]).split('-'); + j = split.length; + next = normalizeLocale(names[i + 1]); + next = next ? next.split('-') : null; + while (j > 0) { + locale = loadLocale(split.slice(0, j).join('-')); + if (locale) { + return locale; + } + if (next && next.length >= j && compareArrays(split, next, true) >= j - 1) { + //the next array item is better than a shallower substring of this one + break; + } + j--; + } + i++; + } + return null; +} + +function loadLocale(name) { + var oldLocale = null; + // TODO: Find a better way to register and load all the locales in Node + if (!locales[name] && (typeof module !== 'undefined') && + module && module.exports) { + try { + oldLocale = globalLocale._abbr; + require('./locale/' + name); + // because defineLocale currently also sets the global locale, we + // want to undo that for lazy loaded locales + getSetGlobalLocale(oldLocale); + } catch (e) { } + } + return locales[name]; +} + +// This function will load locale and then set the global locale. If +// no arguments are passed in, it will simply return the current global +// locale key. +function getSetGlobalLocale (key, values) { + var data; + if (key) { + if (isUndefined(values)) { + data = getLocale(key); + } + else { + data = defineLocale(key, values); + } + + if (data) { + // moment.duration._locale = moment._locale = data; + globalLocale = data; + } + } + + return globalLocale._abbr; +} + +function defineLocale (name, config) { + if (config !== null) { + var parentConfig = baseConfig; + config.abbr = name; + if (locales[name] != null) { + deprecateSimple('defineLocaleOverride', + 'use moment.updateLocale(localeName, config) to change ' + + 'an existing locale. moment.defineLocale(localeName, ' + + 'config) should only be used for creating a new locale ' + + 'See http://momentjs.com/guides/#/warnings/define-locale/ for more info.'); + parentConfig = locales[name]._config; + } else if (config.parentLocale != null) { + if (locales[config.parentLocale] != null) { + parentConfig = locales[config.parentLocale]._config; + } else { + if (!localeFamilies[config.parentLocale]) { + localeFamilies[config.parentLocale] = []; + } + localeFamilies[config.parentLocale].push({ + name: name, + config: config + }); + return null; + } + } + locales[name] = new Locale(mergeConfigs(parentConfig, config)); + + if (localeFamilies[name]) { + localeFamilies[name].forEach(function (x) { + defineLocale(x.name, x.config); + }); + } + + // backwards compat for now: also set the locale + // make sure we set the locale AFTER all child locales have been + // created, so we won't end up with the child locale set. + getSetGlobalLocale(name); + + + return locales[name]; + } else { + // useful for testing + delete locales[name]; + return null; + } +} + +function updateLocale(name, config) { + if (config != null) { + var locale, parentConfig = baseConfig; + // MERGE + if (locales[name] != null) { + parentConfig = locales[name]._config; + } + config = mergeConfigs(parentConfig, config); + locale = new Locale(config); + locale.parentLocale = locales[name]; + locales[name] = locale; + + // backwards compat for now: also set the locale + getSetGlobalLocale(name); + } else { + // pass null for config to unupdate, useful for tests + if (locales[name] != null) { + if (locales[name].parentLocale != null) { + locales[name] = locales[name].parentLocale; + } else if (locales[name] != null) { + delete locales[name]; + } + } + } + return locales[name]; +} + +// returns locale data +function getLocale (key) { + var locale; + + if (key && key._locale && key._locale._abbr) { + key = key._locale._abbr; + } + + if (!key) { + return globalLocale; + } + + if (!isArray(key)) { + //short-circuit everything else + locale = loadLocale(key); + if (locale) { + return locale; + } + key = [key]; + } + + return chooseLocale(key); +} + +function listLocales() { + return keys$1(locales); +} + +function checkOverflow (m) { + var overflow; + var a = m._a; + + if (a && getParsingFlags(m).overflow === -2) { + overflow = + a[MONTH] < 0 || a[MONTH] > 11 ? MONTH : + a[DATE] < 1 || a[DATE] > daysInMonth(a[YEAR], a[MONTH]) ? DATE : + a[HOUR] < 0 || a[HOUR] > 24 || (a[HOUR] === 24 && (a[MINUTE] !== 0 || a[SECOND] !== 0 || a[MILLISECOND] !== 0)) ? HOUR : + a[MINUTE] < 0 || a[MINUTE] > 59 ? MINUTE : + a[SECOND] < 0 || a[SECOND] > 59 ? SECOND : + a[MILLISECOND] < 0 || a[MILLISECOND] > 999 ? MILLISECOND : + -1; + + if (getParsingFlags(m)._overflowDayOfYear && (overflow < YEAR || overflow > DATE)) { + overflow = DATE; + } + if (getParsingFlags(m)._overflowWeeks && overflow === -1) { + overflow = WEEK; + } + if (getParsingFlags(m)._overflowWeekday && overflow === -1) { + overflow = WEEKDAY; + } + + getParsingFlags(m).overflow = overflow; + } + + return m; +} + +// iso 8601 regex +// 0000-00-00 0000-W00 or 0000-W00-0 + T + 00 or 00:00 or 00:00:00 or 00:00:00.000 + +00:00 or +0000 or +00) +var extendedIsoRegex = /^\s*((?:[+-]\d{6}|\d{4})-(?:\d\d-\d\d|W\d\d-\d|W\d\d|\d\d\d|\d\d))(?:(T| )(\d\d(?::\d\d(?::\d\d(?:[.,]\d+)?)?)?)([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?$/; +var basicIsoRegex = /^\s*((?:[+-]\d{6}|\d{4})(?:\d\d\d\d|W\d\d\d|W\d\d|\d\d\d|\d\d))(?:(T| )(\d\d(?:\d\d(?:\d\d(?:[.,]\d+)?)?)?)([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?$/; + +var tzRegex = /Z|[+-]\d\d(?::?\d\d)?/; + +var isoDates = [ + ['YYYYYY-MM-DD', /[+-]\d{6}-\d\d-\d\d/], + ['YYYY-MM-DD', /\d{4}-\d\d-\d\d/], + ['GGGG-[W]WW-E', /\d{4}-W\d\d-\d/], + ['GGGG-[W]WW', /\d{4}-W\d\d/, false], + ['YYYY-DDD', /\d{4}-\d{3}/], + ['YYYY-MM', /\d{4}-\d\d/, false], + ['YYYYYYMMDD', /[+-]\d{10}/], + ['YYYYMMDD', /\d{8}/], + // YYYYMM is NOT allowed by the standard + ['GGGG[W]WWE', /\d{4}W\d{3}/], + ['GGGG[W]WW', /\d{4}W\d{2}/, false], + ['YYYYDDD', /\d{7}/] +]; + +// iso time formats and regexes +var isoTimes = [ + ['HH:mm:ss.SSSS', /\d\d:\d\d:\d\d\.\d+/], + ['HH:mm:ss,SSSS', /\d\d:\d\d:\d\d,\d+/], + ['HH:mm:ss', /\d\d:\d\d:\d\d/], + ['HH:mm', /\d\d:\d\d/], + ['HHmmss.SSSS', /\d\d\d\d\d\d\.\d+/], + ['HHmmss,SSSS', /\d\d\d\d\d\d,\d+/], + ['HHmmss', /\d\d\d\d\d\d/], + ['HHmm', /\d\d\d\d/], + ['HH', /\d\d/] +]; + +var aspNetJsonRegex = /^\/?Date\((\-?\d+)/i; + +// date from iso format +function configFromISO(config) { + var i, l, + string = config._i, + match = extendedIsoRegex.exec(string) || basicIsoRegex.exec(string), + allowTime, dateFormat, timeFormat, tzFormat; + + if (match) { + getParsingFlags(config).iso = true; + + for (i = 0, l = isoDates.length; i < l; i++) { + if (isoDates[i][1].exec(match[1])) { + dateFormat = isoDates[i][0]; + allowTime = isoDates[i][2] !== false; + break; + } + } + if (dateFormat == null) { + config._isValid = false; + return; + } + if (match[3]) { + for (i = 0, l = isoTimes.length; i < l; i++) { + if (isoTimes[i][1].exec(match[3])) { + // match[2] should be 'T' or space + timeFormat = (match[2] || ' ') + isoTimes[i][0]; + break; + } + } + if (timeFormat == null) { + config._isValid = false; + return; + } + } + if (!allowTime && timeFormat != null) { + config._isValid = false; + return; + } + if (match[4]) { + if (tzRegex.exec(match[4])) { + tzFormat = 'Z'; + } else { + config._isValid = false; + return; + } + } + config._f = dateFormat + (timeFormat || '') + (tzFormat || ''); + configFromStringAndFormat(config); + } else { + config._isValid = false; + } +} + +// date from iso format or fallback +function configFromString(config) { + var matched = aspNetJsonRegex.exec(config._i); + + if (matched !== null) { + config._d = new Date(+matched[1]); + return; + } + + configFromISO(config); + if (config._isValid === false) { + delete config._isValid; + hooks.createFromInputFallback(config); + } +} + +hooks.createFromInputFallback = deprecate( + 'value provided is not in a recognized ISO format. moment construction falls back to js Date(), ' + + 'which is not reliable across all browsers and versions. Non ISO date formats are ' + + 'discouraged and will be removed in an upcoming major release. Please refer to ' + + 'http://momentjs.com/guides/#/warnings/js-date/ for more info.', + function (config) { + config._d = new Date(config._i + (config._useUTC ? ' UTC' : '')); + } +); + +// Pick the first defined of two or three arguments. +function defaults(a, b, c) { + if (a != null) { + return a; + } + if (b != null) { + return b; + } + return c; +} + +function currentDateArray(config) { + // hooks is actually the exported moment object + var nowValue = new Date(hooks.now()); + if (config._useUTC) { + return [nowValue.getUTCFullYear(), nowValue.getUTCMonth(), nowValue.getUTCDate()]; + } + return [nowValue.getFullYear(), nowValue.getMonth(), nowValue.getDate()]; +} + +// convert an array to a date. +// the array should mirror the parameters below +// note: all values past the year are optional and will default to the lowest possible value. +// [year, month, day , hour, minute, second, millisecond] +function configFromArray (config) { + var i, date, input = [], currentDate, yearToUse; + + if (config._d) { + return; + } + + currentDate = currentDateArray(config); + + //compute day of the year from weeks and weekdays + if (config._w && config._a[DATE] == null && config._a[MONTH] == null) { + dayOfYearFromWeekInfo(config); + } + + //if the day of the year is set, figure out what it is + if (config._dayOfYear) { + yearToUse = defaults(config._a[YEAR], currentDate[YEAR]); + + if (config._dayOfYear > daysInYear(yearToUse)) { + getParsingFlags(config)._overflowDayOfYear = true; + } + + date = createUTCDate(yearToUse, 0, config._dayOfYear); + config._a[MONTH] = date.getUTCMonth(); + config._a[DATE] = date.getUTCDate(); + } + + // Default to current date. + // * if no year, month, day of month are given, default to today + // * if day of month is given, default month and year + // * if month is given, default only year + // * if year is given, don't default anything + for (i = 0; i < 3 && config._a[i] == null; ++i) { + config._a[i] = input[i] = currentDate[i]; + } + + // Zero out whatever was not defaulted, including time + for (; i < 7; i++) { + config._a[i] = input[i] = (config._a[i] == null) ? (i === 2 ? 1 : 0) : config._a[i]; + } + + // Check for 24:00:00.000 + if (config._a[HOUR] === 24 && + config._a[MINUTE] === 0 && + config._a[SECOND] === 0 && + config._a[MILLISECOND] === 0) { + config._nextDay = true; + config._a[HOUR] = 0; + } + + config._d = (config._useUTC ? createUTCDate : createDate).apply(null, input); + // Apply timezone offset from input. The actual utcOffset can be changed + // with parseZone. + if (config._tzm != null) { + config._d.setUTCMinutes(config._d.getUTCMinutes() - config._tzm); + } + + if (config._nextDay) { + config._a[HOUR] = 24; + } +} + +function dayOfYearFromWeekInfo(config) { + var w, weekYear, week, weekday, dow, doy, temp, weekdayOverflow; + + w = config._w; + if (w.GG != null || w.W != null || w.E != null) { + dow = 1; + doy = 4; + + // TODO: We need to take the current isoWeekYear, but that depends on + // how we interpret now (local, utc, fixed offset). So create + // a now version of current config (take local/utc/offset flags, and + // create now). + weekYear = defaults(w.GG, config._a[YEAR], weekOfYear(createLocal(), 1, 4).year); + week = defaults(w.W, 1); + weekday = defaults(w.E, 1); + if (weekday < 1 || weekday > 7) { + weekdayOverflow = true; + } + } else { + dow = config._locale._week.dow; + doy = config._locale._week.doy; + + var curWeek = weekOfYear(createLocal(), dow, doy); + + weekYear = defaults(w.gg, config._a[YEAR], curWeek.year); + + // Default to current week. + week = defaults(w.w, curWeek.week); + + if (w.d != null) { + // weekday -- low day numbers are considered next week + weekday = w.d; + if (weekday < 0 || weekday > 6) { + weekdayOverflow = true; + } + } else if (w.e != null) { + // local weekday -- counting starts from begining of week + weekday = w.e + dow; + if (w.e < 0 || w.e > 6) { + weekdayOverflow = true; + } + } else { + // default to begining of week + weekday = dow; + } + } + if (week < 1 || week > weeksInYear(weekYear, dow, doy)) { + getParsingFlags(config)._overflowWeeks = true; + } else if (weekdayOverflow != null) { + getParsingFlags(config)._overflowWeekday = true; + } else { + temp = dayOfYearFromWeeks(weekYear, week, weekday, dow, doy); + config._a[YEAR] = temp.year; + config._dayOfYear = temp.dayOfYear; + } +} + +// constant that refers to the ISO standard +hooks.ISO_8601 = function () {}; + +// date from string and format string +function configFromStringAndFormat(config) { + // TODO: Move this to another part of the creation flow to prevent circular deps + if (config._f === hooks.ISO_8601) { + configFromISO(config); + return; + } + + config._a = []; + getParsingFlags(config).empty = true; + + // This array is used to make a Date, either with `new Date` or `Date.UTC` + var string = '' + config._i, + i, parsedInput, tokens, token, skipped, + stringLength = string.length, + totalParsedInputLength = 0; + + tokens = expandFormat(config._f, config._locale).match(formattingTokens) || []; + + for (i = 0; i < tokens.length; i++) { + token = tokens[i]; + parsedInput = (string.match(getParseRegexForToken(token, config)) || [])[0]; + // console.log('token', token, 'parsedInput', parsedInput, + // 'regex', getParseRegexForToken(token, config)); + if (parsedInput) { + skipped = string.substr(0, string.indexOf(parsedInput)); + if (skipped.length > 0) { + getParsingFlags(config).unusedInput.push(skipped); + } + string = string.slice(string.indexOf(parsedInput) + parsedInput.length); + totalParsedInputLength += parsedInput.length; + } + // don't parse if it's not a known token + if (formatTokenFunctions[token]) { + if (parsedInput) { + getParsingFlags(config).empty = false; + } + else { + getParsingFlags(config).unusedTokens.push(token); + } + addTimeToArrayFromToken(token, parsedInput, config); + } + else if (config._strict && !parsedInput) { + getParsingFlags(config).unusedTokens.push(token); + } + } + + // add remaining unparsed input length to the string + getParsingFlags(config).charsLeftOver = stringLength - totalParsedInputLength; + if (string.length > 0) { + getParsingFlags(config).unusedInput.push(string); + } + + // clear _12h flag if hour is <= 12 + if (config._a[HOUR] <= 12 && + getParsingFlags(config).bigHour === true && + config._a[HOUR] > 0) { + getParsingFlags(config).bigHour = undefined; + } + + getParsingFlags(config).parsedDateParts = config._a.slice(0); + getParsingFlags(config).meridiem = config._meridiem; + // handle meridiem + config._a[HOUR] = meridiemFixWrap(config._locale, config._a[HOUR], config._meridiem); + + configFromArray(config); + checkOverflow(config); +} + + +function meridiemFixWrap (locale, hour, meridiem) { + var isPm; + + if (meridiem == null) { + // nothing to do + return hour; + } + if (locale.meridiemHour != null) { + return locale.meridiemHour(hour, meridiem); + } else if (locale.isPM != null) { + // Fallback + isPm = locale.isPM(meridiem); + if (isPm && hour < 12) { + hour += 12; + } + if (!isPm && hour === 12) { + hour = 0; + } + return hour; + } else { + // this is not supposed to happen + return hour; + } +} + +// date from string and array of format strings +function configFromStringAndArray(config) { + var tempConfig, + bestMoment, + + scoreToBeat, + i, + currentScore; + + if (config._f.length === 0) { + getParsingFlags(config).invalidFormat = true; + config._d = new Date(NaN); + return; + } + + for (i = 0; i < config._f.length; i++) { + currentScore = 0; + tempConfig = copyConfig({}, config); + if (config._useUTC != null) { + tempConfig._useUTC = config._useUTC; + } + tempConfig._f = config._f[i]; + configFromStringAndFormat(tempConfig); + + if (!isValid(tempConfig)) { + continue; + } + + // if there is any input that was not parsed add a penalty for that format + currentScore += getParsingFlags(tempConfig).charsLeftOver; + + //or tokens + currentScore += getParsingFlags(tempConfig).unusedTokens.length * 10; + + getParsingFlags(tempConfig).score = currentScore; + + if (scoreToBeat == null || currentScore < scoreToBeat) { + scoreToBeat = currentScore; + bestMoment = tempConfig; + } + } + + extend(config, bestMoment || tempConfig); +} + +function configFromObject(config) { + if (config._d) { + return; + } + + var i = normalizeObjectUnits(config._i); + config._a = map([i.year, i.month, i.day || i.date, i.hour, i.minute, i.second, i.millisecond], function (obj) { + return obj && parseInt(obj, 10); + }); + + configFromArray(config); +} + +function createFromConfig (config) { + var res = new Moment(checkOverflow(prepareConfig(config))); + if (res._nextDay) { + // Adding is smart enough around DST + res.add(1, 'd'); + res._nextDay = undefined; + } + + return res; +} + +function prepareConfig (config) { + var input = config._i, + format = config._f; + + config._locale = config._locale || getLocale(config._l); + + if (input === null || (format === undefined && input === '')) { + return createInvalid({nullInput: true}); + } + + if (typeof input === 'string') { + config._i = input = config._locale.preparse(input); + } + + if (isMoment(input)) { + return new Moment(checkOverflow(input)); + } else if (isDate(input)) { + config._d = input; + } else if (isArray(format)) { + configFromStringAndArray(config); + } else if (format) { + configFromStringAndFormat(config); + } else { + configFromInput(config); + } + + if (!isValid(config)) { + config._d = null; + } + + return config; +} + +function configFromInput(config) { + var input = config._i; + if (input === undefined) { + config._d = new Date(hooks.now()); + } else if (isDate(input)) { + config._d = new Date(input.valueOf()); + } else if (typeof input === 'string') { + configFromString(config); + } else if (isArray(input)) { + config._a = map(input.slice(0), function (obj) { + return parseInt(obj, 10); + }); + configFromArray(config); + } else if (typeof(input) === 'object') { + configFromObject(config); + } else if (isNumber(input)) { + // from milliseconds + config._d = new Date(input); + } else { + hooks.createFromInputFallback(config); + } +} + +function createLocalOrUTC (input, format, locale, strict, isUTC) { + var c = {}; + + if (locale === true || locale === false) { + strict = locale; + locale = undefined; + } + + if ((isObject(input) && isObjectEmpty(input)) || + (isArray(input) && input.length === 0)) { + input = undefined; + } + // object construction must be done this way. + // https://github.com/moment/moment/issues/1423 + c._isAMomentObject = true; + c._useUTC = c._isUTC = isUTC; + c._l = locale; + c._i = input; + c._f = format; + c._strict = strict; + + return createFromConfig(c); +} + +function createLocal (input, format, locale, strict) { + return createLocalOrUTC(input, format, locale, strict, false); +} + +var prototypeMin = deprecate( + 'moment().min is deprecated, use moment.max instead. http://momentjs.com/guides/#/warnings/min-max/', + function () { + var other = createLocal.apply(null, arguments); + if (this.isValid() && other.isValid()) { + return other < this ? this : other; + } else { + return createInvalid(); + } + } +); + +var prototypeMax = deprecate( + 'moment().max is deprecated, use moment.min instead. http://momentjs.com/guides/#/warnings/min-max/', + function () { + var other = createLocal.apply(null, arguments); + if (this.isValid() && other.isValid()) { + return other > this ? this : other; + } else { + return createInvalid(); + } + } +); + +// Pick a moment m from moments so that m[fn](other) is true for all +// other. This relies on the function fn to be transitive. +// +// moments should either be an array of moment objects or an array, whose +// first element is an array of moment objects. +function pickBy(fn, moments) { + var res, i; + if (moments.length === 1 && isArray(moments[0])) { + moments = moments[0]; + } + if (!moments.length) { + return createLocal(); + } + res = moments[0]; + for (i = 1; i < moments.length; ++i) { + if (!moments[i].isValid() || moments[i][fn](res)) { + res = moments[i]; + } + } + return res; +} + +// TODO: Use [].sort instead? +function min () { + var args = [].slice.call(arguments, 0); + + return pickBy('isBefore', args); +} + +function max () { + var args = [].slice.call(arguments, 0); + + return pickBy('isAfter', args); +} + +var now = function () { + return Date.now ? Date.now() : +(new Date()); +}; + +function Duration (duration) { + var normalizedInput = normalizeObjectUnits(duration), + years = normalizedInput.year || 0, + quarters = normalizedInput.quarter || 0, + months = normalizedInput.month || 0, + weeks = normalizedInput.week || 0, + days = normalizedInput.day || 0, + hours = normalizedInput.hour || 0, + minutes = normalizedInput.minute || 0, + seconds = normalizedInput.second || 0, + milliseconds = normalizedInput.millisecond || 0; + + // representation for dateAddRemove + this._milliseconds = +milliseconds + + seconds * 1e3 + // 1000 + minutes * 6e4 + // 1000 * 60 + hours * 1000 * 60 * 60; //using 1000 * 60 * 60 instead of 36e5 to avoid floating point rounding errors https://github.com/moment/moment/issues/2978 + // Because of dateAddRemove treats 24 hours as different from a + // day when working around DST, we need to store them separately + this._days = +days + + weeks * 7; + // It is impossible translate months into days without knowing + // which months you are are talking about, so we have to store + // it separately. + this._months = +months + + quarters * 3 + + years * 12; + + this._data = {}; + + this._locale = getLocale(); + + this._bubble(); +} + +function isDuration (obj) { + return obj instanceof Duration; +} + +function absRound (number) { + if (number < 0) { + return Math.round(-1 * number) * -1; + } else { + return Math.round(number); + } +} + +// FORMATTING + +function offset (token, separator) { + addFormatToken(token, 0, 0, function () { + var offset = this.utcOffset(); + var sign = '+'; + if (offset < 0) { + offset = -offset; + sign = '-'; + } + return sign + zeroFill(~~(offset / 60), 2) + separator + zeroFill(~~(offset) % 60, 2); + }); +} + +offset('Z', ':'); +offset('ZZ', ''); + +// PARSING + +addRegexToken('Z', matchShortOffset); +addRegexToken('ZZ', matchShortOffset); +addParseToken(['Z', 'ZZ'], function (input, array, config) { + config._useUTC = true; + config._tzm = offsetFromString(matchShortOffset, input); +}); + +// HELPERS + +// timezone chunker +// '+10:00' > ['10', '00'] +// '-1530' > ['-15', '30'] +var chunkOffset = /([\+\-]|\d\d)/gi; + +function offsetFromString(matcher, string) { + var matches = (string || '').match(matcher); + + if (matches === null) { + return null; + } + + var chunk = matches[matches.length - 1] || []; + var parts = (chunk + '').match(chunkOffset) || ['-', 0, 0]; + var minutes = +(parts[1] * 60) + toInt(parts[2]); + + return minutes === 0 ? + 0 : + parts[0] === '+' ? minutes : -minutes; +} + +// Return a moment from input, that is local/utc/zone equivalent to model. +function cloneWithOffset(input, model) { + var res, diff; + if (model._isUTC) { + res = model.clone(); + diff = (isMoment(input) || isDate(input) ? input.valueOf() : createLocal(input).valueOf()) - res.valueOf(); + // Use low-level api, because this fn is low-level api. + res._d.setTime(res._d.valueOf() + diff); + hooks.updateOffset(res, false); + return res; + } else { + return createLocal(input).local(); + } +} + +function getDateOffset (m) { + // On Firefox.24 Date#getTimezoneOffset returns a floating point. + // https://github.com/moment/moment/pull/1871 + return -Math.round(m._d.getTimezoneOffset() / 15) * 15; +} + +// HOOKS + +// This function will be called whenever a moment is mutated. +// It is intended to keep the offset in sync with the timezone. +hooks.updateOffset = function () {}; + +// MOMENTS + +// keepLocalTime = true means only change the timezone, without +// affecting the local hour. So 5:31:26 +0300 --[utcOffset(2, true)]--> +// 5:31:26 +0200 It is possible that 5:31:26 doesn't exist with offset +// +0200, so we adjust the time as needed, to be valid. +// +// Keeping the time actually adds/subtracts (one hour) +// from the actual represented time. That is why we call updateOffset +// a second time. In case it wants us to change the offset again +// _changeInProgress == true case, then we have to adjust, because +// there is no such time in the given timezone. +function getSetOffset (input, keepLocalTime) { + var offset = this._offset || 0, + localAdjust; + if (!this.isValid()) { + return input != null ? this : NaN; + } + if (input != null) { + if (typeof input === 'string') { + input = offsetFromString(matchShortOffset, input); + if (input === null) { + return this; + } + } else if (Math.abs(input) < 16) { + input = input * 60; + } + if (!this._isUTC && keepLocalTime) { + localAdjust = getDateOffset(this); + } + this._offset = input; + this._isUTC = true; + if (localAdjust != null) { + this.add(localAdjust, 'm'); + } + if (offset !== input) { + if (!keepLocalTime || this._changeInProgress) { + addSubtract(this, createDuration(input - offset, 'm'), 1, false); + } else if (!this._changeInProgress) { + this._changeInProgress = true; + hooks.updateOffset(this, true); + this._changeInProgress = null; + } + } + return this; + } else { + return this._isUTC ? offset : getDateOffset(this); + } +} + +function getSetZone (input, keepLocalTime) { + if (input != null) { + if (typeof input !== 'string') { + input = -input; + } + + this.utcOffset(input, keepLocalTime); + + return this; + } else { + return -this.utcOffset(); + } +} + +function setOffsetToUTC (keepLocalTime) { + return this.utcOffset(0, keepLocalTime); +} + +function setOffsetToLocal (keepLocalTime) { + if (this._isUTC) { + this.utcOffset(0, keepLocalTime); + this._isUTC = false; + + if (keepLocalTime) { + this.subtract(getDateOffset(this), 'm'); + } + } + return this; +} + +function setOffsetToParsedOffset () { + if (this._tzm != null) { + this.utcOffset(this._tzm); + } else if (typeof this._i === 'string') { + var tZone = offsetFromString(matchOffset, this._i); + if (tZone != null) { + this.utcOffset(tZone); + } + else { + this.utcOffset(0, true); + } + } + return this; +} + +function hasAlignedHourOffset (input) { + if (!this.isValid()) { + return false; + } + input = input ? createLocal(input).utcOffset() : 0; + + return (this.utcOffset() - input) % 60 === 0; +} + +function isDaylightSavingTime () { + return ( + this.utcOffset() > this.clone().month(0).utcOffset() || + this.utcOffset() > this.clone().month(5).utcOffset() + ); +} + +function isDaylightSavingTimeShifted () { + if (!isUndefined(this._isDSTShifted)) { + return this._isDSTShifted; + } + + var c = {}; + + copyConfig(c, this); + c = prepareConfig(c); + + if (c._a) { + var other = c._isUTC ? createUTC(c._a) : createLocal(c._a); + this._isDSTShifted = this.isValid() && + compareArrays(c._a, other.toArray()) > 0; + } else { + this._isDSTShifted = false; + } + + return this._isDSTShifted; +} + +function isLocal () { + return this.isValid() ? !this._isUTC : false; +} + +function isUtcOffset () { + return this.isValid() ? this._isUTC : false; +} + +function isUtc () { + return this.isValid() ? this._isUTC && this._offset === 0 : false; +} + +// ASP.NET json date format regex +var aspNetRegex = /^(\-)?(?:(\d*)[. ])?(\d+)\:(\d+)(?:\:(\d+)(\.\d*)?)?$/; + +// from http://docs.closure-library.googlecode.com/git/closure_goog_date_date.js.source.html +// somewhat more in line with 4.4.3.2 2004 spec, but allows decimal anywhere +// and further modified to allow for strings containing both week and day +var isoRegex = /^(-)?P(?:(-?[0-9,.]*)Y)?(?:(-?[0-9,.]*)M)?(?:(-?[0-9,.]*)W)?(?:(-?[0-9,.]*)D)?(?:T(?:(-?[0-9,.]*)H)?(?:(-?[0-9,.]*)M)?(?:(-?[0-9,.]*)S)?)?$/; + +function createDuration (input, key) { + var duration = input, + // matching against regexp is expensive, do it on demand + match = null, + sign, + ret, + diffRes; + + if (isDuration(input)) { + duration = { + ms : input._milliseconds, + d : input._days, + M : input._months + }; + } else if (isNumber(input)) { + duration = {}; + if (key) { + duration[key] = input; + } else { + duration.milliseconds = input; + } + } else if (!!(match = aspNetRegex.exec(input))) { + sign = (match[1] === '-') ? -1 : 1; + duration = { + y : 0, + d : toInt(match[DATE]) * sign, + h : toInt(match[HOUR]) * sign, + m : toInt(match[MINUTE]) * sign, + s : toInt(match[SECOND]) * sign, + ms : toInt(absRound(match[MILLISECOND] * 1000)) * sign // the millisecond decimal point is included in the match + }; + } else if (!!(match = isoRegex.exec(input))) { + sign = (match[1] === '-') ? -1 : 1; + duration = { + y : parseIso(match[2], sign), + M : parseIso(match[3], sign), + w : parseIso(match[4], sign), + d : parseIso(match[5], sign), + h : parseIso(match[6], sign), + m : parseIso(match[7], sign), + s : parseIso(match[8], sign) + }; + } else if (duration == null) {// checks for null or undefined + duration = {}; + } else if (typeof duration === 'object' && ('from' in duration || 'to' in duration)) { + diffRes = momentsDifference(createLocal(duration.from), createLocal(duration.to)); + + duration = {}; + duration.ms = diffRes.milliseconds; + duration.M = diffRes.months; + } + + ret = new Duration(duration); + + if (isDuration(input) && hasOwnProp(input, '_locale')) { + ret._locale = input._locale; + } + + return ret; +} + +createDuration.fn = Duration.prototype; + +function parseIso (inp, sign) { + // We'd normally use ~~inp for this, but unfortunately it also + // converts floats to ints. + // inp may be undefined, so careful calling replace on it. + var res = inp && parseFloat(inp.replace(',', '.')); + // apply sign while we're at it + return (isNaN(res) ? 0 : res) * sign; +} + +function positiveMomentsDifference(base, other) { + var res = {milliseconds: 0, months: 0}; + + res.months = other.month() - base.month() + + (other.year() - base.year()) * 12; + if (base.clone().add(res.months, 'M').isAfter(other)) { + --res.months; + } + + res.milliseconds = +other - +(base.clone().add(res.months, 'M')); + + return res; +} + +function momentsDifference(base, other) { + var res; + if (!(base.isValid() && other.isValid())) { + return {milliseconds: 0, months: 0}; + } + + other = cloneWithOffset(other, base); + if (base.isBefore(other)) { + res = positiveMomentsDifference(base, other); + } else { + res = positiveMomentsDifference(other, base); + res.milliseconds = -res.milliseconds; + res.months = -res.months; + } + + return res; +} + +// TODO: remove 'name' arg after deprecation is removed +function createAdder(direction, name) { + return function (val, period) { + var dur, tmp; + //invert the arguments, but complain about it + if (period !== null && !isNaN(+period)) { + deprecateSimple(name, 'moment().' + name + '(period, number) is deprecated. Please use moment().' + name + '(number, period). ' + + 'See http://momentjs.com/guides/#/warnings/add-inverted-param/ for more info.'); + tmp = val; val = period; period = tmp; + } + + val = typeof val === 'string' ? +val : val; + dur = createDuration(val, period); + addSubtract(this, dur, direction); + return this; + }; +} + +function addSubtract (mom, duration, isAdding, updateOffset) { + var milliseconds = duration._milliseconds, + days = absRound(duration._days), + months = absRound(duration._months); + + if (!mom.isValid()) { + // No op + return; + } + + updateOffset = updateOffset == null ? true : updateOffset; + + if (milliseconds) { + mom._d.setTime(mom._d.valueOf() + milliseconds * isAdding); + } + if (days) { + set$1(mom, 'Date', get(mom, 'Date') + days * isAdding); + } + if (months) { + setMonth(mom, get(mom, 'Month') + months * isAdding); + } + if (updateOffset) { + hooks.updateOffset(mom, days || months); + } +} + +var add = createAdder(1, 'add'); +var subtract = createAdder(-1, 'subtract'); + +function getCalendarFormat(myMoment, now) { + var diff = myMoment.diff(now, 'days', true); + return diff < -6 ? 'sameElse' : + diff < -1 ? 'lastWeek' : + diff < 0 ? 'lastDay' : + diff < 1 ? 'sameDay' : + diff < 2 ? 'nextDay' : + diff < 7 ? 'nextWeek' : 'sameElse'; +} + +function calendar$1 (time, formats) { + // We want to compare the start of today, vs this. + // Getting start-of-today depends on whether we're local/utc/offset or not. + var now = time || createLocal(), + sod = cloneWithOffset(now, this).startOf('day'), + format = hooks.calendarFormat(this, sod) || 'sameElse'; + + var output = formats && (isFunction(formats[format]) ? formats[format].call(this, now) : formats[format]); + + return this.format(output || this.localeData().calendar(format, this, createLocal(now))); +} + +function clone () { + return new Moment(this); +} + +function isAfter (input, units) { + var localInput = isMoment(input) ? input : createLocal(input); + if (!(this.isValid() && localInput.isValid())) { + return false; + } + units = normalizeUnits(!isUndefined(units) ? units : 'millisecond'); + if (units === 'millisecond') { + return this.valueOf() > localInput.valueOf(); + } else { + return localInput.valueOf() < this.clone().startOf(units).valueOf(); + } +} + +function isBefore (input, units) { + var localInput = isMoment(input) ? input : createLocal(input); + if (!(this.isValid() && localInput.isValid())) { + return false; + } + units = normalizeUnits(!isUndefined(units) ? units : 'millisecond'); + if (units === 'millisecond') { + return this.valueOf() < localInput.valueOf(); + } else { + return this.clone().endOf(units).valueOf() < localInput.valueOf(); + } +} + +function isBetween (from, to, units, inclusivity) { + inclusivity = inclusivity || '()'; + return (inclusivity[0] === '(' ? this.isAfter(from, units) : !this.isBefore(from, units)) && + (inclusivity[1] === ')' ? this.isBefore(to, units) : !this.isAfter(to, units)); +} + +function isSame (input, units) { + var localInput = isMoment(input) ? input : createLocal(input), + inputMs; + if (!(this.isValid() && localInput.isValid())) { + return false; + } + units = normalizeUnits(units || 'millisecond'); + if (units === 'millisecond') { + return this.valueOf() === localInput.valueOf(); + } else { + inputMs = localInput.valueOf(); + return this.clone().startOf(units).valueOf() <= inputMs && inputMs <= this.clone().endOf(units).valueOf(); + } +} + +function isSameOrAfter (input, units) { + return this.isSame(input, units) || this.isAfter(input,units); +} + +function isSameOrBefore (input, units) { + return this.isSame(input, units) || this.isBefore(input,units); +} + +function diff (input, units, asFloat) { + var that, + zoneDelta, + delta, output; + + if (!this.isValid()) { + return NaN; + } + + that = cloneWithOffset(input, this); + + if (!that.isValid()) { + return NaN; + } + + zoneDelta = (that.utcOffset() - this.utcOffset()) * 6e4; + + units = normalizeUnits(units); + + if (units === 'year' || units === 'month' || units === 'quarter') { + output = monthDiff(this, that); + if (units === 'quarter') { + output = output / 3; + } else if (units === 'year') { + output = output / 12; + } + } else { + delta = this - that; + output = units === 'second' ? delta / 1e3 : // 1000 + units === 'minute' ? delta / 6e4 : // 1000 * 60 + units === 'hour' ? delta / 36e5 : // 1000 * 60 * 60 + units === 'day' ? (delta - zoneDelta) / 864e5 : // 1000 * 60 * 60 * 24, negate dst + units === 'week' ? (delta - zoneDelta) / 6048e5 : // 1000 * 60 * 60 * 24 * 7, negate dst + delta; + } + return asFloat ? output : absFloor(output); +} + +function monthDiff (a, b) { + // difference in months + var wholeMonthDiff = ((b.year() - a.year()) * 12) + (b.month() - a.month()), + // b is in (anchor - 1 month, anchor + 1 month) + anchor = a.clone().add(wholeMonthDiff, 'months'), + anchor2, adjust; + + if (b - anchor < 0) { + anchor2 = a.clone().add(wholeMonthDiff - 1, 'months'); + // linear across the month + adjust = (b - anchor) / (anchor - anchor2); + } else { + anchor2 = a.clone().add(wholeMonthDiff + 1, 'months'); + // linear across the month + adjust = (b - anchor) / (anchor2 - anchor); + } + + //check for negative zero, return zero if negative zero + return -(wholeMonthDiff + adjust) || 0; +} + +hooks.defaultFormat = 'YYYY-MM-DDTHH:mm:ssZ'; +hooks.defaultFormatUtc = 'YYYY-MM-DDTHH:mm:ss[Z]'; + +function toString () { + return this.clone().locale('en').format('ddd MMM DD YYYY HH:mm:ss [GMT]ZZ'); +} + +function toISOString () { + var m = this.clone().utc(); + if (0 < m.year() && m.year() <= 9999) { + if (isFunction(Date.prototype.toISOString)) { + // native implementation is ~50x faster, use it when we can + return this.toDate().toISOString(); + } else { + return formatMoment(m, 'YYYY-MM-DD[T]HH:mm:ss.SSS[Z]'); + } + } else { + return formatMoment(m, 'YYYYYY-MM-DD[T]HH:mm:ss.SSS[Z]'); + } +} + +/** + * Return a human readable representation of a moment that can + * also be evaluated to get a new moment which is the same + * + * @link https://nodejs.org/dist/latest/docs/api/util.html#util_custom_inspect_function_on_objects + */ +function inspect () { + if (!this.isValid()) { + return 'moment.invalid(/* ' + this._i + ' */)'; + } + var func = 'moment'; + var zone = ''; + if (!this.isLocal()) { + func = this.utcOffset() === 0 ? 'moment.utc' : 'moment.parseZone'; + zone = 'Z'; + } + var prefix = '[' + func + '("]'; + var year = (0 < this.year() && this.year() <= 9999) ? 'YYYY' : 'YYYYYY'; + var datetime = '-MM-DD[T]HH:mm:ss.SSS'; + var suffix = zone + '[")]'; + + return this.format(prefix + year + datetime + suffix); +} + +function format (inputString) { + if (!inputString) { + inputString = this.isUtc() ? hooks.defaultFormatUtc : hooks.defaultFormat; + } + var output = formatMoment(this, inputString); + return this.localeData().postformat(output); +} + +function from (time, withoutSuffix) { + if (this.isValid() && + ((isMoment(time) && time.isValid()) || + createLocal(time).isValid())) { + return createDuration({to: this, from: time}).locale(this.locale()).humanize(!withoutSuffix); + } else { + return this.localeData().invalidDate(); + } +} + +function fromNow (withoutSuffix) { + return this.from(createLocal(), withoutSuffix); +} + +function to (time, withoutSuffix) { + if (this.isValid() && + ((isMoment(time) && time.isValid()) || + createLocal(time).isValid())) { + return createDuration({from: this, to: time}).locale(this.locale()).humanize(!withoutSuffix); + } else { + return this.localeData().invalidDate(); + } +} + +function toNow (withoutSuffix) { + return this.to(createLocal(), withoutSuffix); +} + +// If passed a locale key, it will set the locale for this +// instance. Otherwise, it will return the locale configuration +// variables for this instance. +function locale (key) { + var newLocaleData; + + if (key === undefined) { + return this._locale._abbr; + } else { + newLocaleData = getLocale(key); + if (newLocaleData != null) { + this._locale = newLocaleData; + } + return this; + } +} + +var lang = deprecate( + 'moment().lang() is deprecated. Instead, use moment().localeData() to get the language configuration. Use moment().locale() to change languages.', + function (key) { + if (key === undefined) { + return this.localeData(); + } else { + return this.locale(key); + } + } +); + +function localeData () { + return this._locale; +} + +function startOf (units) { + units = normalizeUnits(units); + // the following switch intentionally omits break keywords + // to utilize falling through the cases. + switch (units) { + case 'year': + this.month(0); + /* falls through */ + case 'quarter': + case 'month': + this.date(1); + /* falls through */ + case 'week': + case 'isoWeek': + case 'day': + case 'date': + this.hours(0); + /* falls through */ + case 'hour': + this.minutes(0); + /* falls through */ + case 'minute': + this.seconds(0); + /* falls through */ + case 'second': + this.milliseconds(0); + } + + // weeks are a special case + if (units === 'week') { + this.weekday(0); + } + if (units === 'isoWeek') { + this.isoWeekday(1); + } + + // quarters are also special + if (units === 'quarter') { + this.month(Math.floor(this.month() / 3) * 3); + } + + return this; +} + +function endOf (units) { + units = normalizeUnits(units); + if (units === undefined || units === 'millisecond') { + return this; + } + + // 'date' is an alias for 'day', so it should be considered as such. + if (units === 'date') { + units = 'day'; + } + + return this.startOf(units).add(1, (units === 'isoWeek' ? 'week' : units)).subtract(1, 'ms'); +} + +function valueOf () { + return this._d.valueOf() - ((this._offset || 0) * 60000); +} + +function unix () { + return Math.floor(this.valueOf() / 1000); +} + +function toDate () { + return new Date(this.valueOf()); +} + +function toArray () { + var m = this; + return [m.year(), m.month(), m.date(), m.hour(), m.minute(), m.second(), m.millisecond()]; +} + +function toObject () { + var m = this; + return { + years: m.year(), + months: m.month(), + date: m.date(), + hours: m.hours(), + minutes: m.minutes(), + seconds: m.seconds(), + milliseconds: m.milliseconds() + }; +} + +function toJSON () { + // new Date(NaN).toJSON() === null + return this.isValid() ? this.toISOString() : null; +} + +function isValid$1 () { + return isValid(this); +} + +function parsingFlags () { + return extend({}, getParsingFlags(this)); +} + +function invalidAt () { + return getParsingFlags(this).overflow; +} + +function creationData() { + return { + input: this._i, + format: this._f, + locale: this._locale, + isUTC: this._isUTC, + strict: this._strict + }; +} + +// FORMATTING + +addFormatToken(0, ['gg', 2], 0, function () { + return this.weekYear() % 100; +}); + +addFormatToken(0, ['GG', 2], 0, function () { + return this.isoWeekYear() % 100; +}); + +function addWeekYearFormatToken (token, getter) { + addFormatToken(0, [token, token.length], 0, getter); +} + +addWeekYearFormatToken('gggg', 'weekYear'); +addWeekYearFormatToken('ggggg', 'weekYear'); +addWeekYearFormatToken('GGGG', 'isoWeekYear'); +addWeekYearFormatToken('GGGGG', 'isoWeekYear'); + +// ALIASES + +addUnitAlias('weekYear', 'gg'); +addUnitAlias('isoWeekYear', 'GG'); + +// PRIORITY + +addUnitPriority('weekYear', 1); +addUnitPriority('isoWeekYear', 1); + + +// PARSING + +addRegexToken('G', matchSigned); +addRegexToken('g', matchSigned); +addRegexToken('GG', match1to2, match2); +addRegexToken('gg', match1to2, match2); +addRegexToken('GGGG', match1to4, match4); +addRegexToken('gggg', match1to4, match4); +addRegexToken('GGGGG', match1to6, match6); +addRegexToken('ggggg', match1to6, match6); + +addWeekParseToken(['gggg', 'ggggg', 'GGGG', 'GGGGG'], function (input, week, config, token) { + week[token.substr(0, 2)] = toInt(input); +}); + +addWeekParseToken(['gg', 'GG'], function (input, week, config, token) { + week[token] = hooks.parseTwoDigitYear(input); +}); + +// MOMENTS + +function getSetWeekYear (input) { + return getSetWeekYearHelper.call(this, + input, + this.week(), + this.weekday(), + this.localeData()._week.dow, + this.localeData()._week.doy); +} + +function getSetISOWeekYear (input) { + return getSetWeekYearHelper.call(this, + input, this.isoWeek(), this.isoWeekday(), 1, 4); +} + +function getISOWeeksInYear () { + return weeksInYear(this.year(), 1, 4); +} + +function getWeeksInYear () { + var weekInfo = this.localeData()._week; + return weeksInYear(this.year(), weekInfo.dow, weekInfo.doy); +} + +function getSetWeekYearHelper(input, week, weekday, dow, doy) { + var weeksTarget; + if (input == null) { + return weekOfYear(this, dow, doy).year; + } else { + weeksTarget = weeksInYear(input, dow, doy); + if (week > weeksTarget) { + week = weeksTarget; + } + return setWeekAll.call(this, input, week, weekday, dow, doy); + } +} + +function setWeekAll(weekYear, week, weekday, dow, doy) { + var dayOfYearData = dayOfYearFromWeeks(weekYear, week, weekday, dow, doy), + date = createUTCDate(dayOfYearData.year, 0, dayOfYearData.dayOfYear); + + this.year(date.getUTCFullYear()); + this.month(date.getUTCMonth()); + this.date(date.getUTCDate()); + return this; +} + +// FORMATTING + +addFormatToken('Q', 0, 'Qo', 'quarter'); + +// ALIASES + +addUnitAlias('quarter', 'Q'); + +// PRIORITY + +addUnitPriority('quarter', 7); + +// PARSING + +addRegexToken('Q', match1); +addParseToken('Q', function (input, array) { + array[MONTH] = (toInt(input) - 1) * 3; +}); + +// MOMENTS + +function getSetQuarter (input) { + return input == null ? Math.ceil((this.month() + 1) / 3) : this.month((input - 1) * 3 + this.month() % 3); +} + +// FORMATTING + +addFormatToken('D', ['DD', 2], 'Do', 'date'); + +// ALIASES + +addUnitAlias('date', 'D'); + +// PRIOROITY +addUnitPriority('date', 9); + +// PARSING + +addRegexToken('D', match1to2); +addRegexToken('DD', match1to2, match2); +addRegexToken('Do', function (isStrict, locale) { + return isStrict ? locale._ordinalParse : locale._ordinalParseLenient; +}); + +addParseToken(['D', 'DD'], DATE); +addParseToken('Do', function (input, array) { + array[DATE] = toInt(input.match(match1to2)[0], 10); +}); + +// MOMENTS + +var getSetDayOfMonth = makeGetSet('Date', true); + +// FORMATTING + +addFormatToken('DDD', ['DDDD', 3], 'DDDo', 'dayOfYear'); + +// ALIASES + +addUnitAlias('dayOfYear', 'DDD'); + +// PRIORITY +addUnitPriority('dayOfYear', 4); + +// PARSING + +addRegexToken('DDD', match1to3); +addRegexToken('DDDD', match3); +addParseToken(['DDD', 'DDDD'], function (input, array, config) { + config._dayOfYear = toInt(input); +}); + +// HELPERS + +// MOMENTS + +function getSetDayOfYear (input) { + var dayOfYear = Math.round((this.clone().startOf('day') - this.clone().startOf('year')) / 864e5) + 1; + return input == null ? dayOfYear : this.add((input - dayOfYear), 'd'); +} + +// FORMATTING + +addFormatToken('m', ['mm', 2], 0, 'minute'); + +// ALIASES + +addUnitAlias('minute', 'm'); + +// PRIORITY + +addUnitPriority('minute', 14); + +// PARSING + +addRegexToken('m', match1to2); +addRegexToken('mm', match1to2, match2); +addParseToken(['m', 'mm'], MINUTE); + +// MOMENTS + +var getSetMinute = makeGetSet('Minutes', false); + +// FORMATTING + +addFormatToken('s', ['ss', 2], 0, 'second'); + +// ALIASES + +addUnitAlias('second', 's'); + +// PRIORITY + +addUnitPriority('second', 15); + +// PARSING + +addRegexToken('s', match1to2); +addRegexToken('ss', match1to2, match2); +addParseToken(['s', 'ss'], SECOND); + +// MOMENTS + +var getSetSecond = makeGetSet('Seconds', false); + +// FORMATTING + +addFormatToken('S', 0, 0, function () { + return ~~(this.millisecond() / 100); +}); + +addFormatToken(0, ['SS', 2], 0, function () { + return ~~(this.millisecond() / 10); +}); + +addFormatToken(0, ['SSS', 3], 0, 'millisecond'); +addFormatToken(0, ['SSSS', 4], 0, function () { + return this.millisecond() * 10; +}); +addFormatToken(0, ['SSSSS', 5], 0, function () { + return this.millisecond() * 100; +}); +addFormatToken(0, ['SSSSSS', 6], 0, function () { + return this.millisecond() * 1000; +}); +addFormatToken(0, ['SSSSSSS', 7], 0, function () { + return this.millisecond() * 10000; +}); +addFormatToken(0, ['SSSSSSSS', 8], 0, function () { + return this.millisecond() * 100000; +}); +addFormatToken(0, ['SSSSSSSSS', 9], 0, function () { + return this.millisecond() * 1000000; +}); + + +// ALIASES + +addUnitAlias('millisecond', 'ms'); + +// PRIORITY + +addUnitPriority('millisecond', 16); + +// PARSING + +addRegexToken('S', match1to3, match1); +addRegexToken('SS', match1to3, match2); +addRegexToken('SSS', match1to3, match3); + +var token; +for (token = 'SSSS'; token.length <= 9; token += 'S') { + addRegexToken(token, matchUnsigned); +} + +function parseMs(input, array) { + array[MILLISECOND] = toInt(('0.' + input) * 1000); +} + +for (token = 'S'; token.length <= 9; token += 'S') { + addParseToken(token, parseMs); +} +// MOMENTS + +var getSetMillisecond = makeGetSet('Milliseconds', false); + +// FORMATTING + +addFormatToken('z', 0, 0, 'zoneAbbr'); +addFormatToken('zz', 0, 0, 'zoneName'); + +// MOMENTS + +function getZoneAbbr () { + return this._isUTC ? 'UTC' : ''; +} + +function getZoneName () { + return this._isUTC ? 'Coordinated Universal Time' : ''; +} + +var proto = Moment.prototype; + +proto.add = add; +proto.calendar = calendar$1; +proto.clone = clone; +proto.diff = diff; +proto.endOf = endOf; +proto.format = format; +proto.from = from; +proto.fromNow = fromNow; +proto.to = to; +proto.toNow = toNow; +proto.get = stringGet; +proto.invalidAt = invalidAt; +proto.isAfter = isAfter; +proto.isBefore = isBefore; +proto.isBetween = isBetween; +proto.isSame = isSame; +proto.isSameOrAfter = isSameOrAfter; +proto.isSameOrBefore = isSameOrBefore; +proto.isValid = isValid$1; +proto.lang = lang; +proto.locale = locale; +proto.localeData = localeData; +proto.max = prototypeMax; +proto.min = prototypeMin; +proto.parsingFlags = parsingFlags; +proto.set = stringSet; +proto.startOf = startOf; +proto.subtract = subtract; +proto.toArray = toArray; +proto.toObject = toObject; +proto.toDate = toDate; +proto.toISOString = toISOString; +proto.inspect = inspect; +proto.toJSON = toJSON; +proto.toString = toString; +proto.unix = unix; +proto.valueOf = valueOf; +proto.creationData = creationData; + +// Year +proto.year = getSetYear; +proto.isLeapYear = getIsLeapYear; + +// Week Year +proto.weekYear = getSetWeekYear; +proto.isoWeekYear = getSetISOWeekYear; + +// Quarter +proto.quarter = proto.quarters = getSetQuarter; + +// Month +proto.month = getSetMonth; +proto.daysInMonth = getDaysInMonth; + +// Week +proto.week = proto.weeks = getSetWeek; +proto.isoWeek = proto.isoWeeks = getSetISOWeek; +proto.weeksInYear = getWeeksInYear; +proto.isoWeeksInYear = getISOWeeksInYear; + +// Day +proto.date = getSetDayOfMonth; +proto.day = proto.days = getSetDayOfWeek; +proto.weekday = getSetLocaleDayOfWeek; +proto.isoWeekday = getSetISODayOfWeek; +proto.dayOfYear = getSetDayOfYear; + +// Hour +proto.hour = proto.hours = getSetHour; + +// Minute +proto.minute = proto.minutes = getSetMinute; + +// Second +proto.second = proto.seconds = getSetSecond; + +// Millisecond +proto.millisecond = proto.milliseconds = getSetMillisecond; + +// Offset +proto.utcOffset = getSetOffset; +proto.utc = setOffsetToUTC; +proto.local = setOffsetToLocal; +proto.parseZone = setOffsetToParsedOffset; +proto.hasAlignedHourOffset = hasAlignedHourOffset; +proto.isDST = isDaylightSavingTime; +proto.isLocal = isLocal; +proto.isUtcOffset = isUtcOffset; +proto.isUtc = isUtc; +proto.isUTC = isUtc; + +// Timezone +proto.zoneAbbr = getZoneAbbr; +proto.zoneName = getZoneName; + +// Deprecations +proto.dates = deprecate('dates accessor is deprecated. Use date instead.', getSetDayOfMonth); +proto.months = deprecate('months accessor is deprecated. Use month instead', getSetMonth); +proto.years = deprecate('years accessor is deprecated. Use year instead', getSetYear); +proto.zone = deprecate('moment().zone is deprecated, use moment().utcOffset instead. http://momentjs.com/guides/#/warnings/zone/', getSetZone); +proto.isDSTShifted = deprecate('isDSTShifted is deprecated. See http://momentjs.com/guides/#/warnings/dst-shifted/ for more information', isDaylightSavingTimeShifted); + +function createUnix (input) { + return createLocal(input * 1000); +} + +function createInZone () { + return createLocal.apply(null, arguments).parseZone(); +} + +function preParsePostFormat (string) { + return string; +} + +var proto$1 = Locale.prototype; + +proto$1.calendar = calendar; +proto$1.longDateFormat = longDateFormat; +proto$1.invalidDate = invalidDate; +proto$1.ordinal = ordinal; +proto$1.preparse = preParsePostFormat; +proto$1.postformat = preParsePostFormat; +proto$1.relativeTime = relativeTime; +proto$1.pastFuture = pastFuture; +proto$1.set = set; + +// Month +proto$1.months = localeMonths; +proto$1.monthsShort = localeMonthsShort; +proto$1.monthsParse = localeMonthsParse; +proto$1.monthsRegex = monthsRegex; +proto$1.monthsShortRegex = monthsShortRegex; + +// Week +proto$1.week = localeWeek; +proto$1.firstDayOfYear = localeFirstDayOfYear; +proto$1.firstDayOfWeek = localeFirstDayOfWeek; + +// Day of Week +proto$1.weekdays = localeWeekdays; +proto$1.weekdaysMin = localeWeekdaysMin; +proto$1.weekdaysShort = localeWeekdaysShort; +proto$1.weekdaysParse = localeWeekdaysParse; + +proto$1.weekdaysRegex = weekdaysRegex; +proto$1.weekdaysShortRegex = weekdaysShortRegex; +proto$1.weekdaysMinRegex = weekdaysMinRegex; + +// Hours +proto$1.isPM = localeIsPM; +proto$1.meridiem = localeMeridiem; + +function get$1 (format, index, field, setter) { + var locale = getLocale(); + var utc = createUTC().set(setter, index); + return locale[field](utc, format); +} + +function listMonthsImpl (format, index, field) { + if (isNumber(format)) { + index = format; + format = undefined; + } + + format = format || ''; + + if (index != null) { + return get$1(format, index, field, 'month'); + } + + var i; + var out = []; + for (i = 0; i < 12; i++) { + out[i] = get$1(format, i, field, 'month'); + } + return out; +} + +// () +// (5) +// (fmt, 5) +// (fmt) +// (true) +// (true, 5) +// (true, fmt, 5) +// (true, fmt) +function listWeekdaysImpl (localeSorted, format, index, field) { + if (typeof localeSorted === 'boolean') { + if (isNumber(format)) { + index = format; + format = undefined; + } + + format = format || ''; + } else { + format = localeSorted; + index = format; + localeSorted = false; + + if (isNumber(format)) { + index = format; + format = undefined; + } + + format = format || ''; + } + + var locale = getLocale(), + shift = localeSorted ? locale._week.dow : 0; + + if (index != null) { + return get$1(format, (index + shift) % 7, field, 'day'); + } + + var i; + var out = []; + for (i = 0; i < 7; i++) { + out[i] = get$1(format, (i + shift) % 7, field, 'day'); + } + return out; +} + +function listMonths (format, index) { + return listMonthsImpl(format, index, 'months'); +} + +function listMonthsShort (format, index) { + return listMonthsImpl(format, index, 'monthsShort'); +} + +function listWeekdays (localeSorted, format, index) { + return listWeekdaysImpl(localeSorted, format, index, 'weekdays'); +} + +function listWeekdaysShort (localeSorted, format, index) { + return listWeekdaysImpl(localeSorted, format, index, 'weekdaysShort'); +} + +function listWeekdaysMin (localeSorted, format, index) { + return listWeekdaysImpl(localeSorted, format, index, 'weekdaysMin'); +} + +getSetGlobalLocale('en', { + ordinalParse: /\d{1,2}(th|st|nd|rd)/, + ordinal : function (number) { + var b = number % 10, + output = (toInt(number % 100 / 10) === 1) ? 'th' : + (b === 1) ? 'st' : + (b === 2) ? 'nd' : + (b === 3) ? 'rd' : 'th'; + return number + output; + } +}); + +// Side effect imports +hooks.lang = deprecate('moment.lang is deprecated. Use moment.locale instead.', getSetGlobalLocale); +hooks.langData = deprecate('moment.langData is deprecated. Use moment.localeData instead.', getLocale); + +var mathAbs = Math.abs; + +function abs () { + var data = this._data; + + this._milliseconds = mathAbs(this._milliseconds); + this._days = mathAbs(this._days); + this._months = mathAbs(this._months); + + data.milliseconds = mathAbs(data.milliseconds); + data.seconds = mathAbs(data.seconds); + data.minutes = mathAbs(data.minutes); + data.hours = mathAbs(data.hours); + data.months = mathAbs(data.months); + data.years = mathAbs(data.years); + + return this; +} + +function addSubtract$1 (duration, input, value, direction) { + var other = createDuration(input, value); + + duration._milliseconds += direction * other._milliseconds; + duration._days += direction * other._days; + duration._months += direction * other._months; + + return duration._bubble(); +} + +// supports only 2.0-style add(1, 's') or add(duration) +function add$1 (input, value) { + return addSubtract$1(this, input, value, 1); +} + +// supports only 2.0-style subtract(1, 's') or subtract(duration) +function subtract$1 (input, value) { + return addSubtract$1(this, input, value, -1); +} + +function absCeil (number) { + if (number < 0) { + return Math.floor(number); + } else { + return Math.ceil(number); + } +} + +function bubble () { + var milliseconds = this._milliseconds; + var days = this._days; + var months = this._months; + var data = this._data; + var seconds, minutes, hours, years, monthsFromDays; + + // if we have a mix of positive and negative values, bubble down first + // check: https://github.com/moment/moment/issues/2166 + if (!((milliseconds >= 0 && days >= 0 && months >= 0) || + (milliseconds <= 0 && days <= 0 && months <= 0))) { + milliseconds += absCeil(monthsToDays(months) + days) * 864e5; + days = 0; + months = 0; + } + + // The following code bubbles up values, see the tests for + // examples of what that means. + data.milliseconds = milliseconds % 1000; + + seconds = absFloor(milliseconds / 1000); + data.seconds = seconds % 60; + + minutes = absFloor(seconds / 60); + data.minutes = minutes % 60; + + hours = absFloor(minutes / 60); + data.hours = hours % 24; + + days += absFloor(hours / 24); + + // convert days to months + monthsFromDays = absFloor(daysToMonths(days)); + months += monthsFromDays; + days -= absCeil(monthsToDays(monthsFromDays)); + + // 12 months -> 1 year + years = absFloor(months / 12); + months %= 12; + + data.days = days; + data.months = months; + data.years = years; + + return this; +} + +function daysToMonths (days) { + // 400 years have 146097 days (taking into account leap year rules) + // 400 years have 12 months === 4800 + return days * 4800 / 146097; +} + +function monthsToDays (months) { + // the reverse of daysToMonths + return months * 146097 / 4800; +} + +function as (units) { + var days; + var months; + var milliseconds = this._milliseconds; + + units = normalizeUnits(units); + + if (units === 'month' || units === 'year') { + days = this._days + milliseconds / 864e5; + months = this._months + daysToMonths(days); + return units === 'month' ? months : months / 12; + } else { + // handle milliseconds separately because of floating point math errors (issue #1867) + days = this._days + Math.round(monthsToDays(this._months)); + switch (units) { + case 'week' : return days / 7 + milliseconds / 6048e5; + case 'day' : return days + milliseconds / 864e5; + case 'hour' : return days * 24 + milliseconds / 36e5; + case 'minute' : return days * 1440 + milliseconds / 6e4; + case 'second' : return days * 86400 + milliseconds / 1000; + // Math.floor prevents floating point math errors here + case 'millisecond': return Math.floor(days * 864e5) + milliseconds; + default: throw new Error('Unknown unit ' + units); + } + } +} + +// TODO: Use this.as('ms')? +function valueOf$1 () { + return ( + this._milliseconds + + this._days * 864e5 + + (this._months % 12) * 2592e6 + + toInt(this._months / 12) * 31536e6 + ); +} + +function makeAs (alias) { + return function () { + return this.as(alias); + }; +} + +var asMilliseconds = makeAs('ms'); +var asSeconds = makeAs('s'); +var asMinutes = makeAs('m'); +var asHours = makeAs('h'); +var asDays = makeAs('d'); +var asWeeks = makeAs('w'); +var asMonths = makeAs('M'); +var asYears = makeAs('y'); + +function get$2 (units) { + units = normalizeUnits(units); + return this[units + 's'](); +} + +function makeGetter(name) { + return function () { + return this._data[name]; + }; +} + +var milliseconds = makeGetter('milliseconds'); +var seconds = makeGetter('seconds'); +var minutes = makeGetter('minutes'); +var hours = makeGetter('hours'); +var days = makeGetter('days'); +var months = makeGetter('months'); +var years = makeGetter('years'); + +function weeks () { + return absFloor(this.days() / 7); +} + +var round = Math.round; +var thresholds = { + s: 45, // seconds to minute + m: 45, // minutes to hour + h: 22, // hours to day + d: 26, // days to month + M: 11 // months to year +}; + +// helper function for moment.fn.from, moment.fn.fromNow, and moment.duration.fn.humanize +function substituteTimeAgo(string, number, withoutSuffix, isFuture, locale) { + return locale.relativeTime(number || 1, !!withoutSuffix, string, isFuture); +} + +function relativeTime$1 (posNegDuration, withoutSuffix, locale) { + var duration = createDuration(posNegDuration).abs(); + var seconds = round(duration.as('s')); + var minutes = round(duration.as('m')); + var hours = round(duration.as('h')); + var days = round(duration.as('d')); + var months = round(duration.as('M')); + var years = round(duration.as('y')); + + var a = seconds < thresholds.s && ['s', seconds] || + minutes <= 1 && ['m'] || + minutes < thresholds.m && ['mm', minutes] || + hours <= 1 && ['h'] || + hours < thresholds.h && ['hh', hours] || + days <= 1 && ['d'] || + days < thresholds.d && ['dd', days] || + months <= 1 && ['M'] || + months < thresholds.M && ['MM', months] || + years <= 1 && ['y'] || ['yy', years]; + + a[2] = withoutSuffix; + a[3] = +posNegDuration > 0; + a[4] = locale; + return substituteTimeAgo.apply(null, a); +} + +// This function allows you to set the rounding function for relative time strings +function getSetRelativeTimeRounding (roundingFunction) { + if (roundingFunction === undefined) { + return round; + } + if (typeof(roundingFunction) === 'function') { + round = roundingFunction; + return true; + } + return false; +} + +// This function allows you to set a threshold for relative time strings +function getSetRelativeTimeThreshold (threshold, limit) { + if (thresholds[threshold] === undefined) { + return false; + } + if (limit === undefined) { + return thresholds[threshold]; + } + thresholds[threshold] = limit; + return true; +} + +function humanize (withSuffix) { + var locale = this.localeData(); + var output = relativeTime$1(this, !withSuffix, locale); + + if (withSuffix) { + output = locale.pastFuture(+this, output); + } + + return locale.postformat(output); +} + +var abs$1 = Math.abs; + +function toISOString$1() { + // for ISO strings we do not use the normal bubbling rules: + // * milliseconds bubble up until they become hours + // * days do not bubble at all + // * months bubble up until they become years + // This is because there is no context-free conversion between hours and days + // (think of clock changes) + // and also not between days and months (28-31 days per month) + var seconds = abs$1(this._milliseconds) / 1000; + var days = abs$1(this._days); + var months = abs$1(this._months); + var minutes, hours, years; + + // 3600 seconds -> 60 minutes -> 1 hour + minutes = absFloor(seconds / 60); + hours = absFloor(minutes / 60); + seconds %= 60; + minutes %= 60; + + // 12 months -> 1 year + years = absFloor(months / 12); + months %= 12; + + + // inspired by https://github.com/dordille/moment-isoduration/blob/master/moment.isoduration.js + var Y = years; + var M = months; + var D = days; + var h = hours; + var m = minutes; + var s = seconds; + var total = this.asSeconds(); + + if (!total) { + // this is the same as C#'s (Noda) and python (isodate)... + // but not other JS (goog.date) + return 'P0D'; + } + + return (total < 0 ? '-' : '') + + 'P' + + (Y ? Y + 'Y' : '') + + (M ? M + 'M' : '') + + (D ? D + 'D' : '') + + ((h || m || s) ? 'T' : '') + + (h ? h + 'H' : '') + + (m ? m + 'M' : '') + + (s ? s + 'S' : ''); +} + +var proto$2 = Duration.prototype; + +proto$2.abs = abs; +proto$2.add = add$1; +proto$2.subtract = subtract$1; +proto$2.as = as; +proto$2.asMilliseconds = asMilliseconds; +proto$2.asSeconds = asSeconds; +proto$2.asMinutes = asMinutes; +proto$2.asHours = asHours; +proto$2.asDays = asDays; +proto$2.asWeeks = asWeeks; +proto$2.asMonths = asMonths; +proto$2.asYears = asYears; +proto$2.valueOf = valueOf$1; +proto$2._bubble = bubble; +proto$2.get = get$2; +proto$2.milliseconds = milliseconds; +proto$2.seconds = seconds; +proto$2.minutes = minutes; +proto$2.hours = hours; +proto$2.days = days; +proto$2.weeks = weeks; +proto$2.months = months; +proto$2.years = years; +proto$2.humanize = humanize; +proto$2.toISOString = toISOString$1; +proto$2.toString = toISOString$1; +proto$2.toJSON = toISOString$1; +proto$2.locale = locale; +proto$2.localeData = localeData; + +// Deprecations +proto$2.toIsoString = deprecate('toIsoString() is deprecated. Please use toISOString() instead (notice the capitals)', toISOString$1); +proto$2.lang = lang; + +// Side effect imports + +// FORMATTING + +addFormatToken('X', 0, 0, 'unix'); +addFormatToken('x', 0, 0, 'valueOf'); + +// PARSING + +addRegexToken('x', matchSigned); +addRegexToken('X', matchTimestamp); +addParseToken('X', function (input, array, config) { + config._d = new Date(parseFloat(input, 10) * 1000); +}); +addParseToken('x', function (input, array, config) { + config._d = new Date(toInt(input)); +}); + +// Side effect imports + +//! moment.js +//! version : 2.17.1 +//! authors : Tim Wood, Iskren Chernev, Moment.js contributors +//! license : MIT +//! momentjs.com + +hooks.version = '2.17.1'; + +setHookCallback(createLocal); + +hooks.fn = proto; +hooks.min = min; +hooks.max = max; +hooks.now = now; +hooks.utc = createUTC; +hooks.unix = createUnix; +hooks.months = listMonths; +hooks.isDate = isDate; +hooks.locale = getSetGlobalLocale; +hooks.invalid = createInvalid; +hooks.duration = createDuration; +hooks.isMoment = isMoment; +hooks.weekdays = listWeekdays; +hooks.parseZone = createInZone; +hooks.localeData = getLocale; +hooks.isDuration = isDuration; +hooks.monthsShort = listMonthsShort; +hooks.weekdaysMin = listWeekdaysMin; +hooks.defineLocale = defineLocale; +hooks.updateLocale = updateLocale; +hooks.locales = listLocales; +hooks.weekdaysShort = listWeekdaysShort; +hooks.normalizeUnits = normalizeUnits; +hooks.relativeTimeRounding = getSetRelativeTimeRounding; +hooks.relativeTimeThreshold = getSetRelativeTimeThreshold; +hooks.calendarFormat = getCalendarFormat; +hooks.prototype = proto; + +//! moment.js locale configuration +//! locale : Afrikaans [af] +//! author : Werner Mollentze : https://github.com/wernerm + +hooks.defineLocale('af', { + months : 'Januarie_Februarie_Maart_April_Mei_Junie_Julie_Augustus_September_Oktober_November_Desember'.split('_'), + monthsShort : 'Jan_Feb_Mrt_Apr_Mei_Jun_Jul_Aug_Sep_Okt_Nov_Des'.split('_'), + weekdays : 'Sondag_Maandag_Dinsdag_Woensdag_Donderdag_Vrydag_Saterdag'.split('_'), + weekdaysShort : 'Son_Maa_Din_Woe_Don_Vry_Sat'.split('_'), + weekdaysMin : 'So_Ma_Di_Wo_Do_Vr_Sa'.split('_'), + meridiemParse: /vm|nm/i, + isPM : function (input) { + return /^nm$/i.test(input); + }, + meridiem : function (hours, minutes, isLower) { + if (hours < 12) { + return isLower ? 'vm' : 'VM'; + } else { + return isLower ? 'nm' : 'NM'; + } + }, + longDateFormat : { + LT : 'HH:mm', + LTS : 'HH:mm:ss', + L : 'DD/MM/YYYY', + LL : 'D MMMM YYYY', + LLL : 'D MMMM YYYY HH:mm', + LLLL : 'dddd, D MMMM YYYY HH:mm' + }, + calendar : { + sameDay : '[Vandag om] LT', + nextDay : '[Môre om] LT', + nextWeek : 'dddd [om] LT', + lastDay : '[Gister om] LT', + lastWeek : '[Laas] dddd [om] LT', + sameElse : 'L' + }, + relativeTime : { + future : 'oor %s', + past : '%s gelede', + s : '\'n paar sekondes', + m : '\'n minuut', + mm : '%d minute', + h : '\'n uur', + hh : '%d ure', + d : '\'n dag', + dd : '%d dae', + M : '\'n maand', + MM : '%d maande', + y : '\'n jaar', + yy : '%d jaar' + }, + ordinalParse: /\d{1,2}(ste|de)/, + ordinal : function (number) { + return number + ((number === 1 || number === 8 || number >= 20) ? 'ste' : 'de'); // Thanks to Joris Röling : https://github.com/jjupiter + }, + week : { + dow : 1, // Maandag is die eerste dag van die week. + doy : 4 // Die week wat die 4de Januarie bevat is die eerste week van die jaar. + } +}); + +//! moment.js locale configuration +//! locale : Arabic (Algeria) [ar-dz] +//! author : Noureddine LOUAHEDJ : https://github.com/noureddineme + +hooks.defineLocale('ar-dz', { + months : 'جانفي_فيفري_مارس_أفريل_ماي_جوان_جويلية_أوت_سبتمبر_أكتوبر_نوفمبر_ديسمبر'.split('_'), + monthsShort : 'جانفي_فيفري_مارس_أفريل_ماي_جوان_جويلية_أوت_سبتمبر_أكتوبر_نوفمبر_ديسمبر'.split('_'), + weekdays : 'الأحد_الإثنين_الثلاثاء_الأربعاء_الخميس_الجمعة_السبت'.split('_'), + weekdaysShort : 'احد_اثنين_ثلاثاء_اربعاء_خميس_جمعة_سبت'.split('_'), + weekdaysMin : 'أح_إث_ثلا_أر_خم_جم_سب'.split('_'), + weekdaysParseExact : true, + longDateFormat : { + LT : 'HH:mm', + LTS : 'HH:mm:ss', + L : 'DD/MM/YYYY', + LL : 'D MMMM YYYY', + LLL : 'D MMMM YYYY HH:mm', + LLLL : 'dddd D MMMM YYYY HH:mm' + }, + calendar : { + sameDay: '[اليوم على الساعة] LT', + nextDay: '[غدا على الساعة] LT', + nextWeek: 'dddd [على الساعة] LT', + lastDay: '[أمس على الساعة] LT', + lastWeek: 'dddd [على الساعة] LT', + sameElse: 'L' + }, + relativeTime : { + future : 'في %s', + past : 'منذ %s', + s : 'ثوان', + m : 'دقيقة', + mm : '%d دقائق', + h : 'ساعة', + hh : '%d ساعات', + d : 'يوم', + dd : '%d أيام', + M : 'شهر', + MM : '%d أشهر', + y : 'سنة', + yy : '%d سنوات' + }, + week : { + dow : 0, // Sunday is the first day of the week. + doy : 4 // The week that contains Jan 1st is the first week of the year. + } +}); + +//! moment.js locale configuration +//! locale : Arabic (Lybia) [ar-ly] +//! author : Ali Hmer: https://github.com/kikoanis + +var symbolMap = { + '1': '1', + '2': '2', + '3': '3', + '4': '4', + '5': '5', + '6': '6', + '7': '7', + '8': '8', + '9': '9', + '0': '0' +}; +var pluralForm = function (n) { + return n === 0 ? 0 : n === 1 ? 1 : n === 2 ? 2 : n % 100 >= 3 && n % 100 <= 10 ? 3 : n % 100 >= 11 ? 4 : 5; +}; +var plurals = { + s : ['أقل من ثانية', 'ثانية واحدة', ['ثانيتان', 'ثانيتين'], '%d ثوان', '%d ثانية', '%d ثانية'], + m : ['أقل من دقيقة', 'دقيقة واحدة', ['دقيقتان', 'دقيقتين'], '%d دقائق', '%d دقيقة', '%d دقيقة'], + h : ['أقل من ساعة', 'ساعة واحدة', ['ساعتان', 'ساعتين'], '%d ساعات', '%d ساعة', '%d ساعة'], + d : ['أقل من يوم', 'يوم واحد', ['يومان', 'يومين'], '%d أيام', '%d يومًا', '%d يوم'], + M : ['أقل من شهر', 'شهر واحد', ['شهران', 'شهرين'], '%d أشهر', '%d شهرا', '%d شهر'], + y : ['أقل من عام', 'عام واحد', ['عامان', 'عامين'], '%d أعوام', '%d عامًا', '%d عام'] +}; +var pluralize = function (u) { + return function (number, withoutSuffix, string, isFuture) { + var f = pluralForm(number), + str = plurals[u][pluralForm(number)]; + if (f === 2) { + str = str[withoutSuffix ? 0 : 1]; + } + return str.replace(/%d/i, number); + }; +}; +var months$1 = [ + 'يناير', + 'فبراير', + 'مارس', + 'أبريل', + 'مايو', + 'يونيو', + 'يوليو', + 'أغسطس', + 'سبتمبر', + 'أكتوبر', + 'نوفمبر', + 'ديسمبر' +]; + +hooks.defineLocale('ar-ly', { + months : months$1, + monthsShort : months$1, + weekdays : 'الأحد_الإثنين_الثلاثاء_الأربعاء_الخميس_الجمعة_السبت'.split('_'), + weekdaysShort : 'أحد_إثنين_ثلاثاء_أربعاء_خميس_جمعة_سبت'.split('_'), + weekdaysMin : 'ح_ن_ث_ر_خ_ج_س'.split('_'), + weekdaysParseExact : true, + longDateFormat : { + LT : 'HH:mm', + LTS : 'HH:mm:ss', + L : 'D/\u200FM/\u200FYYYY', + LL : 'D MMMM YYYY', + LLL : 'D MMMM YYYY HH:mm', + LLLL : 'dddd D MMMM YYYY HH:mm' + }, + meridiemParse: /ص|م/, + isPM : function (input) { + return 'م' === input; + }, + meridiem : function (hour, minute, isLower) { + if (hour < 12) { + return 'ص'; + } else { + return 'م'; + } + }, + calendar : { + sameDay: '[اليوم عند الساعة] LT', + nextDay: '[غدًا عند الساعة] LT', + nextWeek: 'dddd [عند الساعة] LT', + lastDay: '[أمس عند الساعة] LT', + lastWeek: 'dddd [عند الساعة] LT', + sameElse: 'L' + }, + relativeTime : { + future : 'بعد %s', + past : 'منذ %s', + s : pluralize('s'), + m : pluralize('m'), + mm : pluralize('m'), + h : pluralize('h'), + hh : pluralize('h'), + d : pluralize('d'), + dd : pluralize('d'), + M : pluralize('M'), + MM : pluralize('M'), + y : pluralize('y'), + yy : pluralize('y') + }, + preparse: function (string) { + return string.replace(/\u200f/g, '').replace(/،/g, ','); + }, + postformat: function (string) { + return string.replace(/\d/g, function (match) { + return symbolMap[match]; + }).replace(/,/g, '،'); + }, + week : { + dow : 6, // Saturday is the first day of the week. + doy : 12 // The week that contains Jan 1st is the first week of the year. + } +}); + +//! moment.js locale configuration +//! locale : Arabic (Morocco) [ar-ma] +//! author : ElFadili Yassine : https://github.com/ElFadiliY +//! author : Abdel Said : https://github.com/abdelsaid + +hooks.defineLocale('ar-ma', { + months : 'يناير_فبراير_مارس_أبريل_ماي_يونيو_يوليوز_غشت_شتنبر_أكتوبر_نونبر_دجنبر'.split('_'), + monthsShort : 'يناير_فبراير_مارس_أبريل_ماي_يونيو_يوليوز_غشت_شتنبر_أكتوبر_نونبر_دجنبر'.split('_'), + weekdays : 'الأحد_الإتنين_الثلاثاء_الأربعاء_الخميس_الجمعة_السبت'.split('_'), + weekdaysShort : 'احد_اتنين_ثلاثاء_اربعاء_خميس_جمعة_سبت'.split('_'), + weekdaysMin : 'ح_ن_ث_ر_خ_ج_س'.split('_'), + weekdaysParseExact : true, + longDateFormat : { + LT : 'HH:mm', + LTS : 'HH:mm:ss', + L : 'DD/MM/YYYY', + LL : 'D MMMM YYYY', + LLL : 'D MMMM YYYY HH:mm', + LLLL : 'dddd D MMMM YYYY HH:mm' + }, + calendar : { + sameDay: '[اليوم على الساعة] LT', + nextDay: '[غدا على الساعة] LT', + nextWeek: 'dddd [على الساعة] LT', + lastDay: '[أمس على الساعة] LT', + lastWeek: 'dddd [على الساعة] LT', + sameElse: 'L' + }, + relativeTime : { + future : 'في %s', + past : 'منذ %s', + s : 'ثوان', + m : 'دقيقة', + mm : '%d دقائق', + h : 'ساعة', + hh : '%d ساعات', + d : 'يوم', + dd : '%d أيام', + M : 'شهر', + MM : '%d أشهر', + y : 'سنة', + yy : '%d سنوات' + }, + week : { + dow : 6, // Saturday is the first day of the week. + doy : 12 // The week that contains Jan 1st is the first week of the year. + } +}); + +//! moment.js locale configuration +//! locale : Arabic (Saudi Arabia) [ar-sa] +//! author : Suhail Alkowaileet : https://github.com/xsoh + +var symbolMap$1 = { + '1': '١', + '2': '٢', + '3': '٣', + '4': '٤', + '5': '٥', + '6': '٦', + '7': '٧', + '8': '٨', + '9': '٩', + '0': '٠' +}; +var numberMap = { + '١': '1', + '٢': '2', + '٣': '3', + '٤': '4', + '٥': '5', + '٦': '6', + '٧': '7', + '٨': '8', + '٩': '9', + '٠': '0' +}; + +hooks.defineLocale('ar-sa', { + months : 'يناير_فبراير_مارس_أبريل_مايو_يونيو_يوليو_أغسطس_سبتمبر_أكتوبر_نوفمبر_ديسمبر'.split('_'), + monthsShort : 'يناير_فبراير_مارس_أبريل_مايو_يونيو_يوليو_أغسطس_سبتمبر_أكتوبر_نوفمبر_ديسمبر'.split('_'), + weekdays : 'الأحد_الإثنين_الثلاثاء_الأربعاء_الخميس_الجمعة_السبت'.split('_'), + weekdaysShort : 'أحد_إثنين_ثلاثاء_أربعاء_خميس_جمعة_سبت'.split('_'), + weekdaysMin : 'ح_ن_ث_ر_خ_ج_س'.split('_'), + weekdaysParseExact : true, + longDateFormat : { + LT : 'HH:mm', + LTS : 'HH:mm:ss', + L : 'DD/MM/YYYY', + LL : 'D MMMM YYYY', + LLL : 'D MMMM YYYY HH:mm', + LLLL : 'dddd D MMMM YYYY HH:mm' + }, + meridiemParse: /ص|م/, + isPM : function (input) { + return 'م' === input; + }, + meridiem : function (hour, minute, isLower) { + if (hour < 12) { + return 'ص'; + } else { + return 'م'; + } + }, + calendar : { + sameDay: '[اليوم على الساعة] LT', + nextDay: '[غدا على الساعة] LT', + nextWeek: 'dddd [على الساعة] LT', + lastDay: '[أمس على الساعة] LT', + lastWeek: 'dddd [على الساعة] LT', + sameElse: 'L' + }, + relativeTime : { + future : 'في %s', + past : 'منذ %s', + s : 'ثوان', + m : 'دقيقة', + mm : '%d دقائق', + h : 'ساعة', + hh : '%d ساعات', + d : 'يوم', + dd : '%d أيام', + M : 'شهر', + MM : '%d أشهر', + y : 'سنة', + yy : '%d سنوات' + }, + preparse: function (string) { + return string.replace(/[١٢٣٤٥٦٧٨٩٠]/g, function (match) { + return numberMap[match]; + }).replace(/،/g, ','); + }, + postformat: function (string) { + return string.replace(/\d/g, function (match) { + return symbolMap$1[match]; + }).replace(/,/g, '،'); + }, + week : { + dow : 0, // Sunday is the first day of the week. + doy : 6 // The week that contains Jan 1st is the first week of the year. + } +}); + +//! moment.js locale configuration +//! locale : Arabic (Tunisia) [ar-tn] +//! author : Nader Toukabri : https://github.com/naderio + +hooks.defineLocale('ar-tn', { + months: 'جانفي_فيفري_مارس_أفريل_ماي_جوان_جويلية_أوت_سبتمبر_أكتوبر_نوفمبر_ديسمبر'.split('_'), + monthsShort: 'جانفي_فيفري_مارس_أفريل_ماي_جوان_جويلية_أوت_سبتمبر_أكتوبر_نوفمبر_ديسمبر'.split('_'), + weekdays: 'الأحد_الإثنين_الثلاثاء_الأربعاء_الخميس_الجمعة_السبت'.split('_'), + weekdaysShort: 'أحد_إثنين_ثلاثاء_أربعاء_خميس_جمعة_سبت'.split('_'), + weekdaysMin: 'ح_ن_ث_ر_خ_ج_س'.split('_'), + weekdaysParseExact : true, + longDateFormat: { + LT: 'HH:mm', + LTS: 'HH:mm:ss', + L: 'DD/MM/YYYY', + LL: 'D MMMM YYYY', + LLL: 'D MMMM YYYY HH:mm', + LLLL: 'dddd D MMMM YYYY HH:mm' + }, + calendar: { + sameDay: '[اليوم على الساعة] LT', + nextDay: '[غدا على الساعة] LT', + nextWeek: 'dddd [على الساعة] LT', + lastDay: '[أمس على الساعة] LT', + lastWeek: 'dddd [على الساعة] LT', + sameElse: 'L' + }, + relativeTime: { + future: 'في %s', + past: 'منذ %s', + s: 'ثوان', + m: 'دقيقة', + mm: '%d دقائق', + h: 'ساعة', + hh: '%d ساعات', + d: 'يوم', + dd: '%d أيام', + M: 'شهر', + MM: '%d أشهر', + y: 'سنة', + yy: '%d سنوات' + }, + week: { + dow: 1, // Monday is the first day of the week. + doy: 4 // The week that contains Jan 4th is the first week of the year. + } +}); + +//! moment.js locale configuration +//! locale : Arabic [ar] +//! author : Abdel Said: https://github.com/abdelsaid +//! author : Ahmed Elkhatib +//! author : forabi https://github.com/forabi + +var symbolMap$2 = { + '1': '١', + '2': '٢', + '3': '٣', + '4': '٤', + '5': '٥', + '6': '٦', + '7': '٧', + '8': '٨', + '9': '٩', + '0': '٠' +}; +var numberMap$1 = { + '١': '1', + '٢': '2', + '٣': '3', + '٤': '4', + '٥': '5', + '٦': '6', + '٧': '7', + '٨': '8', + '٩': '9', + '٠': '0' +}; +var pluralForm$1 = function (n) { + return n === 0 ? 0 : n === 1 ? 1 : n === 2 ? 2 : n % 100 >= 3 && n % 100 <= 10 ? 3 : n % 100 >= 11 ? 4 : 5; +}; +var plurals$1 = { + s : ['أقل من ثانية', 'ثانية واحدة', ['ثانيتان', 'ثانيتين'], '%d ثوان', '%d ثانية', '%d ثانية'], + m : ['أقل من دقيقة', 'دقيقة واحدة', ['دقيقتان', 'دقيقتين'], '%d دقائق', '%d دقيقة', '%d دقيقة'], + h : ['أقل من ساعة', 'ساعة واحدة', ['ساعتان', 'ساعتين'], '%d ساعات', '%d ساعة', '%d ساعة'], + d : ['أقل من يوم', 'يوم واحد', ['يومان', 'يومين'], '%d أيام', '%d يومًا', '%d يوم'], + M : ['أقل من شهر', 'شهر واحد', ['شهران', 'شهرين'], '%d أشهر', '%d شهرا', '%d شهر'], + y : ['أقل من عام', 'عام واحد', ['عامان', 'عامين'], '%d أعوام', '%d عامًا', '%d عام'] +}; +var pluralize$1 = function (u) { + return function (number, withoutSuffix, string, isFuture) { + var f = pluralForm$1(number), + str = plurals$1[u][pluralForm$1(number)]; + if (f === 2) { + str = str[withoutSuffix ? 0 : 1]; + } + return str.replace(/%d/i, number); + }; +}; +var months$2 = [ + 'كانون الثاني يناير', + 'شباط فبراير', + 'آذار مارس', + 'نيسان أبريل', + 'أيار مايو', + 'حزيران يونيو', + 'تموز يوليو', + 'آب أغسطس', + 'أيلول سبتمبر', + 'تشرين الأول أكتوبر', + 'تشرين الثاني نوفمبر', + 'كانون الأول ديسمبر' +]; + +hooks.defineLocale('ar', { + months : months$2, + monthsShort : months$2, + weekdays : 'الأحد_الإثنين_الثلاثاء_الأربعاء_الخميس_الجمعة_السبت'.split('_'), + weekdaysShort : 'أحد_إثنين_ثلاثاء_أربعاء_خميس_جمعة_سبت'.split('_'), + weekdaysMin : 'ح_ن_ث_ر_خ_ج_س'.split('_'), + weekdaysParseExact : true, + longDateFormat : { + LT : 'HH:mm', + LTS : 'HH:mm:ss', + L : 'D/\u200FM/\u200FYYYY', + LL : 'D MMMM YYYY', + LLL : 'D MMMM YYYY HH:mm', + LLLL : 'dddd D MMMM YYYY HH:mm' + }, + meridiemParse: /ص|م/, + isPM : function (input) { + return 'م' === input; + }, + meridiem : function (hour, minute, isLower) { + if (hour < 12) { + return 'ص'; + } else { + return 'م'; + } + }, + calendar : { + sameDay: '[اليوم عند الساعة] LT', + nextDay: '[غدًا عند الساعة] LT', + nextWeek: 'dddd [عند الساعة] LT', + lastDay: '[أمس عند الساعة] LT', + lastWeek: 'dddd [عند الساعة] LT', + sameElse: 'L' + }, + relativeTime : { + future : 'بعد %s', + past : 'منذ %s', + s : pluralize$1('s'), + m : pluralize$1('m'), + mm : pluralize$1('m'), + h : pluralize$1('h'), + hh : pluralize$1('h'), + d : pluralize$1('d'), + dd : pluralize$1('d'), + M : pluralize$1('M'), + MM : pluralize$1('M'), + y : pluralize$1('y'), + yy : pluralize$1('y') + }, + preparse: function (string) { + return string.replace(/\u200f/g, '').replace(/[١٢٣٤٥٦٧٨٩٠]/g, function (match) { + return numberMap$1[match]; + }).replace(/،/g, ','); + }, + postformat: function (string) { + return string.replace(/\d/g, function (match) { + return symbolMap$2[match]; + }).replace(/,/g, '،'); + }, + week : { + dow : 6, // Saturday is the first day of the week. + doy : 12 // The week that contains Jan 1st is the first week of the year. + } +}); + +//! moment.js locale configuration +//! locale : Azerbaijani [az] +//! author : topchiyev : https://github.com/topchiyev + +var suffixes = { + 1: '-inci', + 5: '-inci', + 8: '-inci', + 70: '-inci', + 80: '-inci', + 2: '-nci', + 7: '-nci', + 20: '-nci', + 50: '-nci', + 3: '-üncü', + 4: '-üncü', + 100: '-üncü', + 6: '-ncı', + 9: '-uncu', + 10: '-uncu', + 30: '-uncu', + 60: '-ıncı', + 90: '-ıncı' +}; + +hooks.defineLocale('az', { + months : 'yanvar_fevral_mart_aprel_may_iyun_iyul_avqust_sentyabr_oktyabr_noyabr_dekabr'.split('_'), + monthsShort : 'yan_fev_mar_apr_may_iyn_iyl_avq_sen_okt_noy_dek'.split('_'), + weekdays : 'Bazar_Bazar ertəsi_Çərşənbə axşamı_Çərşənbə_Cümə axşamı_Cümə_Şənbə'.split('_'), + weekdaysShort : 'Baz_BzE_ÇAx_Çər_CAx_Cüm_Şən'.split('_'), + weekdaysMin : 'Bz_BE_ÇA_Çə_CA_Cü_Şə'.split('_'), + weekdaysParseExact : true, + longDateFormat : { + LT : 'HH:mm', + LTS : 'HH:mm:ss', + L : 'DD.MM.YYYY', + LL : 'D MMMM YYYY', + LLL : 'D MMMM YYYY HH:mm', + LLLL : 'dddd, D MMMM YYYY HH:mm' + }, + calendar : { + sameDay : '[bugün saat] LT', + nextDay : '[sabah saat] LT', + nextWeek : '[gələn həftə] dddd [saat] LT', + lastDay : '[dünən] LT', + lastWeek : '[keçən həftə] dddd [saat] LT', + sameElse : 'L' + }, + relativeTime : { + future : '%s sonra', + past : '%s əvvəl', + s : 'birneçə saniyyə', + m : 'bir dəqiqə', + mm : '%d dəqiqə', + h : 'bir saat', + hh : '%d saat', + d : 'bir gün', + dd : '%d gün', + M : 'bir ay', + MM : '%d ay', + y : 'bir il', + yy : '%d il' + }, + meridiemParse: /gecə|səhər|gündüz|axşam/, + isPM : function (input) { + return /^(gündüz|axşam)$/.test(input); + }, + meridiem : function (hour, minute, isLower) { + if (hour < 4) { + return 'gecə'; + } else if (hour < 12) { + return 'səhər'; + } else if (hour < 17) { + return 'gündüz'; + } else { + return 'axşam'; + } + }, + ordinalParse: /\d{1,2}-(ıncı|inci|nci|üncü|ncı|uncu)/, + ordinal : function (number) { + if (number === 0) { // special case for zero + return number + '-ıncı'; + } + var a = number % 10, + b = number % 100 - a, + c = number >= 100 ? 100 : null; + return number + (suffixes[a] || suffixes[b] || suffixes[c]); + }, + week : { + dow : 1, // Monday is the first day of the week. + doy : 7 // The week that contains Jan 1st is the first week of the year. + } +}); + +//! moment.js locale configuration +//! locale : Belarusian [be] +//! author : Dmitry Demidov : https://github.com/demidov91 +//! author: Praleska: http://praleska.pro/ +//! Author : Menelion Elensúle : https://github.com/Oire + +function plural(word, num) { + var forms = word.split('_'); + return num % 10 === 1 && num % 100 !== 11 ? forms[0] : (num % 10 >= 2 && num % 10 <= 4 && (num % 100 < 10 || num % 100 >= 20) ? forms[1] : forms[2]); +} +function relativeTimeWithPlural(number, withoutSuffix, key) { + var format = { + 'mm': withoutSuffix ? 'хвіліна_хвіліны_хвілін' : 'хвіліну_хвіліны_хвілін', + 'hh': withoutSuffix ? 'гадзіна_гадзіны_гадзін' : 'гадзіну_гадзіны_гадзін', + 'dd': 'дзень_дні_дзён', + 'MM': 'месяц_месяцы_месяцаў', + 'yy': 'год_гады_гадоў' + }; + if (key === 'm') { + return withoutSuffix ? 'хвіліна' : 'хвіліну'; + } + else if (key === 'h') { + return withoutSuffix ? 'гадзіна' : 'гадзіну'; + } + else { + return number + ' ' + plural(format[key], +number); + } +} + +hooks.defineLocale('be', { + months : { + format: 'студзеня_лютага_сакавіка_красавіка_траўня_чэрвеня_ліпеня_жніўня_верасня_кастрычніка_лістапада_снежня'.split('_'), + standalone: 'студзень_люты_сакавік_красавік_травень_чэрвень_ліпень_жнівень_верасень_кастрычнік_лістапад_снежань'.split('_') + }, + monthsShort : 'студ_лют_сак_крас_трав_чэрв_ліп_жнів_вер_каст_ліст_снеж'.split('_'), + weekdays : { + format: 'нядзелю_панядзелак_аўторак_сераду_чацвер_пятніцу_суботу'.split('_'), + standalone: 'нядзеля_панядзелак_аўторак_серада_чацвер_пятніца_субота'.split('_'), + isFormat: /\[ ?[Вв] ?(?:мінулую|наступную)? ?\] ?dddd/ + }, + weekdaysShort : 'нд_пн_ат_ср_чц_пт_сб'.split('_'), + weekdaysMin : 'нд_пн_ат_ср_чц_пт_сб'.split('_'), + longDateFormat : { + LT : 'HH:mm', + LTS : 'HH:mm:ss', + L : 'DD.MM.YYYY', + LL : 'D MMMM YYYY г.', + LLL : 'D MMMM YYYY г., HH:mm', + LLLL : 'dddd, D MMMM YYYY г., HH:mm' + }, + calendar : { + sameDay: '[Сёння ў] LT', + nextDay: '[Заўтра ў] LT', + lastDay: '[Учора ў] LT', + nextWeek: function () { + return '[У] dddd [ў] LT'; + }, + lastWeek: function () { + switch (this.day()) { + case 0: + case 3: + case 5: + case 6: + return '[У мінулую] dddd [ў] LT'; + case 1: + case 2: + case 4: + return '[У мінулы] dddd [ў] LT'; + } + }, + sameElse: 'L' + }, + relativeTime : { + future : 'праз %s', + past : '%s таму', + s : 'некалькі секунд', + m : relativeTimeWithPlural, + mm : relativeTimeWithPlural, + h : relativeTimeWithPlural, + hh : relativeTimeWithPlural, + d : 'дзень', + dd : relativeTimeWithPlural, + M : 'месяц', + MM : relativeTimeWithPlural, + y : 'год', + yy : relativeTimeWithPlural + }, + meridiemParse: /ночы|раніцы|дня|вечара/, + isPM : function (input) { + return /^(дня|вечара)$/.test(input); + }, + meridiem : function (hour, minute, isLower) { + if (hour < 4) { + return 'ночы'; + } else if (hour < 12) { + return 'раніцы'; + } else if (hour < 17) { + return 'дня'; + } else { + return 'вечара'; + } + }, + ordinalParse: /\d{1,2}-(і|ы|га)/, + ordinal: function (number, period) { + switch (period) { + case 'M': + case 'd': + case 'DDD': + case 'w': + case 'W': + return (number % 10 === 2 || number % 10 === 3) && (number % 100 !== 12 && number % 100 !== 13) ? number + '-і' : number + '-ы'; + case 'D': + return number + '-га'; + default: + return number; + } + }, + week : { + dow : 1, // Monday is the first day of the week. + doy : 7 // The week that contains Jan 1st is the first week of the year. + } +}); + +//! moment.js locale configuration +//! locale : Bulgarian [bg] +//! author : Krasen Borisov : https://github.com/kraz + +hooks.defineLocale('bg', { + months : 'януари_февруари_март_април_май_юни_юли_август_септември_октомври_ноември_декември'.split('_'), + monthsShort : 'янр_фев_мар_апр_май_юни_юли_авг_сеп_окт_ное_дек'.split('_'), + weekdays : 'неделя_понеделник_вторник_сряда_четвъртък_петък_събота'.split('_'), + weekdaysShort : 'нед_пон_вто_сря_чет_пет_съб'.split('_'), + weekdaysMin : 'нд_пн_вт_ср_чт_пт_сб'.split('_'), + longDateFormat : { + LT : 'H:mm', + LTS : 'H:mm:ss', + L : 'D.MM.YYYY', + LL : 'D MMMM YYYY', + LLL : 'D MMMM YYYY H:mm', + LLLL : 'dddd, D MMMM YYYY H:mm' + }, + calendar : { + sameDay : '[Днес в] LT', + nextDay : '[Утре в] LT', + nextWeek : 'dddd [в] LT', + lastDay : '[Вчера в] LT', + lastWeek : function () { + switch (this.day()) { + case 0: + case 3: + case 6: + return '[В изминалата] dddd [в] LT'; + case 1: + case 2: + case 4: + case 5: + return '[В изминалия] dddd [в] LT'; + } + }, + sameElse : 'L' + }, + relativeTime : { + future : 'след %s', + past : 'преди %s', + s : 'няколко секунди', + m : 'минута', + mm : '%d минути', + h : 'час', + hh : '%d часа', + d : 'ден', + dd : '%d дни', + M : 'месец', + MM : '%d месеца', + y : 'година', + yy : '%d години' + }, + ordinalParse: /\d{1,2}-(ев|ен|ти|ви|ри|ми)/, + ordinal : function (number) { + var lastDigit = number % 10, + last2Digits = number % 100; + if (number === 0) { + return number + '-ев'; + } else if (last2Digits === 0) { + return number + '-ен'; + } else if (last2Digits > 10 && last2Digits < 20) { + return number + '-ти'; + } else if (lastDigit === 1) { + return number + '-ви'; + } else if (lastDigit === 2) { + return number + '-ри'; + } else if (lastDigit === 7 || lastDigit === 8) { + return number + '-ми'; + } else { + return number + '-ти'; + } + }, + week : { + dow : 1, // Monday is the first day of the week. + doy : 7 // The week that contains Jan 1st is the first week of the year. + } +}); + +//! moment.js locale configuration +//! locale : Bengali [bn] +//! author : Kaushik Gandhi : https://github.com/kaushikgandhi + +var symbolMap$3 = { + '1': '১', + '2': '২', + '3': '৩', + '4': '৪', + '5': '৫', + '6': '৬', + '7': '৭', + '8': '৮', + '9': '৯', + '0': '০' +}; +var numberMap$2 = { + '১': '1', + '২': '2', + '৩': '3', + '৪': '4', + '৫': '5', + '৬': '6', + '৭': '7', + '৮': '8', + '৯': '9', + '০': '0' +}; + +hooks.defineLocale('bn', { + months : 'জানুয়ারী_ফেব্রুয়ারি_মার্চ_এপ্রিল_মে_জুন_জুলাই_আগস্ট_সেপ্টেম্বর_অক্টোবর_নভেম্বর_ডিসেম্বর'.split('_'), + monthsShort : 'জানু_ফেব_মার্চ_এপ্র_মে_জুন_জুল_আগ_সেপ্ট_অক্টো_নভে_ডিসে'.split('_'), + weekdays : 'রবিবার_সোমবার_মঙ্গলবার_বুধবার_বৃহস্পতিবার_শুক্রবার_শনিবার'.split('_'), + weekdaysShort : 'রবি_সোম_মঙ্গল_বুধ_বৃহস্পতি_শুক্র_শনি'.split('_'), + weekdaysMin : 'রবি_সোম_মঙ্গ_বুধ_বৃহঃ_শুক্র_শনি'.split('_'), + longDateFormat : { + LT : 'A h:mm সময়', + LTS : 'A h:mm:ss সময়', + L : 'DD/MM/YYYY', + LL : 'D MMMM YYYY', + LLL : 'D MMMM YYYY, A h:mm সময়', + LLLL : 'dddd, D MMMM YYYY, A h:mm সময়' + }, + calendar : { + sameDay : '[আজ] LT', + nextDay : '[আগামীকাল] LT', + nextWeek : 'dddd, LT', + lastDay : '[গতকাল] LT', + lastWeek : '[গত] dddd, LT', + sameElse : 'L' + }, + relativeTime : { + future : '%s পরে', + past : '%s আগে', + s : 'কয়েক সেকেন্ড', + m : 'এক মিনিট', + mm : '%d মিনিট', + h : 'এক ঘন্টা', + hh : '%d ঘন্টা', + d : 'এক দিন', + dd : '%d দিন', + M : 'এক মাস', + MM : '%d মাস', + y : 'এক বছর', + yy : '%d বছর' + }, + preparse: function (string) { + return string.replace(/[১২৩৪৫৬৭৮৯০]/g, function (match) { + return numberMap$2[match]; + }); + }, + postformat: function (string) { + return string.replace(/\d/g, function (match) { + return symbolMap$3[match]; + }); + }, + meridiemParse: /রাত|সকাল|দুপুর|বিকাল|রাত/, + meridiemHour : function (hour, meridiem) { + if (hour === 12) { + hour = 0; + } + if ((meridiem === 'রাত' && hour >= 4) || + (meridiem === 'দুপুর' && hour < 5) || + meridiem === 'বিকাল') { + return hour + 12; + } else { + return hour; + } + }, + meridiem : function (hour, minute, isLower) { + if (hour < 4) { + return 'রাত'; + } else if (hour < 10) { + return 'সকাল'; + } else if (hour < 17) { + return 'দুপুর'; + } else if (hour < 20) { + return 'বিকাল'; + } else { + return 'রাত'; + } + }, + week : { + dow : 0, // Sunday is the first day of the week. + doy : 6 // The week that contains Jan 1st is the first week of the year. + } +}); + +//! moment.js locale configuration +//! locale : Tibetan [bo] +//! author : Thupten N. Chakrishar : https://github.com/vajradog + +var symbolMap$4 = { + '1': '༡', + '2': '༢', + '3': '༣', + '4': '༤', + '5': '༥', + '6': '༦', + '7': '༧', + '8': '༨', + '9': '༩', + '0': '༠' +}; +var numberMap$3 = { + '༡': '1', + '༢': '2', + '༣': '3', + '༤': '4', + '༥': '5', + '༦': '6', + '༧': '7', + '༨': '8', + '༩': '9', + '༠': '0' +}; + +hooks.defineLocale('bo', { + months : 'ཟླ་བ་དང་པོ_ཟླ་བ་གཉིས་པ_ཟླ་བ་གསུམ་པ_ཟླ་བ་བཞི་པ_ཟླ་བ་ལྔ་པ_ཟླ་བ་དྲུག་པ_ཟླ་བ་བདུན་པ_ཟླ་བ་བརྒྱད་པ_ཟླ་བ་དགུ་པ_ཟླ་བ་བཅུ་པ_ཟླ་བ་བཅུ་གཅིག་པ_ཟླ་བ་བཅུ་གཉིས་པ'.split('_'), + monthsShort : 'ཟླ་བ་དང་པོ_ཟླ་བ་གཉིས་པ_ཟླ་བ་གསུམ་པ_ཟླ་བ་བཞི་པ_ཟླ་བ་ལྔ་པ_ཟླ་བ་དྲུག་པ_ཟླ་བ་བདུན་པ_ཟླ་བ་བརྒྱད་པ_ཟླ་བ་དགུ་པ_ཟླ་བ་བཅུ་པ_ཟླ་བ་བཅུ་གཅིག་པ_ཟླ་བ་བཅུ་གཉིས་པ'.split('_'), + weekdays : 'གཟའ་ཉི་མ་_གཟའ་ཟླ་བ་_གཟའ་མིག་དམར་_གཟའ་ལྷག་པ་_གཟའ་ཕུར་བུ_གཟའ་པ་སངས་_གཟའ་སྤེན་པ་'.split('_'), + weekdaysShort : 'ཉི་མ་_ཟླ་བ་_མིག་དམར་_ལྷག་པ་_ཕུར་བུ_པ་སངས་_སྤེན་པ་'.split('_'), + weekdaysMin : 'ཉི་མ་_ཟླ་བ་_མིག་དམར་_ལྷག་པ་_ཕུར་བུ_པ་སངས་_སྤེན་པ་'.split('_'), + longDateFormat : { + LT : 'A h:mm', + LTS : 'A h:mm:ss', + L : 'DD/MM/YYYY', + LL : 'D MMMM YYYY', + LLL : 'D MMMM YYYY, A h:mm', + LLLL : 'dddd, D MMMM YYYY, A h:mm' + }, + calendar : { + sameDay : '[དི་རིང] LT', + nextDay : '[སང་ཉིན] LT', + nextWeek : '[བདུན་ཕྲག་རྗེས་མ], LT', + lastDay : '[ཁ་སང] LT', + lastWeek : '[བདུན་ཕྲག་མཐའ་མ] dddd, LT', + sameElse : 'L' + }, + relativeTime : { + future : '%s ལ་', + past : '%s སྔན་ལ', + s : 'ལམ་སང', + m : 'སྐར་མ་གཅིག', + mm : '%d སྐར་མ', + h : 'ཆུ་ཚོད་གཅིག', + hh : '%d ཆུ་ཚོད', + d : 'ཉིན་གཅིག', + dd : '%d ཉིན་', + M : 'ཟླ་བ་གཅིག', + MM : '%d ཟླ་བ', + y : 'ལོ་གཅིག', + yy : '%d ལོ' + }, + preparse: function (string) { + return string.replace(/[༡༢༣༤༥༦༧༨༩༠]/g, function (match) { + return numberMap$3[match]; + }); + }, + postformat: function (string) { + return string.replace(/\d/g, function (match) { + return symbolMap$4[match]; + }); + }, + meridiemParse: /མཚན་མོ|ཞོགས་ཀས|ཉིན་གུང|དགོང་དག|མཚན་མོ/, + meridiemHour : function (hour, meridiem) { + if (hour === 12) { + hour = 0; + } + if ((meridiem === 'མཚན་མོ' && hour >= 4) || + (meridiem === 'ཉིན་གུང' && hour < 5) || + meridiem === 'དགོང་དག') { + return hour + 12; + } else { + return hour; + } + }, + meridiem : function (hour, minute, isLower) { + if (hour < 4) { + return 'མཚན་མོ'; + } else if (hour < 10) { + return 'ཞོགས་ཀས'; + } else if (hour < 17) { + return 'ཉིན་གུང'; + } else if (hour < 20) { + return 'དགོང་དག'; + } else { + return 'མཚན་མོ'; + } + }, + week : { + dow : 0, // Sunday is the first day of the week. + doy : 6 // The week that contains Jan 1st is the first week of the year. + } +}); + +//! moment.js locale configuration +//! locale : Breton [br] +//! author : Jean-Baptiste Le Duigou : https://github.com/jbleduigou + +function relativeTimeWithMutation(number, withoutSuffix, key) { + var format = { + 'mm': 'munutenn', + 'MM': 'miz', + 'dd': 'devezh' + }; + return number + ' ' + mutation(format[key], number); +} +function specialMutationForYears(number) { + switch (lastNumber(number)) { + case 1: + case 3: + case 4: + case 5: + case 9: + return number + ' bloaz'; + default: + return number + ' vloaz'; + } +} +function lastNumber(number) { + if (number > 9) { + return lastNumber(number % 10); + } + return number; +} +function mutation(text, number) { + if (number === 2) { + return softMutation(text); + } + return text; +} +function softMutation(text) { + var mutationTable = { + 'm': 'v', + 'b': 'v', + 'd': 'z' + }; + if (mutationTable[text.charAt(0)] === undefined) { + return text; + } + return mutationTable[text.charAt(0)] + text.substring(1); +} + +hooks.defineLocale('br', { + months : 'Genver_C\'hwevrer_Meurzh_Ebrel_Mae_Mezheven_Gouere_Eost_Gwengolo_Here_Du_Kerzu'.split('_'), + monthsShort : 'Gen_C\'hwe_Meu_Ebr_Mae_Eve_Gou_Eos_Gwe_Her_Du_Ker'.split('_'), + weekdays : 'Sul_Lun_Meurzh_Merc\'her_Yaou_Gwener_Sadorn'.split('_'), + weekdaysShort : 'Sul_Lun_Meu_Mer_Yao_Gwe_Sad'.split('_'), + weekdaysMin : 'Su_Lu_Me_Mer_Ya_Gw_Sa'.split('_'), + weekdaysParseExact : true, + longDateFormat : { + LT : 'h[e]mm A', + LTS : 'h[e]mm:ss A', + L : 'DD/MM/YYYY', + LL : 'D [a viz] MMMM YYYY', + LLL : 'D [a viz] MMMM YYYY h[e]mm A', + LLLL : 'dddd, D [a viz] MMMM YYYY h[e]mm A' + }, + calendar : { + sameDay : '[Hiziv da] LT', + nextDay : '[Warc\'hoazh da] LT', + nextWeek : 'dddd [da] LT', + lastDay : '[Dec\'h da] LT', + lastWeek : 'dddd [paset da] LT', + sameElse : 'L' + }, + relativeTime : { + future : 'a-benn %s', + past : '%s \'zo', + s : 'un nebeud segondennoù', + m : 'ur vunutenn', + mm : relativeTimeWithMutation, + h : 'un eur', + hh : '%d eur', + d : 'un devezh', + dd : relativeTimeWithMutation, + M : 'ur miz', + MM : relativeTimeWithMutation, + y : 'ur bloaz', + yy : specialMutationForYears + }, + ordinalParse: /\d{1,2}(añ|vet)/, + ordinal : function (number) { + var output = (number === 1) ? 'añ' : 'vet'; + return number + output; + }, + week : { + dow : 1, // Monday is the first day of the week. + doy : 4 // The week that contains Jan 4th is the first week of the year. + } +}); + +//! moment.js locale configuration +//! locale : Bosnian [bs] +//! author : Nedim Cholich : https://github.com/frontyard +//! based on (hr) translation by Bojan Marković + +function translate(number, withoutSuffix, key) { + var result = number + ' '; + switch (key) { + case 'm': + return withoutSuffix ? 'jedna minuta' : 'jedne minute'; + case 'mm': + if (number === 1) { + result += 'minuta'; + } else if (number === 2 || number === 3 || number === 4) { + result += 'minute'; + } else { + result += 'minuta'; + } + return result; + case 'h': + return withoutSuffix ? 'jedan sat' : 'jednog sata'; + case 'hh': + if (number === 1) { + result += 'sat'; + } else if (number === 2 || number === 3 || number === 4) { + result += 'sata'; + } else { + result += 'sati'; + } + return result; + case 'dd': + if (number === 1) { + result += 'dan'; + } else { + result += 'dana'; + } + return result; + case 'MM': + if (number === 1) { + result += 'mjesec'; + } else if (number === 2 || number === 3 || number === 4) { + result += 'mjeseca'; + } else { + result += 'mjeseci'; + } + return result; + case 'yy': + if (number === 1) { + result += 'godina'; + } else if (number === 2 || number === 3 || number === 4) { + result += 'godine'; + } else { + result += 'godina'; + } + return result; + } +} + +hooks.defineLocale('bs', { + months : 'januar_februar_mart_april_maj_juni_juli_august_septembar_oktobar_novembar_decembar'.split('_'), + monthsShort : 'jan._feb._mar._apr._maj._jun._jul._aug._sep._okt._nov._dec.'.split('_'), + monthsParseExact: true, + weekdays : 'nedjelja_ponedjeljak_utorak_srijeda_četvrtak_petak_subota'.split('_'), + weekdaysShort : 'ned._pon._uto._sri._čet._pet._sub.'.split('_'), + weekdaysMin : 'ne_po_ut_sr_če_pe_su'.split('_'), + weekdaysParseExact : true, + longDateFormat : { + LT : 'H:mm', + LTS : 'H:mm:ss', + L : 'DD.MM.YYYY', + LL : 'D. MMMM YYYY', + LLL : 'D. MMMM YYYY H:mm', + LLLL : 'dddd, D. MMMM YYYY H:mm' + }, + calendar : { + sameDay : '[danas u] LT', + nextDay : '[sutra u] LT', + nextWeek : function () { + switch (this.day()) { + case 0: + return '[u] [nedjelju] [u] LT'; + case 3: + return '[u] [srijedu] [u] LT'; + case 6: + return '[u] [subotu] [u] LT'; + case 1: + case 2: + case 4: + case 5: + return '[u] dddd [u] LT'; + } + }, + lastDay : '[jučer u] LT', + lastWeek : function () { + switch (this.day()) { + case 0: + case 3: + return '[prošlu] dddd [u] LT'; + case 6: + return '[prošle] [subote] [u] LT'; + case 1: + case 2: + case 4: + case 5: + return '[prošli] dddd [u] LT'; + } + }, + sameElse : 'L' + }, + relativeTime : { + future : 'za %s', + past : 'prije %s', + s : 'par sekundi', + m : translate, + mm : translate, + h : translate, + hh : translate, + d : 'dan', + dd : translate, + M : 'mjesec', + MM : translate, + y : 'godinu', + yy : translate + }, + ordinalParse: /\d{1,2}\./, + ordinal : '%d.', + week : { + dow : 1, // Monday is the first day of the week. + doy : 7 // The week that contains Jan 1st is the first week of the year. + } +}); + +//! moment.js locale configuration +//! locale : Catalan [ca] +//! author : Juan G. Hurtado : https://github.com/juanghurtado + +hooks.defineLocale('ca', { + months : 'gener_febrer_març_abril_maig_juny_juliol_agost_setembre_octubre_novembre_desembre'.split('_'), + monthsShort : 'gen._febr._mar._abr._mai._jun._jul._ag._set._oct._nov._des.'.split('_'), + monthsParseExact : true, + weekdays : 'diumenge_dilluns_dimarts_dimecres_dijous_divendres_dissabte'.split('_'), + weekdaysShort : 'dg._dl._dt._dc._dj._dv._ds.'.split('_'), + weekdaysMin : 'Dg_Dl_Dt_Dc_Dj_Dv_Ds'.split('_'), + weekdaysParseExact : true, + longDateFormat : { + LT : 'H:mm', + LTS : 'H:mm:ss', + L : 'DD/MM/YYYY', + LL : 'D MMMM YYYY', + LLL : 'D MMMM YYYY H:mm', + LLLL : 'dddd D MMMM YYYY H:mm' + }, + calendar : { + sameDay : function () { + return '[avui a ' + ((this.hours() !== 1) ? 'les' : 'la') + '] LT'; + }, + nextDay : function () { + return '[demà a ' + ((this.hours() !== 1) ? 'les' : 'la') + '] LT'; + }, + nextWeek : function () { + return 'dddd [a ' + ((this.hours() !== 1) ? 'les' : 'la') + '] LT'; + }, + lastDay : function () { + return '[ahir a ' + ((this.hours() !== 1) ? 'les' : 'la') + '] LT'; + }, + lastWeek : function () { + return '[el] dddd [passat a ' + ((this.hours() !== 1) ? 'les' : 'la') + '] LT'; + }, + sameElse : 'L' + }, + relativeTime : { + future : 'd\'aquí %s', + past : 'fa %s', + s : 'uns segons', + m : 'un minut', + mm : '%d minuts', + h : 'una hora', + hh : '%d hores', + d : 'un dia', + dd : '%d dies', + M : 'un mes', + MM : '%d mesos', + y : 'un any', + yy : '%d anys' + }, + ordinalParse: /\d{1,2}(r|n|t|è|a)/, + ordinal : function (number, period) { + var output = (number === 1) ? 'r' : + (number === 2) ? 'n' : + (number === 3) ? 'r' : + (number === 4) ? 't' : 'è'; + if (period === 'w' || period === 'W') { + output = 'a'; + } + return number + output; + }, + week : { + dow : 1, // Monday is the first day of the week. + doy : 4 // The week that contains Jan 4th is the first week of the year. + } +}); + +//! moment.js locale configuration +//! locale : Czech [cs] +//! author : petrbela : https://github.com/petrbela + +var months$3 = 'leden_únor_březen_duben_květen_červen_červenec_srpen_září_říjen_listopad_prosinec'.split('_'); +var monthsShort = 'led_úno_bře_dub_kvě_čvn_čvc_srp_zář_říj_lis_pro'.split('_'); +function plural$1(n) { + return (n > 1) && (n < 5) && (~~(n / 10) !== 1); +} +function translate$1(number, withoutSuffix, key, isFuture) { + var result = number + ' '; + switch (key) { + case 's': // a few seconds / in a few seconds / a few seconds ago + return (withoutSuffix || isFuture) ? 'pár sekund' : 'pár sekundami'; + case 'm': // a minute / in a minute / a minute ago + return withoutSuffix ? 'minuta' : (isFuture ? 'minutu' : 'minutou'); + case 'mm': // 9 minutes / in 9 minutes / 9 minutes ago + if (withoutSuffix || isFuture) { + return result + (plural$1(number) ? 'minuty' : 'minut'); + } else { + return result + 'minutami'; + } + break; + case 'h': // an hour / in an hour / an hour ago + return withoutSuffix ? 'hodina' : (isFuture ? 'hodinu' : 'hodinou'); + case 'hh': // 9 hours / in 9 hours / 9 hours ago + if (withoutSuffix || isFuture) { + return result + (plural$1(number) ? 'hodiny' : 'hodin'); + } else { + return result + 'hodinami'; + } + break; + case 'd': // a day / in a day / a day ago + return (withoutSuffix || isFuture) ? 'den' : 'dnem'; + case 'dd': // 9 days / in 9 days / 9 days ago + if (withoutSuffix || isFuture) { + return result + (plural$1(number) ? 'dny' : 'dní'); + } else { + return result + 'dny'; + } + break; + case 'M': // a month / in a month / a month ago + return (withoutSuffix || isFuture) ? 'měsíc' : 'měsícem'; + case 'MM': // 9 months / in 9 months / 9 months ago + if (withoutSuffix || isFuture) { + return result + (plural$1(number) ? 'měsíce' : 'měsíců'); + } else { + return result + 'měsíci'; + } + break; + case 'y': // a year / in a year / a year ago + return (withoutSuffix || isFuture) ? 'rok' : 'rokem'; + case 'yy': // 9 years / in 9 years / 9 years ago + if (withoutSuffix || isFuture) { + return result + (plural$1(number) ? 'roky' : 'let'); + } else { + return result + 'lety'; + } + break; + } +} + +hooks.defineLocale('cs', { + months : months$3, + monthsShort : monthsShort, + monthsParse : (function (months, monthsShort) { + var i, _monthsParse = []; + for (i = 0; i < 12; i++) { + // use custom parser to solve problem with July (červenec) + _monthsParse[i] = new RegExp('^' + months[i] + '$|^' + monthsShort[i] + '$', 'i'); + } + return _monthsParse; + }(months$3, monthsShort)), + shortMonthsParse : (function (monthsShort) { + var i, _shortMonthsParse = []; + for (i = 0; i < 12; i++) { + _shortMonthsParse[i] = new RegExp('^' + monthsShort[i] + '$', 'i'); + } + return _shortMonthsParse; + }(monthsShort)), + longMonthsParse : (function (months) { + var i, _longMonthsParse = []; + for (i = 0; i < 12; i++) { + _longMonthsParse[i] = new RegExp('^' + months[i] + '$', 'i'); + } + return _longMonthsParse; + }(months$3)), + weekdays : 'neděle_pondělí_úterý_středa_čtvrtek_pátek_sobota'.split('_'), + weekdaysShort : 'ne_po_út_st_čt_pá_so'.split('_'), + weekdaysMin : 'ne_po_út_st_čt_pá_so'.split('_'), + longDateFormat : { + LT: 'H:mm', + LTS : 'H:mm:ss', + L : 'DD.MM.YYYY', + LL : 'D. MMMM YYYY', + LLL : 'D. MMMM YYYY H:mm', + LLLL : 'dddd D. MMMM YYYY H:mm', + l : 'D. M. YYYY' + }, + calendar : { + sameDay: '[dnes v] LT', + nextDay: '[zítra v] LT', + nextWeek: function () { + switch (this.day()) { + case 0: + return '[v neděli v] LT'; + case 1: + case 2: + return '[v] dddd [v] LT'; + case 3: + return '[ve středu v] LT'; + case 4: + return '[ve čtvrtek v] LT'; + case 5: + return '[v pátek v] LT'; + case 6: + return '[v sobotu v] LT'; + } + }, + lastDay: '[včera v] LT', + lastWeek: function () { + switch (this.day()) { + case 0: + return '[minulou neděli v] LT'; + case 1: + case 2: + return '[minulé] dddd [v] LT'; + case 3: + return '[minulou středu v] LT'; + case 4: + case 5: + return '[minulý] dddd [v] LT'; + case 6: + return '[minulou sobotu v] LT'; + } + }, + sameElse: 'L' + }, + relativeTime : { + future : 'za %s', + past : 'před %s', + s : translate$1, + m : translate$1, + mm : translate$1, + h : translate$1, + hh : translate$1, + d : translate$1, + dd : translate$1, + M : translate$1, + MM : translate$1, + y : translate$1, + yy : translate$1 + }, + ordinalParse : /\d{1,2}\./, + ordinal : '%d.', + week : { + dow : 1, // Monday is the first day of the week. + doy : 4 // The week that contains Jan 4th is the first week of the year. + } +}); + +//! moment.js locale configuration +//! locale : Chuvash [cv] +//! author : Anatoly Mironov : https://github.com/mirontoli + +hooks.defineLocale('cv', { + months : 'кӑрлач_нарӑс_пуш_ака_май_ҫӗртме_утӑ_ҫурла_авӑн_юпа_чӳк_раштав'.split('_'), + monthsShort : 'кӑр_нар_пуш_ака_май_ҫӗр_утӑ_ҫур_авн_юпа_чӳк_раш'.split('_'), + weekdays : 'вырсарникун_тунтикун_ытларикун_юнкун_кӗҫнерникун_эрнекун_шӑматкун'.split('_'), + weekdaysShort : 'выр_тун_ытл_юн_кӗҫ_эрн_шӑм'.split('_'), + weekdaysMin : 'вр_тн_ыт_юн_кҫ_эр_шм'.split('_'), + longDateFormat : { + LT : 'HH:mm', + LTS : 'HH:mm:ss', + L : 'DD-MM-YYYY', + LL : 'YYYY [ҫулхи] MMMM [уйӑхӗн] D[-мӗшӗ]', + LLL : 'YYYY [ҫулхи] MMMM [уйӑхӗн] D[-мӗшӗ], HH:mm', + LLLL : 'dddd, YYYY [ҫулхи] MMMM [уйӑхӗн] D[-мӗшӗ], HH:mm' + }, + calendar : { + sameDay: '[Паян] LT [сехетре]', + nextDay: '[Ыран] LT [сехетре]', + lastDay: '[Ӗнер] LT [сехетре]', + nextWeek: '[Ҫитес] dddd LT [сехетре]', + lastWeek: '[Иртнӗ] dddd LT [сехетре]', + sameElse: 'L' + }, + relativeTime : { + future : function (output) { + var affix = /сехет$/i.exec(output) ? 'рен' : /ҫул$/i.exec(output) ? 'тан' : 'ран'; + return output + affix; + }, + past : '%s каялла', + s : 'пӗр-ик ҫеккунт', + m : 'пӗр минут', + mm : '%d минут', + h : 'пӗр сехет', + hh : '%d сехет', + d : 'пӗр кун', + dd : '%d кун', + M : 'пӗр уйӑх', + MM : '%d уйӑх', + y : 'пӗр ҫул', + yy : '%d ҫул' + }, + ordinalParse: /\d{1,2}-мӗш/, + ordinal : '%d-мӗш', + week : { + dow : 1, // Monday is the first day of the week. + doy : 7 // The week that contains Jan 1st is the first week of the year. + } +}); + +//! moment.js locale configuration +//! locale : Welsh [cy] +//! author : Robert Allen : https://github.com/robgallen +//! author : https://github.com/ryangreaves + +hooks.defineLocale('cy', { + months: 'Ionawr_Chwefror_Mawrth_Ebrill_Mai_Mehefin_Gorffennaf_Awst_Medi_Hydref_Tachwedd_Rhagfyr'.split('_'), + monthsShort: 'Ion_Chwe_Maw_Ebr_Mai_Meh_Gor_Aws_Med_Hyd_Tach_Rhag'.split('_'), + weekdays: 'Dydd Sul_Dydd Llun_Dydd Mawrth_Dydd Mercher_Dydd Iau_Dydd Gwener_Dydd Sadwrn'.split('_'), + weekdaysShort: 'Sul_Llun_Maw_Mer_Iau_Gwe_Sad'.split('_'), + weekdaysMin: 'Su_Ll_Ma_Me_Ia_Gw_Sa'.split('_'), + weekdaysParseExact : true, + // time formats are the same as en-gb + longDateFormat: { + LT: 'HH:mm', + LTS : 'HH:mm:ss', + L: 'DD/MM/YYYY', + LL: 'D MMMM YYYY', + LLL: 'D MMMM YYYY HH:mm', + LLLL: 'dddd, D MMMM YYYY HH:mm' + }, + calendar: { + sameDay: '[Heddiw am] LT', + nextDay: '[Yfory am] LT', + nextWeek: 'dddd [am] LT', + lastDay: '[Ddoe am] LT', + lastWeek: 'dddd [diwethaf am] LT', + sameElse: 'L' + }, + relativeTime: { + future: 'mewn %s', + past: '%s yn ôl', + s: 'ychydig eiliadau', + m: 'munud', + mm: '%d munud', + h: 'awr', + hh: '%d awr', + d: 'diwrnod', + dd: '%d diwrnod', + M: 'mis', + MM: '%d mis', + y: 'blwyddyn', + yy: '%d flynedd' + }, + ordinalParse: /\d{1,2}(fed|ain|af|il|ydd|ed|eg)/, + // traditional ordinal numbers above 31 are not commonly used in colloquial Welsh + ordinal: function (number) { + var b = number, + output = '', + lookup = [ + '', 'af', 'il', 'ydd', 'ydd', 'ed', 'ed', 'ed', 'fed', 'fed', 'fed', // 1af to 10fed + 'eg', 'fed', 'eg', 'eg', 'fed', 'eg', 'eg', 'fed', 'eg', 'fed' // 11eg to 20fed + ]; + if (b > 20) { + if (b === 40 || b === 50 || b === 60 || b === 80 || b === 100) { + output = 'fed'; // not 30ain, 70ain or 90ain + } else { + output = 'ain'; + } + } else if (b > 0) { + output = lookup[b]; + } + return number + output; + }, + week : { + dow : 1, // Monday is the first day of the week. + doy : 4 // The week that contains Jan 4th is the first week of the year. + } +}); + +//! moment.js locale configuration +//! locale : Danish [da] +//! author : Ulrik Nielsen : https://github.com/mrbase + +hooks.defineLocale('da', { + months : 'januar_februar_marts_april_maj_juni_juli_august_september_oktober_november_december'.split('_'), + monthsShort : 'jan_feb_mar_apr_maj_jun_jul_aug_sep_okt_nov_dec'.split('_'), + weekdays : 'søndag_mandag_tirsdag_onsdag_torsdag_fredag_lørdag'.split('_'), + weekdaysShort : 'søn_man_tir_ons_tor_fre_lør'.split('_'), + weekdaysMin : 'sø_ma_ti_on_to_fr_lø'.split('_'), + longDateFormat : { + LT : 'HH:mm', + LTS : 'HH:mm:ss', + L : 'DD/MM/YYYY', + LL : 'D. MMMM YYYY', + LLL : 'D. MMMM YYYY HH:mm', + LLLL : 'dddd [d.] D. MMMM YYYY HH:mm' + }, + calendar : { + sameDay : '[I dag kl.] LT', + nextDay : '[I morgen kl.] LT', + nextWeek : 'dddd [kl.] LT', + lastDay : '[I går kl.] LT', + lastWeek : '[sidste] dddd [kl] LT', + sameElse : 'L' + }, + relativeTime : { + future : 'om %s', + past : '%s siden', + s : 'få sekunder', + m : 'et minut', + mm : '%d minutter', + h : 'en time', + hh : '%d timer', + d : 'en dag', + dd : '%d dage', + M : 'en måned', + MM : '%d måneder', + y : 'et år', + yy : '%d år' + }, + ordinalParse: /\d{1,2}\./, + ordinal : '%d.', + week : { + dow : 1, // Monday is the first day of the week. + doy : 4 // The week that contains Jan 4th is the first week of the year. + } +}); + +//! moment.js locale configuration +//! locale : German (Austria) [de-at] +//! author : lluchs : https://github.com/lluchs +//! author: Menelion Elensúle: https://github.com/Oire +//! author : Martin Groller : https://github.com/MadMG +//! author : Mikolaj Dadela : https://github.com/mik01aj + +function processRelativeTime(number, withoutSuffix, key, isFuture) { + var format = { + 'm': ['eine Minute', 'einer Minute'], + 'h': ['eine Stunde', 'einer Stunde'], + 'd': ['ein Tag', 'einem Tag'], + 'dd': [number + ' Tage', number + ' Tagen'], + 'M': ['ein Monat', 'einem Monat'], + 'MM': [number + ' Monate', number + ' Monaten'], + 'y': ['ein Jahr', 'einem Jahr'], + 'yy': [number + ' Jahre', number + ' Jahren'] + }; + return withoutSuffix ? format[key][0] : format[key][1]; +} + +hooks.defineLocale('de-at', { + months : 'Jänner_Februar_März_April_Mai_Juni_Juli_August_September_Oktober_November_Dezember'.split('_'), + monthsShort : 'Jän._Febr._Mrz._Apr._Mai_Jun._Jul._Aug._Sept._Okt._Nov._Dez.'.split('_'), + monthsParseExact : true, + weekdays : 'Sonntag_Montag_Dienstag_Mittwoch_Donnerstag_Freitag_Samstag'.split('_'), + weekdaysShort : 'So._Mo._Di._Mi._Do._Fr._Sa.'.split('_'), + weekdaysMin : 'So_Mo_Di_Mi_Do_Fr_Sa'.split('_'), + weekdaysParseExact : true, + longDateFormat : { + LT: 'HH:mm', + LTS: 'HH:mm:ss', + L : 'DD.MM.YYYY', + LL : 'D. MMMM YYYY', + LLL : 'D. MMMM YYYY HH:mm', + LLLL : 'dddd, D. MMMM YYYY HH:mm' + }, + calendar : { + sameDay: '[heute um] LT [Uhr]', + sameElse: 'L', + nextDay: '[morgen um] LT [Uhr]', + nextWeek: 'dddd [um] LT [Uhr]', + lastDay: '[gestern um] LT [Uhr]', + lastWeek: '[letzten] dddd [um] LT [Uhr]' + }, + relativeTime : { + future : 'in %s', + past : 'vor %s', + s : 'ein paar Sekunden', + m : processRelativeTime, + mm : '%d Minuten', + h : processRelativeTime, + hh : '%d Stunden', + d : processRelativeTime, + dd : processRelativeTime, + M : processRelativeTime, + MM : processRelativeTime, + y : processRelativeTime, + yy : processRelativeTime + }, + ordinalParse: /\d{1,2}\./, + ordinal : '%d.', + week : { + dow : 1, // Monday is the first day of the week. + doy : 4 // The week that contains Jan 4th is the first week of the year. + } +}); + +//! moment.js locale configuration +//! locale : German [de] +//! author : lluchs : https://github.com/lluchs +//! author: Menelion Elensúle: https://github.com/Oire +//! author : Mikolaj Dadela : https://github.com/mik01aj + +function processRelativeTime$1(number, withoutSuffix, key, isFuture) { + var format = { + 'm': ['eine Minute', 'einer Minute'], + 'h': ['eine Stunde', 'einer Stunde'], + 'd': ['ein Tag', 'einem Tag'], + 'dd': [number + ' Tage', number + ' Tagen'], + 'M': ['ein Monat', 'einem Monat'], + 'MM': [number + ' Monate', number + ' Monaten'], + 'y': ['ein Jahr', 'einem Jahr'], + 'yy': [number + ' Jahre', number + ' Jahren'] + }; + return withoutSuffix ? format[key][0] : format[key][1]; +} + +hooks.defineLocale('de', { + months : 'Januar_Februar_März_April_Mai_Juni_Juli_August_September_Oktober_November_Dezember'.split('_'), + monthsShort : 'Jan._Febr._Mrz._Apr._Mai_Jun._Jul._Aug._Sept._Okt._Nov._Dez.'.split('_'), + monthsParseExact : true, + weekdays : 'Sonntag_Montag_Dienstag_Mittwoch_Donnerstag_Freitag_Samstag'.split('_'), + weekdaysShort : 'So._Mo._Di._Mi._Do._Fr._Sa.'.split('_'), + weekdaysMin : 'So_Mo_Di_Mi_Do_Fr_Sa'.split('_'), + weekdaysParseExact : true, + longDateFormat : { + LT: 'HH:mm', + LTS: 'HH:mm:ss', + L : 'DD.MM.YYYY', + LL : 'D. MMMM YYYY', + LLL : 'D. MMMM YYYY HH:mm', + LLLL : 'dddd, D. MMMM YYYY HH:mm' + }, + calendar : { + sameDay: '[heute um] LT [Uhr]', + sameElse: 'L', + nextDay: '[morgen um] LT [Uhr]', + nextWeek: 'dddd [um] LT [Uhr]', + lastDay: '[gestern um] LT [Uhr]', + lastWeek: '[letzten] dddd [um] LT [Uhr]' + }, + relativeTime : { + future : 'in %s', + past : 'vor %s', + s : 'ein paar Sekunden', + m : processRelativeTime$1, + mm : '%d Minuten', + h : processRelativeTime$1, + hh : '%d Stunden', + d : processRelativeTime$1, + dd : processRelativeTime$1, + M : processRelativeTime$1, + MM : processRelativeTime$1, + y : processRelativeTime$1, + yy : processRelativeTime$1 + }, + ordinalParse: /\d{1,2}\./, + ordinal : '%d.', + week : { + dow : 1, // Monday is the first day of the week. + doy : 4 // The week that contains Jan 4th is the first week of the year. + } +}); + +//! moment.js locale configuration +//! locale : Maldivian [dv] +//! author : Jawish Hameed : https://github.com/jawish + +var months$4 = [ + 'ޖެނުއަރީ', + 'ފެބްރުއަރީ', + 'މާރިޗު', + 'އޭޕްރީލު', + 'މޭ', + 'ޖޫން', + 'ޖުލައި', + 'އޯގަސްޓު', + 'ސެޕްޓެމްބަރު', + 'އޮކްޓޯބަރު', + 'ނޮވެމްބަރު', + 'ޑިސެމްބަރު' +]; +var weekdays = [ + 'އާދިއްތަ', + 'ހޯމަ', + 'އަންގާރަ', + 'ބުދަ', + 'ބުރާސްފަތި', + 'ހުކުރު', + 'ހޮނިހިރު' +]; + +hooks.defineLocale('dv', { + months : months$4, + monthsShort : months$4, + weekdays : weekdays, + weekdaysShort : weekdays, + weekdaysMin : 'އާދި_ހޯމަ_އަން_ބުދަ_ބުރާ_ހުކު_ހޮނި'.split('_'), + longDateFormat : { + + LT : 'HH:mm', + LTS : 'HH:mm:ss', + L : 'D/M/YYYY', + LL : 'D MMMM YYYY', + LLL : 'D MMMM YYYY HH:mm', + LLLL : 'dddd D MMMM YYYY HH:mm' + }, + meridiemParse: /މކ|މފ/, + isPM : function (input) { + return 'މފ' === input; + }, + meridiem : function (hour, minute, isLower) { + if (hour < 12) { + return 'މކ'; + } else { + return 'މފ'; + } + }, + calendar : { + sameDay : '[މިއަދު] LT', + nextDay : '[މާދަމާ] LT', + nextWeek : 'dddd LT', + lastDay : '[އިއްޔެ] LT', + lastWeek : '[ފާއިތުވި] dddd LT', + sameElse : 'L' + }, + relativeTime : { + future : 'ތެރޭގައި %s', + past : 'ކުރިން %s', + s : 'ސިކުންތުކޮޅެއް', + m : 'މިނިޓެއް', + mm : 'މިނިޓު %d', + h : 'ގަޑިއިރެއް', + hh : 'ގަޑިއިރު %d', + d : 'ދުވަހެއް', + dd : 'ދުވަސް %d', + M : 'މަހެއް', + MM : 'މަސް %d', + y : 'އަހަރެއް', + yy : 'އަހަރު %d' + }, + preparse: function (string) { + return string.replace(/،/g, ','); + }, + postformat: function (string) { + return string.replace(/,/g, '،'); + }, + week : { + dow : 7, // Sunday is the first day of the week. + doy : 12 // The week that contains Jan 1st is the first week of the year. + } +}); + +//! moment.js locale configuration +//! locale : Greek [el] +//! author : Aggelos Karalias : https://github.com/mehiel + +hooks.defineLocale('el', { + monthsNominativeEl : 'Ιανουάριος_Φεβρουάριος_Μάρτιος_Απρίλιος_Μάιος_Ιούνιος_Ιούλιος_Αύγουστος_Σεπτέμβριος_Οκτώβριος_Νοέμβριος_Δεκέμβριος'.split('_'), + monthsGenitiveEl : 'Ιανουαρίου_Φεβρουαρίου_Μαρτίου_Απριλίου_Μαΐου_Ιουνίου_Ιουλίου_Αυγούστου_Σεπτεμβρίου_Οκτωβρίου_Νοεμβρίου_Δεκεμβρίου'.split('_'), + months : function (momentToFormat, format) { + if (/D/.test(format.substring(0, format.indexOf('MMMM')))) { // if there is a day number before 'MMMM' + return this._monthsGenitiveEl[momentToFormat.month()]; + } else { + return this._monthsNominativeEl[momentToFormat.month()]; + } + }, + monthsShort : 'Ιαν_Φεβ_Μαρ_Απρ_Μαϊ_Ιουν_Ιουλ_Αυγ_Σεπ_Οκτ_Νοε_Δεκ'.split('_'), + weekdays : 'Κυριακή_Δευτέρα_Τρίτη_Τετάρτη_Πέμπτη_Παρασκευή_Σάββατο'.split('_'), + weekdaysShort : 'Κυρ_Δευ_Τρι_Τετ_Πεμ_Παρ_Σαβ'.split('_'), + weekdaysMin : 'Κυ_Δε_Τρ_Τε_Πε_Πα_Σα'.split('_'), + meridiem : function (hours, minutes, isLower) { + if (hours > 11) { + return isLower ? 'μμ' : 'ΜΜ'; + } else { + return isLower ? 'πμ' : 'ΠΜ'; + } + }, + isPM : function (input) { + return ((input + '').toLowerCase()[0] === 'μ'); + }, + meridiemParse : /[ΠΜ]\.?Μ?\.?/i, + longDateFormat : { + LT : 'h:mm A', + LTS : 'h:mm:ss A', + L : 'DD/MM/YYYY', + LL : 'D MMMM YYYY', + LLL : 'D MMMM YYYY h:mm A', + LLLL : 'dddd, D MMMM YYYY h:mm A' + }, + calendarEl : { + sameDay : '[Σήμερα {}] LT', + nextDay : '[Αύριο {}] LT', + nextWeek : 'dddd [{}] LT', + lastDay : '[Χθες {}] LT', + lastWeek : function () { + switch (this.day()) { + case 6: + return '[το προηγούμενο] dddd [{}] LT'; + default: + return '[την προηγούμενη] dddd [{}] LT'; + } + }, + sameElse : 'L' + }, + calendar : function (key, mom) { + var output = this._calendarEl[key], + hours = mom && mom.hours(); + if (isFunction(output)) { + output = output.apply(mom); + } + return output.replace('{}', (hours % 12 === 1 ? 'στη' : 'στις')); + }, + relativeTime : { + future : 'σε %s', + past : '%s πριν', + s : 'λίγα δευτερόλεπτα', + m : 'ένα λεπτό', + mm : '%d λεπτά', + h : 'μία ώρα', + hh : '%d ώρες', + d : 'μία μέρα', + dd : '%d μέρες', + M : 'ένας μήνας', + MM : '%d μήνες', + y : 'ένας χρόνος', + yy : '%d χρόνια' + }, + ordinalParse: /\d{1,2}η/, + ordinal: '%dη', + week : { + dow : 1, // Monday is the first day of the week. + doy : 4 // The week that contains Jan 4st is the first week of the year. + } +}); + +//! moment.js locale configuration +//! locale : English (Australia) [en-au] +//! author : Jared Morse : https://github.com/jarcoal + +hooks.defineLocale('en-au', { + months : 'January_February_March_April_May_June_July_August_September_October_November_December'.split('_'), + monthsShort : 'Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec'.split('_'), + weekdays : 'Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday'.split('_'), + weekdaysShort : 'Sun_Mon_Tue_Wed_Thu_Fri_Sat'.split('_'), + weekdaysMin : 'Su_Mo_Tu_We_Th_Fr_Sa'.split('_'), + longDateFormat : { + LT : 'h:mm A', + LTS : 'h:mm:ss A', + L : 'DD/MM/YYYY', + LL : 'D MMMM YYYY', + LLL : 'D MMMM YYYY h:mm A', + LLLL : 'dddd, D MMMM YYYY h:mm A' + }, + calendar : { + sameDay : '[Today at] LT', + nextDay : '[Tomorrow at] LT', + nextWeek : 'dddd [at] LT', + lastDay : '[Yesterday at] LT', + lastWeek : '[Last] dddd [at] LT', + sameElse : 'L' + }, + relativeTime : { + future : 'in %s', + past : '%s ago', + s : 'a few seconds', + m : 'a minute', + mm : '%d minutes', + h : 'an hour', + hh : '%d hours', + d : 'a day', + dd : '%d days', + M : 'a month', + MM : '%d months', + y : 'a year', + yy : '%d years' + }, + ordinalParse: /\d{1,2}(st|nd|rd|th)/, + ordinal : function (number) { + var b = number % 10, + output = (~~(number % 100 / 10) === 1) ? 'th' : + (b === 1) ? 'st' : + (b === 2) ? 'nd' : + (b === 3) ? 'rd' : 'th'; + return number + output; + }, + week : { + dow : 1, // Monday is the first day of the week. + doy : 4 // The week that contains Jan 4th is the first week of the year. + } +}); + +//! moment.js locale configuration +//! locale : English (Canada) [en-ca] +//! author : Jonathan Abourbih : https://github.com/jonbca + +hooks.defineLocale('en-ca', { + months : 'January_February_March_April_May_June_July_August_September_October_November_December'.split('_'), + monthsShort : 'Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec'.split('_'), + weekdays : 'Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday'.split('_'), + weekdaysShort : 'Sun_Mon_Tue_Wed_Thu_Fri_Sat'.split('_'), + weekdaysMin : 'Su_Mo_Tu_We_Th_Fr_Sa'.split('_'), + longDateFormat : { + LT : 'h:mm A', + LTS : 'h:mm:ss A', + L : 'YYYY-MM-DD', + LL : 'MMMM D, YYYY', + LLL : 'MMMM D, YYYY h:mm A', + LLLL : 'dddd, MMMM D, YYYY h:mm A' + }, + calendar : { + sameDay : '[Today at] LT', + nextDay : '[Tomorrow at] LT', + nextWeek : 'dddd [at] LT', + lastDay : '[Yesterday at] LT', + lastWeek : '[Last] dddd [at] LT', + sameElse : 'L' + }, + relativeTime : { + future : 'in %s', + past : '%s ago', + s : 'a few seconds', + m : 'a minute', + mm : '%d minutes', + h : 'an hour', + hh : '%d hours', + d : 'a day', + dd : '%d days', + M : 'a month', + MM : '%d months', + y : 'a year', + yy : '%d years' + }, + ordinalParse: /\d{1,2}(st|nd|rd|th)/, + ordinal : function (number) { + var b = number % 10, + output = (~~(number % 100 / 10) === 1) ? 'th' : + (b === 1) ? 'st' : + (b === 2) ? 'nd' : + (b === 3) ? 'rd' : 'th'; + return number + output; + } +}); + +//! moment.js locale configuration +//! locale : English (United Kingdom) [en-gb] +//! author : Chris Gedrim : https://github.com/chrisgedrim + +hooks.defineLocale('en-gb', { + months : 'January_February_March_April_May_June_July_August_September_October_November_December'.split('_'), + monthsShort : 'Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec'.split('_'), + weekdays : 'Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday'.split('_'), + weekdaysShort : 'Sun_Mon_Tue_Wed_Thu_Fri_Sat'.split('_'), + weekdaysMin : 'Su_Mo_Tu_We_Th_Fr_Sa'.split('_'), + longDateFormat : { + LT : 'HH:mm', + LTS : 'HH:mm:ss', + L : 'DD/MM/YYYY', + LL : 'D MMMM YYYY', + LLL : 'D MMMM YYYY HH:mm', + LLLL : 'dddd, D MMMM YYYY HH:mm' + }, + calendar : { + sameDay : '[Today at] LT', + nextDay : '[Tomorrow at] LT', + nextWeek : 'dddd [at] LT', + lastDay : '[Yesterday at] LT', + lastWeek : '[Last] dddd [at] LT', + sameElse : 'L' + }, + relativeTime : { + future : 'in %s', + past : '%s ago', + s : 'a few seconds', + m : 'a minute', + mm : '%d minutes', + h : 'an hour', + hh : '%d hours', + d : 'a day', + dd : '%d days', + M : 'a month', + MM : '%d months', + y : 'a year', + yy : '%d years' + }, + ordinalParse: /\d{1,2}(st|nd|rd|th)/, + ordinal : function (number) { + var b = number % 10, + output = (~~(number % 100 / 10) === 1) ? 'th' : + (b === 1) ? 'st' : + (b === 2) ? 'nd' : + (b === 3) ? 'rd' : 'th'; + return number + output; + }, + week : { + dow : 1, // Monday is the first day of the week. + doy : 4 // The week that contains Jan 4th is the first week of the year. + } +}); + +//! moment.js locale configuration +//! locale : English (Ireland) [en-ie] +//! author : Chris Cartlidge : https://github.com/chriscartlidge + +hooks.defineLocale('en-ie', { + months : 'January_February_March_April_May_June_July_August_September_October_November_December'.split('_'), + monthsShort : 'Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec'.split('_'), + weekdays : 'Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday'.split('_'), + weekdaysShort : 'Sun_Mon_Tue_Wed_Thu_Fri_Sat'.split('_'), + weekdaysMin : 'Su_Mo_Tu_We_Th_Fr_Sa'.split('_'), + longDateFormat : { + LT : 'HH:mm', + LTS : 'HH:mm:ss', + L : 'DD-MM-YYYY', + LL : 'D MMMM YYYY', + LLL : 'D MMMM YYYY HH:mm', + LLLL : 'dddd D MMMM YYYY HH:mm' + }, + calendar : { + sameDay : '[Today at] LT', + nextDay : '[Tomorrow at] LT', + nextWeek : 'dddd [at] LT', + lastDay : '[Yesterday at] LT', + lastWeek : '[Last] dddd [at] LT', + sameElse : 'L' + }, + relativeTime : { + future : 'in %s', + past : '%s ago', + s : 'a few seconds', + m : 'a minute', + mm : '%d minutes', + h : 'an hour', + hh : '%d hours', + d : 'a day', + dd : '%d days', + M : 'a month', + MM : '%d months', + y : 'a year', + yy : '%d years' + }, + ordinalParse: /\d{1,2}(st|nd|rd|th)/, + ordinal : function (number) { + var b = number % 10, + output = (~~(number % 100 / 10) === 1) ? 'th' : + (b === 1) ? 'st' : + (b === 2) ? 'nd' : + (b === 3) ? 'rd' : 'th'; + return number + output; + }, + week : { + dow : 1, // Monday is the first day of the week. + doy : 4 // The week that contains Jan 4th is the first week of the year. + } +}); + +//! moment.js locale configuration +//! locale : English (New Zealand) [en-nz] +//! author : Luke McGregor : https://github.com/lukemcgregor + +hooks.defineLocale('en-nz', { + months : 'January_February_March_April_May_June_July_August_September_October_November_December'.split('_'), + monthsShort : 'Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec'.split('_'), + weekdays : 'Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday'.split('_'), + weekdaysShort : 'Sun_Mon_Tue_Wed_Thu_Fri_Sat'.split('_'), + weekdaysMin : 'Su_Mo_Tu_We_Th_Fr_Sa'.split('_'), + longDateFormat : { + LT : 'h:mm A', + LTS : 'h:mm:ss A', + L : 'DD/MM/YYYY', + LL : 'D MMMM YYYY', + LLL : 'D MMMM YYYY h:mm A', + LLLL : 'dddd, D MMMM YYYY h:mm A' + }, + calendar : { + sameDay : '[Today at] LT', + nextDay : '[Tomorrow at] LT', + nextWeek : 'dddd [at] LT', + lastDay : '[Yesterday at] LT', + lastWeek : '[Last] dddd [at] LT', + sameElse : 'L' + }, + relativeTime : { + future : 'in %s', + past : '%s ago', + s : 'a few seconds', + m : 'a minute', + mm : '%d minutes', + h : 'an hour', + hh : '%d hours', + d : 'a day', + dd : '%d days', + M : 'a month', + MM : '%d months', + y : 'a year', + yy : '%d years' + }, + ordinalParse: /\d{1,2}(st|nd|rd|th)/, + ordinal : function (number) { + var b = number % 10, + output = (~~(number % 100 / 10) === 1) ? 'th' : + (b === 1) ? 'st' : + (b === 2) ? 'nd' : + (b === 3) ? 'rd' : 'th'; + return number + output; + }, + week : { + dow : 1, // Monday is the first day of the week. + doy : 4 // The week that contains Jan 4th is the first week of the year. + } +}); + +//! moment.js locale configuration +//! locale : Esperanto [eo] +//! author : Colin Dean : https://github.com/colindean +//! komento: Mi estas malcerta se mi korekte traktis akuzativojn en tiu traduko. +//! Se ne, bonvolu korekti kaj avizi min por ke mi povas lerni! + +hooks.defineLocale('eo', { + months : 'januaro_februaro_marto_aprilo_majo_junio_julio_aŭgusto_septembro_oktobro_novembro_decembro'.split('_'), + monthsShort : 'jan_feb_mar_apr_maj_jun_jul_aŭg_sep_okt_nov_dec'.split('_'), + weekdays : 'Dimanĉo_Lundo_Mardo_Merkredo_Ĵaŭdo_Vendredo_Sabato'.split('_'), + weekdaysShort : 'Dim_Lun_Mard_Merk_Ĵaŭ_Ven_Sab'.split('_'), + weekdaysMin : 'Di_Lu_Ma_Me_Ĵa_Ve_Sa'.split('_'), + longDateFormat : { + LT : 'HH:mm', + LTS : 'HH:mm:ss', + L : 'YYYY-MM-DD', + LL : 'D[-an de] MMMM, YYYY', + LLL : 'D[-an de] MMMM, YYYY HH:mm', + LLLL : 'dddd, [la] D[-an de] MMMM, YYYY HH:mm' + }, + meridiemParse: /[ap]\.t\.m/i, + isPM: function (input) { + return input.charAt(0).toLowerCase() === 'p'; + }, + meridiem : function (hours, minutes, isLower) { + if (hours > 11) { + return isLower ? 'p.t.m.' : 'P.T.M.'; + } else { + return isLower ? 'a.t.m.' : 'A.T.M.'; + } + }, + calendar : { + sameDay : '[Hodiaŭ je] LT', + nextDay : '[Morgaŭ je] LT', + nextWeek : 'dddd [je] LT', + lastDay : '[Hieraŭ je] LT', + lastWeek : '[pasinta] dddd [je] LT', + sameElse : 'L' + }, + relativeTime : { + future : 'je %s', + past : 'antaŭ %s', + s : 'sekundoj', + m : 'minuto', + mm : '%d minutoj', + h : 'horo', + hh : '%d horoj', + d : 'tago',//ne 'diurno', ĉar estas uzita por proksimumo + dd : '%d tagoj', + M : 'monato', + MM : '%d monatoj', + y : 'jaro', + yy : '%d jaroj' + }, + ordinalParse: /\d{1,2}a/, + ordinal : '%da', + week : { + dow : 1, // Monday is the first day of the week. + doy : 7 // The week that contains Jan 1st is the first week of the year. + } +}); + +//! moment.js locale configuration +//! locale : Spanish (Dominican Republic) [es-do] + +var monthsShortDot = 'ene._feb._mar._abr._may._jun._jul._ago._sep._oct._nov._dic.'.split('_'); +var monthsShort$1 = 'ene_feb_mar_abr_may_jun_jul_ago_sep_oct_nov_dic'.split('_'); + +hooks.defineLocale('es-do', { + months : 'enero_febrero_marzo_abril_mayo_junio_julio_agosto_septiembre_octubre_noviembre_diciembre'.split('_'), + monthsShort : function (m, format) { + if (/-MMM-/.test(format)) { + return monthsShort$1[m.month()]; + } else { + return monthsShortDot[m.month()]; + } + }, + monthsParseExact : true, + weekdays : 'domingo_lunes_martes_miércoles_jueves_viernes_sábado'.split('_'), + weekdaysShort : 'dom._lun._mar._mié._jue._vie._sáb.'.split('_'), + weekdaysMin : 'do_lu_ma_mi_ju_vi_sá'.split('_'), + weekdaysParseExact : true, + longDateFormat : { + LT : 'h:mm A', + LTS : 'h:mm:ss A', + L : 'DD/MM/YYYY', + LL : 'D [de] MMMM [de] YYYY', + LLL : 'D [de] MMMM [de] YYYY h:mm A', + LLLL : 'dddd, D [de] MMMM [de] YYYY h:mm A' + }, + calendar : { + sameDay : function () { + return '[hoy a la' + ((this.hours() !== 1) ? 's' : '') + '] LT'; + }, + nextDay : function () { + return '[mañana a la' + ((this.hours() !== 1) ? 's' : '') + '] LT'; + }, + nextWeek : function () { + return 'dddd [a la' + ((this.hours() !== 1) ? 's' : '') + '] LT'; + }, + lastDay : function () { + return '[ayer a la' + ((this.hours() !== 1) ? 's' : '') + '] LT'; + }, + lastWeek : function () { + return '[el] dddd [pasado a la' + ((this.hours() !== 1) ? 's' : '') + '] LT'; + }, + sameElse : 'L' + }, + relativeTime : { + future : 'en %s', + past : 'hace %s', + s : 'unos segundos', + m : 'un minuto', + mm : '%d minutos', + h : 'una hora', + hh : '%d horas', + d : 'un día', + dd : '%d días', + M : 'un mes', + MM : '%d meses', + y : 'un año', + yy : '%d años' + }, + ordinalParse : /\d{1,2}º/, + ordinal : '%dº', + week : { + dow : 1, // Monday is the first day of the week. + doy : 4 // The week that contains Jan 4th is the first week of the year. + } +}); + +//! moment.js locale configuration +//! locale : Spanish [es] +//! author : Julio Napurí : https://github.com/julionc + +var monthsShortDot$1 = 'ene._feb._mar._abr._may._jun._jul._ago._sep._oct._nov._dic.'.split('_'); +var monthsShort$2 = 'ene_feb_mar_abr_may_jun_jul_ago_sep_oct_nov_dic'.split('_'); + +hooks.defineLocale('es', { + months : 'enero_febrero_marzo_abril_mayo_junio_julio_agosto_septiembre_octubre_noviembre_diciembre'.split('_'), + monthsShort : function (m, format) { + if (/-MMM-/.test(format)) { + return monthsShort$2[m.month()]; + } else { + return monthsShortDot$1[m.month()]; + } + }, + monthsParseExact : true, + weekdays : 'domingo_lunes_martes_miércoles_jueves_viernes_sábado'.split('_'), + weekdaysShort : 'dom._lun._mar._mié._jue._vie._sáb.'.split('_'), + weekdaysMin : 'do_lu_ma_mi_ju_vi_sá'.split('_'), + weekdaysParseExact : true, + longDateFormat : { + LT : 'H:mm', + LTS : 'H:mm:ss', + L : 'DD/MM/YYYY', + LL : 'D [de] MMMM [de] YYYY', + LLL : 'D [de] MMMM [de] YYYY H:mm', + LLLL : 'dddd, D [de] MMMM [de] YYYY H:mm' + }, + calendar : { + sameDay : function () { + return '[hoy a la' + ((this.hours() !== 1) ? 's' : '') + '] LT'; + }, + nextDay : function () { + return '[mañana a la' + ((this.hours() !== 1) ? 's' : '') + '] LT'; + }, + nextWeek : function () { + return 'dddd [a la' + ((this.hours() !== 1) ? 's' : '') + '] LT'; + }, + lastDay : function () { + return '[ayer a la' + ((this.hours() !== 1) ? 's' : '') + '] LT'; + }, + lastWeek : function () { + return '[el] dddd [pasado a la' + ((this.hours() !== 1) ? 's' : '') + '] LT'; + }, + sameElse : 'L' + }, + relativeTime : { + future : 'en %s', + past : 'hace %s', + s : 'unos segundos', + m : 'un minuto', + mm : '%d minutos', + h : 'una hora', + hh : '%d horas', + d : 'un día', + dd : '%d días', + M : 'un mes', + MM : '%d meses', + y : 'un año', + yy : '%d años' + }, + ordinalParse : /\d{1,2}º/, + ordinal : '%dº', + week : { + dow : 1, // Monday is the first day of the week. + doy : 4 // The week that contains Jan 4th is the first week of the year. + } +}); + +//! moment.js locale configuration +//! locale : Estonian [et] +//! author : Henry Kehlmann : https://github.com/madhenry +//! improvements : Illimar Tambek : https://github.com/ragulka + +function processRelativeTime$2(number, withoutSuffix, key, isFuture) { + var format = { + 's' : ['mõne sekundi', 'mõni sekund', 'paar sekundit'], + 'm' : ['ühe minuti', 'üks minut'], + 'mm': [number + ' minuti', number + ' minutit'], + 'h' : ['ühe tunni', 'tund aega', 'üks tund'], + 'hh': [number + ' tunni', number + ' tundi'], + 'd' : ['ühe päeva', 'üks päev'], + 'M' : ['kuu aja', 'kuu aega', 'üks kuu'], + 'MM': [number + ' kuu', number + ' kuud'], + 'y' : ['ühe aasta', 'aasta', 'üks aasta'], + 'yy': [number + ' aasta', number + ' aastat'] + }; + if (withoutSuffix) { + return format[key][2] ? format[key][2] : format[key][1]; + } + return isFuture ? format[key][0] : format[key][1]; +} + +hooks.defineLocale('et', { + months : 'jaanuar_veebruar_märts_aprill_mai_juuni_juuli_august_september_oktoober_november_detsember'.split('_'), + monthsShort : 'jaan_veebr_märts_apr_mai_juuni_juuli_aug_sept_okt_nov_dets'.split('_'), + weekdays : 'pühapäev_esmaspäev_teisipäev_kolmapäev_neljapäev_reede_laupäev'.split('_'), + weekdaysShort : 'P_E_T_K_N_R_L'.split('_'), + weekdaysMin : 'P_E_T_K_N_R_L'.split('_'), + longDateFormat : { + LT : 'H:mm', + LTS : 'H:mm:ss', + L : 'DD.MM.YYYY', + LL : 'D. MMMM YYYY', + LLL : 'D. MMMM YYYY H:mm', + LLLL : 'dddd, D. MMMM YYYY H:mm' + }, + calendar : { + sameDay : '[Täna,] LT', + nextDay : '[Homme,] LT', + nextWeek : '[Järgmine] dddd LT', + lastDay : '[Eile,] LT', + lastWeek : '[Eelmine] dddd LT', + sameElse : 'L' + }, + relativeTime : { + future : '%s pärast', + past : '%s tagasi', + s : processRelativeTime$2, + m : processRelativeTime$2, + mm : processRelativeTime$2, + h : processRelativeTime$2, + hh : processRelativeTime$2, + d : processRelativeTime$2, + dd : '%d päeva', + M : processRelativeTime$2, + MM : processRelativeTime$2, + y : processRelativeTime$2, + yy : processRelativeTime$2 + }, + ordinalParse: /\d{1,2}\./, + ordinal : '%d.', + week : { + dow : 1, // Monday is the first day of the week. + doy : 4 // The week that contains Jan 4th is the first week of the year. + } +}); + +//! moment.js locale configuration +//! locale : Basque [eu] +//! author : Eneko Illarramendi : https://github.com/eillarra + +hooks.defineLocale('eu', { + months : 'urtarrila_otsaila_martxoa_apirila_maiatza_ekaina_uztaila_abuztua_iraila_urria_azaroa_abendua'.split('_'), + monthsShort : 'urt._ots._mar._api._mai._eka._uzt._abu._ira._urr._aza._abe.'.split('_'), + monthsParseExact : true, + weekdays : 'igandea_astelehena_asteartea_asteazkena_osteguna_ostirala_larunbata'.split('_'), + weekdaysShort : 'ig._al._ar._az._og._ol._lr.'.split('_'), + weekdaysMin : 'ig_al_ar_az_og_ol_lr'.split('_'), + weekdaysParseExact : true, + longDateFormat : { + LT : 'HH:mm', + LTS : 'HH:mm:ss', + L : 'YYYY-MM-DD', + LL : 'YYYY[ko] MMMM[ren] D[a]', + LLL : 'YYYY[ko] MMMM[ren] D[a] HH:mm', + LLLL : 'dddd, YYYY[ko] MMMM[ren] D[a] HH:mm', + l : 'YYYY-M-D', + ll : 'YYYY[ko] MMM D[a]', + lll : 'YYYY[ko] MMM D[a] HH:mm', + llll : 'ddd, YYYY[ko] MMM D[a] HH:mm' + }, + calendar : { + sameDay : '[gaur] LT[etan]', + nextDay : '[bihar] LT[etan]', + nextWeek : 'dddd LT[etan]', + lastDay : '[atzo] LT[etan]', + lastWeek : '[aurreko] dddd LT[etan]', + sameElse : 'L' + }, + relativeTime : { + future : '%s barru', + past : 'duela %s', + s : 'segundo batzuk', + m : 'minutu bat', + mm : '%d minutu', + h : 'ordu bat', + hh : '%d ordu', + d : 'egun bat', + dd : '%d egun', + M : 'hilabete bat', + MM : '%d hilabete', + y : 'urte bat', + yy : '%d urte' + }, + ordinalParse: /\d{1,2}\./, + ordinal : '%d.', + week : { + dow : 1, // Monday is the first day of the week. + doy : 7 // The week that contains Jan 1st is the first week of the year. + } +}); + +//! moment.js locale configuration +//! locale : Persian [fa] +//! author : Ebrahim Byagowi : https://github.com/ebraminio + +var symbolMap$5 = { + '1': '۱', + '2': '۲', + '3': '۳', + '4': '۴', + '5': '۵', + '6': '۶', + '7': '۷', + '8': '۸', + '9': '۹', + '0': '۰' +}; +var numberMap$4 = { + '۱': '1', + '۲': '2', + '۳': '3', + '۴': '4', + '۵': '5', + '۶': '6', + '۷': '7', + '۸': '8', + '۹': '9', + '۰': '0' +}; + +hooks.defineLocale('fa', { + months : 'ژانویه_فوریه_مارس_آوریل_مه_ژوئن_ژوئیه_اوت_سپتامبر_اکتبر_نوامبر_دسامبر'.split('_'), + monthsShort : 'ژانویه_فوریه_مارس_آوریل_مه_ژوئن_ژوئیه_اوت_سپتامبر_اکتبر_نوامبر_دسامبر'.split('_'), + weekdays : 'یک\u200cشنبه_دوشنبه_سه\u200cشنبه_چهارشنبه_پنج\u200cشنبه_جمعه_شنبه'.split('_'), + weekdaysShort : 'یک\u200cشنبه_دوشنبه_سه\u200cشنبه_چهارشنبه_پنج\u200cشنبه_جمعه_شنبه'.split('_'), + weekdaysMin : 'ی_د_س_چ_پ_ج_ش'.split('_'), + weekdaysParseExact : true, + longDateFormat : { + LT : 'HH:mm', + LTS : 'HH:mm:ss', + L : 'DD/MM/YYYY', + LL : 'D MMMM YYYY', + LLL : 'D MMMM YYYY HH:mm', + LLLL : 'dddd, D MMMM YYYY HH:mm' + }, + meridiemParse: /قبل از ظهر|بعد از ظهر/, + isPM: function (input) { + return /بعد از ظهر/.test(input); + }, + meridiem : function (hour, minute, isLower) { + if (hour < 12) { + return 'قبل از ظهر'; + } else { + return 'بعد از ظهر'; + } + }, + calendar : { + sameDay : '[امروز ساعت] LT', + nextDay : '[فردا ساعت] LT', + nextWeek : 'dddd [ساعت] LT', + lastDay : '[دیروز ساعت] LT', + lastWeek : 'dddd [پیش] [ساعت] LT', + sameElse : 'L' + }, + relativeTime : { + future : 'در %s', + past : '%s پیش', + s : 'چندین ثانیه', + m : 'یک دقیقه', + mm : '%d دقیقه', + h : 'یک ساعت', + hh : '%d ساعت', + d : 'یک روز', + dd : '%d روز', + M : 'یک ماه', + MM : '%d ماه', + y : 'یک سال', + yy : '%d سال' + }, + preparse: function (string) { + return string.replace(/[۰-۹]/g, function (match) { + return numberMap$4[match]; + }).replace(/،/g, ','); + }, + postformat: function (string) { + return string.replace(/\d/g, function (match) { + return symbolMap$5[match]; + }).replace(/,/g, '،'); + }, + ordinalParse: /\d{1,2}م/, + ordinal : '%dم', + week : { + dow : 6, // Saturday is the first day of the week. + doy : 12 // The week that contains Jan 1st is the first week of the year. + } +}); + +//! moment.js locale configuration +//! locale : Finnish [fi] +//! author : Tarmo Aidantausta : https://github.com/bleadof + +var numbersPast = 'nolla yksi kaksi kolme neljä viisi kuusi seitsemän kahdeksan yhdeksän'.split(' '); +var numbersFuture = [ + 'nolla', 'yhden', 'kahden', 'kolmen', 'neljän', 'viiden', 'kuuden', + numbersPast[7], numbersPast[8], numbersPast[9] + ]; +function translate$2(number, withoutSuffix, key, isFuture) { + var result = ''; + switch (key) { + case 's': + return isFuture ? 'muutaman sekunnin' : 'muutama sekunti'; + case 'm': + return isFuture ? 'minuutin' : 'minuutti'; + case 'mm': + result = isFuture ? 'minuutin' : 'minuuttia'; + break; + case 'h': + return isFuture ? 'tunnin' : 'tunti'; + case 'hh': + result = isFuture ? 'tunnin' : 'tuntia'; + break; + case 'd': + return isFuture ? 'päivän' : 'päivä'; + case 'dd': + result = isFuture ? 'päivän' : 'päivää'; + break; + case 'M': + return isFuture ? 'kuukauden' : 'kuukausi'; + case 'MM': + result = isFuture ? 'kuukauden' : 'kuukautta'; + break; + case 'y': + return isFuture ? 'vuoden' : 'vuosi'; + case 'yy': + result = isFuture ? 'vuoden' : 'vuotta'; + break; + } + result = verbalNumber(number, isFuture) + ' ' + result; + return result; +} +function verbalNumber(number, isFuture) { + return number < 10 ? (isFuture ? numbersFuture[number] : numbersPast[number]) : number; +} + +hooks.defineLocale('fi', { + months : 'tammikuu_helmikuu_maaliskuu_huhtikuu_toukokuu_kesäkuu_heinäkuu_elokuu_syyskuu_lokakuu_marraskuu_joulukuu'.split('_'), + monthsShort : 'tammi_helmi_maalis_huhti_touko_kesä_heinä_elo_syys_loka_marras_joulu'.split('_'), + weekdays : 'sunnuntai_maanantai_tiistai_keskiviikko_torstai_perjantai_lauantai'.split('_'), + weekdaysShort : 'su_ma_ti_ke_to_pe_la'.split('_'), + weekdaysMin : 'su_ma_ti_ke_to_pe_la'.split('_'), + longDateFormat : { + LT : 'HH.mm', + LTS : 'HH.mm.ss', + L : 'DD.MM.YYYY', + LL : 'Do MMMM[ta] YYYY', + LLL : 'Do MMMM[ta] YYYY, [klo] HH.mm', + LLLL : 'dddd, Do MMMM[ta] YYYY, [klo] HH.mm', + l : 'D.M.YYYY', + ll : 'Do MMM YYYY', + lll : 'Do MMM YYYY, [klo] HH.mm', + llll : 'ddd, Do MMM YYYY, [klo] HH.mm' + }, + calendar : { + sameDay : '[tänään] [klo] LT', + nextDay : '[huomenna] [klo] LT', + nextWeek : 'dddd [klo] LT', + lastDay : '[eilen] [klo] LT', + lastWeek : '[viime] dddd[na] [klo] LT', + sameElse : 'L' + }, + relativeTime : { + future : '%s päästä', + past : '%s sitten', + s : translate$2, + m : translate$2, + mm : translate$2, + h : translate$2, + hh : translate$2, + d : translate$2, + dd : translate$2, + M : translate$2, + MM : translate$2, + y : translate$2, + yy : translate$2 + }, + ordinalParse: /\d{1,2}\./, + ordinal : '%d.', + week : { + dow : 1, // Monday is the first day of the week. + doy : 4 // The week that contains Jan 4th is the first week of the year. + } +}); + +//! moment.js locale configuration +//! locale : Faroese [fo] +//! author : Ragnar Johannesen : https://github.com/ragnar123 + +hooks.defineLocale('fo', { + months : 'januar_februar_mars_apríl_mai_juni_juli_august_september_oktober_november_desember'.split('_'), + monthsShort : 'jan_feb_mar_apr_mai_jun_jul_aug_sep_okt_nov_des'.split('_'), + weekdays : 'sunnudagur_mánadagur_týsdagur_mikudagur_hósdagur_fríggjadagur_leygardagur'.split('_'), + weekdaysShort : 'sun_mán_týs_mik_hós_frí_ley'.split('_'), + weekdaysMin : 'su_má_tý_mi_hó_fr_le'.split('_'), + longDateFormat : { + LT : 'HH:mm', + LTS : 'HH:mm:ss', + L : 'DD/MM/YYYY', + LL : 'D MMMM YYYY', + LLL : 'D MMMM YYYY HH:mm', + LLLL : 'dddd D. MMMM, YYYY HH:mm' + }, + calendar : { + sameDay : '[Í dag kl.] LT', + nextDay : '[Í morgin kl.] LT', + nextWeek : 'dddd [kl.] LT', + lastDay : '[Í gjár kl.] LT', + lastWeek : '[síðstu] dddd [kl] LT', + sameElse : 'L' + }, + relativeTime : { + future : 'um %s', + past : '%s síðani', + s : 'fá sekund', + m : 'ein minutt', + mm : '%d minuttir', + h : 'ein tími', + hh : '%d tímar', + d : 'ein dagur', + dd : '%d dagar', + M : 'ein mánaði', + MM : '%d mánaðir', + y : 'eitt ár', + yy : '%d ár' + }, + ordinalParse: /\d{1,2}\./, + ordinal : '%d.', + week : { + dow : 1, // Monday is the first day of the week. + doy : 4 // The week that contains Jan 4th is the first week of the year. + } +}); + +//! moment.js locale configuration +//! locale : French (Canada) [fr-ca] +//! author : Jonathan Abourbih : https://github.com/jonbca + +hooks.defineLocale('fr-ca', { + months : 'janvier_février_mars_avril_mai_juin_juillet_août_septembre_octobre_novembre_décembre'.split('_'), + monthsShort : 'janv._févr._mars_avr._mai_juin_juil._août_sept._oct._nov._déc.'.split('_'), + monthsParseExact : true, + weekdays : 'dimanche_lundi_mardi_mercredi_jeudi_vendredi_samedi'.split('_'), + weekdaysShort : 'dim._lun._mar._mer._jeu._ven._sam.'.split('_'), + weekdaysMin : 'Di_Lu_Ma_Me_Je_Ve_Sa'.split('_'), + weekdaysParseExact : true, + longDateFormat : { + LT : 'HH:mm', + LTS : 'HH:mm:ss', + L : 'YYYY-MM-DD', + LL : 'D MMMM YYYY', + LLL : 'D MMMM YYYY HH:mm', + LLLL : 'dddd D MMMM YYYY HH:mm' + }, + calendar : { + sameDay: '[Aujourd\'hui à] LT', + nextDay: '[Demain à] LT', + nextWeek: 'dddd [à] LT', + lastDay: '[Hier à] LT', + lastWeek: 'dddd [dernier à] LT', + sameElse: 'L' + }, + relativeTime : { + future : 'dans %s', + past : 'il y a %s', + s : 'quelques secondes', + m : 'une minute', + mm : '%d minutes', + h : 'une heure', + hh : '%d heures', + d : 'un jour', + dd : '%d jours', + M : 'un mois', + MM : '%d mois', + y : 'un an', + yy : '%d ans' + }, + ordinalParse: /\d{1,2}(er|e)/, + ordinal : function (number) { + return number + (number === 1 ? 'er' : 'e'); + } +}); + +//! moment.js locale configuration +//! locale : French (Switzerland) [fr-ch] +//! author : Gaspard Bucher : https://github.com/gaspard + +hooks.defineLocale('fr-ch', { + months : 'janvier_février_mars_avril_mai_juin_juillet_août_septembre_octobre_novembre_décembre'.split('_'), + monthsShort : 'janv._févr._mars_avr._mai_juin_juil._août_sept._oct._nov._déc.'.split('_'), + monthsParseExact : true, + weekdays : 'dimanche_lundi_mardi_mercredi_jeudi_vendredi_samedi'.split('_'), + weekdaysShort : 'dim._lun._mar._mer._jeu._ven._sam.'.split('_'), + weekdaysMin : 'Di_Lu_Ma_Me_Je_Ve_Sa'.split('_'), + weekdaysParseExact : true, + longDateFormat : { + LT : 'HH:mm', + LTS : 'HH:mm:ss', + L : 'DD.MM.YYYY', + LL : 'D MMMM YYYY', + LLL : 'D MMMM YYYY HH:mm', + LLLL : 'dddd D MMMM YYYY HH:mm' + }, + calendar : { + sameDay: '[Aujourd\'hui à] LT', + nextDay: '[Demain à] LT', + nextWeek: 'dddd [à] LT', + lastDay: '[Hier à] LT', + lastWeek: 'dddd [dernier à] LT', + sameElse: 'L' + }, + relativeTime : { + future : 'dans %s', + past : 'il y a %s', + s : 'quelques secondes', + m : 'une minute', + mm : '%d minutes', + h : 'une heure', + hh : '%d heures', + d : 'un jour', + dd : '%d jours', + M : 'un mois', + MM : '%d mois', + y : 'un an', + yy : '%d ans' + }, + ordinalParse: /\d{1,2}(er|e)/, + ordinal : function (number) { + return number + (number === 1 ? 'er' : 'e'); + }, + week : { + dow : 1, // Monday is the first day of the week. + doy : 4 // The week that contains Jan 4th is the first week of the year. + } +}); + +//! moment.js locale configuration +//! locale : French [fr] +//! author : John Fischer : https://github.com/jfroffice + +hooks.defineLocale('fr', { + months : 'janvier_février_mars_avril_mai_juin_juillet_août_septembre_octobre_novembre_décembre'.split('_'), + monthsShort : 'janv._févr._mars_avr._mai_juin_juil._août_sept._oct._nov._déc.'.split('_'), + monthsParseExact : true, + weekdays : 'dimanche_lundi_mardi_mercredi_jeudi_vendredi_samedi'.split('_'), + weekdaysShort : 'dim._lun._mar._mer._jeu._ven._sam.'.split('_'), + weekdaysMin : 'Di_Lu_Ma_Me_Je_Ve_Sa'.split('_'), + weekdaysParseExact : true, + longDateFormat : { + LT : 'HH:mm', + LTS : 'HH:mm:ss', + L : 'DD/MM/YYYY', + LL : 'D MMMM YYYY', + LLL : 'D MMMM YYYY HH:mm', + LLLL : 'dddd D MMMM YYYY HH:mm' + }, + calendar : { + sameDay: '[Aujourd\'hui à] LT', + nextDay: '[Demain à] LT', + nextWeek: 'dddd [à] LT', + lastDay: '[Hier à] LT', + lastWeek: 'dddd [dernier à] LT', + sameElse: 'L' + }, + relativeTime : { + future : 'dans %s', + past : 'il y a %s', + s : 'quelques secondes', + m : 'une minute', + mm : '%d minutes', + h : 'une heure', + hh : '%d heures', + d : 'un jour', + dd : '%d jours', + M : 'un mois', + MM : '%d mois', + y : 'un an', + yy : '%d ans' + }, + ordinalParse: /\d{1,2}(er|)/, + ordinal : function (number) { + return number + (number === 1 ? 'er' : ''); + }, + week : { + dow : 1, // Monday is the first day of the week. + doy : 4 // The week that contains Jan 4th is the first week of the year. + } +}); + +//! moment.js locale configuration +//! locale : Frisian [fy] +//! author : Robin van der Vliet : https://github.com/robin0van0der0v + +var monthsShortWithDots = 'jan._feb._mrt._apr._mai_jun._jul._aug._sep._okt._nov._des.'.split('_'); +var monthsShortWithoutDots = 'jan_feb_mrt_apr_mai_jun_jul_aug_sep_okt_nov_des'.split('_'); + +hooks.defineLocale('fy', { + months : 'jannewaris_febrewaris_maart_april_maaie_juny_july_augustus_septimber_oktober_novimber_desimber'.split('_'), + monthsShort : function (m, format) { + if (/-MMM-/.test(format)) { + return monthsShortWithoutDots[m.month()]; + } else { + return monthsShortWithDots[m.month()]; + } + }, + monthsParseExact : true, + weekdays : 'snein_moandei_tiisdei_woansdei_tongersdei_freed_sneon'.split('_'), + weekdaysShort : 'si._mo._ti._wo._to._fr._so.'.split('_'), + weekdaysMin : 'Si_Mo_Ti_Wo_To_Fr_So'.split('_'), + weekdaysParseExact : true, + longDateFormat : { + LT : 'HH:mm', + LTS : 'HH:mm:ss', + L : 'DD-MM-YYYY', + LL : 'D MMMM YYYY', + LLL : 'D MMMM YYYY HH:mm', + LLLL : 'dddd D MMMM YYYY HH:mm' + }, + calendar : { + sameDay: '[hjoed om] LT', + nextDay: '[moarn om] LT', + nextWeek: 'dddd [om] LT', + lastDay: '[juster om] LT', + lastWeek: '[ôfrûne] dddd [om] LT', + sameElse: 'L' + }, + relativeTime : { + future : 'oer %s', + past : '%s lyn', + s : 'in pear sekonden', + m : 'ien minút', + mm : '%d minuten', + h : 'ien oere', + hh : '%d oeren', + d : 'ien dei', + dd : '%d dagen', + M : 'ien moanne', + MM : '%d moannen', + y : 'ien jier', + yy : '%d jierren' + }, + ordinalParse: /\d{1,2}(ste|de)/, + ordinal : function (number) { + return number + ((number === 1 || number === 8 || number >= 20) ? 'ste' : 'de'); + }, + week : { + dow : 1, // Monday is the first day of the week. + doy : 4 // The week that contains Jan 4th is the first week of the year. + } +}); + +//! moment.js locale configuration +//! locale : Scottish Gaelic [gd] +//! author : Jon Ashdown : https://github.com/jonashdown + +var months$5 = [ + 'Am Faoilleach', 'An Gearran', 'Am Màrt', 'An Giblean', 'An Cèitean', 'An t-Ògmhios', 'An t-Iuchar', 'An Lùnastal', 'An t-Sultain', 'An Dàmhair', 'An t-Samhain', 'An Dùbhlachd' +]; + +var monthsShort$3 = ['Faoi', 'Gear', 'Màrt', 'Gibl', 'Cèit', 'Ògmh', 'Iuch', 'Lùn', 'Sult', 'Dàmh', 'Samh', 'Dùbh']; + +var weekdays$1 = ['Didòmhnaich', 'Diluain', 'Dimàirt', 'Diciadain', 'Diardaoin', 'Dihaoine', 'Disathairne']; + +var weekdaysShort = ['Did', 'Dil', 'Dim', 'Dic', 'Dia', 'Dih', 'Dis']; + +var weekdaysMin = ['Dò', 'Lu', 'Mà', 'Ci', 'Ar', 'Ha', 'Sa']; + +hooks.defineLocale('gd', { + months : months$5, + monthsShort : monthsShort$3, + monthsParseExact : true, + weekdays : weekdays$1, + weekdaysShort : weekdaysShort, + weekdaysMin : weekdaysMin, + longDateFormat : { + LT : 'HH:mm', + LTS : 'HH:mm:ss', + L : 'DD/MM/YYYY', + LL : 'D MMMM YYYY', + LLL : 'D MMMM YYYY HH:mm', + LLLL : 'dddd, D MMMM YYYY HH:mm' + }, + calendar : { + sameDay : '[An-diugh aig] LT', + nextDay : '[A-màireach aig] LT', + nextWeek : 'dddd [aig] LT', + lastDay : '[An-dè aig] LT', + lastWeek : 'dddd [seo chaidh] [aig] LT', + sameElse : 'L' + }, + relativeTime : { + future : 'ann an %s', + past : 'bho chionn %s', + s : 'beagan diogan', + m : 'mionaid', + mm : '%d mionaidean', + h : 'uair', + hh : '%d uairean', + d : 'latha', + dd : '%d latha', + M : 'mìos', + MM : '%d mìosan', + y : 'bliadhna', + yy : '%d bliadhna' + }, + ordinalParse : /\d{1,2}(d|na|mh)/, + ordinal : function (number) { + var output = number === 1 ? 'd' : number % 10 === 2 ? 'na' : 'mh'; + return number + output; + }, + week : { + dow : 1, // Monday is the first day of the week. + doy : 4 // The week that contains Jan 4th is the first week of the year. + } +}); + +//! moment.js locale configuration +//! locale : Galician [gl] +//! author : Juan G. Hurtado : https://github.com/juanghurtado + +hooks.defineLocale('gl', { + months : 'xaneiro_febreiro_marzo_abril_maio_xuño_xullo_agosto_setembro_outubro_novembro_decembro'.split('_'), + monthsShort : 'xan._feb._mar._abr._mai._xuñ._xul._ago._set._out._nov._dec.'.split('_'), + monthsParseExact: true, + weekdays : 'domingo_luns_martes_mércores_xoves_venres_sábado'.split('_'), + weekdaysShort : 'dom._lun._mar._mér._xov._ven._sáb.'.split('_'), + weekdaysMin : 'do_lu_ma_mé_xo_ve_sá'.split('_'), + weekdaysParseExact : true, + longDateFormat : { + LT : 'H:mm', + LTS : 'H:mm:ss', + L : 'DD/MM/YYYY', + LL : 'D [de] MMMM [de] YYYY', + LLL : 'D [de] MMMM [de] YYYY H:mm', + LLLL : 'dddd, D [de] MMMM [de] YYYY H:mm' + }, + calendar : { + sameDay : function () { + return '[hoxe ' + ((this.hours() !== 1) ? 'ás' : 'á') + '] LT'; + }, + nextDay : function () { + return '[mañá ' + ((this.hours() !== 1) ? 'ás' : 'á') + '] LT'; + }, + nextWeek : function () { + return 'dddd [' + ((this.hours() !== 1) ? 'ás' : 'a') + '] LT'; + }, + lastDay : function () { + return '[onte ' + ((this.hours() !== 1) ? 'á' : 'a') + '] LT'; + }, + lastWeek : function () { + return '[o] dddd [pasado ' + ((this.hours() !== 1) ? 'ás' : 'a') + '] LT'; + }, + sameElse : 'L' + }, + relativeTime : { + future : function (str) { + if (str.indexOf('un') === 0) { + return 'n' + str; + } + return 'en ' + str; + }, + past : 'hai %s', + s : 'uns segundos', + m : 'un minuto', + mm : '%d minutos', + h : 'unha hora', + hh : '%d horas', + d : 'un día', + dd : '%d días', + M : 'un mes', + MM : '%d meses', + y : 'un ano', + yy : '%d anos' + }, + ordinalParse : /\d{1,2}º/, + ordinal : '%dº', + week : { + dow : 1, // Monday is the first day of the week. + doy : 4 // The week that contains Jan 4th is the first week of the year. + } +}); + +//! moment.js locale configuration +//! locale : Hebrew [he] +//! author : Tomer Cohen : https://github.com/tomer +//! author : Moshe Simantov : https://github.com/DevelopmentIL +//! author : Tal Ater : https://github.com/TalAter + +hooks.defineLocale('he', { + months : 'ינואר_פברואר_מרץ_אפריל_מאי_יוני_יולי_אוגוסט_ספטמבר_אוקטובר_נובמבר_דצמבר'.split('_'), + monthsShort : 'ינו׳_פבר׳_מרץ_אפר׳_מאי_יוני_יולי_אוג׳_ספט׳_אוק׳_נוב׳_דצמ׳'.split('_'), + weekdays : 'ראשון_שני_שלישי_רביעי_חמישי_שישי_שבת'.split('_'), + weekdaysShort : 'א׳_ב׳_ג׳_ד׳_ה׳_ו׳_ש׳'.split('_'), + weekdaysMin : 'א_ב_ג_ד_ה_ו_ש'.split('_'), + longDateFormat : { + LT : 'HH:mm', + LTS : 'HH:mm:ss', + L : 'DD/MM/YYYY', + LL : 'D [ב]MMMM YYYY', + LLL : 'D [ב]MMMM YYYY HH:mm', + LLLL : 'dddd, D [ב]MMMM YYYY HH:mm', + l : 'D/M/YYYY', + ll : 'D MMM YYYY', + lll : 'D MMM YYYY HH:mm', + llll : 'ddd, D MMM YYYY HH:mm' + }, + calendar : { + sameDay : '[היום ב־]LT', + nextDay : '[מחר ב־]LT', + nextWeek : 'dddd [בשעה] LT', + lastDay : '[אתמול ב־]LT', + lastWeek : '[ביום] dddd [האחרון בשעה] LT', + sameElse : 'L' + }, + relativeTime : { + future : 'בעוד %s', + past : 'לפני %s', + s : 'מספר שניות', + m : 'דקה', + mm : '%d דקות', + h : 'שעה', + hh : function (number) { + if (number === 2) { + return 'שעתיים'; + } + return number + ' שעות'; + }, + d : 'יום', + dd : function (number) { + if (number === 2) { + return 'יומיים'; + } + return number + ' ימים'; + }, + M : 'חודש', + MM : function (number) { + if (number === 2) { + return 'חודשיים'; + } + return number + ' חודשים'; + }, + y : 'שנה', + yy : function (number) { + if (number === 2) { + return 'שנתיים'; + } else if (number % 10 === 0 && number !== 10) { + return number + ' שנה'; + } + return number + ' שנים'; + } + }, + meridiemParse: /אחה"צ|לפנה"צ|אחרי הצהריים|לפני הצהריים|לפנות בוקר|בבוקר|בערב/i, + isPM : function (input) { + return /^(אחה"צ|אחרי הצהריים|בערב)$/.test(input); + }, + meridiem : function (hour, minute, isLower) { + if (hour < 5) { + return 'לפנות בוקר'; + } else if (hour < 10) { + return 'בבוקר'; + } else if (hour < 12) { + return isLower ? 'לפנה"צ' : 'לפני הצהריים'; + } else if (hour < 18) { + return isLower ? 'אחה"צ' : 'אחרי הצהריים'; + } else { + return 'בערב'; + } + } +}); + +//! moment.js locale configuration +//! locale : Hindi [hi] +//! author : Mayank Singhal : https://github.com/mayanksinghal + +var symbolMap$6 = { + '1': '१', + '2': '२', + '3': '३', + '4': '४', + '5': '५', + '6': '६', + '7': '७', + '8': '८', + '9': '९', + '0': '०' +}; +var numberMap$5 = { + '१': '1', + '२': '2', + '३': '3', + '४': '4', + '५': '5', + '६': '6', + '७': '7', + '८': '8', + '९': '9', + '०': '0' +}; + +hooks.defineLocale('hi', { + months : 'जनवरी_फ़रवरी_मार्च_अप्रैल_मई_जून_जुलाई_अगस्त_सितम्बर_अक्टूबर_नवम्बर_दिसम्बर'.split('_'), + monthsShort : 'जन._फ़र._मार्च_अप्रै._मई_जून_जुल._अग._सित._अक्टू._नव._दिस.'.split('_'), + monthsParseExact: true, + weekdays : 'रविवार_सोमवार_मंगलवार_बुधवार_गुरूवार_शुक्रवार_शनिवार'.split('_'), + weekdaysShort : 'रवि_सोम_मंगल_बुध_गुरू_शुक्र_शनि'.split('_'), + weekdaysMin : 'र_सो_मं_बु_गु_शु_श'.split('_'), + longDateFormat : { + LT : 'A h:mm बजे', + LTS : 'A h:mm:ss बजे', + L : 'DD/MM/YYYY', + LL : 'D MMMM YYYY', + LLL : 'D MMMM YYYY, A h:mm बजे', + LLLL : 'dddd, D MMMM YYYY, A h:mm बजे' + }, + calendar : { + sameDay : '[आज] LT', + nextDay : '[कल] LT', + nextWeek : 'dddd, LT', + lastDay : '[कल] LT', + lastWeek : '[पिछले] dddd, LT', + sameElse : 'L' + }, + relativeTime : { + future : '%s में', + past : '%s पहले', + s : 'कुछ ही क्षण', + m : 'एक मिनट', + mm : '%d मिनट', + h : 'एक घंटा', + hh : '%d घंटे', + d : 'एक दिन', + dd : '%d दिन', + M : 'एक महीने', + MM : '%d महीने', + y : 'एक वर्ष', + yy : '%d वर्ष' + }, + preparse: function (string) { + return string.replace(/[१२३४५६७८९०]/g, function (match) { + return numberMap$5[match]; + }); + }, + postformat: function (string) { + return string.replace(/\d/g, function (match) { + return symbolMap$6[match]; + }); + }, + // Hindi notation for meridiems are quite fuzzy in practice. While there exists + // a rigid notion of a 'Pahar' it is not used as rigidly in modern Hindi. + meridiemParse: /रात|सुबह|दोपहर|शाम/, + meridiemHour : function (hour, meridiem) { + if (hour === 12) { + hour = 0; + } + if (meridiem === 'रात') { + return hour < 4 ? hour : hour + 12; + } else if (meridiem === 'सुबह') { + return hour; + } else if (meridiem === 'दोपहर') { + return hour >= 10 ? hour : hour + 12; + } else if (meridiem === 'शाम') { + return hour + 12; + } + }, + meridiem : function (hour, minute, isLower) { + if (hour < 4) { + return 'रात'; + } else if (hour < 10) { + return 'सुबह'; + } else if (hour < 17) { + return 'दोपहर'; + } else if (hour < 20) { + return 'शाम'; + } else { + return 'रात'; + } + }, + week : { + dow : 0, // Sunday is the first day of the week. + doy : 6 // The week that contains Jan 1st is the first week of the year. + } +}); + +//! moment.js locale configuration +//! locale : Croatian [hr] +//! author : Bojan Marković : https://github.com/bmarkovic + +function translate$3(number, withoutSuffix, key) { + var result = number + ' '; + switch (key) { + case 'm': + return withoutSuffix ? 'jedna minuta' : 'jedne minute'; + case 'mm': + if (number === 1) { + result += 'minuta'; + } else if (number === 2 || number === 3 || number === 4) { + result += 'minute'; + } else { + result += 'minuta'; + } + return result; + case 'h': + return withoutSuffix ? 'jedan sat' : 'jednog sata'; + case 'hh': + if (number === 1) { + result += 'sat'; + } else if (number === 2 || number === 3 || number === 4) { + result += 'sata'; + } else { + result += 'sati'; + } + return result; + case 'dd': + if (number === 1) { + result += 'dan'; + } else { + result += 'dana'; + } + return result; + case 'MM': + if (number === 1) { + result += 'mjesec'; + } else if (number === 2 || number === 3 || number === 4) { + result += 'mjeseca'; + } else { + result += 'mjeseci'; + } + return result; + case 'yy': + if (number === 1) { + result += 'godina'; + } else if (number === 2 || number === 3 || number === 4) { + result += 'godine'; + } else { + result += 'godina'; + } + return result; + } +} + +hooks.defineLocale('hr', { + months : { + format: 'siječnja_veljače_ožujka_travnja_svibnja_lipnja_srpnja_kolovoza_rujna_listopada_studenoga_prosinca'.split('_'), + standalone: 'siječanj_veljača_ožujak_travanj_svibanj_lipanj_srpanj_kolovoz_rujan_listopad_studeni_prosinac'.split('_') + }, + monthsShort : 'sij._velj._ožu._tra._svi._lip._srp._kol._ruj._lis._stu._pro.'.split('_'), + monthsParseExact: true, + weekdays : 'nedjelja_ponedjeljak_utorak_srijeda_četvrtak_petak_subota'.split('_'), + weekdaysShort : 'ned._pon._uto._sri._čet._pet._sub.'.split('_'), + weekdaysMin : 'ne_po_ut_sr_če_pe_su'.split('_'), + weekdaysParseExact : true, + longDateFormat : { + LT : 'H:mm', + LTS : 'H:mm:ss', + L : 'DD.MM.YYYY', + LL : 'D. MMMM YYYY', + LLL : 'D. MMMM YYYY H:mm', + LLLL : 'dddd, D. MMMM YYYY H:mm' + }, + calendar : { + sameDay : '[danas u] LT', + nextDay : '[sutra u] LT', + nextWeek : function () { + switch (this.day()) { + case 0: + return '[u] [nedjelju] [u] LT'; + case 3: + return '[u] [srijedu] [u] LT'; + case 6: + return '[u] [subotu] [u] LT'; + case 1: + case 2: + case 4: + case 5: + return '[u] dddd [u] LT'; + } + }, + lastDay : '[jučer u] LT', + lastWeek : function () { + switch (this.day()) { + case 0: + case 3: + return '[prošlu] dddd [u] LT'; + case 6: + return '[prošle] [subote] [u] LT'; + case 1: + case 2: + case 4: + case 5: + return '[prošli] dddd [u] LT'; + } + }, + sameElse : 'L' + }, + relativeTime : { + future : 'za %s', + past : 'prije %s', + s : 'par sekundi', + m : translate$3, + mm : translate$3, + h : translate$3, + hh : translate$3, + d : 'dan', + dd : translate$3, + M : 'mjesec', + MM : translate$3, + y : 'godinu', + yy : translate$3 + }, + ordinalParse: /\d{1,2}\./, + ordinal : '%d.', + week : { + dow : 1, // Monday is the first day of the week. + doy : 7 // The week that contains Jan 1st is the first week of the year. + } +}); + +//! moment.js locale configuration +//! locale : Hungarian [hu] +//! author : Adam Brunner : https://github.com/adambrunner + +var weekEndings = 'vasárnap hétfőn kedden szerdán csütörtökön pénteken szombaton'.split(' '); +function translate$4(number, withoutSuffix, key, isFuture) { + var num = number, + suffix; + switch (key) { + case 's': + return (isFuture || withoutSuffix) ? 'néhány másodperc' : 'néhány másodperce'; + case 'm': + return 'egy' + (isFuture || withoutSuffix ? ' perc' : ' perce'); + case 'mm': + return num + (isFuture || withoutSuffix ? ' perc' : ' perce'); + case 'h': + return 'egy' + (isFuture || withoutSuffix ? ' óra' : ' órája'); + case 'hh': + return num + (isFuture || withoutSuffix ? ' óra' : ' órája'); + case 'd': + return 'egy' + (isFuture || withoutSuffix ? ' nap' : ' napja'); + case 'dd': + return num + (isFuture || withoutSuffix ? ' nap' : ' napja'); + case 'M': + return 'egy' + (isFuture || withoutSuffix ? ' hónap' : ' hónapja'); + case 'MM': + return num + (isFuture || withoutSuffix ? ' hónap' : ' hónapja'); + case 'y': + return 'egy' + (isFuture || withoutSuffix ? ' év' : ' éve'); + case 'yy': + return num + (isFuture || withoutSuffix ? ' év' : ' éve'); + } + return ''; +} +function week(isFuture) { + return (isFuture ? '' : '[múlt] ') + '[' + weekEndings[this.day()] + '] LT[-kor]'; +} + +hooks.defineLocale('hu', { + months : 'január_február_március_április_május_június_július_augusztus_szeptember_október_november_december'.split('_'), + monthsShort : 'jan_feb_márc_ápr_máj_jún_júl_aug_szept_okt_nov_dec'.split('_'), + weekdays : 'vasárnap_hétfő_kedd_szerda_csütörtök_péntek_szombat'.split('_'), + weekdaysShort : 'vas_hét_kedd_sze_csüt_pén_szo'.split('_'), + weekdaysMin : 'v_h_k_sze_cs_p_szo'.split('_'), + longDateFormat : { + LT : 'H:mm', + LTS : 'H:mm:ss', + L : 'YYYY.MM.DD.', + LL : 'YYYY. MMMM D.', + LLL : 'YYYY. MMMM D. H:mm', + LLLL : 'YYYY. MMMM D., dddd H:mm' + }, + meridiemParse: /de|du/i, + isPM: function (input) { + return input.charAt(1).toLowerCase() === 'u'; + }, + meridiem : function (hours, minutes, isLower) { + if (hours < 12) { + return isLower === true ? 'de' : 'DE'; + } else { + return isLower === true ? 'du' : 'DU'; + } + }, + calendar : { + sameDay : '[ma] LT[-kor]', + nextDay : '[holnap] LT[-kor]', + nextWeek : function () { + return week.call(this, true); + }, + lastDay : '[tegnap] LT[-kor]', + lastWeek : function () { + return week.call(this, false); + }, + sameElse : 'L' + }, + relativeTime : { + future : '%s múlva', + past : '%s', + s : translate$4, + m : translate$4, + mm : translate$4, + h : translate$4, + hh : translate$4, + d : translate$4, + dd : translate$4, + M : translate$4, + MM : translate$4, + y : translate$4, + yy : translate$4 + }, + ordinalParse: /\d{1,2}\./, + ordinal : '%d.', + week : { + dow : 1, // Monday is the first day of the week. + doy : 4 // The week that contains Jan 4th is the first week of the year. + } +}); + +//! moment.js locale configuration +//! locale : Armenian [hy-am] +//! author : Armendarabyan : https://github.com/armendarabyan + +hooks.defineLocale('hy-am', { + months : { + format: 'հունվարի_փետրվարի_մարտի_ապրիլի_մայիսի_հունիսի_հուլիսի_օգոստոսի_սեպտեմբերի_հոկտեմբերի_նոյեմբերի_դեկտեմբերի'.split('_'), + standalone: 'հունվար_փետրվար_մարտ_ապրիլ_մայիս_հունիս_հուլիս_օգոստոս_սեպտեմբեր_հոկտեմբեր_նոյեմբեր_դեկտեմբեր'.split('_') + }, + monthsShort : 'հնվ_փտր_մրտ_ապր_մյս_հնս_հլս_օգս_սպտ_հկտ_նմբ_դկտ'.split('_'), + weekdays : 'կիրակի_երկուշաբթի_երեքշաբթի_չորեքշաբթի_հինգշաբթի_ուրբաթ_շաբաթ'.split('_'), + weekdaysShort : 'կրկ_երկ_երք_չրք_հնգ_ուրբ_շբթ'.split('_'), + weekdaysMin : 'կրկ_երկ_երք_չրք_հնգ_ուրբ_շբթ'.split('_'), + longDateFormat : { + LT : 'HH:mm', + LTS : 'HH:mm:ss', + L : 'DD.MM.YYYY', + LL : 'D MMMM YYYY թ.', + LLL : 'D MMMM YYYY թ., HH:mm', + LLLL : 'dddd, D MMMM YYYY թ., HH:mm' + }, + calendar : { + sameDay: '[այսօր] LT', + nextDay: '[վաղը] LT', + lastDay: '[երեկ] LT', + nextWeek: function () { + return 'dddd [օրը ժամը] LT'; + }, + lastWeek: function () { + return '[անցած] dddd [օրը ժամը] LT'; + }, + sameElse: 'L' + }, + relativeTime : { + future : '%s հետո', + past : '%s առաջ', + s : 'մի քանի վայրկյան', + m : 'րոպե', + mm : '%d րոպե', + h : 'ժամ', + hh : '%d ժամ', + d : 'օր', + dd : '%d օր', + M : 'ամիս', + MM : '%d ամիս', + y : 'տարի', + yy : '%d տարի' + }, + meridiemParse: /գիշերվա|առավոտվա|ցերեկվա|երեկոյան/, + isPM: function (input) { + return /^(ցերեկվա|երեկոյան)$/.test(input); + }, + meridiem : function (hour) { + if (hour < 4) { + return 'գիշերվա'; + } else if (hour < 12) { + return 'առավոտվա'; + } else if (hour < 17) { + return 'ցերեկվա'; + } else { + return 'երեկոյան'; + } + }, + ordinalParse: /\d{1,2}|\d{1,2}-(ին|րդ)/, + ordinal: function (number, period) { + switch (period) { + case 'DDD': + case 'w': + case 'W': + case 'DDDo': + if (number === 1) { + return number + '-ին'; + } + return number + '-րդ'; + default: + return number; + } + }, + week : { + dow : 1, // Monday is the first day of the week. + doy : 7 // The week that contains Jan 1st is the first week of the year. + } +}); + +//! moment.js locale configuration +//! locale : Indonesian [id] +//! author : Mohammad Satrio Utomo : https://github.com/tyok +//! reference: http://id.wikisource.org/wiki/Pedoman_Umum_Ejaan_Bahasa_Indonesia_yang_Disempurnakan + +hooks.defineLocale('id', { + months : 'Januari_Februari_Maret_April_Mei_Juni_Juli_Agustus_September_Oktober_November_Desember'.split('_'), + monthsShort : 'Jan_Feb_Mar_Apr_Mei_Jun_Jul_Ags_Sep_Okt_Nov_Des'.split('_'), + weekdays : 'Minggu_Senin_Selasa_Rabu_Kamis_Jumat_Sabtu'.split('_'), + weekdaysShort : 'Min_Sen_Sel_Rab_Kam_Jum_Sab'.split('_'), + weekdaysMin : 'Mg_Sn_Sl_Rb_Km_Jm_Sb'.split('_'), + longDateFormat : { + LT : 'HH.mm', + LTS : 'HH.mm.ss', + L : 'DD/MM/YYYY', + LL : 'D MMMM YYYY', + LLL : 'D MMMM YYYY [pukul] HH.mm', + LLLL : 'dddd, D MMMM YYYY [pukul] HH.mm' + }, + meridiemParse: /pagi|siang|sore|malam/, + meridiemHour : function (hour, meridiem) { + if (hour === 12) { + hour = 0; + } + if (meridiem === 'pagi') { + return hour; + } else if (meridiem === 'siang') { + return hour >= 11 ? hour : hour + 12; + } else if (meridiem === 'sore' || meridiem === 'malam') { + return hour + 12; + } + }, + meridiem : function (hours, minutes, isLower) { + if (hours < 11) { + return 'pagi'; + } else if (hours < 15) { + return 'siang'; + } else if (hours < 19) { + return 'sore'; + } else { + return 'malam'; + } + }, + calendar : { + sameDay : '[Hari ini pukul] LT', + nextDay : '[Besok pukul] LT', + nextWeek : 'dddd [pukul] LT', + lastDay : '[Kemarin pukul] LT', + lastWeek : 'dddd [lalu pukul] LT', + sameElse : 'L' + }, + relativeTime : { + future : 'dalam %s', + past : '%s yang lalu', + s : 'beberapa detik', + m : 'semenit', + mm : '%d menit', + h : 'sejam', + hh : '%d jam', + d : 'sehari', + dd : '%d hari', + M : 'sebulan', + MM : '%d bulan', + y : 'setahun', + yy : '%d tahun' + }, + week : { + dow : 1, // Monday is the first day of the week. + doy : 7 // The week that contains Jan 1st is the first week of the year. + } +}); + +//! moment.js locale configuration +//! locale : Icelandic [is] +//! author : Hinrik Örn Sigurðsson : https://github.com/hinrik + +function plural$2(n) { + if (n % 100 === 11) { + return true; + } else if (n % 10 === 1) { + return false; + } + return true; +} +function translate$5(number, withoutSuffix, key, isFuture) { + var result = number + ' '; + switch (key) { + case 's': + return withoutSuffix || isFuture ? 'nokkrar sekúndur' : 'nokkrum sekúndum'; + case 'm': + return withoutSuffix ? 'mínúta' : 'mínútu'; + case 'mm': + if (plural$2(number)) { + return result + (withoutSuffix || isFuture ? 'mínútur' : 'mínútum'); + } else if (withoutSuffix) { + return result + 'mínúta'; + } + return result + 'mínútu'; + case 'hh': + if (plural$2(number)) { + return result + (withoutSuffix || isFuture ? 'klukkustundir' : 'klukkustundum'); + } + return result + 'klukkustund'; + case 'd': + if (withoutSuffix) { + return 'dagur'; + } + return isFuture ? 'dag' : 'degi'; + case 'dd': + if (plural$2(number)) { + if (withoutSuffix) { + return result + 'dagar'; + } + return result + (isFuture ? 'daga' : 'dögum'); + } else if (withoutSuffix) { + return result + 'dagur'; + } + return result + (isFuture ? 'dag' : 'degi'); + case 'M': + if (withoutSuffix) { + return 'mánuður'; + } + return isFuture ? 'mánuð' : 'mánuði'; + case 'MM': + if (plural$2(number)) { + if (withoutSuffix) { + return result + 'mánuðir'; + } + return result + (isFuture ? 'mánuði' : 'mánuðum'); + } else if (withoutSuffix) { + return result + 'mánuður'; + } + return result + (isFuture ? 'mánuð' : 'mánuði'); + case 'y': + return withoutSuffix || isFuture ? 'ár' : 'ári'; + case 'yy': + if (plural$2(number)) { + return result + (withoutSuffix || isFuture ? 'ár' : 'árum'); + } + return result + (withoutSuffix || isFuture ? 'ár' : 'ári'); + } +} + +hooks.defineLocale('is', { + months : 'janúar_febrúar_mars_apríl_maí_júní_júlí_ágúst_september_október_nóvember_desember'.split('_'), + monthsShort : 'jan_feb_mar_apr_maí_jún_júl_ágú_sep_okt_nóv_des'.split('_'), + weekdays : 'sunnudagur_mánudagur_þriðjudagur_miðvikudagur_fimmtudagur_föstudagur_laugardagur'.split('_'), + weekdaysShort : 'sun_mán_þri_mið_fim_fös_lau'.split('_'), + weekdaysMin : 'Su_Má_Þr_Mi_Fi_Fö_La'.split('_'), + longDateFormat : { + LT : 'H:mm', + LTS : 'H:mm:ss', + L : 'DD.MM.YYYY', + LL : 'D. MMMM YYYY', + LLL : 'D. MMMM YYYY [kl.] H:mm', + LLLL : 'dddd, D. MMMM YYYY [kl.] H:mm' + }, + calendar : { + sameDay : '[í dag kl.] LT', + nextDay : '[á morgun kl.] LT', + nextWeek : 'dddd [kl.] LT', + lastDay : '[í gær kl.] LT', + lastWeek : '[síðasta] dddd [kl.] LT', + sameElse : 'L' + }, + relativeTime : { + future : 'eftir %s', + past : 'fyrir %s síðan', + s : translate$5, + m : translate$5, + mm : translate$5, + h : 'klukkustund', + hh : translate$5, + d : translate$5, + dd : translate$5, + M : translate$5, + MM : translate$5, + y : translate$5, + yy : translate$5 + }, + ordinalParse: /\d{1,2}\./, + ordinal : '%d.', + week : { + dow : 1, // Monday is the first day of the week. + doy : 4 // The week that contains Jan 4th is the first week of the year. + } +}); + +//! moment.js locale configuration +//! locale : Italian [it] +//! author : Lorenzo : https://github.com/aliem +//! author: Mattia Larentis: https://github.com/nostalgiaz + +hooks.defineLocale('it', { + months : 'gennaio_febbraio_marzo_aprile_maggio_giugno_luglio_agosto_settembre_ottobre_novembre_dicembre'.split('_'), + monthsShort : 'gen_feb_mar_apr_mag_giu_lug_ago_set_ott_nov_dic'.split('_'), + weekdays : 'Domenica_Lunedì_Martedì_Mercoledì_Giovedì_Venerdì_Sabato'.split('_'), + weekdaysShort : 'Dom_Lun_Mar_Mer_Gio_Ven_Sab'.split('_'), + weekdaysMin : 'Do_Lu_Ma_Me_Gi_Ve_Sa'.split('_'), + longDateFormat : { + LT : 'HH:mm', + LTS : 'HH:mm:ss', + L : 'DD/MM/YYYY', + LL : 'D MMMM YYYY', + LLL : 'D MMMM YYYY HH:mm', + LLLL : 'dddd, D MMMM YYYY HH:mm' + }, + calendar : { + sameDay: '[Oggi alle] LT', + nextDay: '[Domani alle] LT', + nextWeek: 'dddd [alle] LT', + lastDay: '[Ieri alle] LT', + lastWeek: function () { + switch (this.day()) { + case 0: + return '[la scorsa] dddd [alle] LT'; + default: + return '[lo scorso] dddd [alle] LT'; + } + }, + sameElse: 'L' + }, + relativeTime : { + future : function (s) { + return ((/^[0-9].+$/).test(s) ? 'tra' : 'in') + ' ' + s; + }, + past : '%s fa', + s : 'alcuni secondi', + m : 'un minuto', + mm : '%d minuti', + h : 'un\'ora', + hh : '%d ore', + d : 'un giorno', + dd : '%d giorni', + M : 'un mese', + MM : '%d mesi', + y : 'un anno', + yy : '%d anni' + }, + ordinalParse : /\d{1,2}º/, + ordinal: '%dº', + week : { + dow : 1, // Monday is the first day of the week. + doy : 4 // The week that contains Jan 4th is the first week of the year. + } +}); + +//! moment.js locale configuration +//! locale : Japanese [ja] +//! author : LI Long : https://github.com/baryon + +hooks.defineLocale('ja', { + months : '1月_2月_3月_4月_5月_6月_7月_8月_9月_10月_11月_12月'.split('_'), + monthsShort : '1月_2月_3月_4月_5月_6月_7月_8月_9月_10月_11月_12月'.split('_'), + weekdays : '日曜日_月曜日_火曜日_水曜日_木曜日_金曜日_土曜日'.split('_'), + weekdaysShort : '日_月_火_水_木_金_土'.split('_'), + weekdaysMin : '日_月_火_水_木_金_土'.split('_'), + longDateFormat : { + LT : 'Ah時m分', + LTS : 'Ah時m分s秒', + L : 'YYYY/MM/DD', + LL : 'YYYY年M月D日', + LLL : 'YYYY年M月D日Ah時m分', + LLLL : 'YYYY年M月D日Ah時m分 dddd' + }, + meridiemParse: /午前|午後/i, + isPM : function (input) { + return input === '午後'; + }, + meridiem : function (hour, minute, isLower) { + if (hour < 12) { + return '午前'; + } else { + return '午後'; + } + }, + calendar : { + sameDay : '[今日] LT', + nextDay : '[明日] LT', + nextWeek : '[来週]dddd LT', + lastDay : '[昨日] LT', + lastWeek : '[前週]dddd LT', + sameElse : 'L' + }, + ordinalParse : /\d{1,2}日/, + ordinal : function (number, period) { + switch (period) { + case 'd': + case 'D': + case 'DDD': + return number + '日'; + default: + return number; + } + }, + relativeTime : { + future : '%s後', + past : '%s前', + s : '数秒', + m : '1分', + mm : '%d分', + h : '1時間', + hh : '%d時間', + d : '1日', + dd : '%d日', + M : '1ヶ月', + MM : '%dヶ月', + y : '1年', + yy : '%d年' + } +}); + +//! moment.js locale configuration +//! locale : Javanese [jv] +//! author : Rony Lantip : https://github.com/lantip +//! reference: http://jv.wikipedia.org/wiki/Basa_Jawa + +hooks.defineLocale('jv', { + months : 'Januari_Februari_Maret_April_Mei_Juni_Juli_Agustus_September_Oktober_Nopember_Desember'.split('_'), + monthsShort : 'Jan_Feb_Mar_Apr_Mei_Jun_Jul_Ags_Sep_Okt_Nop_Des'.split('_'), + weekdays : 'Minggu_Senen_Seloso_Rebu_Kemis_Jemuwah_Septu'.split('_'), + weekdaysShort : 'Min_Sen_Sel_Reb_Kem_Jem_Sep'.split('_'), + weekdaysMin : 'Mg_Sn_Sl_Rb_Km_Jm_Sp'.split('_'), + longDateFormat : { + LT : 'HH.mm', + LTS : 'HH.mm.ss', + L : 'DD/MM/YYYY', + LL : 'D MMMM YYYY', + LLL : 'D MMMM YYYY [pukul] HH.mm', + LLLL : 'dddd, D MMMM YYYY [pukul] HH.mm' + }, + meridiemParse: /enjing|siyang|sonten|ndalu/, + meridiemHour : function (hour, meridiem) { + if (hour === 12) { + hour = 0; + } + if (meridiem === 'enjing') { + return hour; + } else if (meridiem === 'siyang') { + return hour >= 11 ? hour : hour + 12; + } else if (meridiem === 'sonten' || meridiem === 'ndalu') { + return hour + 12; + } + }, + meridiem : function (hours, minutes, isLower) { + if (hours < 11) { + return 'enjing'; + } else if (hours < 15) { + return 'siyang'; + } else if (hours < 19) { + return 'sonten'; + } else { + return 'ndalu'; + } + }, + calendar : { + sameDay : '[Dinten puniko pukul] LT', + nextDay : '[Mbenjang pukul] LT', + nextWeek : 'dddd [pukul] LT', + lastDay : '[Kala wingi pukul] LT', + lastWeek : 'dddd [kepengker pukul] LT', + sameElse : 'L' + }, + relativeTime : { + future : 'wonten ing %s', + past : '%s ingkang kepengker', + s : 'sawetawis detik', + m : 'setunggal menit', + mm : '%d menit', + h : 'setunggal jam', + hh : '%d jam', + d : 'sedinten', + dd : '%d dinten', + M : 'sewulan', + MM : '%d wulan', + y : 'setaun', + yy : '%d taun' + }, + week : { + dow : 1, // Monday is the first day of the week. + doy : 7 // The week that contains Jan 1st is the first week of the year. + } +}); + +//! moment.js locale configuration +//! locale : Georgian [ka] +//! author : Irakli Janiashvili : https://github.com/irakli-janiashvili + +hooks.defineLocale('ka', { + months : { + standalone: 'იანვარი_თებერვალი_მარტი_აპრილი_მაისი_ივნისი_ივლისი_აგვისტო_სექტემბერი_ოქტომბერი_ნოემბერი_დეკემბერი'.split('_'), + format: 'იანვარს_თებერვალს_მარტს_აპრილის_მაისს_ივნისს_ივლისს_აგვისტს_სექტემბერს_ოქტომბერს_ნოემბერს_დეკემბერს'.split('_') + }, + monthsShort : 'იან_თებ_მარ_აპრ_მაი_ივნ_ივლ_აგვ_სექ_ოქტ_ნოე_დეკ'.split('_'), + weekdays : { + standalone: 'კვირა_ორშაბათი_სამშაბათი_ოთხშაბათი_ხუთშაბათი_პარასკევი_შაბათი'.split('_'), + format: 'კვირას_ორშაბათს_სამშაბათს_ოთხშაბათს_ხუთშაბათს_პარასკევს_შაბათს'.split('_'), + isFormat: /(წინა|შემდეგ)/ + }, + weekdaysShort : 'კვი_ორშ_სამ_ოთხ_ხუთ_პარ_შაბ'.split('_'), + weekdaysMin : 'კვ_ორ_სა_ოთ_ხუ_პა_შა'.split('_'), + longDateFormat : { + LT : 'h:mm A', + LTS : 'h:mm:ss A', + L : 'DD/MM/YYYY', + LL : 'D MMMM YYYY', + LLL : 'D MMMM YYYY h:mm A', + LLLL : 'dddd, D MMMM YYYY h:mm A' + }, + calendar : { + sameDay : '[დღეს] LT[-ზე]', + nextDay : '[ხვალ] LT[-ზე]', + lastDay : '[გუშინ] LT[-ზე]', + nextWeek : '[შემდეგ] dddd LT[-ზე]', + lastWeek : '[წინა] dddd LT-ზე', + sameElse : 'L' + }, + relativeTime : { + future : function (s) { + return (/(წამი|წუთი|საათი|წელი)/).test(s) ? + s.replace(/ი$/, 'ში') : + s + 'ში'; + }, + past : function (s) { + if ((/(წამი|წუთი|საათი|დღე|თვე)/).test(s)) { + return s.replace(/(ი|ე)$/, 'ის წინ'); + } + if ((/წელი/).test(s)) { + return s.replace(/წელი$/, 'წლის წინ'); + } + }, + s : 'რამდენიმე წამი', + m : 'წუთი', + mm : '%d წუთი', + h : 'საათი', + hh : '%d საათი', + d : 'დღე', + dd : '%d დღე', + M : 'თვე', + MM : '%d თვე', + y : 'წელი', + yy : '%d წელი' + }, + ordinalParse: /0|1-ლი|მე-\d{1,2}|\d{1,2}-ე/, + ordinal : function (number) { + if (number === 0) { + return number; + } + if (number === 1) { + return number + '-ლი'; + } + if ((number < 20) || (number <= 100 && (number % 20 === 0)) || (number % 100 === 0)) { + return 'მე-' + number; + } + return number + '-ე'; + }, + week : { + dow : 1, + doy : 7 + } +}); + +//! moment.js locale configuration +//! locale : Kazakh [kk] +//! authors : Nurlan Rakhimzhanov : https://github.com/nurlan + +var suffixes$1 = { + 0: '-ші', + 1: '-ші', + 2: '-ші', + 3: '-ші', + 4: '-ші', + 5: '-ші', + 6: '-шы', + 7: '-ші', + 8: '-ші', + 9: '-шы', + 10: '-шы', + 20: '-шы', + 30: '-шы', + 40: '-шы', + 50: '-ші', + 60: '-шы', + 70: '-ші', + 80: '-ші', + 90: '-шы', + 100: '-ші' +}; + +hooks.defineLocale('kk', { + months : 'қаңтар_ақпан_наурыз_сәуір_мамыр_маусым_шілде_тамыз_қыркүйек_қазан_қараша_желтоқсан'.split('_'), + monthsShort : 'қаң_ақп_нау_сәу_мам_мау_шіл_там_қыр_қаз_қар_жел'.split('_'), + weekdays : 'жексенбі_дүйсенбі_сейсенбі_сәрсенбі_бейсенбі_жұма_сенбі'.split('_'), + weekdaysShort : 'жек_дүй_сей_сәр_бей_жұм_сен'.split('_'), + weekdaysMin : 'жк_дй_сй_ср_бй_жм_сн'.split('_'), + longDateFormat : { + LT : 'HH:mm', + LTS : 'HH:mm:ss', + L : 'DD.MM.YYYY', + LL : 'D MMMM YYYY', + LLL : 'D MMMM YYYY HH:mm', + LLLL : 'dddd, D MMMM YYYY HH:mm' + }, + calendar : { + sameDay : '[Бүгін сағат] LT', + nextDay : '[Ертең сағат] LT', + nextWeek : 'dddd [сағат] LT', + lastDay : '[Кеше сағат] LT', + lastWeek : '[Өткен аптаның] dddd [сағат] LT', + sameElse : 'L' + }, + relativeTime : { + future : '%s ішінде', + past : '%s бұрын', + s : 'бірнеше секунд', + m : 'бір минут', + mm : '%d минут', + h : 'бір сағат', + hh : '%d сағат', + d : 'бір күн', + dd : '%d күн', + M : 'бір ай', + MM : '%d ай', + y : 'бір жыл', + yy : '%d жыл' + }, + ordinalParse: /\d{1,2}-(ші|шы)/, + ordinal : function (number) { + var a = number % 10, + b = number >= 100 ? 100 : null; + return number + (suffixes$1[number] || suffixes$1[a] || suffixes$1[b]); + }, + week : { + dow : 1, // Monday is the first day of the week. + doy : 7 // The week that contains Jan 1st is the first week of the year. + } +}); + +//! moment.js locale configuration +//! locale : Cambodian [km] +//! author : Kruy Vanna : https://github.com/kruyvanna + +hooks.defineLocale('km', { + months: 'មករា_កុម្ភៈ_មីនា_មេសា_ឧសភា_មិថុនា_កក្កដា_សីហា_កញ្ញា_តុលា_វិច្ឆិកា_ធ្នូ'.split('_'), + monthsShort: 'មករា_កុម្ភៈ_មីនា_មេសា_ឧសភា_មិថុនា_កក្កដា_សីហា_កញ្ញា_តុលា_វិច្ឆិកា_ធ្នូ'.split('_'), + weekdays: 'អាទិត្យ_ច័ន្ទ_អង្គារ_ពុធ_ព្រហស្បតិ៍_សុក្រ_សៅរ៍'.split('_'), + weekdaysShort: 'អាទិត្យ_ច័ន្ទ_អង្គារ_ពុធ_ព្រហស្បតិ៍_សុក្រ_សៅរ៍'.split('_'), + weekdaysMin: 'អាទិត្យ_ច័ន្ទ_អង្គារ_ពុធ_ព្រហស្បតិ៍_សុក្រ_សៅរ៍'.split('_'), + longDateFormat: { + LT: 'HH:mm', + LTS : 'HH:mm:ss', + L: 'DD/MM/YYYY', + LL: 'D MMMM YYYY', + LLL: 'D MMMM YYYY HH:mm', + LLLL: 'dddd, D MMMM YYYY HH:mm' + }, + calendar: { + sameDay: '[ថ្ងៃនេះ ម៉ោង] LT', + nextDay: '[ស្អែក ម៉ោង] LT', + nextWeek: 'dddd [ម៉ោង] LT', + lastDay: '[ម្សិលមិញ ម៉ោង] LT', + lastWeek: 'dddd [សប្តាហ៍មុន] [ម៉ោង] LT', + sameElse: 'L' + }, + relativeTime: { + future: '%sទៀត', + past: '%sមុន', + s: 'ប៉ុន្មានវិនាទី', + m: 'មួយនាទី', + mm: '%d នាទី', + h: 'មួយម៉ោង', + hh: '%d ម៉ោង', + d: 'មួយថ្ងៃ', + dd: '%d ថ្ងៃ', + M: 'មួយខែ', + MM: '%d ខែ', + y: 'មួយឆ្នាំ', + yy: '%d ឆ្នាំ' + }, + week: { + dow: 1, // Monday is the first day of the week. + doy: 4 // The week that contains Jan 4th is the first week of the year. + } +}); + +//! moment.js locale configuration +//! locale : Korean [ko] +//! author : Kyungwook, Park : https://github.com/kyungw00k +//! author : Jeeeyul Lee + +hooks.defineLocale('ko', { + months : '1월_2월_3월_4월_5월_6월_7월_8월_9월_10월_11월_12월'.split('_'), + monthsShort : '1월_2월_3월_4월_5월_6월_7월_8월_9월_10월_11월_12월'.split('_'), + weekdays : '일요일_월요일_화요일_수요일_목요일_금요일_토요일'.split('_'), + weekdaysShort : '일_월_화_수_목_금_토'.split('_'), + weekdaysMin : '일_월_화_수_목_금_토'.split('_'), + longDateFormat : { + LT : 'A h시 m분', + LTS : 'A h시 m분 s초', + L : 'YYYY.MM.DD', + LL : 'YYYY년 MMMM D일', + LLL : 'YYYY년 MMMM D일 A h시 m분', + LLLL : 'YYYY년 MMMM D일 dddd A h시 m분' + }, + calendar : { + sameDay : '오늘 LT', + nextDay : '내일 LT', + nextWeek : 'dddd LT', + lastDay : '어제 LT', + lastWeek : '지난주 dddd LT', + sameElse : 'L' + }, + relativeTime : { + future : '%s 후', + past : '%s 전', + s : '몇 초', + ss : '%d초', + m : '일분', + mm : '%d분', + h : '한 시간', + hh : '%d시간', + d : '하루', + dd : '%d일', + M : '한 달', + MM : '%d달', + y : '일 년', + yy : '%d년' + }, + ordinalParse : /\d{1,2}일/, + ordinal : '%d일', + meridiemParse : /오전|오후/, + isPM : function (token) { + return token === '오후'; + }, + meridiem : function (hour, minute, isUpper) { + return hour < 12 ? '오전' : '오후'; + } +}); + +//! moment.js locale configuration +//! locale : Kyrgyz [ky] +//! author : Chyngyz Arystan uulu : https://github.com/chyngyz + + +var suffixes$2 = { + 0: '-чү', + 1: '-чи', + 2: '-чи', + 3: '-чү', + 4: '-чү', + 5: '-чи', + 6: '-чы', + 7: '-чи', + 8: '-чи', + 9: '-чу', + 10: '-чу', + 20: '-чы', + 30: '-чу', + 40: '-чы', + 50: '-чү', + 60: '-чы', + 70: '-чи', + 80: '-чи', + 90: '-чу', + 100: '-чү' +}; + +hooks.defineLocale('ky', { + months : 'январь_февраль_март_апрель_май_июнь_июль_август_сентябрь_октябрь_ноябрь_декабрь'.split('_'), + monthsShort : 'янв_фев_март_апр_май_июнь_июль_авг_сен_окт_ноя_дек'.split('_'), + weekdays : 'Жекшемби_Дүйшөмбү_Шейшемби_Шаршемби_Бейшемби_Жума_Ишемби'.split('_'), + weekdaysShort : 'Жек_Дүй_Шей_Шар_Бей_Жум_Ише'.split('_'), + weekdaysMin : 'Жк_Дй_Шй_Шр_Бй_Жм_Иш'.split('_'), + longDateFormat : { + LT : 'HH:mm', + LTS : 'HH:mm:ss', + L : 'DD.MM.YYYY', + LL : 'D MMMM YYYY', + LLL : 'D MMMM YYYY HH:mm', + LLLL : 'dddd, D MMMM YYYY HH:mm' + }, + calendar : { + sameDay : '[Бүгүн саат] LT', + nextDay : '[Эртең саат] LT', + nextWeek : 'dddd [саат] LT', + lastDay : '[Кече саат] LT', + lastWeek : '[Өткен аптанын] dddd [күнү] [саат] LT', + sameElse : 'L' + }, + relativeTime : { + future : '%s ичинде', + past : '%s мурун', + s : 'бирнече секунд', + m : 'бир мүнөт', + mm : '%d мүнөт', + h : 'бир саат', + hh : '%d саат', + d : 'бир күн', + dd : '%d күн', + M : 'бир ай', + MM : '%d ай', + y : 'бир жыл', + yy : '%d жыл' + }, + ordinalParse: /\d{1,2}-(чи|чы|чү|чу)/, + ordinal : function (number) { + var a = number % 10, + b = number >= 100 ? 100 : null; + return number + (suffixes$2[number] || suffixes$2[a] || suffixes$2[b]); + }, + week : { + dow : 1, // Monday is the first day of the week. + doy : 7 // The week that contains Jan 1st is the first week of the year. + } +}); + +//! moment.js locale configuration +//! locale : Luxembourgish [lb] +//! author : mweimerskirch : https://github.com/mweimerskirch +//! author : David Raison : https://github.com/kwisatz + +function processRelativeTime$3(number, withoutSuffix, key, isFuture) { + var format = { + 'm': ['eng Minutt', 'enger Minutt'], + 'h': ['eng Stonn', 'enger Stonn'], + 'd': ['een Dag', 'engem Dag'], + 'M': ['ee Mount', 'engem Mount'], + 'y': ['ee Joer', 'engem Joer'] + }; + return withoutSuffix ? format[key][0] : format[key][1]; +} +function processFutureTime(string) { + var number = string.substr(0, string.indexOf(' ')); + if (eifelerRegelAppliesToNumber(number)) { + return 'a ' + string; + } + return 'an ' + string; +} +function processPastTime(string) { + var number = string.substr(0, string.indexOf(' ')); + if (eifelerRegelAppliesToNumber(number)) { + return 'viru ' + string; + } + return 'virun ' + string; +} +/** + * Returns true if the word before the given number loses the '-n' ending. + * e.g. 'an 10 Deeg' but 'a 5 Deeg' + * + * @param number {integer} + * @returns {boolean} + */ +function eifelerRegelAppliesToNumber(number) { + number = parseInt(number, 10); + if (isNaN(number)) { + return false; + } + if (number < 0) { + // Negative Number --> always true + return true; + } else if (number < 10) { + // Only 1 digit + if (4 <= number && number <= 7) { + return true; + } + return false; + } else if (number < 100) { + // 2 digits + var lastDigit = number % 10, firstDigit = number / 10; + if (lastDigit === 0) { + return eifelerRegelAppliesToNumber(firstDigit); + } + return eifelerRegelAppliesToNumber(lastDigit); + } else if (number < 10000) { + // 3 or 4 digits --> recursively check first digit + while (number >= 10) { + number = number / 10; + } + return eifelerRegelAppliesToNumber(number); + } else { + // Anything larger than 4 digits: recursively check first n-3 digits + number = number / 1000; + return eifelerRegelAppliesToNumber(number); + } +} + +hooks.defineLocale('lb', { + months: 'Januar_Februar_Mäerz_Abrëll_Mee_Juni_Juli_August_September_Oktober_November_Dezember'.split('_'), + monthsShort: 'Jan._Febr._Mrz._Abr._Mee_Jun._Jul._Aug._Sept._Okt._Nov._Dez.'.split('_'), + monthsParseExact : true, + weekdays: 'Sonndeg_Méindeg_Dënschdeg_Mëttwoch_Donneschdeg_Freideg_Samschdeg'.split('_'), + weekdaysShort: 'So._Mé._Dë._Më._Do._Fr._Sa.'.split('_'), + weekdaysMin: 'So_Mé_Dë_Më_Do_Fr_Sa'.split('_'), + weekdaysParseExact : true, + longDateFormat: { + LT: 'H:mm [Auer]', + LTS: 'H:mm:ss [Auer]', + L: 'DD.MM.YYYY', + LL: 'D. MMMM YYYY', + LLL: 'D. MMMM YYYY H:mm [Auer]', + LLLL: 'dddd, D. MMMM YYYY H:mm [Auer]' + }, + calendar: { + sameDay: '[Haut um] LT', + sameElse: 'L', + nextDay: '[Muer um] LT', + nextWeek: 'dddd [um] LT', + lastDay: '[Gëschter um] LT', + lastWeek: function () { + // Different date string for 'Dënschdeg' (Tuesday) and 'Donneschdeg' (Thursday) due to phonological rule + switch (this.day()) { + case 2: + case 4: + return '[Leschten] dddd [um] LT'; + default: + return '[Leschte] dddd [um] LT'; + } + } + }, + relativeTime : { + future : processFutureTime, + past : processPastTime, + s : 'e puer Sekonnen', + m : processRelativeTime$3, + mm : '%d Minutten', + h : processRelativeTime$3, + hh : '%d Stonnen', + d : processRelativeTime$3, + dd : '%d Deeg', + M : processRelativeTime$3, + MM : '%d Méint', + y : processRelativeTime$3, + yy : '%d Joer' + }, + ordinalParse: /\d{1,2}\./, + ordinal: '%d.', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4 // The week that contains Jan 4th is the first week of the year. + } +}); + +//! moment.js locale configuration +//! locale : Lao [lo] +//! author : Ryan Hart : https://github.com/ryanhart2 + +hooks.defineLocale('lo', { + months : 'ມັງກອນ_ກຸມພາ_ມີນາ_ເມສາ_ພຶດສະພາ_ມິຖຸນາ_ກໍລະກົດ_ສິງຫາ_ກັນຍາ_ຕຸລາ_ພະຈິກ_ທັນວາ'.split('_'), + monthsShort : 'ມັງກອນ_ກຸມພາ_ມີນາ_ເມສາ_ພຶດສະພາ_ມິຖຸນາ_ກໍລະກົດ_ສິງຫາ_ກັນຍາ_ຕຸລາ_ພະຈິກ_ທັນວາ'.split('_'), + weekdays : 'ອາທິດ_ຈັນ_ອັງຄານ_ພຸດ_ພະຫັດ_ສຸກ_ເສົາ'.split('_'), + weekdaysShort : 'ທິດ_ຈັນ_ອັງຄານ_ພຸດ_ພະຫັດ_ສຸກ_ເສົາ'.split('_'), + weekdaysMin : 'ທ_ຈ_ອຄ_ພ_ພຫ_ສກ_ສ'.split('_'), + weekdaysParseExact : true, + longDateFormat : { + LT : 'HH:mm', + LTS : 'HH:mm:ss', + L : 'DD/MM/YYYY', + LL : 'D MMMM YYYY', + LLL : 'D MMMM YYYY HH:mm', + LLLL : 'ວັນdddd D MMMM YYYY HH:mm' + }, + meridiemParse: /ຕອນເຊົ້າ|ຕອນແລງ/, + isPM: function (input) { + return input === 'ຕອນແລງ'; + }, + meridiem : function (hour, minute, isLower) { + if (hour < 12) { + return 'ຕອນເຊົ້າ'; + } else { + return 'ຕອນແລງ'; + } + }, + calendar : { + sameDay : '[ມື້ນີ້ເວລາ] LT', + nextDay : '[ມື້ອື່ນເວລາ] LT', + nextWeek : '[ວັນ]dddd[ໜ້າເວລາ] LT', + lastDay : '[ມື້ວານນີ້ເວລາ] LT', + lastWeek : '[ວັນ]dddd[ແລ້ວນີ້ເວລາ] LT', + sameElse : 'L' + }, + relativeTime : { + future : 'ອີກ %s', + past : '%sຜ່ານມາ', + s : 'ບໍ່ເທົ່າໃດວິນາທີ', + m : '1 ນາທີ', + mm : '%d ນາທີ', + h : '1 ຊົ່ວໂມງ', + hh : '%d ຊົ່ວໂມງ', + d : '1 ມື້', + dd : '%d ມື້', + M : '1 ເດືອນ', + MM : '%d ເດືອນ', + y : '1 ປີ', + yy : '%d ປີ' + }, + ordinalParse: /(ທີ່)\d{1,2}/, + ordinal : function (number) { + return 'ທີ່' + number; + } +}); + +//! moment.js locale configuration +//! locale : Lithuanian [lt] +//! author : Mindaugas Mozūras : https://github.com/mmozuras + +var units = { + 'm' : 'minutė_minutės_minutę', + 'mm': 'minutės_minučių_minutes', + 'h' : 'valanda_valandos_valandą', + 'hh': 'valandos_valandų_valandas', + 'd' : 'diena_dienos_dieną', + 'dd': 'dienos_dienų_dienas', + 'M' : 'mėnuo_mėnesio_mėnesį', + 'MM': 'mėnesiai_mėnesių_mėnesius', + 'y' : 'metai_metų_metus', + 'yy': 'metai_metų_metus' +}; +function translateSeconds(number, withoutSuffix, key, isFuture) { + if (withoutSuffix) { + return 'kelios sekundės'; + } else { + return isFuture ? 'kelių sekundžių' : 'kelias sekundes'; + } +} +function translateSingular(number, withoutSuffix, key, isFuture) { + return withoutSuffix ? forms(key)[0] : (isFuture ? forms(key)[1] : forms(key)[2]); +} +function special(number) { + return number % 10 === 0 || (number > 10 && number < 20); +} +function forms(key) { + return units[key].split('_'); +} +function translate$6(number, withoutSuffix, key, isFuture) { + var result = number + ' '; + if (number === 1) { + return result + translateSingular(number, withoutSuffix, key[0], isFuture); + } else if (withoutSuffix) { + return result + (special(number) ? forms(key)[1] : forms(key)[0]); + } else { + if (isFuture) { + return result + forms(key)[1]; + } else { + return result + (special(number) ? forms(key)[1] : forms(key)[2]); + } + } +} +hooks.defineLocale('lt', { + months : { + format: 'sausio_vasario_kovo_balandžio_gegužės_birželio_liepos_rugpjūčio_rugsėjo_spalio_lapkričio_gruodžio'.split('_'), + standalone: 'sausis_vasaris_kovas_balandis_gegužė_birželis_liepa_rugpjūtis_rugsėjis_spalis_lapkritis_gruodis'.split('_'), + isFormat: /D[oD]?(\[[^\[\]]*\]|\s)+MMMM?|MMMM?(\[[^\[\]]*\]|\s)+D[oD]?/ + }, + monthsShort : 'sau_vas_kov_bal_geg_bir_lie_rgp_rgs_spa_lap_grd'.split('_'), + weekdays : { + format: 'sekmadienį_pirmadienį_antradienį_trečiadienį_ketvirtadienį_penktadienį_šeštadienį'.split('_'), + standalone: 'sekmadienis_pirmadienis_antradienis_trečiadienis_ketvirtadienis_penktadienis_šeštadienis'.split('_'), + isFormat: /dddd HH:mm/ + }, + weekdaysShort : 'Sek_Pir_Ant_Tre_Ket_Pen_Šeš'.split('_'), + weekdaysMin : 'S_P_A_T_K_Pn_Š'.split('_'), + weekdaysParseExact : true, + longDateFormat : { + LT : 'HH:mm', + LTS : 'HH:mm:ss', + L : 'YYYY-MM-DD', + LL : 'YYYY [m.] MMMM D [d.]', + LLL : 'YYYY [m.] MMMM D [d.], HH:mm [val.]', + LLLL : 'YYYY [m.] MMMM D [d.], dddd, HH:mm [val.]', + l : 'YYYY-MM-DD', + ll : 'YYYY [m.] MMMM D [d.]', + lll : 'YYYY [m.] MMMM D [d.], HH:mm [val.]', + llll : 'YYYY [m.] MMMM D [d.], ddd, HH:mm [val.]' + }, + calendar : { + sameDay : '[Šiandien] LT', + nextDay : '[Rytoj] LT', + nextWeek : 'dddd LT', + lastDay : '[Vakar] LT', + lastWeek : '[Praėjusį] dddd LT', + sameElse : 'L' + }, + relativeTime : { + future : 'po %s', + past : 'prieš %s', + s : translateSeconds, + m : translateSingular, + mm : translate$6, + h : translateSingular, + hh : translate$6, + d : translateSingular, + dd : translate$6, + M : translateSingular, + MM : translate$6, + y : translateSingular, + yy : translate$6 + }, + ordinalParse: /\d{1,2}-oji/, + ordinal : function (number) { + return number + '-oji'; + }, + week : { + dow : 1, // Monday is the first day of the week. + doy : 4 // The week that contains Jan 4th is the first week of the year. + } +}); + +//! moment.js locale configuration +//! locale : Latvian [lv] +//! author : Kristaps Karlsons : https://github.com/skakri +//! author : Jānis Elmeris : https://github.com/JanisE + +var units$1 = { + 'm': 'minūtes_minūtēm_minūte_minūtes'.split('_'), + 'mm': 'minūtes_minūtēm_minūte_minūtes'.split('_'), + 'h': 'stundas_stundām_stunda_stundas'.split('_'), + 'hh': 'stundas_stundām_stunda_stundas'.split('_'), + 'd': 'dienas_dienām_diena_dienas'.split('_'), + 'dd': 'dienas_dienām_diena_dienas'.split('_'), + 'M': 'mēneša_mēnešiem_mēnesis_mēneši'.split('_'), + 'MM': 'mēneša_mēnešiem_mēnesis_mēneši'.split('_'), + 'y': 'gada_gadiem_gads_gadi'.split('_'), + 'yy': 'gada_gadiem_gads_gadi'.split('_') +}; +/** + * @param withoutSuffix boolean true = a length of time; false = before/after a period of time. + */ +function format$1(forms, number, withoutSuffix) { + if (withoutSuffix) { + // E.g. "21 minūte", "3 minūtes". + return number % 10 === 1 && number % 100 !== 11 ? forms[2] : forms[3]; + } else { + // E.g. "21 minūtes" as in "pēc 21 minūtes". + // E.g. "3 minūtēm" as in "pēc 3 minūtēm". + return number % 10 === 1 && number % 100 !== 11 ? forms[0] : forms[1]; + } +} +function relativeTimeWithPlural$1(number, withoutSuffix, key) { + return number + ' ' + format$1(units$1[key], number, withoutSuffix); +} +function relativeTimeWithSingular(number, withoutSuffix, key) { + return format$1(units$1[key], number, withoutSuffix); +} +function relativeSeconds(number, withoutSuffix) { + return withoutSuffix ? 'dažas sekundes' : 'dažām sekundēm'; +} + +hooks.defineLocale('lv', { + months : 'janvāris_februāris_marts_aprīlis_maijs_jūnijs_jūlijs_augusts_septembris_oktobris_novembris_decembris'.split('_'), + monthsShort : 'jan_feb_mar_apr_mai_jūn_jūl_aug_sep_okt_nov_dec'.split('_'), + weekdays : 'svētdiena_pirmdiena_otrdiena_trešdiena_ceturtdiena_piektdiena_sestdiena'.split('_'), + weekdaysShort : 'Sv_P_O_T_C_Pk_S'.split('_'), + weekdaysMin : 'Sv_P_O_T_C_Pk_S'.split('_'), + weekdaysParseExact : true, + longDateFormat : { + LT : 'HH:mm', + LTS : 'HH:mm:ss', + L : 'DD.MM.YYYY.', + LL : 'YYYY. [gada] D. MMMM', + LLL : 'YYYY. [gada] D. MMMM, HH:mm', + LLLL : 'YYYY. [gada] D. MMMM, dddd, HH:mm' + }, + calendar : { + sameDay : '[Šodien pulksten] LT', + nextDay : '[Rīt pulksten] LT', + nextWeek : 'dddd [pulksten] LT', + lastDay : '[Vakar pulksten] LT', + lastWeek : '[Pagājušā] dddd [pulksten] LT', + sameElse : 'L' + }, + relativeTime : { + future : 'pēc %s', + past : 'pirms %s', + s : relativeSeconds, + m : relativeTimeWithSingular, + mm : relativeTimeWithPlural$1, + h : relativeTimeWithSingular, + hh : relativeTimeWithPlural$1, + d : relativeTimeWithSingular, + dd : relativeTimeWithPlural$1, + M : relativeTimeWithSingular, + MM : relativeTimeWithPlural$1, + y : relativeTimeWithSingular, + yy : relativeTimeWithPlural$1 + }, + ordinalParse: /\d{1,2}\./, + ordinal : '%d.', + week : { + dow : 1, // Monday is the first day of the week. + doy : 4 // The week that contains Jan 4th is the first week of the year. + } +}); + +//! moment.js locale configuration +//! locale : Montenegrin [me] +//! author : Miodrag Nikač : https://github.com/miodragnikac + +var translator = { + words: { //Different grammatical cases + m: ['jedan minut', 'jednog minuta'], + mm: ['minut', 'minuta', 'minuta'], + h: ['jedan sat', 'jednog sata'], + hh: ['sat', 'sata', 'sati'], + dd: ['dan', 'dana', 'dana'], + MM: ['mjesec', 'mjeseca', 'mjeseci'], + yy: ['godina', 'godine', 'godina'] + }, + correctGrammaticalCase: function (number, wordKey) { + return number === 1 ? wordKey[0] : (number >= 2 && number <= 4 ? wordKey[1] : wordKey[2]); + }, + translate: function (number, withoutSuffix, key) { + var wordKey = translator.words[key]; + if (key.length === 1) { + return withoutSuffix ? wordKey[0] : wordKey[1]; + } else { + return number + ' ' + translator.correctGrammaticalCase(number, wordKey); + } + } +}; + +hooks.defineLocale('me', { + months: 'januar_februar_mart_april_maj_jun_jul_avgust_septembar_oktobar_novembar_decembar'.split('_'), + monthsShort: 'jan._feb._mar._apr._maj_jun_jul_avg._sep._okt._nov._dec.'.split('_'), + monthsParseExact : true, + weekdays: 'nedjelja_ponedjeljak_utorak_srijeda_četvrtak_petak_subota'.split('_'), + weekdaysShort: 'ned._pon._uto._sri._čet._pet._sub.'.split('_'), + weekdaysMin: 'ne_po_ut_sr_če_pe_su'.split('_'), + weekdaysParseExact : true, + longDateFormat: { + LT: 'H:mm', + LTS : 'H:mm:ss', + L: 'DD.MM.YYYY', + LL: 'D. MMMM YYYY', + LLL: 'D. MMMM YYYY H:mm', + LLLL: 'dddd, D. MMMM YYYY H:mm' + }, + calendar: { + sameDay: '[danas u] LT', + nextDay: '[sjutra u] LT', + + nextWeek: function () { + switch (this.day()) { + case 0: + return '[u] [nedjelju] [u] LT'; + case 3: + return '[u] [srijedu] [u] LT'; + case 6: + return '[u] [subotu] [u] LT'; + case 1: + case 2: + case 4: + case 5: + return '[u] dddd [u] LT'; + } + }, + lastDay : '[juče u] LT', + lastWeek : function () { + var lastWeekDays = [ + '[prošle] [nedjelje] [u] LT', + '[prošlog] [ponedjeljka] [u] LT', + '[prošlog] [utorka] [u] LT', + '[prošle] [srijede] [u] LT', + '[prošlog] [četvrtka] [u] LT', + '[prošlog] [petka] [u] LT', + '[prošle] [subote] [u] LT' + ]; + return lastWeekDays[this.day()]; + }, + sameElse : 'L' + }, + relativeTime : { + future : 'za %s', + past : 'prije %s', + s : 'nekoliko sekundi', + m : translator.translate, + mm : translator.translate, + h : translator.translate, + hh : translator.translate, + d : 'dan', + dd : translator.translate, + M : 'mjesec', + MM : translator.translate, + y : 'godinu', + yy : translator.translate + }, + ordinalParse: /\d{1,2}\./, + ordinal : '%d.', + week : { + dow : 1, // Monday is the first day of the week. + doy : 7 // The week that contains Jan 1st is the first week of the year. + } +}); + +//! moment.js locale configuration +//! locale : Maori [mi] +//! author : John Corrigan : https://github.com/johnideal + +hooks.defineLocale('mi', { + months: 'Kohi-tāte_Hui-tanguru_Poutū-te-rangi_Paenga-whāwhā_Haratua_Pipiri_Hōngoingoi_Here-turi-kōkā_Mahuru_Whiringa-ā-nuku_Whiringa-ā-rangi_Hakihea'.split('_'), + monthsShort: 'Kohi_Hui_Pou_Pae_Hara_Pipi_Hōngoi_Here_Mahu_Whi-nu_Whi-ra_Haki'.split('_'), + monthsRegex: /(?:['a-z\u0101\u014D\u016B]+\-?){1,3}/i, + monthsStrictRegex: /(?:['a-z\u0101\u014D\u016B]+\-?){1,3}/i, + monthsShortRegex: /(?:['a-z\u0101\u014D\u016B]+\-?){1,3}/i, + monthsShortStrictRegex: /(?:['a-z\u0101\u014D\u016B]+\-?){1,2}/i, + weekdays: 'Rātapu_Mane_Tūrei_Wenerei_Tāite_Paraire_Hātarei'.split('_'), + weekdaysShort: 'Ta_Ma_Tū_We_Tāi_Pa_Hā'.split('_'), + weekdaysMin: 'Ta_Ma_Tū_We_Tāi_Pa_Hā'.split('_'), + longDateFormat: { + LT: 'HH:mm', + LTS: 'HH:mm:ss', + L: 'DD/MM/YYYY', + LL: 'D MMMM YYYY', + LLL: 'D MMMM YYYY [i] HH:mm', + LLLL: 'dddd, D MMMM YYYY [i] HH:mm' + }, + calendar: { + sameDay: '[i teie mahana, i] LT', + nextDay: '[apopo i] LT', + nextWeek: 'dddd [i] LT', + lastDay: '[inanahi i] LT', + lastWeek: 'dddd [whakamutunga i] LT', + sameElse: 'L' + }, + relativeTime: { + future: 'i roto i %s', + past: '%s i mua', + s: 'te hēkona ruarua', + m: 'he meneti', + mm: '%d meneti', + h: 'te haora', + hh: '%d haora', + d: 'he ra', + dd: '%d ra', + M: 'he marama', + MM: '%d marama', + y: 'he tau', + yy: '%d tau' + }, + ordinalParse: /\d{1,2}º/, + ordinal: '%dº', + week : { + dow : 1, // Monday is the first day of the week. + doy : 4 // The week that contains Jan 4th is the first week of the year. + } +}); + +//! moment.js locale configuration +//! locale : Macedonian [mk] +//! author : Borislav Mickov : https://github.com/B0k0 + +hooks.defineLocale('mk', { + months : 'јануари_февруари_март_април_мај_јуни_јули_август_септември_октомври_ноември_декември'.split('_'), + monthsShort : 'јан_фев_мар_апр_мај_јун_јул_авг_сеп_окт_ное_дек'.split('_'), + weekdays : 'недела_понеделник_вторник_среда_четврток_петок_сабота'.split('_'), + weekdaysShort : 'нед_пон_вто_сре_чет_пет_саб'.split('_'), + weekdaysMin : 'нe_пo_вт_ср_че_пе_сa'.split('_'), + longDateFormat : { + LT : 'H:mm', + LTS : 'H:mm:ss', + L : 'D.MM.YYYY', + LL : 'D MMMM YYYY', + LLL : 'D MMMM YYYY H:mm', + LLLL : 'dddd, D MMMM YYYY H:mm' + }, + calendar : { + sameDay : '[Денес во] LT', + nextDay : '[Утре во] LT', + nextWeek : '[Во] dddd [во] LT', + lastDay : '[Вчера во] LT', + lastWeek : function () { + switch (this.day()) { + case 0: + case 3: + case 6: + return '[Изминатата] dddd [во] LT'; + case 1: + case 2: + case 4: + case 5: + return '[Изминатиот] dddd [во] LT'; + } + }, + sameElse : 'L' + }, + relativeTime : { + future : 'после %s', + past : 'пред %s', + s : 'неколку секунди', + m : 'минута', + mm : '%d минути', + h : 'час', + hh : '%d часа', + d : 'ден', + dd : '%d дена', + M : 'месец', + MM : '%d месеци', + y : 'година', + yy : '%d години' + }, + ordinalParse: /\d{1,2}-(ев|ен|ти|ви|ри|ми)/, + ordinal : function (number) { + var lastDigit = number % 10, + last2Digits = number % 100; + if (number === 0) { + return number + '-ев'; + } else if (last2Digits === 0) { + return number + '-ен'; + } else if (last2Digits > 10 && last2Digits < 20) { + return number + '-ти'; + } else if (lastDigit === 1) { + return number + '-ви'; + } else if (lastDigit === 2) { + return number + '-ри'; + } else if (lastDigit === 7 || lastDigit === 8) { + return number + '-ми'; + } else { + return number + '-ти'; + } + }, + week : { + dow : 1, // Monday is the first day of the week. + doy : 7 // The week that contains Jan 1st is the first week of the year. + } +}); + +//! moment.js locale configuration +//! locale : Malayalam [ml] +//! author : Floyd Pink : https://github.com/floydpink + +hooks.defineLocale('ml', { + months : 'ജനുവരി_ഫെബ്രുവരി_മാർച്ച്_ഏപ്രിൽ_മേയ്_ജൂൺ_ജൂലൈ_ഓഗസ്റ്റ്_സെപ്റ്റംബർ_ഒക്ടോബർ_നവംബർ_ഡിസംബർ'.split('_'), + monthsShort : 'ജനു._ഫെബ്രു._മാർ._ഏപ്രി._മേയ്_ജൂൺ_ജൂലൈ._ഓഗ._സെപ്റ്റ._ഒക്ടോ._നവം._ഡിസം.'.split('_'), + monthsParseExact : true, + weekdays : 'ഞായറാഴ്ച_തിങ്കളാഴ്ച_ചൊവ്വാഴ്ച_ബുധനാഴ്ച_വ്യാഴാഴ്ച_വെള്ളിയാഴ്ച_ശനിയാഴ്ച'.split('_'), + weekdaysShort : 'ഞായർ_തിങ്കൾ_ചൊവ്വ_ബുധൻ_വ്യാഴം_വെള്ളി_ശനി'.split('_'), + weekdaysMin : 'ഞാ_തി_ചൊ_ബു_വ്യാ_വെ_ശ'.split('_'), + longDateFormat : { + LT : 'A h:mm -നു', + LTS : 'A h:mm:ss -നു', + L : 'DD/MM/YYYY', + LL : 'D MMMM YYYY', + LLL : 'D MMMM YYYY, A h:mm -നു', + LLLL : 'dddd, D MMMM YYYY, A h:mm -നു' + }, + calendar : { + sameDay : '[ഇന്ന്] LT', + nextDay : '[നാളെ] LT', + nextWeek : 'dddd, LT', + lastDay : '[ഇന്നലെ] LT', + lastWeek : '[കഴിഞ്ഞ] dddd, LT', + sameElse : 'L' + }, + relativeTime : { + future : '%s കഴിഞ്ഞ്', + past : '%s മുൻപ്', + s : 'അൽപ നിമിഷങ്ങൾ', + m : 'ഒരു മിനിറ്റ്', + mm : '%d മിനിറ്റ്', + h : 'ഒരു മണിക്കൂർ', + hh : '%d മണിക്കൂർ', + d : 'ഒരു ദിവസം', + dd : '%d ദിവസം', + M : 'ഒരു മാസം', + MM : '%d മാസം', + y : 'ഒരു വർഷം', + yy : '%d വർഷം' + }, + meridiemParse: /രാത്രി|രാവിലെ|ഉച്ച കഴിഞ്ഞ്|വൈകുന്നേരം|രാത്രി/i, + meridiemHour : function (hour, meridiem) { + if (hour === 12) { + hour = 0; + } + if ((meridiem === 'രാത്രി' && hour >= 4) || + meridiem === 'ഉച്ച കഴിഞ്ഞ്' || + meridiem === 'വൈകുന്നേരം') { + return hour + 12; + } else { + return hour; + } + }, + meridiem : function (hour, minute, isLower) { + if (hour < 4) { + return 'രാത്രി'; + } else if (hour < 12) { + return 'രാവിലെ'; + } else if (hour < 17) { + return 'ഉച്ച കഴിഞ്ഞ്'; + } else if (hour < 20) { + return 'വൈകുന്നേരം'; + } else { + return 'രാത്രി'; + } + } +}); + +//! moment.js locale configuration +//! locale : Marathi [mr] +//! author : Harshad Kale : https://github.com/kalehv +//! author : Vivek Athalye : https://github.com/vnathalye + +var symbolMap$7 = { + '1': '१', + '2': '२', + '3': '३', + '4': '४', + '5': '५', + '6': '६', + '7': '७', + '8': '८', + '9': '९', + '0': '०' +}; +var numberMap$6 = { + '१': '1', + '२': '2', + '३': '3', + '४': '4', + '५': '5', + '६': '6', + '७': '7', + '८': '8', + '९': '9', + '०': '0' +}; + +function relativeTimeMr(number, withoutSuffix, string, isFuture) +{ + var output = ''; + if (withoutSuffix) { + switch (string) { + case 's': output = 'काही सेकंद'; break; + case 'm': output = 'एक मिनिट'; break; + case 'mm': output = '%d मिनिटे'; break; + case 'h': output = 'एक तास'; break; + case 'hh': output = '%d तास'; break; + case 'd': output = 'एक दिवस'; break; + case 'dd': output = '%d दिवस'; break; + case 'M': output = 'एक महिना'; break; + case 'MM': output = '%d महिने'; break; + case 'y': output = 'एक वर्ष'; break; + case 'yy': output = '%d वर्षे'; break; + } + } + else { + switch (string) { + case 's': output = 'काही सेकंदां'; break; + case 'm': output = 'एका मिनिटा'; break; + case 'mm': output = '%d मिनिटां'; break; + case 'h': output = 'एका तासा'; break; + case 'hh': output = '%d तासां'; break; + case 'd': output = 'एका दिवसा'; break; + case 'dd': output = '%d दिवसां'; break; + case 'M': output = 'एका महिन्या'; break; + case 'MM': output = '%d महिन्यां'; break; + case 'y': output = 'एका वर्षा'; break; + case 'yy': output = '%d वर्षां'; break; + } + } + return output.replace(/%d/i, number); +} + +hooks.defineLocale('mr', { + months : 'जानेवारी_फेब्रुवारी_मार्च_एप्रिल_मे_जून_जुलै_ऑगस्ट_सप्टेंबर_ऑक्टोबर_नोव्हेंबर_डिसेंबर'.split('_'), + monthsShort: 'जाने._फेब्रु._मार्च._एप्रि._मे._जून._जुलै._ऑग._सप्टें._ऑक्टो._नोव्हें._डिसें.'.split('_'), + monthsParseExact : true, + weekdays : 'रविवार_सोमवार_मंगळवार_बुधवार_गुरूवार_शुक्रवार_शनिवार'.split('_'), + weekdaysShort : 'रवि_सोम_मंगळ_बुध_गुरू_शुक्र_शनि'.split('_'), + weekdaysMin : 'र_सो_मं_बु_गु_शु_श'.split('_'), + longDateFormat : { + LT : 'A h:mm वाजता', + LTS : 'A h:mm:ss वाजता', + L : 'DD/MM/YYYY', + LL : 'D MMMM YYYY', + LLL : 'D MMMM YYYY, A h:mm वाजता', + LLLL : 'dddd, D MMMM YYYY, A h:mm वाजता' + }, + calendar : { + sameDay : '[आज] LT', + nextDay : '[उद्या] LT', + nextWeek : 'dddd, LT', + lastDay : '[काल] LT', + lastWeek: '[मागील] dddd, LT', + sameElse : 'L' + }, + relativeTime : { + future: '%sमध्ये', + past: '%sपूर्वी', + s: relativeTimeMr, + m: relativeTimeMr, + mm: relativeTimeMr, + h: relativeTimeMr, + hh: relativeTimeMr, + d: relativeTimeMr, + dd: relativeTimeMr, + M: relativeTimeMr, + MM: relativeTimeMr, + y: relativeTimeMr, + yy: relativeTimeMr + }, + preparse: function (string) { + return string.replace(/[१२३४५६७८९०]/g, function (match) { + return numberMap$6[match]; + }); + }, + postformat: function (string) { + return string.replace(/\d/g, function (match) { + return symbolMap$7[match]; + }); + }, + meridiemParse: /रात्री|सकाळी|दुपारी|सायंकाळी/, + meridiemHour : function (hour, meridiem) { + if (hour === 12) { + hour = 0; + } + if (meridiem === 'रात्री') { + return hour < 4 ? hour : hour + 12; + } else if (meridiem === 'सकाळी') { + return hour; + } else if (meridiem === 'दुपारी') { + return hour >= 10 ? hour : hour + 12; + } else if (meridiem === 'सायंकाळी') { + return hour + 12; + } + }, + meridiem: function (hour, minute, isLower) { + if (hour < 4) { + return 'रात्री'; + } else if (hour < 10) { + return 'सकाळी'; + } else if (hour < 17) { + return 'दुपारी'; + } else if (hour < 20) { + return 'सायंकाळी'; + } else { + return 'रात्री'; + } + }, + week : { + dow : 0, // Sunday is the first day of the week. + doy : 6 // The week that contains Jan 1st is the first week of the year. + } +}); + +//! moment.js locale configuration +//! locale : Malay [ms-my] +//! note : DEPRECATED, the correct one is [ms] +//! author : Weldan Jamili : https://github.com/weldan + +hooks.defineLocale('ms-my', { + months : 'Januari_Februari_Mac_April_Mei_Jun_Julai_Ogos_September_Oktober_November_Disember'.split('_'), + monthsShort : 'Jan_Feb_Mac_Apr_Mei_Jun_Jul_Ogs_Sep_Okt_Nov_Dis'.split('_'), + weekdays : 'Ahad_Isnin_Selasa_Rabu_Khamis_Jumaat_Sabtu'.split('_'), + weekdaysShort : 'Ahd_Isn_Sel_Rab_Kha_Jum_Sab'.split('_'), + weekdaysMin : 'Ah_Is_Sl_Rb_Km_Jm_Sb'.split('_'), + longDateFormat : { + LT : 'HH.mm', + LTS : 'HH.mm.ss', + L : 'DD/MM/YYYY', + LL : 'D MMMM YYYY', + LLL : 'D MMMM YYYY [pukul] HH.mm', + LLLL : 'dddd, D MMMM YYYY [pukul] HH.mm' + }, + meridiemParse: /pagi|tengahari|petang|malam/, + meridiemHour: function (hour, meridiem) { + if (hour === 12) { + hour = 0; + } + if (meridiem === 'pagi') { + return hour; + } else if (meridiem === 'tengahari') { + return hour >= 11 ? hour : hour + 12; + } else if (meridiem === 'petang' || meridiem === 'malam') { + return hour + 12; + } + }, + meridiem : function (hours, minutes, isLower) { + if (hours < 11) { + return 'pagi'; + } else if (hours < 15) { + return 'tengahari'; + } else if (hours < 19) { + return 'petang'; + } else { + return 'malam'; + } + }, + calendar : { + sameDay : '[Hari ini pukul] LT', + nextDay : '[Esok pukul] LT', + nextWeek : 'dddd [pukul] LT', + lastDay : '[Kelmarin pukul] LT', + lastWeek : 'dddd [lepas pukul] LT', + sameElse : 'L' + }, + relativeTime : { + future : 'dalam %s', + past : '%s yang lepas', + s : 'beberapa saat', + m : 'seminit', + mm : '%d minit', + h : 'sejam', + hh : '%d jam', + d : 'sehari', + dd : '%d hari', + M : 'sebulan', + MM : '%d bulan', + y : 'setahun', + yy : '%d tahun' + }, + week : { + dow : 1, // Monday is the first day of the week. + doy : 7 // The week that contains Jan 1st is the first week of the year. + } +}); + +//! moment.js locale configuration +//! locale : Malay [ms] +//! author : Weldan Jamili : https://github.com/weldan + +hooks.defineLocale('ms', { + months : 'Januari_Februari_Mac_April_Mei_Jun_Julai_Ogos_September_Oktober_November_Disember'.split('_'), + monthsShort : 'Jan_Feb_Mac_Apr_Mei_Jun_Jul_Ogs_Sep_Okt_Nov_Dis'.split('_'), + weekdays : 'Ahad_Isnin_Selasa_Rabu_Khamis_Jumaat_Sabtu'.split('_'), + weekdaysShort : 'Ahd_Isn_Sel_Rab_Kha_Jum_Sab'.split('_'), + weekdaysMin : 'Ah_Is_Sl_Rb_Km_Jm_Sb'.split('_'), + longDateFormat : { + LT : 'HH.mm', + LTS : 'HH.mm.ss', + L : 'DD/MM/YYYY', + LL : 'D MMMM YYYY', + LLL : 'D MMMM YYYY [pukul] HH.mm', + LLLL : 'dddd, D MMMM YYYY [pukul] HH.mm' + }, + meridiemParse: /pagi|tengahari|petang|malam/, + meridiemHour: function (hour, meridiem) { + if (hour === 12) { + hour = 0; + } + if (meridiem === 'pagi') { + return hour; + } else if (meridiem === 'tengahari') { + return hour >= 11 ? hour : hour + 12; + } else if (meridiem === 'petang' || meridiem === 'malam') { + return hour + 12; + } + }, + meridiem : function (hours, minutes, isLower) { + if (hours < 11) { + return 'pagi'; + } else if (hours < 15) { + return 'tengahari'; + } else if (hours < 19) { + return 'petang'; + } else { + return 'malam'; + } + }, + calendar : { + sameDay : '[Hari ini pukul] LT', + nextDay : '[Esok pukul] LT', + nextWeek : 'dddd [pukul] LT', + lastDay : '[Kelmarin pukul] LT', + lastWeek : 'dddd [lepas pukul] LT', + sameElse : 'L' + }, + relativeTime : { + future : 'dalam %s', + past : '%s yang lepas', + s : 'beberapa saat', + m : 'seminit', + mm : '%d minit', + h : 'sejam', + hh : '%d jam', + d : 'sehari', + dd : '%d hari', + M : 'sebulan', + MM : '%d bulan', + y : 'setahun', + yy : '%d tahun' + }, + week : { + dow : 1, // Monday is the first day of the week. + doy : 7 // The week that contains Jan 1st is the first week of the year. + } +}); + +//! moment.js locale configuration +//! locale : Burmese [my] +//! author : Squar team, mysquar.com +//! author : David Rossellat : https://github.com/gholadr +//! author : Tin Aung Lin : https://github.com/thanyawzinmin + +var symbolMap$8 = { + '1': '၁', + '2': '၂', + '3': '၃', + '4': '၄', + '5': '၅', + '6': '၆', + '7': '၇', + '8': '၈', + '9': '၉', + '0': '၀' +}; +var numberMap$7 = { + '၁': '1', + '၂': '2', + '၃': '3', + '၄': '4', + '၅': '5', + '၆': '6', + '၇': '7', + '၈': '8', + '၉': '9', + '၀': '0' +}; + +hooks.defineLocale('my', { + months: 'ဇန်နဝါရီ_ဖေဖော်ဝါရီ_မတ်_ဧပြီ_မေ_ဇွန်_ဇူလိုင်_သြဂုတ်_စက်တင်ဘာ_အောက်တိုဘာ_နိုဝင်ဘာ_ဒီဇင်ဘာ'.split('_'), + monthsShort: 'ဇန်_ဖေ_မတ်_ပြီ_မေ_ဇွန်_လိုင်_သြ_စက်_အောက်_နို_ဒီ'.split('_'), + weekdays: 'တနင်္ဂနွေ_တနင်္လာ_အင်္ဂါ_ဗုဒ္ဓဟူး_ကြာသပတေး_သောကြာ_စနေ'.split('_'), + weekdaysShort: 'နွေ_လာ_ဂါ_ဟူး_ကြာ_သော_နေ'.split('_'), + weekdaysMin: 'နွေ_လာ_ဂါ_ဟူး_ကြာ_သော_နေ'.split('_'), + + longDateFormat: { + LT: 'HH:mm', + LTS: 'HH:mm:ss', + L: 'DD/MM/YYYY', + LL: 'D MMMM YYYY', + LLL: 'D MMMM YYYY HH:mm', + LLLL: 'dddd D MMMM YYYY HH:mm' + }, + calendar: { + sameDay: '[ယနေ.] LT [မှာ]', + nextDay: '[မနက်ဖြန်] LT [မှာ]', + nextWeek: 'dddd LT [မှာ]', + lastDay: '[မနေ.က] LT [မှာ]', + lastWeek: '[ပြီးခဲ့သော] dddd LT [မှာ]', + sameElse: 'L' + }, + relativeTime: { + future: 'လာမည့် %s မှာ', + past: 'လွန်ခဲ့သော %s က', + s: 'စက္ကန်.အနည်းငယ်', + m: 'တစ်မိနစ်', + mm: '%d မိနစ်', + h: 'တစ်နာရီ', + hh: '%d နာရီ', + d: 'တစ်ရက်', + dd: '%d ရက်', + M: 'တစ်လ', + MM: '%d လ', + y: 'တစ်နှစ်', + yy: '%d နှစ်' + }, + preparse: function (string) { + return string.replace(/[၁၂၃၄၅၆၇၈၉၀]/g, function (match) { + return numberMap$7[match]; + }); + }, + postformat: function (string) { + return string.replace(/\d/g, function (match) { + return symbolMap$8[match]; + }); + }, + week: { + dow: 1, // Monday is the first day of the week. + doy: 4 // The week that contains Jan 1st is the first week of the year. + } +}); + +//! moment.js locale configuration +//! locale : Norwegian Bokmål [nb] +//! authors : Espen Hovlandsdal : https://github.com/rexxars +//! Sigurd Gartmann : https://github.com/sigurdga + +hooks.defineLocale('nb', { + months : 'januar_februar_mars_april_mai_juni_juli_august_september_oktober_november_desember'.split('_'), + monthsShort : 'jan._feb._mars_april_mai_juni_juli_aug._sep._okt._nov._des.'.split('_'), + monthsParseExact : true, + weekdays : 'søndag_mandag_tirsdag_onsdag_torsdag_fredag_lørdag'.split('_'), + weekdaysShort : 'sø._ma._ti._on._to._fr._lø.'.split('_'), + weekdaysMin : 'sø_ma_ti_on_to_fr_lø'.split('_'), + weekdaysParseExact : true, + longDateFormat : { + LT : 'HH:mm', + LTS : 'HH:mm:ss', + L : 'DD.MM.YYYY', + LL : 'D. MMMM YYYY', + LLL : 'D. MMMM YYYY [kl.] HH:mm', + LLLL : 'dddd D. MMMM YYYY [kl.] HH:mm' + }, + calendar : { + sameDay: '[i dag kl.] LT', + nextDay: '[i morgen kl.] LT', + nextWeek: 'dddd [kl.] LT', + lastDay: '[i går kl.] LT', + lastWeek: '[forrige] dddd [kl.] LT', + sameElse: 'L' + }, + relativeTime : { + future : 'om %s', + past : '%s siden', + s : 'noen sekunder', + m : 'ett minutt', + mm : '%d minutter', + h : 'en time', + hh : '%d timer', + d : 'en dag', + dd : '%d dager', + M : 'en måned', + MM : '%d måneder', + y : 'ett år', + yy : '%d år' + }, + ordinalParse: /\d{1,2}\./, + ordinal : '%d.', + week : { + dow : 1, // Monday is the first day of the week. + doy : 4 // The week that contains Jan 4th is the first week of the year. + } +}); + +//! moment.js locale configuration +//! locale : Nepalese [ne] +//! author : suvash : https://github.com/suvash + +var symbolMap$9 = { + '1': '१', + '2': '२', + '3': '३', + '4': '४', + '5': '५', + '6': '६', + '7': '७', + '8': '८', + '9': '९', + '0': '०' +}; +var numberMap$8 = { + '१': '1', + '२': '2', + '३': '3', + '४': '4', + '५': '5', + '६': '6', + '७': '7', + '८': '8', + '९': '9', + '०': '0' +}; + +hooks.defineLocale('ne', { + months : 'जनवरी_फेब्रुवरी_मार्च_अप्रिल_मई_जुन_जुलाई_अगष्ट_सेप्टेम्बर_अक्टोबर_नोभेम्बर_डिसेम्बर'.split('_'), + monthsShort : 'जन._फेब्रु._मार्च_अप्रि._मई_जुन_जुलाई._अग._सेप्ट._अक्टो._नोभे._डिसे.'.split('_'), + monthsParseExact : true, + weekdays : 'आइतबार_सोमबार_मङ्गलबार_बुधबार_बिहिबार_शुक्रबार_शनिबार'.split('_'), + weekdaysShort : 'आइत._सोम._मङ्गल._बुध._बिहि._शुक्र._शनि.'.split('_'), + weekdaysMin : 'आ._सो._मं._बु._बि._शु._श.'.split('_'), + weekdaysParseExact : true, + longDateFormat : { + LT : 'Aको h:mm बजे', + LTS : 'Aको h:mm:ss बजे', + L : 'DD/MM/YYYY', + LL : 'D MMMM YYYY', + LLL : 'D MMMM YYYY, Aको h:mm बजे', + LLLL : 'dddd, D MMMM YYYY, Aको h:mm बजे' + }, + preparse: function (string) { + return string.replace(/[१२३४५६७८९०]/g, function (match) { + return numberMap$8[match]; + }); + }, + postformat: function (string) { + return string.replace(/\d/g, function (match) { + return symbolMap$9[match]; + }); + }, + meridiemParse: /राति|बिहान|दिउँसो|साँझ/, + meridiemHour : function (hour, meridiem) { + if (hour === 12) { + hour = 0; + } + if (meridiem === 'राति') { + return hour < 4 ? hour : hour + 12; + } else if (meridiem === 'बिहान') { + return hour; + } else if (meridiem === 'दिउँसो') { + return hour >= 10 ? hour : hour + 12; + } else if (meridiem === 'साँझ') { + return hour + 12; + } + }, + meridiem : function (hour, minute, isLower) { + if (hour < 3) { + return 'राति'; + } else if (hour < 12) { + return 'बिहान'; + } else if (hour < 16) { + return 'दिउँसो'; + } else if (hour < 20) { + return 'साँझ'; + } else { + return 'राति'; + } + }, + calendar : { + sameDay : '[आज] LT', + nextDay : '[भोलि] LT', + nextWeek : '[आउँदो] dddd[,] LT', + lastDay : '[हिजो] LT', + lastWeek : '[गएको] dddd[,] LT', + sameElse : 'L' + }, + relativeTime : { + future : '%sमा', + past : '%s अगाडि', + s : 'केही क्षण', + m : 'एक मिनेट', + mm : '%d मिनेट', + h : 'एक घण्टा', + hh : '%d घण्टा', + d : 'एक दिन', + dd : '%d दिन', + M : 'एक महिना', + MM : '%d महिना', + y : 'एक बर्ष', + yy : '%d बर्ष' + }, + week : { + dow : 0, // Sunday is the first day of the week. + doy : 6 // The week that contains Jan 1st is the first week of the year. + } +}); + +//! moment.js locale configuration +//! locale : Dutch (Belgium) [nl-be] +//! author : Joris Röling : https://github.com/jorisroling +//! author : Jacob Middag : https://github.com/middagj + +var monthsShortWithDots$1 = 'jan._feb._mrt._apr._mei_jun._jul._aug._sep._okt._nov._dec.'.split('_'); +var monthsShortWithoutDots$1 = 'jan_feb_mrt_apr_mei_jun_jul_aug_sep_okt_nov_dec'.split('_'); + +var monthsParse = [/^jan/i, /^feb/i, /^maart|mrt.?$/i, /^apr/i, /^mei$/i, /^jun[i.]?$/i, /^jul[i.]?$/i, /^aug/i, /^sep/i, /^okt/i, /^nov/i, /^dec/i]; +var monthsRegex$1 = /^(januari|februari|maart|april|mei|april|ju[nl]i|augustus|september|oktober|november|december|jan\.?|feb\.?|mrt\.?|apr\.?|ju[nl]\.?|aug\.?|sep\.?|okt\.?|nov\.?|dec\.?)/i; + +hooks.defineLocale('nl-be', { + months : 'januari_februari_maart_april_mei_juni_juli_augustus_september_oktober_november_december'.split('_'), + monthsShort : function (m, format) { + if (/-MMM-/.test(format)) { + return monthsShortWithoutDots$1[m.month()]; + } else { + return monthsShortWithDots$1[m.month()]; + } + }, + + monthsRegex: monthsRegex$1, + monthsShortRegex: monthsRegex$1, + monthsStrictRegex: /^(januari|februari|maart|mei|ju[nl]i|april|augustus|september|oktober|november|december)/i, + monthsShortStrictRegex: /^(jan\.?|feb\.?|mrt\.?|apr\.?|mei|ju[nl]\.?|aug\.?|sep\.?|okt\.?|nov\.?|dec\.?)/i, + + monthsParse : monthsParse, + longMonthsParse : monthsParse, + shortMonthsParse : monthsParse, + + weekdays : 'zondag_maandag_dinsdag_woensdag_donderdag_vrijdag_zaterdag'.split('_'), + weekdaysShort : 'zo._ma._di._wo._do._vr._za.'.split('_'), + weekdaysMin : 'Zo_Ma_Di_Wo_Do_Vr_Za'.split('_'), + weekdaysParseExact : true, + longDateFormat : { + LT : 'HH:mm', + LTS : 'HH:mm:ss', + L : 'DD/MM/YYYY', + LL : 'D MMMM YYYY', + LLL : 'D MMMM YYYY HH:mm', + LLLL : 'dddd D MMMM YYYY HH:mm' + }, + calendar : { + sameDay: '[vandaag om] LT', + nextDay: '[morgen om] LT', + nextWeek: 'dddd [om] LT', + lastDay: '[gisteren om] LT', + lastWeek: '[afgelopen] dddd [om] LT', + sameElse: 'L' + }, + relativeTime : { + future : 'over %s', + past : '%s geleden', + s : 'een paar seconden', + m : 'één minuut', + mm : '%d minuten', + h : 'één uur', + hh : '%d uur', + d : 'één dag', + dd : '%d dagen', + M : 'één maand', + MM : '%d maanden', + y : 'één jaar', + yy : '%d jaar' + }, + ordinalParse: /\d{1,2}(ste|de)/, + ordinal : function (number) { + return number + ((number === 1 || number === 8 || number >= 20) ? 'ste' : 'de'); + }, + week : { + dow : 1, // Monday is the first day of the week. + doy : 4 // The week that contains Jan 4th is the first week of the year. + } +}); + +//! moment.js locale configuration +//! locale : Dutch [nl] +//! author : Joris Röling : https://github.com/jorisroling +//! author : Jacob Middag : https://github.com/middagj + +var monthsShortWithDots$2 = 'jan._feb._mrt._apr._mei_jun._jul._aug._sep._okt._nov._dec.'.split('_'); +var monthsShortWithoutDots$2 = 'jan_feb_mrt_apr_mei_jun_jul_aug_sep_okt_nov_dec'.split('_'); + +var monthsParse$1 = [/^jan/i, /^feb/i, /^maart|mrt.?$/i, /^apr/i, /^mei$/i, /^jun[i.]?$/i, /^jul[i.]?$/i, /^aug/i, /^sep/i, /^okt/i, /^nov/i, /^dec/i]; +var monthsRegex$2 = /^(januari|februari|maart|april|mei|april|ju[nl]i|augustus|september|oktober|november|december|jan\.?|feb\.?|mrt\.?|apr\.?|ju[nl]\.?|aug\.?|sep\.?|okt\.?|nov\.?|dec\.?)/i; + +hooks.defineLocale('nl', { + months : 'januari_februari_maart_april_mei_juni_juli_augustus_september_oktober_november_december'.split('_'), + monthsShort : function (m, format) { + if (/-MMM-/.test(format)) { + return monthsShortWithoutDots$2[m.month()]; + } else { + return monthsShortWithDots$2[m.month()]; + } + }, + + monthsRegex: monthsRegex$2, + monthsShortRegex: monthsRegex$2, + monthsStrictRegex: /^(januari|februari|maart|mei|ju[nl]i|april|augustus|september|oktober|november|december)/i, + monthsShortStrictRegex: /^(jan\.?|feb\.?|mrt\.?|apr\.?|mei|ju[nl]\.?|aug\.?|sep\.?|okt\.?|nov\.?|dec\.?)/i, + + monthsParse : monthsParse$1, + longMonthsParse : monthsParse$1, + shortMonthsParse : monthsParse$1, + + weekdays : 'zondag_maandag_dinsdag_woensdag_donderdag_vrijdag_zaterdag'.split('_'), + weekdaysShort : 'zo._ma._di._wo._do._vr._za.'.split('_'), + weekdaysMin : 'Zo_Ma_Di_Wo_Do_Vr_Za'.split('_'), + weekdaysParseExact : true, + longDateFormat : { + LT : 'HH:mm', + LTS : 'HH:mm:ss', + L : 'DD-MM-YYYY', + LL : 'D MMMM YYYY', + LLL : 'D MMMM YYYY HH:mm', + LLLL : 'dddd D MMMM YYYY HH:mm' + }, + calendar : { + sameDay: '[vandaag om] LT', + nextDay: '[morgen om] LT', + nextWeek: 'dddd [om] LT', + lastDay: '[gisteren om] LT', + lastWeek: '[afgelopen] dddd [om] LT', + sameElse: 'L' + }, + relativeTime : { + future : 'over %s', + past : '%s geleden', + s : 'een paar seconden', + m : 'één minuut', + mm : '%d minuten', + h : 'één uur', + hh : '%d uur', + d : 'één dag', + dd : '%d dagen', + M : 'één maand', + MM : '%d maanden', + y : 'één jaar', + yy : '%d jaar' + }, + ordinalParse: /\d{1,2}(ste|de)/, + ordinal : function (number) { + return number + ((number === 1 || number === 8 || number >= 20) ? 'ste' : 'de'); + }, + week : { + dow : 1, // Monday is the first day of the week. + doy : 4 // The week that contains Jan 4th is the first week of the year. + } +}); + +//! moment.js locale configuration +//! locale : Nynorsk [nn] +//! author : https://github.com/mechuwind + +hooks.defineLocale('nn', { + months : 'januar_februar_mars_april_mai_juni_juli_august_september_oktober_november_desember'.split('_'), + monthsShort : 'jan_feb_mar_apr_mai_jun_jul_aug_sep_okt_nov_des'.split('_'), + weekdays : 'sundag_måndag_tysdag_onsdag_torsdag_fredag_laurdag'.split('_'), + weekdaysShort : 'sun_mån_tys_ons_tor_fre_lau'.split('_'), + weekdaysMin : 'su_må_ty_on_to_fr_lø'.split('_'), + longDateFormat : { + LT : 'HH:mm', + LTS : 'HH:mm:ss', + L : 'DD.MM.YYYY', + LL : 'D. MMMM YYYY', + LLL : 'D. MMMM YYYY [kl.] H:mm', + LLLL : 'dddd D. MMMM YYYY [kl.] HH:mm' + }, + calendar : { + sameDay: '[I dag klokka] LT', + nextDay: '[I morgon klokka] LT', + nextWeek: 'dddd [klokka] LT', + lastDay: '[I går klokka] LT', + lastWeek: '[Føregåande] dddd [klokka] LT', + sameElse: 'L' + }, + relativeTime : { + future : 'om %s', + past : '%s sidan', + s : 'nokre sekund', + m : 'eit minutt', + mm : '%d minutt', + h : 'ein time', + hh : '%d timar', + d : 'ein dag', + dd : '%d dagar', + M : 'ein månad', + MM : '%d månader', + y : 'eit år', + yy : '%d år' + }, + ordinalParse: /\d{1,2}\./, + ordinal : '%d.', + week : { + dow : 1, // Monday is the first day of the week. + doy : 4 // The week that contains Jan 4th is the first week of the year. + } +}); + +//! moment.js locale configuration +//! locale : Punjabi (India) [pa-in] +//! author : Harpreet Singh : https://github.com/harpreetkhalsagtbit + +var symbolMap$10 = { + '1': '੧', + '2': '੨', + '3': '੩', + '4': '੪', + '5': '੫', + '6': '੬', + '7': '੭', + '8': '੮', + '9': '੯', + '0': '੦' +}; +var numberMap$9 = { + '੧': '1', + '੨': '2', + '੩': '3', + '੪': '4', + '੫': '5', + '੬': '6', + '੭': '7', + '੮': '8', + '੯': '9', + '੦': '0' +}; + +hooks.defineLocale('pa-in', { + // There are months name as per Nanakshahi Calender but they are not used as rigidly in modern Punjabi. + months : 'ਜਨਵਰੀ_ਫ਼ਰਵਰੀ_ਮਾਰਚ_ਅਪ੍ਰੈਲ_ਮਈ_ਜੂਨ_ਜੁਲਾਈ_ਅਗਸਤ_ਸਤੰਬਰ_ਅਕਤੂਬਰ_ਨਵੰਬਰ_ਦਸੰਬਰ'.split('_'), + monthsShort : 'ਜਨਵਰੀ_ਫ਼ਰਵਰੀ_ਮਾਰਚ_ਅਪ੍ਰੈਲ_ਮਈ_ਜੂਨ_ਜੁਲਾਈ_ਅਗਸਤ_ਸਤੰਬਰ_ਅਕਤੂਬਰ_ਨਵੰਬਰ_ਦਸੰਬਰ'.split('_'), + weekdays : 'ਐਤਵਾਰ_ਸੋਮਵਾਰ_ਮੰਗਲਵਾਰ_ਬੁਧਵਾਰ_ਵੀਰਵਾਰ_ਸ਼ੁੱਕਰਵਾਰ_ਸ਼ਨੀਚਰਵਾਰ'.split('_'), + weekdaysShort : 'ਐਤ_ਸੋਮ_ਮੰਗਲ_ਬੁਧ_ਵੀਰ_ਸ਼ੁਕਰ_ਸ਼ਨੀ'.split('_'), + weekdaysMin : 'ਐਤ_ਸੋਮ_ਮੰਗਲ_ਬੁਧ_ਵੀਰ_ਸ਼ੁਕਰ_ਸ਼ਨੀ'.split('_'), + longDateFormat : { + LT : 'A h:mm ਵਜੇ', + LTS : 'A h:mm:ss ਵਜੇ', + L : 'DD/MM/YYYY', + LL : 'D MMMM YYYY', + LLL : 'D MMMM YYYY, A h:mm ਵਜੇ', + LLLL : 'dddd, D MMMM YYYY, A h:mm ਵਜੇ' + }, + calendar : { + sameDay : '[ਅਜ] LT', + nextDay : '[ਕਲ] LT', + nextWeek : 'dddd, LT', + lastDay : '[ਕਲ] LT', + lastWeek : '[ਪਿਛਲੇ] dddd, LT', + sameElse : 'L' + }, + relativeTime : { + future : '%s ਵਿੱਚ', + past : '%s ਪਿਛਲੇ', + s : 'ਕੁਝ ਸਕਿੰਟ', + m : 'ਇਕ ਮਿੰਟ', + mm : '%d ਮਿੰਟ', + h : 'ਇੱਕ ਘੰਟਾ', + hh : '%d ਘੰਟੇ', + d : 'ਇੱਕ ਦਿਨ', + dd : '%d ਦਿਨ', + M : 'ਇੱਕ ਮਹੀਨਾ', + MM : '%d ਮਹੀਨੇ', + y : 'ਇੱਕ ਸਾਲ', + yy : '%d ਸਾਲ' + }, + preparse: function (string) { + return string.replace(/[੧੨੩੪੫੬੭੮੯੦]/g, function (match) { + return numberMap$9[match]; + }); + }, + postformat: function (string) { + return string.replace(/\d/g, function (match) { + return symbolMap$10[match]; + }); + }, + // Punjabi notation for meridiems are quite fuzzy in practice. While there exists + // a rigid notion of a 'Pahar' it is not used as rigidly in modern Punjabi. + meridiemParse: /ਰਾਤ|ਸਵੇਰ|ਦੁਪਹਿਰ|ਸ਼ਾਮ/, + meridiemHour : function (hour, meridiem) { + if (hour === 12) { + hour = 0; + } + if (meridiem === 'ਰਾਤ') { + return hour < 4 ? hour : hour + 12; + } else if (meridiem === 'ਸਵੇਰ') { + return hour; + } else if (meridiem === 'ਦੁਪਹਿਰ') { + return hour >= 10 ? hour : hour + 12; + } else if (meridiem === 'ਸ਼ਾਮ') { + return hour + 12; + } + }, + meridiem : function (hour, minute, isLower) { + if (hour < 4) { + return 'ਰਾਤ'; + } else if (hour < 10) { + return 'ਸਵੇਰ'; + } else if (hour < 17) { + return 'ਦੁਪਹਿਰ'; + } else if (hour < 20) { + return 'ਸ਼ਾਮ'; + } else { + return 'ਰਾਤ'; + } + }, + week : { + dow : 0, // Sunday is the first day of the week. + doy : 6 // The week that contains Jan 1st is the first week of the year. + } +}); + +//! moment.js locale configuration +//! locale : Polish [pl] +//! author : Rafal Hirsz : https://github.com/evoL + +var monthsNominative = 'styczeń_luty_marzec_kwiecień_maj_czerwiec_lipiec_sierpień_wrzesień_październik_listopad_grudzień'.split('_'); +var monthsSubjective = 'stycznia_lutego_marca_kwietnia_maja_czerwca_lipca_sierpnia_września_października_listopada_grudnia'.split('_'); +function plural$3(n) { + return (n % 10 < 5) && (n % 10 > 1) && ((~~(n / 10) % 10) !== 1); +} +function translate$7(number, withoutSuffix, key) { + var result = number + ' '; + switch (key) { + case 'm': + return withoutSuffix ? 'minuta' : 'minutę'; + case 'mm': + return result + (plural$3(number) ? 'minuty' : 'minut'); + case 'h': + return withoutSuffix ? 'godzina' : 'godzinę'; + case 'hh': + return result + (plural$3(number) ? 'godziny' : 'godzin'); + case 'MM': + return result + (plural$3(number) ? 'miesiące' : 'miesięcy'); + case 'yy': + return result + (plural$3(number) ? 'lata' : 'lat'); + } +} + +hooks.defineLocale('pl', { + months : function (momentToFormat, format) { + if (format === '') { + // Hack: if format empty we know this is used to generate + // RegExp by moment. Give then back both valid forms of months + // in RegExp ready format. + return '(' + monthsSubjective[momentToFormat.month()] + '|' + monthsNominative[momentToFormat.month()] + ')'; + } else if (/D MMMM/.test(format)) { + return monthsSubjective[momentToFormat.month()]; + } else { + return monthsNominative[momentToFormat.month()]; + } + }, + monthsShort : 'sty_lut_mar_kwi_maj_cze_lip_sie_wrz_paź_lis_gru'.split('_'), + weekdays : 'niedziela_poniedziałek_wtorek_środa_czwartek_piątek_sobota'.split('_'), + weekdaysShort : 'ndz_pon_wt_śr_czw_pt_sob'.split('_'), + weekdaysMin : 'Nd_Pn_Wt_Śr_Cz_Pt_So'.split('_'), + longDateFormat : { + LT : 'HH:mm', + LTS : 'HH:mm:ss', + L : 'DD.MM.YYYY', + LL : 'D MMMM YYYY', + LLL : 'D MMMM YYYY HH:mm', + LLLL : 'dddd, D MMMM YYYY HH:mm' + }, + calendar : { + sameDay: '[Dziś o] LT', + nextDay: '[Jutro o] LT', + nextWeek: '[W] dddd [o] LT', + lastDay: '[Wczoraj o] LT', + lastWeek: function () { + switch (this.day()) { + case 0: + return '[W zeszłą niedzielę o] LT'; + case 3: + return '[W zeszłą środę o] LT'; + case 6: + return '[W zeszłą sobotę o] LT'; + default: + return '[W zeszły] dddd [o] LT'; + } + }, + sameElse: 'L' + }, + relativeTime : { + future : 'za %s', + past : '%s temu', + s : 'kilka sekund', + m : translate$7, + mm : translate$7, + h : translate$7, + hh : translate$7, + d : '1 dzień', + dd : '%d dni', + M : 'miesiąc', + MM : translate$7, + y : 'rok', + yy : translate$7 + }, + ordinalParse: /\d{1,2}\./, + ordinal : '%d.', + week : { + dow : 1, // Monday is the first day of the week. + doy : 4 // The week that contains Jan 4th is the first week of the year. + } +}); + +//! moment.js locale configuration +//! locale : Portuguese (Brazil) [pt-br] +//! author : Caio Ribeiro Pereira : https://github.com/caio-ribeiro-pereira + +hooks.defineLocale('pt-br', { + months : 'Janeiro_Fevereiro_Março_Abril_Maio_Junho_Julho_Agosto_Setembro_Outubro_Novembro_Dezembro'.split('_'), + monthsShort : 'Jan_Fev_Mar_Abr_Mai_Jun_Jul_Ago_Set_Out_Nov_Dez'.split('_'), + weekdays : 'Domingo_Segunda-feira_Terça-feira_Quarta-feira_Quinta-feira_Sexta-feira_Sábado'.split('_'), + weekdaysShort : 'Dom_Seg_Ter_Qua_Qui_Sex_Sáb'.split('_'), + weekdaysMin : 'Dom_2ª_3ª_4ª_5ª_6ª_Sáb'.split('_'), + weekdaysParseExact : true, + longDateFormat : { + LT : 'HH:mm', + LTS : 'HH:mm:ss', + L : 'DD/MM/YYYY', + LL : 'D [de] MMMM [de] YYYY', + LLL : 'D [de] MMMM [de] YYYY [às] HH:mm', + LLLL : 'dddd, D [de] MMMM [de] YYYY [às] HH:mm' + }, + calendar : { + sameDay: '[Hoje às] LT', + nextDay: '[Amanhã às] LT', + nextWeek: 'dddd [às] LT', + lastDay: '[Ontem às] LT', + lastWeek: function () { + return (this.day() === 0 || this.day() === 6) ? + '[Último] dddd [às] LT' : // Saturday + Sunday + '[Última] dddd [às] LT'; // Monday - Friday + }, + sameElse: 'L' + }, + relativeTime : { + future : 'em %s', + past : '%s atrás', + s : 'poucos segundos', + m : 'um minuto', + mm : '%d minutos', + h : 'uma hora', + hh : '%d horas', + d : 'um dia', + dd : '%d dias', + M : 'um mês', + MM : '%d meses', + y : 'um ano', + yy : '%d anos' + }, + ordinalParse: /\d{1,2}º/, + ordinal : '%dº' +}); + +//! moment.js locale configuration +//! locale : Portuguese [pt] +//! author : Jefferson : https://github.com/jalex79 + +hooks.defineLocale('pt', { + months : 'Janeiro_Fevereiro_Março_Abril_Maio_Junho_Julho_Agosto_Setembro_Outubro_Novembro_Dezembro'.split('_'), + monthsShort : 'Jan_Fev_Mar_Abr_Mai_Jun_Jul_Ago_Set_Out_Nov_Dez'.split('_'), + weekdays : 'Domingo_Segunda-Feira_Terça-Feira_Quarta-Feira_Quinta-Feira_Sexta-Feira_Sábado'.split('_'), + weekdaysShort : 'Dom_Seg_Ter_Qua_Qui_Sex_Sáb'.split('_'), + weekdaysMin : 'Dom_2ª_3ª_4ª_5ª_6ª_Sáb'.split('_'), + weekdaysParseExact : true, + longDateFormat : { + LT : 'HH:mm', + LTS : 'HH:mm:ss', + L : 'DD/MM/YYYY', + LL : 'D [de] MMMM [de] YYYY', + LLL : 'D [de] MMMM [de] YYYY HH:mm', + LLLL : 'dddd, D [de] MMMM [de] YYYY HH:mm' + }, + calendar : { + sameDay: '[Hoje às] LT', + nextDay: '[Amanhã às] LT', + nextWeek: 'dddd [às] LT', + lastDay: '[Ontem às] LT', + lastWeek: function () { + return (this.day() === 0 || this.day() === 6) ? + '[Último] dddd [às] LT' : // Saturday + Sunday + '[Última] dddd [às] LT'; // Monday - Friday + }, + sameElse: 'L' + }, + relativeTime : { + future : 'em %s', + past : 'há %s', + s : 'segundos', + m : 'um minuto', + mm : '%d minutos', + h : 'uma hora', + hh : '%d horas', + d : 'um dia', + dd : '%d dias', + M : 'um mês', + MM : '%d meses', + y : 'um ano', + yy : '%d anos' + }, + ordinalParse: /\d{1,2}º/, + ordinal : '%dº', + week : { + dow : 1, // Monday is the first day of the week. + doy : 4 // The week that contains Jan 4th is the first week of the year. + } +}); + +//! moment.js locale configuration +//! locale : Romanian [ro] +//! author : Vlad Gurdiga : https://github.com/gurdiga +//! author : Valentin Agachi : https://github.com/avaly + +function relativeTimeWithPlural$2(number, withoutSuffix, key) { + var format = { + 'mm': 'minute', + 'hh': 'ore', + 'dd': 'zile', + 'MM': 'luni', + 'yy': 'ani' + }, + separator = ' '; + if (number % 100 >= 20 || (number >= 100 && number % 100 === 0)) { + separator = ' de '; + } + return number + separator + format[key]; +} + +hooks.defineLocale('ro', { + months : 'ianuarie_februarie_martie_aprilie_mai_iunie_iulie_august_septembrie_octombrie_noiembrie_decembrie'.split('_'), + monthsShort : 'ian._febr._mart._apr._mai_iun._iul._aug._sept._oct._nov._dec.'.split('_'), + monthsParseExact: true, + weekdays : 'duminică_luni_marți_miercuri_joi_vineri_sâmbătă'.split('_'), + weekdaysShort : 'Dum_Lun_Mar_Mie_Joi_Vin_Sâm'.split('_'), + weekdaysMin : 'Du_Lu_Ma_Mi_Jo_Vi_Sâ'.split('_'), + longDateFormat : { + LT : 'H:mm', + LTS : 'H:mm:ss', + L : 'DD.MM.YYYY', + LL : 'D MMMM YYYY', + LLL : 'D MMMM YYYY H:mm', + LLLL : 'dddd, D MMMM YYYY H:mm' + }, + calendar : { + sameDay: '[azi la] LT', + nextDay: '[mâine la] LT', + nextWeek: 'dddd [la] LT', + lastDay: '[ieri la] LT', + lastWeek: '[fosta] dddd [la] LT', + sameElse: 'L' + }, + relativeTime : { + future : 'peste %s', + past : '%s în urmă', + s : 'câteva secunde', + m : 'un minut', + mm : relativeTimeWithPlural$2, + h : 'o oră', + hh : relativeTimeWithPlural$2, + d : 'o zi', + dd : relativeTimeWithPlural$2, + M : 'o lună', + MM : relativeTimeWithPlural$2, + y : 'un an', + yy : relativeTimeWithPlural$2 + }, + week : { + dow : 1, // Monday is the first day of the week. + doy : 7 // The week that contains Jan 1st is the first week of the year. + } +}); + +//! moment.js locale configuration +//! locale : Russian [ru] +//! author : Viktorminator : https://github.com/Viktorminator +//! Author : Menelion Elensúle : https://github.com/Oire +//! author : Коренберг Марк : https://github.com/socketpair + +function plural$4(word, num) { + var forms = word.split('_'); + return num % 10 === 1 && num % 100 !== 11 ? forms[0] : (num % 10 >= 2 && num % 10 <= 4 && (num % 100 < 10 || num % 100 >= 20) ? forms[1] : forms[2]); +} +function relativeTimeWithPlural$3(number, withoutSuffix, key) { + var format = { + 'mm': withoutSuffix ? 'минута_минуты_минут' : 'минуту_минуты_минут', + 'hh': 'час_часа_часов', + 'dd': 'день_дня_дней', + 'MM': 'месяц_месяца_месяцев', + 'yy': 'год_года_лет' + }; + if (key === 'm') { + return withoutSuffix ? 'минута' : 'минуту'; + } + else { + return number + ' ' + plural$4(format[key], +number); + } +} +var monthsParse$2 = [/^янв/i, /^фев/i, /^мар/i, /^апр/i, /^ма[йя]/i, /^июн/i, /^июл/i, /^авг/i, /^сен/i, /^окт/i, /^ноя/i, /^дек/i]; + +// http://new.gramota.ru/spravka/rules/139-prop : § 103 +// Сокращения месяцев: http://new.gramota.ru/spravka/buro/search-answer?s=242637 +// CLDR data: http://www.unicode.org/cldr/charts/28/summary/ru.html#1753 +hooks.defineLocale('ru', { + months : { + format: 'января_февраля_марта_апреля_мая_июня_июля_августа_сентября_октября_ноября_декабря'.split('_'), + standalone: 'январь_февраль_март_апрель_май_июнь_июль_август_сентябрь_октябрь_ноябрь_декабрь'.split('_') + }, + monthsShort : { + // по CLDR именно "июл." и "июн.", но какой смысл менять букву на точку ? + format: 'янв._февр._мар._апр._мая_июня_июля_авг._сент._окт._нояб._дек.'.split('_'), + standalone: 'янв._февр._март_апр._май_июнь_июль_авг._сент._окт._нояб._дек.'.split('_') + }, + weekdays : { + standalone: 'воскресенье_понедельник_вторник_среда_четверг_пятница_суббота'.split('_'), + format: 'воскресенье_понедельник_вторник_среду_четверг_пятницу_субботу'.split('_'), + isFormat: /\[ ?[Вв] ?(?:прошлую|следующую|эту)? ?\] ?dddd/ + }, + weekdaysShort : 'вс_пн_вт_ср_чт_пт_сб'.split('_'), + weekdaysMin : 'вс_пн_вт_ср_чт_пт_сб'.split('_'), + monthsParse : monthsParse$2, + longMonthsParse : monthsParse$2, + shortMonthsParse : monthsParse$2, + + // полные названия с падежами, по три буквы, для некоторых, по 4 буквы, сокращения с точкой и без точки + monthsRegex: /^(январ[ья]|янв\.?|феврал[ья]|февр?\.?|марта?|мар\.?|апрел[ья]|апр\.?|ма[йя]|июн[ья]|июн\.?|июл[ья]|июл\.?|августа?|авг\.?|сентябр[ья]|сент?\.?|октябр[ья]|окт\.?|ноябр[ья]|нояб?\.?|декабр[ья]|дек\.?)/i, + + // копия предыдущего + monthsShortRegex: /^(январ[ья]|янв\.?|феврал[ья]|февр?\.?|марта?|мар\.?|апрел[ья]|апр\.?|ма[йя]|июн[ья]|июн\.?|июл[ья]|июл\.?|августа?|авг\.?|сентябр[ья]|сент?\.?|октябр[ья]|окт\.?|ноябр[ья]|нояб?\.?|декабр[ья]|дек\.?)/i, + + // полные названия с падежами + monthsStrictRegex: /^(январ[яь]|феврал[яь]|марта?|апрел[яь]|ма[яй]|июн[яь]|июл[яь]|августа?|сентябр[яь]|октябр[яь]|ноябр[яь]|декабр[яь])/i, + + // Выражение, которое соотвествует только сокращённым формам + monthsShortStrictRegex: /^(янв\.|февр?\.|мар[т.]|апр\.|ма[яй]|июн[ья.]|июл[ья.]|авг\.|сент?\.|окт\.|нояб?\.|дек\.)/i, + longDateFormat : { + LT : 'HH:mm', + LTS : 'HH:mm:ss', + L : 'DD.MM.YYYY', + LL : 'D MMMM YYYY г.', + LLL : 'D MMMM YYYY г., HH:mm', + LLLL : 'dddd, D MMMM YYYY г., HH:mm' + }, + calendar : { + sameDay: '[Сегодня в] LT', + nextDay: '[Завтра в] LT', + lastDay: '[Вчера в] LT', + nextWeek: function (now) { + if (now.week() !== this.week()) { + switch (this.day()) { + case 0: + return '[В следующее] dddd [в] LT'; + case 1: + case 2: + case 4: + return '[В следующий] dddd [в] LT'; + case 3: + case 5: + case 6: + return '[В следующую] dddd [в] LT'; + } + } else { + if (this.day() === 2) { + return '[Во] dddd [в] LT'; + } else { + return '[В] dddd [в] LT'; + } + } + }, + lastWeek: function (now) { + if (now.week() !== this.week()) { + switch (this.day()) { + case 0: + return '[В прошлое] dddd [в] LT'; + case 1: + case 2: + case 4: + return '[В прошлый] dddd [в] LT'; + case 3: + case 5: + case 6: + return '[В прошлую] dddd [в] LT'; + } + } else { + if (this.day() === 2) { + return '[Во] dddd [в] LT'; + } else { + return '[В] dddd [в] LT'; + } + } + }, + sameElse: 'L' + }, + relativeTime : { + future : 'через %s', + past : '%s назад', + s : 'несколько секунд', + m : relativeTimeWithPlural$3, + mm : relativeTimeWithPlural$3, + h : 'час', + hh : relativeTimeWithPlural$3, + d : 'день', + dd : relativeTimeWithPlural$3, + M : 'месяц', + MM : relativeTimeWithPlural$3, + y : 'год', + yy : relativeTimeWithPlural$3 + }, + meridiemParse: /ночи|утра|дня|вечера/i, + isPM : function (input) { + return /^(дня|вечера)$/.test(input); + }, + meridiem : function (hour, minute, isLower) { + if (hour < 4) { + return 'ночи'; + } else if (hour < 12) { + return 'утра'; + } else if (hour < 17) { + return 'дня'; + } else { + return 'вечера'; + } + }, + ordinalParse: /\d{1,2}-(й|го|я)/, + ordinal: function (number, period) { + switch (period) { + case 'M': + case 'd': + case 'DDD': + return number + '-й'; + case 'D': + return number + '-го'; + case 'w': + case 'W': + return number + '-я'; + default: + return number; + } + }, + week : { + dow : 1, // Monday is the first day of the week. + doy : 7 // The week that contains Jan 1st is the first week of the year. + } +}); + +//! moment.js locale configuration +//! locale : Northern Sami [se] +//! authors : Bård Rolstad Henriksen : https://github.com/karamell + + +hooks.defineLocale('se', { + months : 'ođđajagemánnu_guovvamánnu_njukčamánnu_cuoŋománnu_miessemánnu_geassemánnu_suoidnemánnu_borgemánnu_čakčamánnu_golggotmánnu_skábmamánnu_juovlamánnu'.split('_'), + monthsShort : 'ođđj_guov_njuk_cuo_mies_geas_suoi_borg_čakč_golg_skáb_juov'.split('_'), + weekdays : 'sotnabeaivi_vuossárga_maŋŋebárga_gaskavahkku_duorastat_bearjadat_lávvardat'.split('_'), + weekdaysShort : 'sotn_vuos_maŋ_gask_duor_bear_láv'.split('_'), + weekdaysMin : 's_v_m_g_d_b_L'.split('_'), + longDateFormat : { + LT : 'HH:mm', + LTS : 'HH:mm:ss', + L : 'DD.MM.YYYY', + LL : 'MMMM D. [b.] YYYY', + LLL : 'MMMM D. [b.] YYYY [ti.] HH:mm', + LLLL : 'dddd, MMMM D. [b.] YYYY [ti.] HH:mm' + }, + calendar : { + sameDay: '[otne ti] LT', + nextDay: '[ihttin ti] LT', + nextWeek: 'dddd [ti] LT', + lastDay: '[ikte ti] LT', + lastWeek: '[ovddit] dddd [ti] LT', + sameElse: 'L' + }, + relativeTime : { + future : '%s geažes', + past : 'maŋit %s', + s : 'moadde sekunddat', + m : 'okta minuhta', + mm : '%d minuhtat', + h : 'okta diimmu', + hh : '%d diimmut', + d : 'okta beaivi', + dd : '%d beaivvit', + M : 'okta mánnu', + MM : '%d mánut', + y : 'okta jahki', + yy : '%d jagit' + }, + ordinalParse: /\d{1,2}\./, + ordinal : '%d.', + week : { + dow : 1, // Monday is the first day of the week. + doy : 4 // The week that contains Jan 4th is the first week of the year. + } +}); + +//! moment.js locale configuration +//! locale : Sinhalese [si] +//! author : Sampath Sitinamaluwa : https://github.com/sampathsris + +/*jshint -W100*/ +hooks.defineLocale('si', { + months : 'ජනවාරි_පෙබරවාරි_මාර්තු_අප්‍රේල්_මැයි_ජූනි_ජූලි_අගෝස්තු_සැප්තැම්බර්_ඔක්තෝබර්_නොවැම්බර්_දෙසැම්බර්'.split('_'), + monthsShort : 'ජන_පෙබ_මාර්_අප්_මැයි_ජූනි_ජූලි_අගෝ_සැප්_ඔක්_නොවැ_දෙසැ'.split('_'), + weekdays : 'ඉරිදා_සඳුදා_අඟහරුවාදා_බදාදා_බ්‍රහස්පතින්දා_සිකුරාදා_සෙනසුරාදා'.split('_'), + weekdaysShort : 'ඉරි_සඳු_අඟ_බදා_බ්‍රහ_සිකු_සෙන'.split('_'), + weekdaysMin : 'ඉ_ස_අ_බ_බ්‍ර_සි_සෙ'.split('_'), + weekdaysParseExact : true, + longDateFormat : { + LT : 'a h:mm', + LTS : 'a h:mm:ss', + L : 'YYYY/MM/DD', + LL : 'YYYY MMMM D', + LLL : 'YYYY MMMM D, a h:mm', + LLLL : 'YYYY MMMM D [වැනි] dddd, a h:mm:ss' + }, + calendar : { + sameDay : '[අද] LT[ට]', + nextDay : '[හෙට] LT[ට]', + nextWeek : 'dddd LT[ට]', + lastDay : '[ඊයේ] LT[ට]', + lastWeek : '[පසුගිය] dddd LT[ට]', + sameElse : 'L' + }, + relativeTime : { + future : '%sකින්', + past : '%sකට පෙර', + s : 'තත්පර කිහිපය', + m : 'මිනිත්තුව', + mm : 'මිනිත්තු %d', + h : 'පැය', + hh : 'පැය %d', + d : 'දිනය', + dd : 'දින %d', + M : 'මාසය', + MM : 'මාස %d', + y : 'වසර', + yy : 'වසර %d' + }, + ordinalParse: /\d{1,2} වැනි/, + ordinal : function (number) { + return number + ' වැනි'; + }, + meridiemParse : /පෙර වරු|පස් වරු|පෙ.ව|ප.ව./, + isPM : function (input) { + return input === 'ප.ව.' || input === 'පස් වරු'; + }, + meridiem : function (hours, minutes, isLower) { + if (hours > 11) { + return isLower ? 'ප.ව.' : 'පස් වරු'; + } else { + return isLower ? 'පෙ.ව.' : 'පෙර වරු'; + } + } +}); + +//! moment.js locale configuration +//! locale : Slovak [sk] +//! author : Martin Minka : https://github.com/k2s +//! based on work of petrbela : https://github.com/petrbela + +var months$6 = 'január_február_marec_apríl_máj_jún_júl_august_september_október_november_december'.split('_'); +var monthsShort$4 = 'jan_feb_mar_apr_máj_jún_júl_aug_sep_okt_nov_dec'.split('_'); +function plural$5(n) { + return (n > 1) && (n < 5); +} +function translate$8(number, withoutSuffix, key, isFuture) { + var result = number + ' '; + switch (key) { + case 's': // a few seconds / in a few seconds / a few seconds ago + return (withoutSuffix || isFuture) ? 'pár sekúnd' : 'pár sekundami'; + case 'm': // a minute / in a minute / a minute ago + return withoutSuffix ? 'minúta' : (isFuture ? 'minútu' : 'minútou'); + case 'mm': // 9 minutes / in 9 minutes / 9 minutes ago + if (withoutSuffix || isFuture) { + return result + (plural$5(number) ? 'minúty' : 'minút'); + } else { + return result + 'minútami'; + } + break; + case 'h': // an hour / in an hour / an hour ago + return withoutSuffix ? 'hodina' : (isFuture ? 'hodinu' : 'hodinou'); + case 'hh': // 9 hours / in 9 hours / 9 hours ago + if (withoutSuffix || isFuture) { + return result + (plural$5(number) ? 'hodiny' : 'hodín'); + } else { + return result + 'hodinami'; + } + break; + case 'd': // a day / in a day / a day ago + return (withoutSuffix || isFuture) ? 'deň' : 'dňom'; + case 'dd': // 9 days / in 9 days / 9 days ago + if (withoutSuffix || isFuture) { + return result + (plural$5(number) ? 'dni' : 'dní'); + } else { + return result + 'dňami'; + } + break; + case 'M': // a month / in a month / a month ago + return (withoutSuffix || isFuture) ? 'mesiac' : 'mesiacom'; + case 'MM': // 9 months / in 9 months / 9 months ago + if (withoutSuffix || isFuture) { + return result + (plural$5(number) ? 'mesiace' : 'mesiacov'); + } else { + return result + 'mesiacmi'; + } + break; + case 'y': // a year / in a year / a year ago + return (withoutSuffix || isFuture) ? 'rok' : 'rokom'; + case 'yy': // 9 years / in 9 years / 9 years ago + if (withoutSuffix || isFuture) { + return result + (plural$5(number) ? 'roky' : 'rokov'); + } else { + return result + 'rokmi'; + } + break; + } +} + +hooks.defineLocale('sk', { + months : months$6, + monthsShort : monthsShort$4, + weekdays : 'nedeľa_pondelok_utorok_streda_štvrtok_piatok_sobota'.split('_'), + weekdaysShort : 'ne_po_ut_st_št_pi_so'.split('_'), + weekdaysMin : 'ne_po_ut_st_št_pi_so'.split('_'), + longDateFormat : { + LT: 'H:mm', + LTS : 'H:mm:ss', + L : 'DD.MM.YYYY', + LL : 'D. MMMM YYYY', + LLL : 'D. MMMM YYYY H:mm', + LLLL : 'dddd D. MMMM YYYY H:mm' + }, + calendar : { + sameDay: '[dnes o] LT', + nextDay: '[zajtra o] LT', + nextWeek: function () { + switch (this.day()) { + case 0: + return '[v nedeľu o] LT'; + case 1: + case 2: + return '[v] dddd [o] LT'; + case 3: + return '[v stredu o] LT'; + case 4: + return '[vo štvrtok o] LT'; + case 5: + return '[v piatok o] LT'; + case 6: + return '[v sobotu o] LT'; + } + }, + lastDay: '[včera o] LT', + lastWeek: function () { + switch (this.day()) { + case 0: + return '[minulú nedeľu o] LT'; + case 1: + case 2: + return '[minulý] dddd [o] LT'; + case 3: + return '[minulú stredu o] LT'; + case 4: + case 5: + return '[minulý] dddd [o] LT'; + case 6: + return '[minulú sobotu o] LT'; + } + }, + sameElse: 'L' + }, + relativeTime : { + future : 'za %s', + past : 'pred %s', + s : translate$8, + m : translate$8, + mm : translate$8, + h : translate$8, + hh : translate$8, + d : translate$8, + dd : translate$8, + M : translate$8, + MM : translate$8, + y : translate$8, + yy : translate$8 + }, + ordinalParse: /\d{1,2}\./, + ordinal : '%d.', + week : { + dow : 1, // Monday is the first day of the week. + doy : 4 // The week that contains Jan 4th is the first week of the year. + } +}); + +//! moment.js locale configuration +//! locale : Slovenian [sl] +//! author : Robert Sedovšek : https://github.com/sedovsek + +function processRelativeTime$4(number, withoutSuffix, key, isFuture) { + var result = number + ' '; + switch (key) { + case 's': + return withoutSuffix || isFuture ? 'nekaj sekund' : 'nekaj sekundami'; + case 'm': + return withoutSuffix ? 'ena minuta' : 'eno minuto'; + case 'mm': + if (number === 1) { + result += withoutSuffix ? 'minuta' : 'minuto'; + } else if (number === 2) { + result += withoutSuffix || isFuture ? 'minuti' : 'minutama'; + } else if (number < 5) { + result += withoutSuffix || isFuture ? 'minute' : 'minutami'; + } else { + result += withoutSuffix || isFuture ? 'minut' : 'minutami'; + } + return result; + case 'h': + return withoutSuffix ? 'ena ura' : 'eno uro'; + case 'hh': + if (number === 1) { + result += withoutSuffix ? 'ura' : 'uro'; + } else if (number === 2) { + result += withoutSuffix || isFuture ? 'uri' : 'urama'; + } else if (number < 5) { + result += withoutSuffix || isFuture ? 'ure' : 'urami'; + } else { + result += withoutSuffix || isFuture ? 'ur' : 'urami'; + } + return result; + case 'd': + return withoutSuffix || isFuture ? 'en dan' : 'enim dnem'; + case 'dd': + if (number === 1) { + result += withoutSuffix || isFuture ? 'dan' : 'dnem'; + } else if (number === 2) { + result += withoutSuffix || isFuture ? 'dni' : 'dnevoma'; + } else { + result += withoutSuffix || isFuture ? 'dni' : 'dnevi'; + } + return result; + case 'M': + return withoutSuffix || isFuture ? 'en mesec' : 'enim mesecem'; + case 'MM': + if (number === 1) { + result += withoutSuffix || isFuture ? 'mesec' : 'mesecem'; + } else if (number === 2) { + result += withoutSuffix || isFuture ? 'meseca' : 'mesecema'; + } else if (number < 5) { + result += withoutSuffix || isFuture ? 'mesece' : 'meseci'; + } else { + result += withoutSuffix || isFuture ? 'mesecev' : 'meseci'; + } + return result; + case 'y': + return withoutSuffix || isFuture ? 'eno leto' : 'enim letom'; + case 'yy': + if (number === 1) { + result += withoutSuffix || isFuture ? 'leto' : 'letom'; + } else if (number === 2) { + result += withoutSuffix || isFuture ? 'leti' : 'letoma'; + } else if (number < 5) { + result += withoutSuffix || isFuture ? 'leta' : 'leti'; + } else { + result += withoutSuffix || isFuture ? 'let' : 'leti'; + } + return result; + } +} + +hooks.defineLocale('sl', { + months : 'januar_februar_marec_april_maj_junij_julij_avgust_september_oktober_november_december'.split('_'), + monthsShort : 'jan._feb._mar._apr._maj._jun._jul._avg._sep._okt._nov._dec.'.split('_'), + monthsParseExact: true, + weekdays : 'nedelja_ponedeljek_torek_sreda_četrtek_petek_sobota'.split('_'), + weekdaysShort : 'ned._pon._tor._sre._čet._pet._sob.'.split('_'), + weekdaysMin : 'ne_po_to_sr_če_pe_so'.split('_'), + weekdaysParseExact : true, + longDateFormat : { + LT : 'H:mm', + LTS : 'H:mm:ss', + L : 'DD.MM.YYYY', + LL : 'D. MMMM YYYY', + LLL : 'D. MMMM YYYY H:mm', + LLLL : 'dddd, D. MMMM YYYY H:mm' + }, + calendar : { + sameDay : '[danes ob] LT', + nextDay : '[jutri ob] LT', + + nextWeek : function () { + switch (this.day()) { + case 0: + return '[v] [nedeljo] [ob] LT'; + case 3: + return '[v] [sredo] [ob] LT'; + case 6: + return '[v] [soboto] [ob] LT'; + case 1: + case 2: + case 4: + case 5: + return '[v] dddd [ob] LT'; + } + }, + lastDay : '[včeraj ob] LT', + lastWeek : function () { + switch (this.day()) { + case 0: + return '[prejšnjo] [nedeljo] [ob] LT'; + case 3: + return '[prejšnjo] [sredo] [ob] LT'; + case 6: + return '[prejšnjo] [soboto] [ob] LT'; + case 1: + case 2: + case 4: + case 5: + return '[prejšnji] dddd [ob] LT'; + } + }, + sameElse : 'L' + }, + relativeTime : { + future : 'čez %s', + past : 'pred %s', + s : processRelativeTime$4, + m : processRelativeTime$4, + mm : processRelativeTime$4, + h : processRelativeTime$4, + hh : processRelativeTime$4, + d : processRelativeTime$4, + dd : processRelativeTime$4, + M : processRelativeTime$4, + MM : processRelativeTime$4, + y : processRelativeTime$4, + yy : processRelativeTime$4 + }, + ordinalParse: /\d{1,2}\./, + ordinal : '%d.', + week : { + dow : 1, // Monday is the first day of the week. + doy : 7 // The week that contains Jan 1st is the first week of the year. + } +}); + +//! moment.js locale configuration +//! locale : Albanian [sq] +//! author : Flakërim Ismani : https://github.com/flakerimi +//! author : Menelion Elensúle : https://github.com/Oire +//! author : Oerd Cukalla : https://github.com/oerd + +hooks.defineLocale('sq', { + months : 'Janar_Shkurt_Mars_Prill_Maj_Qershor_Korrik_Gusht_Shtator_Tetor_Nëntor_Dhjetor'.split('_'), + monthsShort : 'Jan_Shk_Mar_Pri_Maj_Qer_Kor_Gus_Sht_Tet_Nën_Dhj'.split('_'), + weekdays : 'E Diel_E Hënë_E Martë_E Mërkurë_E Enjte_E Premte_E Shtunë'.split('_'), + weekdaysShort : 'Die_Hën_Mar_Mër_Enj_Pre_Sht'.split('_'), + weekdaysMin : 'D_H_Ma_Më_E_P_Sh'.split('_'), + weekdaysParseExact : true, + meridiemParse: /PD|MD/, + isPM: function (input) { + return input.charAt(0) === 'M'; + }, + meridiem : function (hours, minutes, isLower) { + return hours < 12 ? 'PD' : 'MD'; + }, + longDateFormat : { + LT : 'HH:mm', + LTS : 'HH:mm:ss', + L : 'DD/MM/YYYY', + LL : 'D MMMM YYYY', + LLL : 'D MMMM YYYY HH:mm', + LLLL : 'dddd, D MMMM YYYY HH:mm' + }, + calendar : { + sameDay : '[Sot në] LT', + nextDay : '[Nesër në] LT', + nextWeek : 'dddd [në] LT', + lastDay : '[Dje në] LT', + lastWeek : 'dddd [e kaluar në] LT', + sameElse : 'L' + }, + relativeTime : { + future : 'në %s', + past : '%s më parë', + s : 'disa sekonda', + m : 'një minutë', + mm : '%d minuta', + h : 'një orë', + hh : '%d orë', + d : 'një ditë', + dd : '%d ditë', + M : 'një muaj', + MM : '%d muaj', + y : 'një vit', + yy : '%d vite' + }, + ordinalParse: /\d{1,2}\./, + ordinal : '%d.', + week : { + dow : 1, // Monday is the first day of the week. + doy : 4 // The week that contains Jan 4th is the first week of the year. + } +}); + +//! moment.js locale configuration +//! locale : Serbian Cyrillic [sr-cyrl] +//! author : Milan Janačković : https://github.com/milan-j + +var translator$1 = { + words: { //Different grammatical cases + m: ['један минут', 'једне минуте'], + mm: ['минут', 'минуте', 'минута'], + h: ['један сат', 'једног сата'], + hh: ['сат', 'сата', 'сати'], + dd: ['дан', 'дана', 'дана'], + MM: ['месец', 'месеца', 'месеци'], + yy: ['година', 'године', 'година'] + }, + correctGrammaticalCase: function (number, wordKey) { + return number === 1 ? wordKey[0] : (number >= 2 && number <= 4 ? wordKey[1] : wordKey[2]); + }, + translate: function (number, withoutSuffix, key) { + var wordKey = translator$1.words[key]; + if (key.length === 1) { + return withoutSuffix ? wordKey[0] : wordKey[1]; + } else { + return number + ' ' + translator$1.correctGrammaticalCase(number, wordKey); + } + } +}; + +hooks.defineLocale('sr-cyrl', { + months: 'јануар_фебруар_март_април_мај_јун_јул_август_септембар_октобар_новембар_децембар'.split('_'), + monthsShort: 'јан._феб._мар._апр._мај_јун_јул_авг._сеп._окт._нов._дец.'.split('_'), + monthsParseExact: true, + weekdays: 'недеља_понедељак_уторак_среда_четвртак_петак_субота'.split('_'), + weekdaysShort: 'нед._пон._уто._сре._чет._пет._суб.'.split('_'), + weekdaysMin: 'не_по_ут_ср_че_пе_су'.split('_'), + weekdaysParseExact : true, + longDateFormat: { + LT: 'H:mm', + LTS : 'H:mm:ss', + L: 'DD.MM.YYYY', + LL: 'D. MMMM YYYY', + LLL: 'D. MMMM YYYY H:mm', + LLLL: 'dddd, D. MMMM YYYY H:mm' + }, + calendar: { + sameDay: '[данас у] LT', + nextDay: '[сутра у] LT', + nextWeek: function () { + switch (this.day()) { + case 0: + return '[у] [недељу] [у] LT'; + case 3: + return '[у] [среду] [у] LT'; + case 6: + return '[у] [суботу] [у] LT'; + case 1: + case 2: + case 4: + case 5: + return '[у] dddd [у] LT'; + } + }, + lastDay : '[јуче у] LT', + lastWeek : function () { + var lastWeekDays = [ + '[прошле] [недеље] [у] LT', + '[прошлог] [понедељка] [у] LT', + '[прошлог] [уторка] [у] LT', + '[прошле] [среде] [у] LT', + '[прошлог] [четвртка] [у] LT', + '[прошлог] [петка] [у] LT', + '[прошле] [суботе] [у] LT' + ]; + return lastWeekDays[this.day()]; + }, + sameElse : 'L' + }, + relativeTime : { + future : 'за %s', + past : 'пре %s', + s : 'неколико секунди', + m : translator$1.translate, + mm : translator$1.translate, + h : translator$1.translate, + hh : translator$1.translate, + d : 'дан', + dd : translator$1.translate, + M : 'месец', + MM : translator$1.translate, + y : 'годину', + yy : translator$1.translate + }, + ordinalParse: /\d{1,2}\./, + ordinal : '%d.', + week : { + dow : 1, // Monday is the first day of the week. + doy : 7 // The week that contains Jan 1st is the first week of the year. + } +}); + +//! moment.js locale configuration +//! locale : Serbian [sr] +//! author : Milan Janačković : https://github.com/milan-j + +var translator$2 = { + words: { //Different grammatical cases + m: ['jedan minut', 'jedne minute'], + mm: ['minut', 'minute', 'minuta'], + h: ['jedan sat', 'jednog sata'], + hh: ['sat', 'sata', 'sati'], + dd: ['dan', 'dana', 'dana'], + MM: ['mesec', 'meseca', 'meseci'], + yy: ['godina', 'godine', 'godina'] + }, + correctGrammaticalCase: function (number, wordKey) { + return number === 1 ? wordKey[0] : (number >= 2 && number <= 4 ? wordKey[1] : wordKey[2]); + }, + translate: function (number, withoutSuffix, key) { + var wordKey = translator$2.words[key]; + if (key.length === 1) { + return withoutSuffix ? wordKey[0] : wordKey[1]; + } else { + return number + ' ' + translator$2.correctGrammaticalCase(number, wordKey); + } + } +}; + +hooks.defineLocale('sr', { + months: 'januar_februar_mart_april_maj_jun_jul_avgust_septembar_oktobar_novembar_decembar'.split('_'), + monthsShort: 'jan._feb._mar._apr._maj_jun_jul_avg._sep._okt._nov._dec.'.split('_'), + monthsParseExact: true, + weekdays: 'nedelja_ponedeljak_utorak_sreda_četvrtak_petak_subota'.split('_'), + weekdaysShort: 'ned._pon._uto._sre._čet._pet._sub.'.split('_'), + weekdaysMin: 'ne_po_ut_sr_če_pe_su'.split('_'), + weekdaysParseExact : true, + longDateFormat: { + LT: 'H:mm', + LTS : 'H:mm:ss', + L: 'DD.MM.YYYY', + LL: 'D. MMMM YYYY', + LLL: 'D. MMMM YYYY H:mm', + LLLL: 'dddd, D. MMMM YYYY H:mm' + }, + calendar: { + sameDay: '[danas u] LT', + nextDay: '[sutra u] LT', + nextWeek: function () { + switch (this.day()) { + case 0: + return '[u] [nedelju] [u] LT'; + case 3: + return '[u] [sredu] [u] LT'; + case 6: + return '[u] [subotu] [u] LT'; + case 1: + case 2: + case 4: + case 5: + return '[u] dddd [u] LT'; + } + }, + lastDay : '[juče u] LT', + lastWeek : function () { + var lastWeekDays = [ + '[prošle] [nedelje] [u] LT', + '[prošlog] [ponedeljka] [u] LT', + '[prošlog] [utorka] [u] LT', + '[prošle] [srede] [u] LT', + '[prošlog] [četvrtka] [u] LT', + '[prošlog] [petka] [u] LT', + '[prošle] [subote] [u] LT' + ]; + return lastWeekDays[this.day()]; + }, + sameElse : 'L' + }, + relativeTime : { + future : 'za %s', + past : 'pre %s', + s : 'nekoliko sekundi', + m : translator$2.translate, + mm : translator$2.translate, + h : translator$2.translate, + hh : translator$2.translate, + d : 'dan', + dd : translator$2.translate, + M : 'mesec', + MM : translator$2.translate, + y : 'godinu', + yy : translator$2.translate + }, + ordinalParse: /\d{1,2}\./, + ordinal : '%d.', + week : { + dow : 1, // Monday is the first day of the week. + doy : 7 // The week that contains Jan 1st is the first week of the year. + } +}); + +//! moment.js locale configuration +//! locale : siSwati [ss] +//! author : Nicolai Davies : https://github.com/nicolaidavies + + +hooks.defineLocale('ss', { + months : "Bhimbidvwane_Indlovana_Indlov'lenkhulu_Mabasa_Inkhwekhweti_Inhlaba_Kholwane_Ingci_Inyoni_Imphala_Lweti_Ingongoni".split('_'), + monthsShort : 'Bhi_Ina_Inu_Mab_Ink_Inh_Kho_Igc_Iny_Imp_Lwe_Igo'.split('_'), + weekdays : 'Lisontfo_Umsombuluko_Lesibili_Lesitsatfu_Lesine_Lesihlanu_Umgcibelo'.split('_'), + weekdaysShort : 'Lis_Umb_Lsb_Les_Lsi_Lsh_Umg'.split('_'), + weekdaysMin : 'Li_Us_Lb_Lt_Ls_Lh_Ug'.split('_'), + weekdaysParseExact : true, + longDateFormat : { + LT : 'h:mm A', + LTS : 'h:mm:ss A', + L : 'DD/MM/YYYY', + LL : 'D MMMM YYYY', + LLL : 'D MMMM YYYY h:mm A', + LLLL : 'dddd, D MMMM YYYY h:mm A' + }, + calendar : { + sameDay : '[Namuhla nga] LT', + nextDay : '[Kusasa nga] LT', + nextWeek : 'dddd [nga] LT', + lastDay : '[Itolo nga] LT', + lastWeek : 'dddd [leliphelile] [nga] LT', + sameElse : 'L' + }, + relativeTime : { + future : 'nga %s', + past : 'wenteka nga %s', + s : 'emizuzwana lomcane', + m : 'umzuzu', + mm : '%d emizuzu', + h : 'lihora', + hh : '%d emahora', + d : 'lilanga', + dd : '%d emalanga', + M : 'inyanga', + MM : '%d tinyanga', + y : 'umnyaka', + yy : '%d iminyaka' + }, + meridiemParse: /ekuseni|emini|entsambama|ebusuku/, + meridiem : function (hours, minutes, isLower) { + if (hours < 11) { + return 'ekuseni'; + } else if (hours < 15) { + return 'emini'; + } else if (hours < 19) { + return 'entsambama'; + } else { + return 'ebusuku'; + } + }, + meridiemHour : function (hour, meridiem) { + if (hour === 12) { + hour = 0; + } + if (meridiem === 'ekuseni') { + return hour; + } else if (meridiem === 'emini') { + return hour >= 11 ? hour : hour + 12; + } else if (meridiem === 'entsambama' || meridiem === 'ebusuku') { + if (hour === 0) { + return 0; + } + return hour + 12; + } + }, + ordinalParse: /\d{1,2}/, + ordinal : '%d', + week : { + dow : 1, // Monday is the first day of the week. + doy : 4 // The week that contains Jan 4th is the first week of the year. + } +}); + +//! moment.js locale configuration +//! locale : Swedish [sv] +//! author : Jens Alm : https://github.com/ulmus + +hooks.defineLocale('sv', { + months : 'januari_februari_mars_april_maj_juni_juli_augusti_september_oktober_november_december'.split('_'), + monthsShort : 'jan_feb_mar_apr_maj_jun_jul_aug_sep_okt_nov_dec'.split('_'), + weekdays : 'söndag_måndag_tisdag_onsdag_torsdag_fredag_lördag'.split('_'), + weekdaysShort : 'sön_mån_tis_ons_tor_fre_lör'.split('_'), + weekdaysMin : 'sö_må_ti_on_to_fr_lö'.split('_'), + longDateFormat : { + LT : 'HH:mm', + LTS : 'HH:mm:ss', + L : 'YYYY-MM-DD', + LL : 'D MMMM YYYY', + LLL : 'D MMMM YYYY [kl.] HH:mm', + LLLL : 'dddd D MMMM YYYY [kl.] HH:mm', + lll : 'D MMM YYYY HH:mm', + llll : 'ddd D MMM YYYY HH:mm' + }, + calendar : { + sameDay: '[Idag] LT', + nextDay: '[Imorgon] LT', + lastDay: '[Igår] LT', + nextWeek: '[På] dddd LT', + lastWeek: '[I] dddd[s] LT', + sameElse: 'L' + }, + relativeTime : { + future : 'om %s', + past : 'för %s sedan', + s : 'några sekunder', + m : 'en minut', + mm : '%d minuter', + h : 'en timme', + hh : '%d timmar', + d : 'en dag', + dd : '%d dagar', + M : 'en månad', + MM : '%d månader', + y : 'ett år', + yy : '%d år' + }, + ordinalParse: /\d{1,2}(e|a)/, + ordinal : function (number) { + var b = number % 10, + output = (~~(number % 100 / 10) === 1) ? 'e' : + (b === 1) ? 'a' : + (b === 2) ? 'a' : + (b === 3) ? 'e' : 'e'; + return number + output; + }, + week : { + dow : 1, // Monday is the first day of the week. + doy : 4 // The week that contains Jan 4th is the first week of the year. + } +}); + +//! moment.js locale configuration +//! locale : Swahili [sw] +//! author : Fahad Kassim : https://github.com/fadsel + +hooks.defineLocale('sw', { + months : 'Januari_Februari_Machi_Aprili_Mei_Juni_Julai_Agosti_Septemba_Oktoba_Novemba_Desemba'.split('_'), + monthsShort : 'Jan_Feb_Mac_Apr_Mei_Jun_Jul_Ago_Sep_Okt_Nov_Des'.split('_'), + weekdays : 'Jumapili_Jumatatu_Jumanne_Jumatano_Alhamisi_Ijumaa_Jumamosi'.split('_'), + weekdaysShort : 'Jpl_Jtat_Jnne_Jtan_Alh_Ijm_Jmos'.split('_'), + weekdaysMin : 'J2_J3_J4_J5_Al_Ij_J1'.split('_'), + weekdaysParseExact : true, + longDateFormat : { + LT : 'HH:mm', + LTS : 'HH:mm:ss', + L : 'DD.MM.YYYY', + LL : 'D MMMM YYYY', + LLL : 'D MMMM YYYY HH:mm', + LLLL : 'dddd, D MMMM YYYY HH:mm' + }, + calendar : { + sameDay : '[leo saa] LT', + nextDay : '[kesho saa] LT', + nextWeek : '[wiki ijayo] dddd [saat] LT', + lastDay : '[jana] LT', + lastWeek : '[wiki iliyopita] dddd [saat] LT', + sameElse : 'L' + }, + relativeTime : { + future : '%s baadaye', + past : 'tokea %s', + s : 'hivi punde', + m : 'dakika moja', + mm : 'dakika %d', + h : 'saa limoja', + hh : 'masaa %d', + d : 'siku moja', + dd : 'masiku %d', + M : 'mwezi mmoja', + MM : 'miezi %d', + y : 'mwaka mmoja', + yy : 'miaka %d' + }, + week : { + dow : 1, // Monday is the first day of the week. + doy : 7 // The week that contains Jan 1st is the first week of the year. + } +}); + +//! moment.js locale configuration +//! locale : Tamil [ta] +//! author : Arjunkumar Krishnamoorthy : https://github.com/tk120404 + +var symbolMap$11 = { + '1': '௧', + '2': '௨', + '3': '௩', + '4': '௪', + '5': '௫', + '6': '௬', + '7': '௭', + '8': '௮', + '9': '௯', + '0': '௦' +}; +var numberMap$10 = { + '௧': '1', + '௨': '2', + '௩': '3', + '௪': '4', + '௫': '5', + '௬': '6', + '௭': '7', + '௮': '8', + '௯': '9', + '௦': '0' +}; + +hooks.defineLocale('ta', { + months : 'ஜனவரி_பிப்ரவரி_மார்ச்_ஏப்ரல்_மே_ஜூன்_ஜூலை_ஆகஸ்ட்_செப்டெம்பர்_அக்டோபர்_நவம்பர்_டிசம்பர்'.split('_'), + monthsShort : 'ஜனவரி_பிப்ரவரி_மார்ச்_ஏப்ரல்_மே_ஜூன்_ஜூலை_ஆகஸ்ட்_செப்டெம்பர்_அக்டோபர்_நவம்பர்_டிசம்பர்'.split('_'), + weekdays : 'ஞாயிற்றுக்கிழமை_திங்கட்கிழமை_செவ்வாய்கிழமை_புதன்கிழமை_வியாழக்கிழமை_வெள்ளிக்கிழமை_சனிக்கிழமை'.split('_'), + weekdaysShort : 'ஞாயிறு_திங்கள்_செவ்வாய்_புதன்_வியாழன்_வெள்ளி_சனி'.split('_'), + weekdaysMin : 'ஞா_தி_செ_பு_வி_வெ_ச'.split('_'), + longDateFormat : { + LT : 'HH:mm', + LTS : 'HH:mm:ss', + L : 'DD/MM/YYYY', + LL : 'D MMMM YYYY', + LLL : 'D MMMM YYYY, HH:mm', + LLLL : 'dddd, D MMMM YYYY, HH:mm' + }, + calendar : { + sameDay : '[இன்று] LT', + nextDay : '[நாளை] LT', + nextWeek : 'dddd, LT', + lastDay : '[நேற்று] LT', + lastWeek : '[கடந்த வாரம்] dddd, LT', + sameElse : 'L' + }, + relativeTime : { + future : '%s இல்', + past : '%s முன்', + s : 'ஒரு சில விநாடிகள்', + m : 'ஒரு நிமிடம்', + mm : '%d நிமிடங்கள்', + h : 'ஒரு மணி நேரம்', + hh : '%d மணி நேரம்', + d : 'ஒரு நாள்', + dd : '%d நாட்கள்', + M : 'ஒரு மாதம்', + MM : '%d மாதங்கள்', + y : 'ஒரு வருடம்', + yy : '%d ஆண்டுகள்' + }, + ordinalParse: /\d{1,2}வது/, + ordinal : function (number) { + return number + 'வது'; + }, + preparse: function (string) { + return string.replace(/[௧௨௩௪௫௬௭௮௯௦]/g, function (match) { + return numberMap$10[match]; + }); + }, + postformat: function (string) { + return string.replace(/\d/g, function (match) { + return symbolMap$11[match]; + }); + }, + // refer http://ta.wikipedia.org/s/1er1 + meridiemParse: /யாமம்|வைகறை|காலை|நண்பகல்|எற்பாடு|மாலை/, + meridiem : function (hour, minute, isLower) { + if (hour < 2) { + return ' யாமம்'; + } else if (hour < 6) { + return ' வைகறை'; // வைகறை + } else if (hour < 10) { + return ' காலை'; // காலை + } else if (hour < 14) { + return ' நண்பகல்'; // நண்பகல் + } else if (hour < 18) { + return ' எற்பாடு'; // எற்பாடு + } else if (hour < 22) { + return ' மாலை'; // மாலை + } else { + return ' யாமம்'; + } + }, + meridiemHour : function (hour, meridiem) { + if (hour === 12) { + hour = 0; + } + if (meridiem === 'யாமம்') { + return hour < 2 ? hour : hour + 12; + } else if (meridiem === 'வைகறை' || meridiem === 'காலை') { + return hour; + } else if (meridiem === 'நண்பகல்') { + return hour >= 10 ? hour : hour + 12; + } else { + return hour + 12; + } + }, + week : { + dow : 0, // Sunday is the first day of the week. + doy : 6 // The week that contains Jan 1st is the first week of the year. + } +}); + +//! moment.js locale configuration +//! locale : Telugu [te] +//! author : Krishna Chaitanya Thota : https://github.com/kcthota + +hooks.defineLocale('te', { + months : 'జనవరి_ఫిబ్రవరి_మార్చి_ఏప్రిల్_మే_జూన్_జూలై_ఆగస్టు_సెప్టెంబర్_అక్టోబర్_నవంబర్_డిసెంబర్'.split('_'), + monthsShort : 'జన._ఫిబ్ర._మార్చి_ఏప్రి._మే_జూన్_జూలై_ఆగ._సెప్._అక్టో._నవ._డిసె.'.split('_'), + monthsParseExact : true, + weekdays : 'ఆదివారం_సోమవారం_మంగళవారం_బుధవారం_గురువారం_శుక్రవారం_శనివారం'.split('_'), + weekdaysShort : 'ఆది_సోమ_మంగళ_బుధ_గురు_శుక్ర_శని'.split('_'), + weekdaysMin : 'ఆ_సో_మం_బు_గు_శు_శ'.split('_'), + longDateFormat : { + LT : 'A h:mm', + LTS : 'A h:mm:ss', + L : 'DD/MM/YYYY', + LL : 'D MMMM YYYY', + LLL : 'D MMMM YYYY, A h:mm', + LLLL : 'dddd, D MMMM YYYY, A h:mm' + }, + calendar : { + sameDay : '[నేడు] LT', + nextDay : '[రేపు] LT', + nextWeek : 'dddd, LT', + lastDay : '[నిన్న] LT', + lastWeek : '[గత] dddd, LT', + sameElse : 'L' + }, + relativeTime : { + future : '%s లో', + past : '%s క్రితం', + s : 'కొన్ని క్షణాలు', + m : 'ఒక నిమిషం', + mm : '%d నిమిషాలు', + h : 'ఒక గంట', + hh : '%d గంటలు', + d : 'ఒక రోజు', + dd : '%d రోజులు', + M : 'ఒక నెల', + MM : '%d నెలలు', + y : 'ఒక సంవత్సరం', + yy : '%d సంవత్సరాలు' + }, + ordinalParse : /\d{1,2}వ/, + ordinal : '%dవ', + meridiemParse: /రాత్రి|ఉదయం|మధ్యాహ్నం|సాయంత్రం/, + meridiemHour : function (hour, meridiem) { + if (hour === 12) { + hour = 0; + } + if (meridiem === 'రాత్రి') { + return hour < 4 ? hour : hour + 12; + } else if (meridiem === 'ఉదయం') { + return hour; + } else if (meridiem === 'మధ్యాహ్నం') { + return hour >= 10 ? hour : hour + 12; + } else if (meridiem === 'సాయంత్రం') { + return hour + 12; + } + }, + meridiem : function (hour, minute, isLower) { + if (hour < 4) { + return 'రాత్రి'; + } else if (hour < 10) { + return 'ఉదయం'; + } else if (hour < 17) { + return 'మధ్యాహ్నం'; + } else if (hour < 20) { + return 'సాయంత్రం'; + } else { + return 'రాత్రి'; + } + }, + week : { + dow : 0, // Sunday is the first day of the week. + doy : 6 // The week that contains Jan 1st is the first week of the year. + } +}); + +//! moment.js locale configuration +//! locale : Tetun Dili (East Timor) [tet] +//! author : Joshua Brooks : https://github.com/joshbrooks +//! author : Onorio De J. Afonso : https://github.com/marobo + +hooks.defineLocale('tet', { + months : 'Janeiru_Fevereiru_Marsu_Abril_Maiu_Juniu_Juliu_Augustu_Setembru_Outubru_Novembru_Dezembru'.split('_'), + monthsShort : 'Jan_Fev_Mar_Abr_Mai_Jun_Jul_Aug_Set_Out_Nov_Dez'.split('_'), + weekdays : 'Domingu_Segunda_Tersa_Kuarta_Kinta_Sexta_Sabadu'.split('_'), + weekdaysShort : 'Dom_Seg_Ters_Kua_Kint_Sext_Sab'.split('_'), + weekdaysMin : 'Do_Seg_Te_Ku_Ki_Sex_Sa'.split('_'), + longDateFormat : { + LT : 'HH:mm', + LTS : 'HH:mm:ss', + L : 'DD/MM/YYYY', + LL : 'D MMMM YYYY', + LLL : 'D MMMM YYYY HH:mm', + LLLL : 'dddd, D MMMM YYYY HH:mm' + }, + calendar : { + sameDay: '[Ohin iha] LT', + nextDay: '[Aban iha] LT', + nextWeek: 'dddd [iha] LT', + lastDay: '[Horiseik iha] LT', + lastWeek: 'dddd [semana kotuk] [iha] LT', + sameElse: 'L' + }, + relativeTime : { + future : 'iha %s', + past : '%s liuba', + s : 'minutu balun', + m : 'minutu ida', + mm : 'minutus %d', + h : 'horas ida', + hh : 'horas %d', + d : 'loron ida', + dd : 'loron %d', + M : 'fulan ida', + MM : 'fulan %d', + y : 'tinan ida', + yy : 'tinan %d' + }, + ordinalParse: /\d{1,2}(st|nd|rd|th)/, + ordinal : function (number) { + var b = number % 10, + output = (~~(number % 100 / 10) === 1) ? 'th' : + (b === 1) ? 'st' : + (b === 2) ? 'nd' : + (b === 3) ? 'rd' : 'th'; + return number + output; + }, + week : { + dow : 1, // Monday is the first day of the week. + doy : 4 // The week that contains Jan 4th is the first week of the year. + } +}); + +//! moment.js locale configuration +//! locale : Thai [th] +//! author : Kridsada Thanabulpong : https://github.com/sirn + +hooks.defineLocale('th', { + months : 'มกราคม_กุมภาพันธ์_มีนาคม_เมษายน_พฤษภาคม_มิถุนายน_กรกฎาคม_สิงหาคม_กันยายน_ตุลาคม_พฤศจิกายน_ธันวาคม'.split('_'), + monthsShort : 'ม.ค._ก.พ._มี.ค._เม.ย._พ.ค._มิ.ย._ก.ค._ส.ค._ก.ย._ต.ค._พ.ย._ธ.ค.'.split('_'), + monthsParseExact: true, + weekdays : 'อาทิตย์_จันทร์_อังคาร_พุธ_พฤหัสบดี_ศุกร์_เสาร์'.split('_'), + weekdaysShort : 'อาทิตย์_จันทร์_อังคาร_พุธ_พฤหัส_ศุกร์_เสาร์'.split('_'), // yes, three characters difference + weekdaysMin : 'อา._จ._อ._พ._พฤ._ศ._ส.'.split('_'), + weekdaysParseExact : true, + longDateFormat : { + LT : 'H:mm', + LTS : 'H:mm:ss', + L : 'YYYY/MM/DD', + LL : 'D MMMM YYYY', + LLL : 'D MMMM YYYY เวลา H:mm', + LLLL : 'วันddddที่ D MMMM YYYY เวลา H:mm' + }, + meridiemParse: /ก่อนเที่ยง|หลังเที่ยง/, + isPM: function (input) { + return input === 'หลังเที่ยง'; + }, + meridiem : function (hour, minute, isLower) { + if (hour < 12) { + return 'ก่อนเที่ยง'; + } else { + return 'หลังเที่ยง'; + } + }, + calendar : { + sameDay : '[วันนี้ เวลา] LT', + nextDay : '[พรุ่งนี้ เวลา] LT', + nextWeek : 'dddd[หน้า เวลา] LT', + lastDay : '[เมื่อวานนี้ เวลา] LT', + lastWeek : '[วัน]dddd[ที่แล้ว เวลา] LT', + sameElse : 'L' + }, + relativeTime : { + future : 'อีก %s', + past : '%sที่แล้ว', + s : 'ไม่กี่วินาที', + m : '1 นาที', + mm : '%d นาที', + h : '1 ชั่วโมง', + hh : '%d ชั่วโมง', + d : '1 วัน', + dd : '%d วัน', + M : '1 เดือน', + MM : '%d เดือน', + y : '1 ปี', + yy : '%d ปี' + } +}); + +//! moment.js locale configuration +//! locale : Tagalog (Philippines) [tl-ph] +//! author : Dan Hagman : https://github.com/hagmandan + +hooks.defineLocale('tl-ph', { + months : 'Enero_Pebrero_Marso_Abril_Mayo_Hunyo_Hulyo_Agosto_Setyembre_Oktubre_Nobyembre_Disyembre'.split('_'), + monthsShort : 'Ene_Peb_Mar_Abr_May_Hun_Hul_Ago_Set_Okt_Nob_Dis'.split('_'), + weekdays : 'Linggo_Lunes_Martes_Miyerkules_Huwebes_Biyernes_Sabado'.split('_'), + weekdaysShort : 'Lin_Lun_Mar_Miy_Huw_Biy_Sab'.split('_'), + weekdaysMin : 'Li_Lu_Ma_Mi_Hu_Bi_Sab'.split('_'), + longDateFormat : { + LT : 'HH:mm', + LTS : 'HH:mm:ss', + L : 'MM/D/YYYY', + LL : 'MMMM D, YYYY', + LLL : 'MMMM D, YYYY HH:mm', + LLLL : 'dddd, MMMM DD, YYYY HH:mm' + }, + calendar : { + sameDay: 'LT [ngayong araw]', + nextDay: '[Bukas ng] LT', + nextWeek: 'LT [sa susunod na] dddd', + lastDay: 'LT [kahapon]', + lastWeek: 'LT [noong nakaraang] dddd', + sameElse: 'L' + }, + relativeTime : { + future : 'sa loob ng %s', + past : '%s ang nakalipas', + s : 'ilang segundo', + m : 'isang minuto', + mm : '%d minuto', + h : 'isang oras', + hh : '%d oras', + d : 'isang araw', + dd : '%d araw', + M : 'isang buwan', + MM : '%d buwan', + y : 'isang taon', + yy : '%d taon' + }, + ordinalParse: /\d{1,2}/, + ordinal : function (number) { + return number; + }, + week : { + dow : 1, // Monday is the first day of the week. + doy : 4 // The week that contains Jan 4th is the first week of the year. + } +}); + +//! moment.js locale configuration +//! locale : Klingon [tlh] +//! author : Dominika Kruk : https://github.com/amaranthrose + +var numbersNouns = 'pagh_wa’_cha’_wej_loS_vagh_jav_Soch_chorgh_Hut'.split('_'); + +function translateFuture(output) { + var time = output; + time = (output.indexOf('jaj') !== -1) ? + time.slice(0, -3) + 'leS' : + (output.indexOf('jar') !== -1) ? + time.slice(0, -3) + 'waQ' : + (output.indexOf('DIS') !== -1) ? + time.slice(0, -3) + 'nem' : + time + ' pIq'; + return time; +} + +function translatePast(output) { + var time = output; + time = (output.indexOf('jaj') !== -1) ? + time.slice(0, -3) + 'Hu’' : + (output.indexOf('jar') !== -1) ? + time.slice(0, -3) + 'wen' : + (output.indexOf('DIS') !== -1) ? + time.slice(0, -3) + 'ben' : + time + ' ret'; + return time; +} + +function translate$9(number, withoutSuffix, string, isFuture) { + var numberNoun = numberAsNoun(number); + switch (string) { + case 'mm': + return numberNoun + ' tup'; + case 'hh': + return numberNoun + ' rep'; + case 'dd': + return numberNoun + ' jaj'; + case 'MM': + return numberNoun + ' jar'; + case 'yy': + return numberNoun + ' DIS'; + } +} + +function numberAsNoun(number) { + var hundred = Math.floor((number % 1000) / 100), + ten = Math.floor((number % 100) / 10), + one = number % 10, + word = ''; + if (hundred > 0) { + word += numbersNouns[hundred] + 'vatlh'; + } + if (ten > 0) { + word += ((word !== '') ? ' ' : '') + numbersNouns[ten] + 'maH'; + } + if (one > 0) { + word += ((word !== '') ? ' ' : '') + numbersNouns[one]; + } + return (word === '') ? 'pagh' : word; +} + +hooks.defineLocale('tlh', { + months : 'tera’ jar wa’_tera’ jar cha’_tera’ jar wej_tera’ jar loS_tera’ jar vagh_tera’ jar jav_tera’ jar Soch_tera’ jar chorgh_tera’ jar Hut_tera’ jar wa’maH_tera’ jar wa’maH wa’_tera’ jar wa’maH cha’'.split('_'), + monthsShort : 'jar wa’_jar cha’_jar wej_jar loS_jar vagh_jar jav_jar Soch_jar chorgh_jar Hut_jar wa’maH_jar wa’maH wa’_jar wa’maH cha’'.split('_'), + monthsParseExact : true, + weekdays : 'lojmItjaj_DaSjaj_povjaj_ghItlhjaj_loghjaj_buqjaj_ghInjaj'.split('_'), + weekdaysShort : 'lojmItjaj_DaSjaj_povjaj_ghItlhjaj_loghjaj_buqjaj_ghInjaj'.split('_'), + weekdaysMin : 'lojmItjaj_DaSjaj_povjaj_ghItlhjaj_loghjaj_buqjaj_ghInjaj'.split('_'), + longDateFormat : { + LT : 'HH:mm', + LTS : 'HH:mm:ss', + L : 'DD.MM.YYYY', + LL : 'D MMMM YYYY', + LLL : 'D MMMM YYYY HH:mm', + LLLL : 'dddd, D MMMM YYYY HH:mm' + }, + calendar : { + sameDay: '[DaHjaj] LT', + nextDay: '[wa’leS] LT', + nextWeek: 'LLL', + lastDay: '[wa’Hu’] LT', + lastWeek: 'LLL', + sameElse: 'L' + }, + relativeTime : { + future : translateFuture, + past : translatePast, + s : 'puS lup', + m : 'wa’ tup', + mm : translate$9, + h : 'wa’ rep', + hh : translate$9, + d : 'wa’ jaj', + dd : translate$9, + M : 'wa’ jar', + MM : translate$9, + y : 'wa’ DIS', + yy : translate$9 + }, + ordinalParse: /\d{1,2}\./, + ordinal : '%d.', + week : { + dow : 1, // Monday is the first day of the week. + doy : 4 // The week that contains Jan 4th is the first week of the year. + } +}); + +//! moment.js locale configuration +//! locale : Turkish [tr] +//! authors : Erhan Gundogan : https://github.com/erhangundogan, +//! Burak Yiğit Kaya: https://github.com/BYK + +var suffixes$3 = { + 1: '\'inci', + 5: '\'inci', + 8: '\'inci', + 70: '\'inci', + 80: '\'inci', + 2: '\'nci', + 7: '\'nci', + 20: '\'nci', + 50: '\'nci', + 3: '\'üncü', + 4: '\'üncü', + 100: '\'üncü', + 6: '\'ncı', + 9: '\'uncu', + 10: '\'uncu', + 30: '\'uncu', + 60: '\'ıncı', + 90: '\'ıncı' +}; + +hooks.defineLocale('tr', { + months : 'Ocak_Şubat_Mart_Nisan_Mayıs_Haziran_Temmuz_Ağustos_Eylül_Ekim_Kasım_Aralık'.split('_'), + monthsShort : 'Oca_Şub_Mar_Nis_May_Haz_Tem_Ağu_Eyl_Eki_Kas_Ara'.split('_'), + weekdays : 'Pazar_Pazartesi_Salı_Çarşamba_Perşembe_Cuma_Cumartesi'.split('_'), + weekdaysShort : 'Paz_Pts_Sal_Çar_Per_Cum_Cts'.split('_'), + weekdaysMin : 'Pz_Pt_Sa_Ça_Pe_Cu_Ct'.split('_'), + longDateFormat : { + LT : 'HH:mm', + LTS : 'HH:mm:ss', + L : 'DD.MM.YYYY', + LL : 'D MMMM YYYY', + LLL : 'D MMMM YYYY HH:mm', + LLLL : 'dddd, D MMMM YYYY HH:mm' + }, + calendar : { + sameDay : '[bugün saat] LT', + nextDay : '[yarın saat] LT', + nextWeek : '[haftaya] dddd [saat] LT', + lastDay : '[dün] LT', + lastWeek : '[geçen hafta] dddd [saat] LT', + sameElse : 'L' + }, + relativeTime : { + future : '%s sonra', + past : '%s önce', + s : 'birkaç saniye', + m : 'bir dakika', + mm : '%d dakika', + h : 'bir saat', + hh : '%d saat', + d : 'bir gün', + dd : '%d gün', + M : 'bir ay', + MM : '%d ay', + y : 'bir yıl', + yy : '%d yıl' + }, + ordinalParse: /\d{1,2}'(inci|nci|üncü|ncı|uncu|ıncı)/, + ordinal : function (number) { + if (number === 0) { // special case for zero + return number + '\'ıncı'; + } + var a = number % 10, + b = number % 100 - a, + c = number >= 100 ? 100 : null; + return number + (suffixes$3[a] || suffixes$3[b] || suffixes$3[c]); + }, + week : { + dow : 1, // Monday is the first day of the week. + doy : 7 // The week that contains Jan 1st is the first week of the year. + } +}); + +//! moment.js locale configuration +//! locale : Talossan [tzl] +//! author : Robin van der Vliet : https://github.com/robin0van0der0v +//! author : Iustì Canun + +// After the year there should be a slash and the amount of years since December 26, 1979 in Roman numerals. +// This is currently too difficult (maybe even impossible) to add. +hooks.defineLocale('tzl', { + months : 'Januar_Fevraglh_Març_Avrïu_Mai_Gün_Julia_Guscht_Setemvar_Listopäts_Noemvar_Zecemvar'.split('_'), + monthsShort : 'Jan_Fev_Mar_Avr_Mai_Gün_Jul_Gus_Set_Lis_Noe_Zec'.split('_'), + weekdays : 'Súladi_Lúneçi_Maitzi_Márcuri_Xhúadi_Viénerçi_Sáturi'.split('_'), + weekdaysShort : 'Súl_Lún_Mai_Már_Xhú_Vié_Sát'.split('_'), + weekdaysMin : 'Sú_Lú_Ma_Má_Xh_Vi_Sá'.split('_'), + longDateFormat : { + LT : 'HH.mm', + LTS : 'HH.mm.ss', + L : 'DD.MM.YYYY', + LL : 'D. MMMM [dallas] YYYY', + LLL : 'D. MMMM [dallas] YYYY HH.mm', + LLLL : 'dddd, [li] D. MMMM [dallas] YYYY HH.mm' + }, + meridiemParse: /d\'o|d\'a/i, + isPM : function (input) { + return 'd\'o' === input.toLowerCase(); + }, + meridiem : function (hours, minutes, isLower) { + if (hours > 11) { + return isLower ? 'd\'o' : 'D\'O'; + } else { + return isLower ? 'd\'a' : 'D\'A'; + } + }, + calendar : { + sameDay : '[oxhi à] LT', + nextDay : '[demà à] LT', + nextWeek : 'dddd [à] LT', + lastDay : '[ieiri à] LT', + lastWeek : '[sür el] dddd [lasteu à] LT', + sameElse : 'L' + }, + relativeTime : { + future : 'osprei %s', + past : 'ja%s', + s : processRelativeTime$5, + m : processRelativeTime$5, + mm : processRelativeTime$5, + h : processRelativeTime$5, + hh : processRelativeTime$5, + d : processRelativeTime$5, + dd : processRelativeTime$5, + M : processRelativeTime$5, + MM : processRelativeTime$5, + y : processRelativeTime$5, + yy : processRelativeTime$5 + }, + ordinalParse: /\d{1,2}\./, + ordinal : '%d.', + week : { + dow : 1, // Monday is the first day of the week. + doy : 4 // The week that contains Jan 4th is the first week of the year. + } +}); + +function processRelativeTime$5(number, withoutSuffix, key, isFuture) { + var format = { + 's': ['viensas secunds', '\'iensas secunds'], + 'm': ['\'n míut', '\'iens míut'], + 'mm': [number + ' míuts', '' + number + ' míuts'], + 'h': ['\'n þora', '\'iensa þora'], + 'hh': [number + ' þoras', '' + number + ' þoras'], + 'd': ['\'n ziua', '\'iensa ziua'], + 'dd': [number + ' ziuas', '' + number + ' ziuas'], + 'M': ['\'n mes', '\'iens mes'], + 'MM': [number + ' mesen', '' + number + ' mesen'], + 'y': ['\'n ar', '\'iens ar'], + 'yy': [number + ' ars', '' + number + ' ars'] + }; + return isFuture ? format[key][0] : (withoutSuffix ? format[key][0] : format[key][1]); +} + +//! moment.js locale configuration +//! locale : Central Atlas Tamazight Latin [tzm-latn] +//! author : Abdel Said : https://github.com/abdelsaid + +hooks.defineLocale('tzm-latn', { + months : 'innayr_brˤayrˤ_marˤsˤ_ibrir_mayyw_ywnyw_ywlywz_ɣwšt_šwtanbir_ktˤwbrˤ_nwwanbir_dwjnbir'.split('_'), + monthsShort : 'innayr_brˤayrˤ_marˤsˤ_ibrir_mayyw_ywnyw_ywlywz_ɣwšt_šwtanbir_ktˤwbrˤ_nwwanbir_dwjnbir'.split('_'), + weekdays : 'asamas_aynas_asinas_akras_akwas_asimwas_asiḍyas'.split('_'), + weekdaysShort : 'asamas_aynas_asinas_akras_akwas_asimwas_asiḍyas'.split('_'), + weekdaysMin : 'asamas_aynas_asinas_akras_akwas_asimwas_asiḍyas'.split('_'), + longDateFormat : { + LT : 'HH:mm', + LTS : 'HH:mm:ss', + L : 'DD/MM/YYYY', + LL : 'D MMMM YYYY', + LLL : 'D MMMM YYYY HH:mm', + LLLL : 'dddd D MMMM YYYY HH:mm' + }, + calendar : { + sameDay: '[asdkh g] LT', + nextDay: '[aska g] LT', + nextWeek: 'dddd [g] LT', + lastDay: '[assant g] LT', + lastWeek: 'dddd [g] LT', + sameElse: 'L' + }, + relativeTime : { + future : 'dadkh s yan %s', + past : 'yan %s', + s : 'imik', + m : 'minuḍ', + mm : '%d minuḍ', + h : 'saɛa', + hh : '%d tassaɛin', + d : 'ass', + dd : '%d ossan', + M : 'ayowr', + MM : '%d iyyirn', + y : 'asgas', + yy : '%d isgasn' + }, + week : { + dow : 6, // Saturday is the first day of the week. + doy : 12 // The week that contains Jan 1st is the first week of the year. + } +}); + +//! moment.js locale configuration +//! locale : Central Atlas Tamazight [tzm] +//! author : Abdel Said : https://github.com/abdelsaid + +hooks.defineLocale('tzm', { + months : 'ⵉⵏⵏⴰⵢⵔ_ⴱⵕⴰⵢⵕ_ⵎⴰⵕⵚ_ⵉⴱⵔⵉⵔ_ⵎⴰⵢⵢⵓ_ⵢⵓⵏⵢⵓ_ⵢⵓⵍⵢⵓⵣ_ⵖⵓⵛⵜ_ⵛⵓⵜⴰⵏⴱⵉⵔ_ⴽⵟⵓⴱⵕ_ⵏⵓⵡⴰⵏⴱⵉⵔ_ⴷⵓⵊⵏⴱⵉⵔ'.split('_'), + monthsShort : 'ⵉⵏⵏⴰⵢⵔ_ⴱⵕⴰⵢⵕ_ⵎⴰⵕⵚ_ⵉⴱⵔⵉⵔ_ⵎⴰⵢⵢⵓ_ⵢⵓⵏⵢⵓ_ⵢⵓⵍⵢⵓⵣ_ⵖⵓⵛⵜ_ⵛⵓⵜⴰⵏⴱⵉⵔ_ⴽⵟⵓⴱⵕ_ⵏⵓⵡⴰⵏⴱⵉⵔ_ⴷⵓⵊⵏⴱⵉⵔ'.split('_'), + weekdays : 'ⴰⵙⴰⵎⴰⵙ_ⴰⵢⵏⴰⵙ_ⴰⵙⵉⵏⴰⵙ_ⴰⴽⵔⴰⵙ_ⴰⴽⵡⴰⵙ_ⴰⵙⵉⵎⵡⴰⵙ_ⴰⵙⵉⴹⵢⴰⵙ'.split('_'), + weekdaysShort : 'ⴰⵙⴰⵎⴰⵙ_ⴰⵢⵏⴰⵙ_ⴰⵙⵉⵏⴰⵙ_ⴰⴽⵔⴰⵙ_ⴰⴽⵡⴰⵙ_ⴰⵙⵉⵎⵡⴰⵙ_ⴰⵙⵉⴹⵢⴰⵙ'.split('_'), + weekdaysMin : 'ⴰⵙⴰⵎⴰⵙ_ⴰⵢⵏⴰⵙ_ⴰⵙⵉⵏⴰⵙ_ⴰⴽⵔⴰⵙ_ⴰⴽⵡⴰⵙ_ⴰⵙⵉⵎⵡⴰⵙ_ⴰⵙⵉⴹⵢⴰⵙ'.split('_'), + longDateFormat : { + LT : 'HH:mm', + LTS: 'HH:mm:ss', + L : 'DD/MM/YYYY', + LL : 'D MMMM YYYY', + LLL : 'D MMMM YYYY HH:mm', + LLLL : 'dddd D MMMM YYYY HH:mm' + }, + calendar : { + sameDay: '[ⴰⵙⴷⵅ ⴴ] LT', + nextDay: '[ⴰⵙⴽⴰ ⴴ] LT', + nextWeek: 'dddd [ⴴ] LT', + lastDay: '[ⴰⵚⴰⵏⵜ ⴴ] LT', + lastWeek: 'dddd [ⴴ] LT', + sameElse: 'L' + }, + relativeTime : { + future : 'ⴷⴰⴷⵅ ⵙ ⵢⴰⵏ %s', + past : 'ⵢⴰⵏ %s', + s : 'ⵉⵎⵉⴽ', + m : 'ⵎⵉⵏⵓⴺ', + mm : '%d ⵎⵉⵏⵓⴺ', + h : 'ⵙⴰⵄⴰ', + hh : '%d ⵜⴰⵙⵙⴰⵄⵉⵏ', + d : 'ⴰⵙⵙ', + dd : '%d oⵙⵙⴰⵏ', + M : 'ⴰⵢoⵓⵔ', + MM : '%d ⵉⵢⵢⵉⵔⵏ', + y : 'ⴰⵙⴳⴰⵙ', + yy : '%d ⵉⵙⴳⴰⵙⵏ' + }, + week : { + dow : 6, // Saturday is the first day of the week. + doy : 12 // The week that contains Jan 1st is the first week of the year. + } +}); + +//! moment.js locale configuration +//! locale : Ukrainian [uk] +//! author : zemlanin : https://github.com/zemlanin +//! Author : Menelion Elensúle : https://github.com/Oire + +function plural$6(word, num) { + var forms = word.split('_'); + return num % 10 === 1 && num % 100 !== 11 ? forms[0] : (num % 10 >= 2 && num % 10 <= 4 && (num % 100 < 10 || num % 100 >= 20) ? forms[1] : forms[2]); +} +function relativeTimeWithPlural$4(number, withoutSuffix, key) { + var format = { + 'mm': withoutSuffix ? 'хвилина_хвилини_хвилин' : 'хвилину_хвилини_хвилин', + 'hh': withoutSuffix ? 'година_години_годин' : 'годину_години_годин', + 'dd': 'день_дні_днів', + 'MM': 'місяць_місяці_місяців', + 'yy': 'рік_роки_років' + }; + if (key === 'm') { + return withoutSuffix ? 'хвилина' : 'хвилину'; + } + else if (key === 'h') { + return withoutSuffix ? 'година' : 'годину'; + } + else { + return number + ' ' + plural$6(format[key], +number); + } +} +function weekdaysCaseReplace(m, format) { + var weekdays = { + 'nominative': 'неділя_понеділок_вівторок_середа_четвер_п’ятниця_субота'.split('_'), + 'accusative': 'неділю_понеділок_вівторок_середу_четвер_п’ятницю_суботу'.split('_'), + 'genitive': 'неділі_понеділка_вівторка_середи_четверга_п’ятниці_суботи'.split('_') + }, + nounCase = (/(\[[ВвУу]\]) ?dddd/).test(format) ? + 'accusative' : + ((/\[?(?:минулої|наступної)? ?\] ?dddd/).test(format) ? + 'genitive' : + 'nominative'); + return weekdays[nounCase][m.day()]; +} +function processHoursFunction(str) { + return function () { + return str + 'о' + (this.hours() === 11 ? 'б' : '') + '] LT'; + }; +} + +hooks.defineLocale('uk', { + months : { + 'format': 'січня_лютого_березня_квітня_травня_червня_липня_серпня_вересня_жовтня_листопада_грудня'.split('_'), + 'standalone': 'січень_лютий_березень_квітень_травень_червень_липень_серпень_вересень_жовтень_листопад_грудень'.split('_') + }, + monthsShort : 'січ_лют_бер_квіт_трав_черв_лип_серп_вер_жовт_лист_груд'.split('_'), + weekdays : weekdaysCaseReplace, + weekdaysShort : 'нд_пн_вт_ср_чт_пт_сб'.split('_'), + weekdaysMin : 'нд_пн_вт_ср_чт_пт_сб'.split('_'), + longDateFormat : { + LT : 'HH:mm', + LTS : 'HH:mm:ss', + L : 'DD.MM.YYYY', + LL : 'D MMMM YYYY р.', + LLL : 'D MMMM YYYY р., HH:mm', + LLLL : 'dddd, D MMMM YYYY р., HH:mm' + }, + calendar : { + sameDay: processHoursFunction('[Сьогодні '), + nextDay: processHoursFunction('[Завтра '), + lastDay: processHoursFunction('[Вчора '), + nextWeek: processHoursFunction('[У] dddd ['), + lastWeek: function () { + switch (this.day()) { + case 0: + case 3: + case 5: + case 6: + return processHoursFunction('[Минулої] dddd [').call(this); + case 1: + case 2: + case 4: + return processHoursFunction('[Минулого] dddd [').call(this); + } + }, + sameElse: 'L' + }, + relativeTime : { + future : 'за %s', + past : '%s тому', + s : 'декілька секунд', + m : relativeTimeWithPlural$4, + mm : relativeTimeWithPlural$4, + h : 'годину', + hh : relativeTimeWithPlural$4, + d : 'день', + dd : relativeTimeWithPlural$4, + M : 'місяць', + MM : relativeTimeWithPlural$4, + y : 'рік', + yy : relativeTimeWithPlural$4 + }, + // M. E.: those two are virtually unused but a user might want to implement them for his/her website for some reason + meridiemParse: /ночі|ранку|дня|вечора/, + isPM: function (input) { + return /^(дня|вечора)$/.test(input); + }, + meridiem : function (hour, minute, isLower) { + if (hour < 4) { + return 'ночі'; + } else if (hour < 12) { + return 'ранку'; + } else if (hour < 17) { + return 'дня'; + } else { + return 'вечора'; + } + }, + ordinalParse: /\d{1,2}-(й|го)/, + ordinal: function (number, period) { + switch (period) { + case 'M': + case 'd': + case 'DDD': + case 'w': + case 'W': + return number + '-й'; + case 'D': + return number + '-го'; + default: + return number; + } + }, + week : { + dow : 1, // Monday is the first day of the week. + doy : 7 // The week that contains Jan 1st is the first week of the year. + } +}); + +//! moment.js locale configuration +//! locale : Uzbek [uz] +//! author : Sardor Muminov : https://github.com/muminoff + +hooks.defineLocale('uz', { + months : 'январ_феврал_март_апрел_май_июн_июл_август_сентябр_октябр_ноябр_декабр'.split('_'), + monthsShort : 'янв_фев_мар_апр_май_июн_июл_авг_сен_окт_ноя_дек'.split('_'), + weekdays : 'Якшанба_Душанба_Сешанба_Чоршанба_Пайшанба_Жума_Шанба'.split('_'), + weekdaysShort : 'Якш_Душ_Сеш_Чор_Пай_Жум_Шан'.split('_'), + weekdaysMin : 'Як_Ду_Се_Чо_Па_Жу_Ша'.split('_'), + longDateFormat : { + LT : 'HH:mm', + LTS : 'HH:mm:ss', + L : 'DD/MM/YYYY', + LL : 'D MMMM YYYY', + LLL : 'D MMMM YYYY HH:mm', + LLLL : 'D MMMM YYYY, dddd HH:mm' + }, + calendar : { + sameDay : '[Бугун соат] LT [да]', + nextDay : '[Эртага] LT [да]', + nextWeek : 'dddd [куни соат] LT [да]', + lastDay : '[Кеча соат] LT [да]', + lastWeek : '[Утган] dddd [куни соат] LT [да]', + sameElse : 'L' + }, + relativeTime : { + future : 'Якин %s ичида', + past : 'Бир неча %s олдин', + s : 'фурсат', + m : 'бир дакика', + mm : '%d дакика', + h : 'бир соат', + hh : '%d соат', + d : 'бир кун', + dd : '%d кун', + M : 'бир ой', + MM : '%d ой', + y : 'бир йил', + yy : '%d йил' + }, + week : { + dow : 1, // Monday is the first day of the week. + doy : 7 // The week that contains Jan 4th is the first week of the year. + } +}); + +//! moment.js locale configuration +//! locale : Vietnamese [vi] +//! author : Bang Nguyen : https://github.com/bangnk + +hooks.defineLocale('vi', { + months : 'tháng 1_tháng 2_tháng 3_tháng 4_tháng 5_tháng 6_tháng 7_tháng 8_tháng 9_tháng 10_tháng 11_tháng 12'.split('_'), + monthsShort : 'Th01_Th02_Th03_Th04_Th05_Th06_Th07_Th08_Th09_Th10_Th11_Th12'.split('_'), + monthsParseExact : true, + weekdays : 'chủ nhật_thứ hai_thứ ba_thứ tư_thứ năm_thứ sáu_thứ bảy'.split('_'), + weekdaysShort : 'CN_T2_T3_T4_T5_T6_T7'.split('_'), + weekdaysMin : 'CN_T2_T3_T4_T5_T6_T7'.split('_'), + weekdaysParseExact : true, + meridiemParse: /sa|ch/i, + isPM : function (input) { + return /^ch$/i.test(input); + }, + meridiem : function (hours, minutes, isLower) { + if (hours < 12) { + return isLower ? 'sa' : 'SA'; + } else { + return isLower ? 'ch' : 'CH'; + } + }, + longDateFormat : { + LT : 'HH:mm', + LTS : 'HH:mm:ss', + L : 'DD/MM/YYYY', + LL : 'D MMMM [năm] YYYY', + LLL : 'D MMMM [năm] YYYY HH:mm', + LLLL : 'dddd, D MMMM [năm] YYYY HH:mm', + l : 'DD/M/YYYY', + ll : 'D MMM YYYY', + lll : 'D MMM YYYY HH:mm', + llll : 'ddd, D MMM YYYY HH:mm' + }, + calendar : { + sameDay: '[Hôm nay lúc] LT', + nextDay: '[Ngày mai lúc] LT', + nextWeek: 'dddd [tuần tới lúc] LT', + lastDay: '[Hôm qua lúc] LT', + lastWeek: 'dddd [tuần rồi lúc] LT', + sameElse: 'L' + }, + relativeTime : { + future : '%s tới', + past : '%s trước', + s : 'vài giây', + m : 'một phút', + mm : '%d phút', + h : 'một giờ', + hh : '%d giờ', + d : 'một ngày', + dd : '%d ngày', + M : 'một tháng', + MM : '%d tháng', + y : 'một năm', + yy : '%d năm' + }, + ordinalParse: /\d{1,2}/, + ordinal : function (number) { + return number; + }, + week : { + dow : 1, // Monday is the first day of the week. + doy : 4 // The week that contains Jan 4th is the first week of the year. + } +}); + +//! moment.js locale configuration +//! locale : Pseudo [x-pseudo] +//! author : Andrew Hood : https://github.com/andrewhood125 + +hooks.defineLocale('x-pseudo', { + months : 'J~áñúá~rý_F~ébrú~árý_~Márc~h_Áp~ríl_~Máý_~Júñé~_Júl~ý_Áú~gúst~_Sép~témb~ér_Ó~ctób~ér_Ñ~óvém~bér_~Décé~mbér'.split('_'), + monthsShort : 'J~áñ_~Féb_~Már_~Ápr_~Máý_~Júñ_~Júl_~Áúg_~Sép_~Óct_~Ñóv_~Déc'.split('_'), + monthsParseExact : true, + weekdays : 'S~úñdá~ý_Mó~ñdáý~_Túé~sdáý~_Wéd~ñésd~áý_T~húrs~dáý_~Fríd~áý_S~átúr~dáý'.split('_'), + weekdaysShort : 'S~úñ_~Móñ_~Túé_~Wéd_~Thú_~Frí_~Sát'.split('_'), + weekdaysMin : 'S~ú_Mó~_Tú_~Wé_T~h_Fr~_Sá'.split('_'), + weekdaysParseExact : true, + longDateFormat : { + LT : 'HH:mm', + L : 'DD/MM/YYYY', + LL : 'D MMMM YYYY', + LLL : 'D MMMM YYYY HH:mm', + LLLL : 'dddd, D MMMM YYYY HH:mm' + }, + calendar : { + sameDay : '[T~ódá~ý át] LT', + nextDay : '[T~ómó~rró~w át] LT', + nextWeek : 'dddd [át] LT', + lastDay : '[Ý~ést~érdá~ý át] LT', + lastWeek : '[L~ást] dddd [át] LT', + sameElse : 'L' + }, + relativeTime : { + future : 'í~ñ %s', + past : '%s á~gó', + s : 'á ~féw ~sécó~ñds', + m : 'á ~míñ~úté', + mm : '%d m~íñú~tés', + h : 'á~ñ hó~úr', + hh : '%d h~óúrs', + d : 'á ~dáý', + dd : '%d d~áýs', + M : 'á ~móñ~th', + MM : '%d m~óñt~hs', + y : 'á ~ýéár', + yy : '%d ý~éárs' + }, + ordinalParse: /\d{1,2}(th|st|nd|rd)/, + ordinal : function (number) { + var b = number % 10, + output = (~~(number % 100 / 10) === 1) ? 'th' : + (b === 1) ? 'st' : + (b === 2) ? 'nd' : + (b === 3) ? 'rd' : 'th'; + return number + output; + }, + week : { + dow : 1, // Monday is the first day of the week. + doy : 4 // The week that contains Jan 4th is the first week of the year. + } +}); + +//! moment.js locale configuration +//! locale : Yoruba Nigeria [yo] +//! author : Atolagbe Abisoye : https://github.com/andela-batolagbe + +hooks.defineLocale('yo', { + months : 'Sẹ́rẹ́_Èrèlè_Ẹrẹ̀nà_Ìgbé_Èbibi_Òkùdu_Agẹmo_Ògún_Owewe_Ọ̀wàrà_Bélú_Ọ̀pẹ̀̀'.split('_'), + monthsShort : 'Sẹ́r_Èrl_Ẹrn_Ìgb_Èbi_Òkù_Agẹ_Ògú_Owe_Ọ̀wà_Bél_Ọ̀pẹ̀̀'.split('_'), + weekdays : 'Àìkú_Ajé_Ìsẹ́gun_Ọjọ́rú_Ọjọ́bọ_Ẹtì_Àbámẹ́ta'.split('_'), + weekdaysShort : 'Àìk_Ajé_Ìsẹ́_Ọjr_Ọjb_Ẹtì_Àbá'.split('_'), + weekdaysMin : 'Àì_Aj_Ìs_Ọr_Ọb_Ẹt_Àb'.split('_'), + longDateFormat : { + LT : 'h:mm A', + LTS : 'h:mm:ss A', + L : 'DD/MM/YYYY', + LL : 'D MMMM YYYY', + LLL : 'D MMMM YYYY h:mm A', + LLLL : 'dddd, D MMMM YYYY h:mm A' + }, + calendar : { + sameDay : '[Ònì ni] LT', + nextDay : '[Ọ̀la ni] LT', + nextWeek : 'dddd [Ọsẹ̀ tón\'bọ] [ni] LT', + lastDay : '[Àna ni] LT', + lastWeek : 'dddd [Ọsẹ̀ tólọ́] [ni] LT', + sameElse : 'L' + }, + relativeTime : { + future : 'ní %s', + past : '%s kọjá', + s : 'ìsẹjú aayá die', + m : 'ìsẹjú kan', + mm : 'ìsẹjú %d', + h : 'wákati kan', + hh : 'wákati %d', + d : 'ọjọ́ kan', + dd : 'ọjọ́ %d', + M : 'osù kan', + MM : 'osù %d', + y : 'ọdún kan', + yy : 'ọdún %d' + }, + ordinalParse : /ọjọ́\s\d{1,2}/, + ordinal : 'ọjọ́ %d', + week : { + dow : 1, // Monday is the first day of the week. + doy : 4 // The week that contains Jan 4th is the first week of the year. + } +}); + +//! moment.js locale configuration +//! locale : Chinese (China) [zh-cn] +//! author : suupic : https://github.com/suupic +//! author : Zeno Zeng : https://github.com/zenozeng + +hooks.defineLocale('zh-cn', { + months : '一月_二月_三月_四月_五月_六月_七月_八月_九月_十月_十一月_十二月'.split('_'), + monthsShort : '1月_2月_3月_4月_5月_6月_7月_8月_9月_10月_11月_12月'.split('_'), + weekdays : '星期日_星期一_星期二_星期三_星期四_星期五_星期六'.split('_'), + weekdaysShort : '周日_周一_周二_周三_周四_周五_周六'.split('_'), + weekdaysMin : '日_一_二_三_四_五_六'.split('_'), + longDateFormat : { + LT : 'Ah点mm分', + LTS : 'Ah点m分s秒', + L : 'YYYY-MM-DD', + LL : 'YYYY年MMMD日', + LLL : 'YYYY年MMMD日Ah点mm分', + LLLL : 'YYYY年MMMD日ddddAh点mm分', + l : 'YYYY-MM-DD', + ll : 'YYYY年MMMD日', + lll : 'YYYY年MMMD日Ah点mm分', + llll : 'YYYY年MMMD日ddddAh点mm分' + }, + meridiemParse: /凌晨|早上|上午|中午|下午|晚上/, + meridiemHour: function (hour, meridiem) { + if (hour === 12) { + hour = 0; + } + if (meridiem === '凌晨' || meridiem === '早上' || + meridiem === '上午') { + return hour; + } else if (meridiem === '下午' || meridiem === '晚上') { + return hour + 12; + } else { + // '中午' + return hour >= 11 ? hour : hour + 12; + } + }, + meridiem : function (hour, minute, isLower) { + var hm = hour * 100 + minute; + if (hm < 600) { + return '凌晨'; + } else if (hm < 900) { + return '早上'; + } else if (hm < 1130) { + return '上午'; + } else if (hm < 1230) { + return '中午'; + } else if (hm < 1800) { + return '下午'; + } else { + return '晚上'; + } + }, + calendar : { + sameDay : function () { + return this.minutes() === 0 ? '[今天]Ah[点整]' : '[今天]LT'; + }, + nextDay : function () { + return this.minutes() === 0 ? '[明天]Ah[点整]' : '[明天]LT'; + }, + lastDay : function () { + return this.minutes() === 0 ? '[昨天]Ah[点整]' : '[昨天]LT'; + }, + nextWeek : function () { + var startOfWeek, prefix; + startOfWeek = hooks().startOf('week'); + prefix = this.diff(startOfWeek, 'days') >= 7 ? '[下]' : '[本]'; + return this.minutes() === 0 ? prefix + 'dddAh点整' : prefix + 'dddAh点mm'; + }, + lastWeek : function () { + var startOfWeek, prefix; + startOfWeek = hooks().startOf('week'); + prefix = this.unix() < startOfWeek.unix() ? '[上]' : '[本]'; + return this.minutes() === 0 ? prefix + 'dddAh点整' : prefix + 'dddAh点mm'; + }, + sameElse : 'LL' + }, + ordinalParse: /\d{1,2}(日|月|周)/, + ordinal : function (number, period) { + switch (period) { + case 'd': + case 'D': + case 'DDD': + return number + '日'; + case 'M': + return number + '月'; + case 'w': + case 'W': + return number + '周'; + default: + return number; + } + }, + relativeTime : { + future : '%s内', + past : '%s前', + s : '几秒', + m : '1 分钟', + mm : '%d 分钟', + h : '1 小时', + hh : '%d 小时', + d : '1 天', + dd : '%d 天', + M : '1 个月', + MM : '%d 个月', + y : '1 年', + yy : '%d 年' + }, + week : { + // GB/T 7408-1994《数据元和交换格式·信息交换·日期和时间表示法》与ISO 8601:1988等效 + dow : 1, // Monday is the first day of the week. + doy : 4 // The week that contains Jan 4th is the first week of the year. + } +}); + +//! moment.js locale configuration +//! locale : Chinese (Hong Kong) [zh-hk] +//! author : Ben : https://github.com/ben-lin +//! author : Chris Lam : https://github.com/hehachris +//! author : Konstantin : https://github.com/skfd + +hooks.defineLocale('zh-hk', { + months : '一月_二月_三月_四月_五月_六月_七月_八月_九月_十月_十一月_十二月'.split('_'), + monthsShort : '1月_2月_3月_4月_5月_6月_7月_8月_9月_10月_11月_12月'.split('_'), + weekdays : '星期日_星期一_星期二_星期三_星期四_星期五_星期六'.split('_'), + weekdaysShort : '週日_週一_週二_週三_週四_週五_週六'.split('_'), + weekdaysMin : '日_一_二_三_四_五_六'.split('_'), + longDateFormat : { + LT : 'Ah點mm分', + LTS : 'Ah點m分s秒', + L : 'YYYY年MMMD日', + LL : 'YYYY年MMMD日', + LLL : 'YYYY年MMMD日Ah點mm分', + LLLL : 'YYYY年MMMD日ddddAh點mm分', + l : 'YYYY年MMMD日', + ll : 'YYYY年MMMD日', + lll : 'YYYY年MMMD日Ah點mm分', + llll : 'YYYY年MMMD日ddddAh點mm分' + }, + meridiemParse: /凌晨|早上|上午|中午|下午|晚上/, + meridiemHour : function (hour, meridiem) { + if (hour === 12) { + hour = 0; + } + if (meridiem === '凌晨' || meridiem === '早上' || meridiem === '上午') { + return hour; + } else if (meridiem === '中午') { + return hour >= 11 ? hour : hour + 12; + } else if (meridiem === '下午' || meridiem === '晚上') { + return hour + 12; + } + }, + meridiem : function (hour, minute, isLower) { + var hm = hour * 100 + minute; + if (hm < 600) { + return '凌晨'; + } else if (hm < 900) { + return '早上'; + } else if (hm < 1130) { + return '上午'; + } else if (hm < 1230) { + return '中午'; + } else if (hm < 1800) { + return '下午'; + } else { + return '晚上'; + } + }, + calendar : { + sameDay : '[今天]LT', + nextDay : '[明天]LT', + nextWeek : '[下]ddddLT', + lastDay : '[昨天]LT', + lastWeek : '[上]ddddLT', + sameElse : 'L' + }, + ordinalParse: /\d{1,2}(日|月|週)/, + ordinal : function (number, period) { + switch (period) { + case 'd' : + case 'D' : + case 'DDD' : + return number + '日'; + case 'M' : + return number + '月'; + case 'w' : + case 'W' : + return number + '週'; + default : + return number; + } + }, + relativeTime : { + future : '%s內', + past : '%s前', + s : '幾秒', + m : '1 分鐘', + mm : '%d 分鐘', + h : '1 小時', + hh : '%d 小時', + d : '1 天', + dd : '%d 天', + M : '1 個月', + MM : '%d 個月', + y : '1 年', + yy : '%d 年' + } +}); + +//! moment.js locale configuration +//! locale : Chinese (Taiwan) [zh-tw] +//! author : Ben : https://github.com/ben-lin +//! author : Chris Lam : https://github.com/hehachris + +hooks.defineLocale('zh-tw', { + months : '一月_二月_三月_四月_五月_六月_七月_八月_九月_十月_十一月_十二月'.split('_'), + monthsShort : '1月_2月_3月_4月_5月_6月_7月_8月_9月_10月_11月_12月'.split('_'), + weekdays : '星期日_星期一_星期二_星期三_星期四_星期五_星期六'.split('_'), + weekdaysShort : '週日_週一_週二_週三_週四_週五_週六'.split('_'), + weekdaysMin : '日_一_二_三_四_五_六'.split('_'), + longDateFormat : { + LT : 'Ah點mm分', + LTS : 'Ah點m分s秒', + L : 'YYYY年MMMD日', + LL : 'YYYY年MMMD日', + LLL : 'YYYY年MMMD日Ah點mm分', + LLLL : 'YYYY年MMMD日ddddAh點mm分', + l : 'YYYY年MMMD日', + ll : 'YYYY年MMMD日', + lll : 'YYYY年MMMD日Ah點mm分', + llll : 'YYYY年MMMD日ddddAh點mm分' + }, + meridiemParse: /凌晨|早上|上午|中午|下午|晚上/, + meridiemHour : function (hour, meridiem) { + if (hour === 12) { + hour = 0; + } + if (meridiem === '凌晨' || meridiem === '早上' || meridiem === '上午') { + return hour; + } else if (meridiem === '中午') { + return hour >= 11 ? hour : hour + 12; + } else if (meridiem === '下午' || meridiem === '晚上') { + return hour + 12; + } + }, + meridiem : function (hour, minute, isLower) { + var hm = hour * 100 + minute; + if (hm < 600) { + return '凌晨'; + } else if (hm < 900) { + return '早上'; + } else if (hm < 1130) { + return '上午'; + } else if (hm < 1230) { + return '中午'; + } else if (hm < 1800) { + return '下午'; + } else { + return '晚上'; + } + }, + calendar : { + sameDay : '[今天]LT', + nextDay : '[明天]LT', + nextWeek : '[下]ddddLT', + lastDay : '[昨天]LT', + lastWeek : '[上]ddddLT', + sameElse : 'L' + }, + ordinalParse: /\d{1,2}(日|月|週)/, + ordinal : function (number, period) { + switch (period) { + case 'd' : + case 'D' : + case 'DDD' : + return number + '日'; + case 'M' : + return number + '月'; + case 'w' : + case 'W' : + return number + '週'; + default : + return number; + } + }, + relativeTime : { + future : '%s內', + past : '%s前', + s : '幾秒', + m : '1 分鐘', + mm : '%d 分鐘', + h : '1 小時', + hh : '%d 小時', + d : '1 天', + dd : '%d 天', + M : '1 個月', + MM : '%d 個月', + y : '1 年', + yy : '%d 年' + } +}); + +hooks.locale('en'); + +return hooks; + +}))); diff --git a/easy_gantt/assets/javascripts/easy_gantt/libs/mustache.js b/easy_gantt/assets/javascripts/easy_gantt/libs/mustache.js new file mode 100644 index 0000000..6ed26c7 --- /dev/null +++ b/easy_gantt/assets/javascripts/easy_gantt/libs/mustache.js @@ -0,0 +1,740 @@ +// This file has been generated from mustache.mjs +(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : + typeof define === 'function' && define.amd ? define(factory) : + (global = global || self, global.Mustache = factory()); +}(this, (function () { 'use strict'; + + /*! + * mustache.js - Logic-less {{mustache}} templates with JavaScript + * http://github.com/janl/mustache.js + */ + + var objectToString = Object.prototype.toString; + var isArray = Array.isArray || function isArrayPolyfill (object) { + return objectToString.call(object) === '[object Array]'; + }; + + function isFunction (object) { + return typeof object === 'function'; + } + + /** + * More correct typeof string handling array + * which normally returns typeof 'object' + */ + function typeStr (obj) { + return isArray(obj) ? 'array' : typeof obj; + } + + function escapeRegExp (string) { + return string.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g, '\\$&'); + } + + /** + * Null safe way of checking whether or not an object, + * including its prototype, has a given property + */ + function hasProperty (obj, propName) { + return obj != null && typeof obj === 'object' && (propName in obj); + } + + /** + * Safe way of detecting whether or not the given thing is a primitive and + * whether it has the given property + */ + function primitiveHasOwnProperty (primitive, propName) { + return ( + primitive != null + && typeof primitive !== 'object' + && primitive.hasOwnProperty + && primitive.hasOwnProperty(propName) + ); + } + + // Workaround for https://issues.apache.org/jira/browse/COUCHDB-577 + // See https://github.com/janl/mustache.js/issues/189 + var regExpTest = RegExp.prototype.test; + function testRegExp (re, string) { + return regExpTest.call(re, string); + } + + var nonSpaceRe = /\S/; + function isWhitespace (string) { + return !testRegExp(nonSpaceRe, string); + } + + var entityMap = { + '&': '&', + '<': '<', + '>': '>', + '"': '"', + "'": ''', + '/': '/', + '`': '`', + '=': '=' + }; + + function escapeHtml (string) { + return String(string).replace(/[&<>"'`=\/]/g, function fromEntityMap (s) { + return entityMap[s]; + }); + } + + var whiteRe = /\s*/; + var spaceRe = /\s+/; + var equalsRe = /\s*=/; + var curlyRe = /\s*\}/; + var tagRe = /#|\^|\/|>|\{|&|=|!/; + + /** + * Breaks up the given `template` string into a tree of tokens. If the `tags` + * argument is given here it must be an array with two string values: the + * opening and closing tags used in the template (e.g. [ "<%", "%>" ]). Of + * course, the default is to use mustaches (i.e. mustache.tags). + * + * A token is an array with at least 4 elements. The first element is the + * mustache symbol that was used inside the tag, e.g. "#" or "&". If the tag + * did not contain a symbol (i.e. {{myValue}}) this element is "name". For + * all text that appears outside a symbol this element is "text". + * + * The second element of a token is its "value". For mustache tags this is + * whatever else was inside the tag besides the opening symbol. For text tokens + * this is the text itself. + * + * The third and fourth elements of the token are the start and end indices, + * respectively, of the token in the original template. + * + * Tokens that are the root node of a subtree contain two more elements: 1) an + * array of tokens in the subtree and 2) the index in the original template at + * which the closing tag for that section begins. + * + * Tokens for partials also contain two more elements: 1) a string value of + * indendation prior to that tag and 2) the index of that tag on that line - + * eg a value of 2 indicates the partial is the third tag on this line. + */ + function parseTemplate (template, tags) { + if (!template) + return []; + var lineHasNonSpace = false; + var sections = []; // Stack to hold section tokens + var tokens = []; // Buffer to hold the tokens + var spaces = []; // Indices of whitespace tokens on the current line + var hasTag = false; // Is there a {{tag}} on the current line? + var nonSpace = false; // Is there a non-space char on the current line? + var indentation = ''; // Tracks indentation for tags that use it + var tagIndex = 0; // Stores a count of number of tags encountered on a line + + // Strips all whitespace tokens array for the current line + // if there was a {{#tag}} on it and otherwise only space. + function stripSpace () { + if (hasTag && !nonSpace) { + while (spaces.length) + delete tokens[spaces.pop()]; + } else { + spaces = []; + } + + hasTag = false; + nonSpace = false; + } + + var openingTagRe, closingTagRe, closingCurlyRe; + function compileTags (tagsToCompile) { + if (typeof tagsToCompile === 'string') + tagsToCompile = tagsToCompile.split(spaceRe, 2); + + if (!isArray(tagsToCompile) || tagsToCompile.length !== 2) + throw new Error('Invalid tags: ' + tagsToCompile); + + openingTagRe = new RegExp(escapeRegExp(tagsToCompile[0]) + '\\s*'); + closingTagRe = new RegExp('\\s*' + escapeRegExp(tagsToCompile[1])); + closingCurlyRe = new RegExp('\\s*' + escapeRegExp('}' + tagsToCompile[1])); + } + + compileTags(tags || mustache.tags); + + var scanner = new Scanner(template); + + var start, type, value, chr, token, openSection; + while (!scanner.eos()) { + start = scanner.pos; + + // Match any text between tags. + value = scanner.scanUntil(openingTagRe); + + if (value) { + for (var i = 0, valueLength = value.length; i < valueLength; ++i) { + chr = value.charAt(i); + + if (isWhitespace(chr)) { + spaces.push(tokens.length); + indentation += chr; + } else { + nonSpace = true; + lineHasNonSpace = true; + indentation += ' '; + } + + tokens.push([ 'text', chr, start, start + 1 ]); + start += 1; + + // Check for whitespace on the current line. + if (chr === '\n') { + stripSpace(); + indentation = ''; + tagIndex = 0; + lineHasNonSpace = false; + } + } + } + + // Match the opening tag. + if (!scanner.scan(openingTagRe)) + break; + + hasTag = true; + + // Get the tag type. + type = scanner.scan(tagRe) || 'name'; + scanner.scan(whiteRe); + + // Get the tag value. + if (type === '=') { + value = scanner.scanUntil(equalsRe); + scanner.scan(equalsRe); + scanner.scanUntil(closingTagRe); + } else if (type === '{') { + value = scanner.scanUntil(closingCurlyRe); + scanner.scan(curlyRe); + scanner.scanUntil(closingTagRe); + type = '&'; + } else { + value = scanner.scanUntil(closingTagRe); + } + + // Match the closing tag. + if (!scanner.scan(closingTagRe)) + throw new Error('Unclosed tag at ' + scanner.pos); + + if (type == '>') { + token = [ type, value, start, scanner.pos, indentation, tagIndex, lineHasNonSpace ]; + } else { + token = [ type, value, start, scanner.pos ]; + } + tagIndex++; + tokens.push(token); + + if (type === '#' || type === '^') { + sections.push(token); + } else if (type === '/') { + // Check section nesting. + openSection = sections.pop(); + + if (!openSection) + throw new Error('Unopened section "' + value + '" at ' + start); + + if (openSection[1] !== value) + throw new Error('Unclosed section "' + openSection[1] + '" at ' + start); + } else if (type === 'name' || type === '{' || type === '&') { + nonSpace = true; + } else if (type === '=') { + // Set the tags for the next time around. + compileTags(value); + } + } + + stripSpace(); + + // Make sure there are no open sections when we're done. + openSection = sections.pop(); + + if (openSection) + throw new Error('Unclosed section "' + openSection[1] + '" at ' + scanner.pos); + + return nestTokens(squashTokens(tokens)); + } + + /** + * Combines the values of consecutive text tokens in the given `tokens` array + * to a single token. + */ + function squashTokens (tokens) { + var squashedTokens = []; + + var token, lastToken; + for (var i = 0, numTokens = tokens.length; i < numTokens; ++i) { + token = tokens[i]; + + if (token) { + if (token[0] === 'text' && lastToken && lastToken[0] === 'text') { + lastToken[1] += token[1]; + lastToken[3] = token[3]; + } else { + squashedTokens.push(token); + lastToken = token; + } + } + } + + return squashedTokens; + } + + /** + * Forms the given array of `tokens` into a nested tree structure where + * tokens that represent a section have two additional items: 1) an array of + * all tokens that appear in that section and 2) the index in the original + * template that represents the end of that section. + */ + function nestTokens (tokens) { + var nestedTokens = []; + var collector = nestedTokens; + var sections = []; + + var token, section; + for (var i = 0, numTokens = tokens.length; i < numTokens; ++i) { + token = tokens[i]; + + switch (token[0]) { + case '#': + case '^': + collector.push(token); + sections.push(token); + collector = token[4] = []; + break; + case '/': + section = sections.pop(); + section[5] = token[2]; + collector = sections.length > 0 ? sections[sections.length - 1][4] : nestedTokens; + break; + default: + collector.push(token); + } + } + + return nestedTokens; + } + + /** + * A simple string scanner that is used by the template parser to find + * tokens in template strings. + */ + function Scanner (string) { + this.string = string; + this.tail = string; + this.pos = 0; + } + + /** + * Returns `true` if the tail is empty (end of string). + */ + Scanner.prototype.eos = function eos () { + return this.tail === ''; + }; + + /** + * Tries to match the given regular expression at the current position. + * Returns the matched text if it can match, the empty string otherwise. + */ + Scanner.prototype.scan = function scan (re) { + var match = this.tail.match(re); + + if (!match || match.index !== 0) + return ''; + + var string = match[0]; + + this.tail = this.tail.substring(string.length); + this.pos += string.length; + + return string; + }; + + /** + * Skips all text until the given regular expression can be matched. Returns + * the skipped string, which is the entire tail if no match can be made. + */ + Scanner.prototype.scanUntil = function scanUntil (re) { + var index = this.tail.search(re), match; + + switch (index) { + case -1: + match = this.tail; + this.tail = ''; + break; + case 0: + match = ''; + break; + default: + match = this.tail.substring(0, index); + this.tail = this.tail.substring(index); + } + + this.pos += match.length; + + return match; + }; + + /** + * Represents a rendering context by wrapping a view object and + * maintaining a reference to the parent context. + */ + function Context (view, parentContext) { + this.view = view; + this.cache = { '.': this.view }; + this.parent = parentContext; + } + + /** + * Creates a new context using the given view with this context + * as the parent. + */ + Context.prototype.push = function push (view) { + return new Context(view, this); + }; + + /** + * Returns the value of the given name in this context, traversing + * up the context hierarchy if the value is absent in this context's view. + */ + Context.prototype.lookup = function lookup (name) { + var cache = this.cache; + + var value; + if (cache.hasOwnProperty(name)) { + value = cache[name]; + } else { + var context = this, intermediateValue, names, index, lookupHit = false; + + while (context) { + if (name.indexOf('.') > 0) { + intermediateValue = context.view; + names = name.split('.'); + index = 0; + + /** + * Using the dot notion path in `name`, we descend through the + * nested objects. + * + * To be certain that the lookup has been successful, we have to + * check if the last object in the path actually has the property + * we are looking for. We store the result in `lookupHit`. + * + * This is specially necessary for when the value has been set to + * `undefined` and we want to avoid looking up parent contexts. + * + * In the case where dot notation is used, we consider the lookup + * to be successful even if the last "object" in the path is + * not actually an object but a primitive (e.g., a string, or an + * integer), because it is sometimes useful to access a property + * of an autoboxed primitive, such as the length of a string. + **/ + while (intermediateValue != null && index < names.length) { + if (index === names.length - 1) + lookupHit = ( + hasProperty(intermediateValue, names[index]) + || primitiveHasOwnProperty(intermediateValue, names[index]) + ); + + intermediateValue = intermediateValue[names[index++]]; + } + } else { + intermediateValue = context.view[name]; + + /** + * Only checking against `hasProperty`, which always returns `false` if + * `context.view` is not an object. Deliberately omitting the check + * against `primitiveHasOwnProperty` if dot notation is not used. + * + * Consider this example: + * ``` + * Mustache.render("The length of a football field is {{#length}}{{length}}{{/length}}.", {length: "100 yards"}) + * ``` + * + * If we were to check also against `primitiveHasOwnProperty`, as we do + * in the dot notation case, then render call would return: + * + * "The length of a football field is 9." + * + * rather than the expected: + * + * "The length of a football field is 100 yards." + **/ + lookupHit = hasProperty(context.view, name); + } + + if (lookupHit) { + value = intermediateValue; + break; + } + + context = context.parent; + } + + cache[name] = value; + } + + if (isFunction(value)) + value = value.call(this.view); + + return value; + }; + + /** + * A Writer knows how to take a stream of tokens and render them to a + * string, given a context. It also maintains a cache of templates to + * avoid the need to parse the same template twice. + */ + function Writer () { + this.templateCache = { + _cache: {}, + set: function set (key, value) { + this._cache[key] = value; + }, + get: function get (key) { + return this._cache[key]; + }, + clear: function clear () { + this._cache = {}; + } + }; + } + + /** + * Clears all cached templates in this writer. + */ + Writer.prototype.clearCache = function clearCache () { + if (typeof this.templateCache !== 'undefined') { + this.templateCache.clear(); + } + }; + + /** + * Parses and caches the given `template` according to the given `tags` or + * `mustache.tags` if `tags` is omitted, and returns the array of tokens + * that is generated from the parse. + */ + Writer.prototype.parse = function parse (template, tags) { + var cache = this.templateCache; + var cacheKey = template + ':' + (tags || mustache.tags).join(':'); + var isCacheEnabled = typeof cache !== 'undefined'; + var tokens = isCacheEnabled ? cache.get(cacheKey) : undefined; + + if (tokens == undefined) { + tokens = parseTemplate(template, tags); + isCacheEnabled && cache.set(cacheKey, tokens); + } + return tokens; + }; + + /** + * High-level method that is used to render the given `template` with + * the given `view`. + * + * The optional `partials` argument may be an object that contains the + * names and templates of partials that are used in the template. It may + * also be a function that is used to load partial templates on the fly + * that takes a single argument: the name of the partial. + * + * If the optional `tags` argument is given here it must be an array with two + * string values: the opening and closing tags used in the template (e.g. + * [ "<%", "%>" ]). The default is to mustache.tags. + */ + Writer.prototype.render = function render (template, view, partials, tags) { + var tokens = this.parse(template, tags); + var context = (view instanceof Context) ? view : new Context(view, undefined); + return this.renderTokens(tokens, context, partials, template, tags); + }; + + /** + * Low-level method that renders the given array of `tokens` using + * the given `context` and `partials`. + * + * Note: The `originalTemplate` is only ever used to extract the portion + * of the original template that was contained in a higher-order section. + * If the template doesn't use higher-order sections, this argument may + * be omitted. + */ + Writer.prototype.renderTokens = function renderTokens (tokens, context, partials, originalTemplate, tags) { + var buffer = ''; + + var token, symbol, value; + for (var i = 0, numTokens = tokens.length; i < numTokens; ++i) { + value = undefined; + token = tokens[i]; + symbol = token[0]; + + if (symbol === '#') value = this.renderSection(token, context, partials, originalTemplate); + else if (symbol === '^') value = this.renderInverted(token, context, partials, originalTemplate); + else if (symbol === '>') value = this.renderPartial(token, context, partials, tags); + else if (symbol === '&') value = this.unescapedValue(token, context); + else if (symbol === 'name') value = this.escapedValue(token, context); + else if (symbol === 'text') value = this.rawValue(token); + + if (value !== undefined) + buffer += value; + } + + return buffer; + }; + + Writer.prototype.renderSection = function renderSection (token, context, partials, originalTemplate) { + var self = this; + var buffer = ''; + var value = context.lookup(token[1]); + + // This function is used to render an arbitrary template + // in the current context by higher-order sections. + function subRender (template) { + return self.render(template, context, partials); + } + + if (!value) return; + + if (isArray(value)) { + for (var j = 0, valueLength = value.length; j < valueLength; ++j) { + buffer += this.renderTokens(token[4], context.push(value[j]), partials, originalTemplate); + } + } else if (typeof value === 'object' || typeof value === 'string' || typeof value === 'number') { + buffer += this.renderTokens(token[4], context.push(value), partials, originalTemplate); + } else if (isFunction(value)) { + if (typeof originalTemplate !== 'string') + throw new Error('Cannot use higher-order sections without the original template'); + + // Extract the portion of the original template that the section contains. + value = value.call(context.view, originalTemplate.slice(token[3], token[5]), subRender); + + if (value != null) + buffer += value; + } else { + buffer += this.renderTokens(token[4], context, partials, originalTemplate); + } + return buffer; + }; + + Writer.prototype.renderInverted = function renderInverted (token, context, partials, originalTemplate) { + var value = context.lookup(token[1]); + + // Use JavaScript's definition of falsy. Include empty arrays. + // See https://github.com/janl/mustache.js/issues/186 + if (!value || (isArray(value) && value.length === 0)) + return this.renderTokens(token[4], context, partials, originalTemplate); + }; + + Writer.prototype.indentPartial = function indentPartial (partial, indentation, lineHasNonSpace) { + var filteredIndentation = indentation.replace(/[^ \t]/g, ''); + var partialByNl = partial.split('\n'); + for (var i = 0; i < partialByNl.length; i++) { + if (partialByNl[i].length && (i > 0 || !lineHasNonSpace)) { + partialByNl[i] = filteredIndentation + partialByNl[i]; + } + } + return partialByNl.join('\n'); + }; + + Writer.prototype.renderPartial = function renderPartial (token, context, partials, tags) { + if (!partials) return; + + var value = isFunction(partials) ? partials(token[1]) : partials[token[1]]; + if (value != null) { + var lineHasNonSpace = token[6]; + var tagIndex = token[5]; + var indentation = token[4]; + var indentedValue = value; + if (tagIndex == 0 && indentation) { + indentedValue = this.indentPartial(value, indentation, lineHasNonSpace); + } + return this.renderTokens(this.parse(indentedValue, tags), context, partials, indentedValue, tags); + } + }; + + Writer.prototype.unescapedValue = function unescapedValue (token, context) { + var value = context.lookup(token[1]); + if (value != null) + return value; + }; + + Writer.prototype.escapedValue = function escapedValue (token, context) { + var value = context.lookup(token[1]); + if (value != null) + return mustache.escape(value); + }; + + Writer.prototype.rawValue = function rawValue (token) { + return token[1]; + }; + + var mustache = { + name: 'mustache.js', + version: '4.0.1', + tags: [ '{{', '}}' ], + clearCache: undefined, + escape: undefined, + parse: undefined, + render: undefined, + Scanner: undefined, + Context: undefined, + Writer: undefined, + /** + * Allows a user to override the default caching strategy, by providing an + * object with set, get and clear methods. This can also be used to disable + * the cache by setting it to the literal `undefined`. + */ + set templateCache (cache) { + defaultWriter.templateCache = cache; + }, + /** + * Gets the default or overridden caching object from the default writer. + */ + get templateCache () { + return defaultWriter.templateCache; + } + }; + + // All high-level mustache.* functions use this writer. + var defaultWriter = new Writer(); + + /** + * Clears all cached templates in the default writer. + */ + mustache.clearCache = function clearCache () { + return defaultWriter.clearCache(); + }; + + /** + * Parses and caches the given template in the default writer and returns the + * array of tokens it contains. Doing this ahead of time avoids the need to + * parse templates on the fly as they are rendered. + */ + mustache.parse = function parse (template, tags) { + return defaultWriter.parse(template, tags); + }; + + /** + * Renders the `template` with the given `view` and `partials` using the + * default writer. If the optional `tags` argument is given here it must be an + * array with two string values: the opening and closing tags used in the + * template (e.g. [ "<%", "%>" ]). The default is to mustache.tags. + */ + mustache.render = function render (template, view, partials, tags) { + if (typeof template !== 'string') { + throw new TypeError('Invalid template! Template should be a "string" ' + + 'but "' + typeStr(template) + '" was given as the first ' + + 'argument for mustache#render(template, view, partials)'); + } + + return defaultWriter.render(template, view, partials, tags); + }; + + // Export the escaping function so that the user may override it. + // See https://github.com/janl/mustache.js/issues/244 + mustache.escape = escapeHtml; + + // Export these mainly for testing, but also for advanced usage. + mustache.Scanner = Scanner; + mustache.Context = Context; + mustache.Writer = Writer; + + return mustache; + +}))); diff --git a/easy_gantt/assets/javascripts/easy_gantt/libs/raf.js b/easy_gantt/assets/javascripts/easy_gantt/libs/raf.js new file mode 100644 index 0000000..4c5feda --- /dev/null +++ b/easy_gantt/assets/javascripts/easy_gantt/libs/raf.js @@ -0,0 +1,9 @@ +// RAF compatibility +window.requestAnimFrame = (function () { + return window.requestAnimationFrame || + window.webkitRequestAnimationFrame || + window.mozRequestAnimationFrame || + function (callback) { + window.setTimeout(callback, 1000 / 60); + }; +})(); \ No newline at end of file diff --git a/easy_gantt/assets/javascripts/easy_gantt/libs/svg.js b/easy_gantt/assets/javascripts/easy_gantt/libs/svg.js new file mode 100644 index 0000000..06e1e1f --- /dev/null +++ b/easy_gantt/assets/javascripts/easy_gantt/libs/svg.js @@ -0,0 +1,5472 @@ +/*! +* svg.js - A lightweight library for manipulating and animating SVG. +* @version 2.4.0 +* https://svgdotjs.github.io/ +* +* @copyright Wout Fierens +* @license MIT +* +* BUILT: Sun Feb 05 2017 03:51:27 GMT+0100 (CET) +*/; +(function(root, factory) { + if (typeof define === 'function' && define.amd) { + define(function(){ + return factory(root, root.document) + }) + } else if (typeof exports === 'object') { + module.exports = root.document ? factory(root, root.document) : function(w){ return factory(w, w.document) } + } else { + root.SVG = factory(root, root.document) + } +}(typeof window !== "undefined" ? window : this, function(window, document) { + +// The main wrapping element +var SVG = this.SVG = function(element) { + if (SVG.supported) { + element = new SVG.Doc(element) + + if(!SVG.parser.draw) + SVG.prepare() + + return element + } +} + +// Default namespaces +SVG.ns = 'http://www.w3.org/2000/svg' +SVG.xmlns = 'http://www.w3.org/2000/xmlns/' +SVG.xlink = 'http://www.w3.org/1999/xlink' +SVG.svgjs = 'http://svgjs.com/svgjs' + +// Svg support test +SVG.supported = (function() { + return !! document.createElementNS && + !! document.createElementNS(SVG.ns,'svg').createSVGRect +})() + +// Don't bother to continue if SVG is not supported +if (!SVG.supported) return false + +// Element id sequence +SVG.did = 1000 + +// Get next named element id +SVG.eid = function(name) { + return 'Svgjs' + capitalize(name) + (SVG.did++) +} + +// Method for element creation +SVG.create = function(name) { + // create element + var element = document.createElementNS(this.ns, name) + + // apply unique id + element.setAttribute('id', this.eid(name)) + + return element +} + +// Method for extending objects +SVG.extend = function() { + var modules, methods, key, i + + // Get list of modules + modules = [].slice.call(arguments) + + // Get object with extensions + methods = modules.pop() + + for (i = modules.length - 1; i >= 0; i--) + if (modules[i]) + for (key in methods) + modules[i].prototype[key] = methods[key] + + // Make sure SVG.Set inherits any newly added methods + if (SVG.Set && SVG.Set.inherit) + SVG.Set.inherit() +} + +// Invent new element +SVG.invent = function(config) { + // Create element initializer + var initializer = typeof config.create == 'function' ? + config.create : + function() { + this.constructor.call(this, SVG.create(config.create)) + } + + // Inherit prototype + if (config.inherit) + initializer.prototype = new config.inherit + + // Extend with methods + if (config.extend) + SVG.extend(initializer, config.extend) + + // Attach construct method to parent + if (config.construct) + SVG.extend(config.parent || SVG.Container, config.construct) + + return initializer +} + +// Adopt existing svg elements +SVG.adopt = function(node) { + // check for presence of node + if (!node) return null + + // make sure a node isn't already adopted + if (node.instance) return node.instance + + // initialize variables + var element + + // adopt with element-specific settings + if (node.nodeName == 'svg') + element = node.parentNode instanceof SVGElement ? new SVG.Nested : new SVG.Doc + else if (node.nodeName == 'linearGradient') + element = new SVG.Gradient('linear') + else if (node.nodeName == 'radialGradient') + element = new SVG.Gradient('radial') + else if (SVG[capitalize(node.nodeName)]) + element = new SVG[capitalize(node.nodeName)] + else + element = new SVG.Element(node) + + // ensure references + element.type = node.nodeName + element.node = node + node.instance = element + + // SVG.Class specific preparations + if (element instanceof SVG.Doc) + element.namespace().defs() + + // pull svgjs data from the dom (getAttributeNS doesn't work in html5) + element.setData(JSON.parse(node.getAttribute('svgjs:data')) || {}) + + return element +} + +// Initialize parsing element +SVG.prepare = function() { + // Select document body and create invisible svg element + var body = document.getElementsByTagName('body')[0] + , draw = (body ? new SVG.Doc(body) : new SVG.Doc(document.documentElement).nested()).size(2, 0) + + // Create parser object + SVG.parser = { + body: body || document.documentElement + , draw: draw.style('opacity:0;position:fixed;left:100%;top:100%;overflow:hidden') + , poly: draw.polyline().node + , path: draw.path().node + , native: SVG.create('svg') + } +} + +SVG.parser = { + native: SVG.create('svg') +} + +document.addEventListener('DOMContentLoaded', function() { + if(!SVG.parser.draw) + SVG.prepare() +}, false) + +// Storage for regular expressions +SVG.regex = { + // Parse unit value + numberAndUnit: /^([+-]?(\d+(\.\d*)?|\.\d+)(e[+-]?\d+)?)([a-z%]*)$/i + + // Parse hex value +, hex: /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i + + // Parse rgb value +, rgb: /rgb\((\d+),(\d+),(\d+)\)/ + + // Parse reference id +, reference: /#([a-z0-9\-_]+)/i + + // Parse matrix wrapper +, matrix: /matrix\(|\)/g + + // Elements of a matrix +, matrixElements: /,*\s+|,/ + + // Whitespace +, whitespace: /\s/g + + // Test hex value +, isHex: /^#[a-f0-9]{3,6}$/i + + // Test rgb value +, isRgb: /^rgb\(/ + + // Test css declaration +, isCss: /[^:]+:[^;]+;?/ + + // Test for blank string +, isBlank: /^(\s+)?$/ + + // Test for numeric string +, isNumber: /^[+-]?(\d+(\.\d*)?|\.\d+)(e[+-]?\d+)?$/i + + // Test for percent value +, isPercent: /^-?[\d\.]+%$/ + + // Test for image url +, isImage: /\.(jpg|jpeg|png|gif|svg)(\?[^=]+.*)?/i + + // The following regex are used to parse the d attribute of a path + + // Replaces all negative exponents +, negExp: /e\-/gi + + // Replaces all comma +, comma: /,/g + + // Replaces all hyphens +, hyphen: /\-/g + + // Replaces and tests for all path letters +, pathLetters: /[MLHVCSQTAZ]/gi + + // yes we need this one, too +, isPathLetter: /[MLHVCSQTAZ]/i + + // split at whitespaces +, whitespaces: /\s+/ + + // matches X +, X: /X/g +} + +SVG.utils = { + // Map function + map: function(array, block) { + var i + , il = array.length + , result = [] + + for (i = 0; i < il; i++) + result.push(block(array[i])) + + return result + } + + // Filter function +, filter: function(array, block) { + var i + , il = array.length + , result = [] + + for (i = 0; i < il; i++) + if (block(array[i])) + result.push(array[i]) + + return result + } + + // Degrees to radians +, radians: function(d) { + return d % 360 * Math.PI / 180 + } + + // Radians to degrees +, degrees: function(r) { + return r * 180 / Math.PI % 360 + } + +, filterSVGElements: function(nodes) { + return this.filter( nodes, function(el) { return el instanceof SVGElement }) + } + +} + +SVG.defaults = { + // Default attribute values + attrs: { + // fill and stroke + 'fill-opacity': 1 + , 'stroke-opacity': 1 + , 'stroke-width': 0 + , 'stroke-linejoin': 'miter' + , 'stroke-linecap': 'butt' + , fill: '#000000' + , stroke: '#000000' + , opacity: 1 + // position + , x: 0 + , y: 0 + , cx: 0 + , cy: 0 + // size + , width: 0 + , height: 0 + // radius + , r: 0 + , rx: 0 + , ry: 0 + // gradient + , offset: 0 + , 'stop-opacity': 1 + , 'stop-color': '#000000' + // text + , 'font-size': 16 + , 'font-family': 'Helvetica, Arial, sans-serif' + , 'text-anchor': 'start' + } + +} +// Module for color convertions +SVG.Color = function(color) { + var match + + // initialize defaults + this.r = 0 + this.g = 0 + this.b = 0 + + if(!color) return + + // parse color + if (typeof color === 'string') { + if (SVG.regex.isRgb.test(color)) { + // get rgb values + match = SVG.regex.rgb.exec(color.replace(/\s/g,'')) + + // parse numeric values + this.r = parseInt(match[1]) + this.g = parseInt(match[2]) + this.b = parseInt(match[3]) + + } else if (SVG.regex.isHex.test(color)) { + // get hex values + match = SVG.regex.hex.exec(fullHex(color)) + + // parse numeric values + this.r = parseInt(match[1], 16) + this.g = parseInt(match[2], 16) + this.b = parseInt(match[3], 16) + + } + + } else if (typeof color === 'object') { + this.r = color.r + this.g = color.g + this.b = color.b + + } + +} + +SVG.extend(SVG.Color, { + // Default to hex conversion + toString: function() { + return this.toHex() + } + // Build hex value +, toHex: function() { + return '#' + + compToHex(this.r) + + compToHex(this.g) + + compToHex(this.b) + } + // Build rgb value +, toRgb: function() { + return 'rgb(' + [this.r, this.g, this.b].join() + ')' + } + // Calculate true brightness +, brightness: function() { + return (this.r / 255 * 0.30) + + (this.g / 255 * 0.59) + + (this.b / 255 * 0.11) + } + // Make color morphable +, morph: function(color) { + this.destination = new SVG.Color(color) + + return this + } + // Get morphed color at given position +, at: function(pos) { + // make sure a destination is defined + if (!this.destination) return this + + // normalise pos + pos = pos < 0 ? 0 : pos > 1 ? 1 : pos + + // generate morphed color + return new SVG.Color({ + r: ~~(this.r + (this.destination.r - this.r) * pos) + , g: ~~(this.g + (this.destination.g - this.g) * pos) + , b: ~~(this.b + (this.destination.b - this.b) * pos) + }) + } + +}) + +// Testers + +// Test if given value is a color string +SVG.Color.test = function(color) { + color += '' + return SVG.regex.isHex.test(color) + || SVG.regex.isRgb.test(color) +} + +// Test if given value is a rgb object +SVG.Color.isRgb = function(color) { + return color && typeof color.r == 'number' + && typeof color.g == 'number' + && typeof color.b == 'number' +} + +// Test if given value is a color +SVG.Color.isColor = function(color) { + return SVG.Color.isRgb(color) || SVG.Color.test(color) +} +// Module for array conversion +SVG.Array = function(array, fallback) { + array = (array || []).valueOf() + + // if array is empty and fallback is provided, use fallback + if (array.length == 0 && fallback) + array = fallback.valueOf() + + // parse array + this.value = this.parse(array) +} + +SVG.extend(SVG.Array, { + // Make array morphable + morph: function(array) { + this.destination = this.parse(array) + + // normalize length of arrays + if (this.value.length != this.destination.length) { + var lastValue = this.value[this.value.length - 1] + , lastDestination = this.destination[this.destination.length - 1] + + while(this.value.length > this.destination.length) + this.destination.push(lastDestination) + while(this.value.length < this.destination.length) + this.value.push(lastValue) + } + + return this + } + // Clean up any duplicate points +, settle: function() { + // find all unique values + for (var i = 0, il = this.value.length, seen = []; i < il; i++) + if (seen.indexOf(this.value[i]) == -1) + seen.push(this.value[i]) + + // set new value + return this.value = seen + } + // Get morphed array at given position +, at: function(pos) { + // make sure a destination is defined + if (!this.destination) return this + + // generate morphed array + for (var i = 0, il = this.value.length, array = []; i < il; i++) + array.push(this.value[i] + (this.destination[i] - this.value[i]) * pos) + + return new SVG.Array(array) + } + // Convert array to string +, toString: function() { + return this.value.join(' ') + } + // Real value +, valueOf: function() { + return this.value + } + // Parse whitespace separated string +, parse: function(array) { + array = array.valueOf() + + // if already is an array, no need to parse it + if (Array.isArray(array)) return array + + return this.split(array) + } + // Strip unnecessary whitespace +, split: function(string) { + return string.trim().split(/\s+/) + } + // Reverse array +, reverse: function() { + this.value.reverse() + + return this + } + +}) +// Poly points array +SVG.PointArray = function(array, fallback) { + this.constructor.call(this, array, fallback || [[0,0]]) +} + +// Inherit from SVG.Array +SVG.PointArray.prototype = new SVG.Array + +SVG.extend(SVG.PointArray, { + // Convert array to string + toString: function() { + // convert to a poly point string + for (var i = 0, il = this.value.length, array = []; i < il; i++) + array.push(this.value[i].join(',')) + + return array.join(' ') + } + // Convert array to line object +, toLine: function() { + return { + x1: this.value[0][0] + , y1: this.value[0][1] + , x2: this.value[1][0] + , y2: this.value[1][1] + } + } + // Get morphed array at given position +, at: function(pos) { + // make sure a destination is defined + if (!this.destination) return this + + // generate morphed point string + for (var i = 0, il = this.value.length, array = []; i < il; i++) + array.push([ + this.value[i][0] + (this.destination[i][0] - this.value[i][0]) * pos + , this.value[i][1] + (this.destination[i][1] - this.value[i][1]) * pos + ]) + + return new SVG.PointArray(array) + } + // Parse point string +, parse: function(array) { + var points = [] + + array = array.valueOf() + + // if already is an array, no need to parse it + if (Array.isArray(array)) return array + + // parse points + array = array.trim().split(/\s+|,/) + + // validate points - https://svgwg.org/svg2-draft/shapes.html#DataTypePoints + // Odd number of coordinates is an error. In such cases, drop the last odd coordinate. + if (array.length % 2 !== 0) array.pop() + + // wrap points in two-tuples and parse points as floats + for(var i = 0, len = array.length; i < len; i = i + 2) + points.push([ parseFloat(array[i]), parseFloat(array[i+1]) ]) + + return points + } + // Move point string +, move: function(x, y) { + var box = this.bbox() + + // get relative offset + x -= box.x + y -= box.y + + // move every point + if (!isNaN(x) && !isNaN(y)) + for (var i = this.value.length - 1; i >= 0; i--) + this.value[i] = [this.value[i][0] + x, this.value[i][1] + y] + + return this + } + // Resize poly string +, size: function(width, height) { + var i, box = this.bbox() + + // recalculate position of all points according to new size + for (i = this.value.length - 1; i >= 0; i--) { + this.value[i][0] = ((this.value[i][0] - box.x) * width) / box.width + box.x + this.value[i][1] = ((this.value[i][1] - box.y) * height) / box.height + box.y + } + + return this + } + // Get bounding box of points +, bbox: function() { + SVG.parser.poly.setAttribute('points', this.toString()) + + return SVG.parser.poly.getBBox() + } + +}) +// Path points array +SVG.PathArray = function(array, fallback) { + this.constructor.call(this, array, fallback || [['M', 0, 0]]) +} + +// Inherit from SVG.Array +SVG.PathArray.prototype = new SVG.Array + +SVG.extend(SVG.PathArray, { + // Convert array to string + toString: function() { + return arrayToString(this.value) + } + // Move path string +, move: function(x, y) { + // get bounding box of current situation + var box = this.bbox() + + // get relative offset + x -= box.x + y -= box.y + + if (!isNaN(x) && !isNaN(y)) { + // move every point + for (var l, i = this.value.length - 1; i >= 0; i--) { + l = this.value[i][0] + + if (l == 'M' || l == 'L' || l == 'T') { + this.value[i][1] += x + this.value[i][2] += y + + } else if (l == 'H') { + this.value[i][1] += x + + } else if (l == 'V') { + this.value[i][1] += y + + } else if (l == 'C' || l == 'S' || l == 'Q') { + this.value[i][1] += x + this.value[i][2] += y + this.value[i][3] += x + this.value[i][4] += y + + if (l == 'C') { + this.value[i][5] += x + this.value[i][6] += y + } + + } else if (l == 'A') { + this.value[i][6] += x + this.value[i][7] += y + } + + } + } + + return this + } + // Resize path string +, size: function(width, height) { + // get bounding box of current situation + var i, l, box = this.bbox() + + // recalculate position of all points according to new size + for (i = this.value.length - 1; i >= 0; i--) { + l = this.value[i][0] + + if (l == 'M' || l == 'L' || l == 'T') { + this.value[i][1] = ((this.value[i][1] - box.x) * width) / box.width + box.x + this.value[i][2] = ((this.value[i][2] - box.y) * height) / box.height + box.y + + } else if (l == 'H') { + this.value[i][1] = ((this.value[i][1] - box.x) * width) / box.width + box.x + + } else if (l == 'V') { + this.value[i][1] = ((this.value[i][1] - box.y) * height) / box.height + box.y + + } else if (l == 'C' || l == 'S' || l == 'Q') { + this.value[i][1] = ((this.value[i][1] - box.x) * width) / box.width + box.x + this.value[i][2] = ((this.value[i][2] - box.y) * height) / box.height + box.y + this.value[i][3] = ((this.value[i][3] - box.x) * width) / box.width + box.x + this.value[i][4] = ((this.value[i][4] - box.y) * height) / box.height + box.y + + if (l == 'C') { + this.value[i][5] = ((this.value[i][5] - box.x) * width) / box.width + box.x + this.value[i][6] = ((this.value[i][6] - box.y) * height) / box.height + box.y + } + + } else if (l == 'A') { + // resize radii + this.value[i][1] = (this.value[i][1] * width) / box.width + this.value[i][2] = (this.value[i][2] * height) / box.height + + // move position values + this.value[i][6] = ((this.value[i][6] - box.x) * width) / box.width + box.x + this.value[i][7] = ((this.value[i][7] - box.y) * height) / box.height + box.y + } + + } + + return this + } + // Test if the passed path array use the same path data commands as this path array +, equalCommands: function(pathArray) { + var i, il, equalCommands + + pathArray = new SVG.PathArray(pathArray) + + equalCommands = this.value.length === pathArray.value.length + for(i = 0, il = this.value.length; equalCommands && i < il; i++) { + equalCommands = this.value[i][0] === pathArray.value[i][0] + } + + return equalCommands + } + // Make path array morphable +, morph: function(pathArray) { + pathArray = new SVG.PathArray(pathArray) + + if(this.equalCommands(pathArray)) { + this.destination = pathArray + } else { + this.destination = null + } + + return this + } + // Get morphed path array at given position +, at: function(pos) { + // make sure a destination is defined + if (!this.destination) return this + + var sourceArray = this.value + , destinationArray = this.destination.value + , array = [], pathArray = new SVG.PathArray() + , i, il, j, jl + + // Animate has specified in the SVG spec + // See: https://www.w3.org/TR/SVG11/paths.html#PathElement + for (i = 0, il = sourceArray.length; i < il; i++) { + array[i] = [sourceArray[i][0]] + for(j = 1, jl = sourceArray[i].length; j < jl; j++) { + array[i][j] = sourceArray[i][j] + (destinationArray[i][j] - sourceArray[i][j]) * pos + } + // For the two flags of the elliptical arc command, the SVG spec say: + // Flags and booleans are interpolated as fractions between zero and one, with any non-zero value considered to be a value of one/true + // Elliptical arc command as an array followed by corresponding indexes: + // ['A', rx, ry, x-axis-rotation, large-arc-flag, sweep-flag, x, y] + // 0 1 2 3 4 5 6 7 + if(array[i][0] === 'A') { + array[i][4] = +(array[i][4] != 0) + array[i][5] = +(array[i][5] != 0) + } + } + + // Directly modify the value of a path array, this is done this way for performance + pathArray.value = array + return pathArray + } + // Absolutize and parse path to array +, parse: function(array) { + // if it's already a patharray, no need to parse it + if (array instanceof SVG.PathArray) return array.valueOf() + + // prepare for parsing + var i, x0, y0, s, seg, arr + , x = 0 + , y = 0 + , paramCnt = { 'M':2, 'L':2, 'H':1, 'V':1, 'C':6, 'S':4, 'Q':4, 'T':2, 'A':7 } + + if(typeof array == 'string'){ + + array = array + .replace(SVG.regex.negExp, 'X') // replace all negative exponents with certain char + .replace(SVG.regex.pathLetters, ' $& ') // put some room between letters and numbers + .replace(SVG.regex.hyphen, ' -') // add space before hyphen + .replace(SVG.regex.comma, ' ') // unify all spaces + .replace(SVG.regex.X, 'e-') // add back the expoent + .trim() // trim + .split(SVG.regex.whitespaces) // split into array + + // at this place there could be parts like ['3.124.854.32'] because we could not determine the point as seperator till now + // we fix this elements in the next loop + for(i = array.length; --i;){ + if(array[i].indexOf('.') != array[i].lastIndexOf('.')){ + var split = array[i].split('.') // split at the point + var first = [split.shift(), split.shift()].join('.') // join the first number together + array.splice.apply(array, [i, 1].concat(first, split.map(function(el){ return '.'+el }))) // add first and all other entries back to array + } + } + + }else{ + array = array.reduce(function(prev, curr){ + return [].concat.apply(prev, curr) + }, []) + } + + // array now is an array containing all parts of a path e.g. ['M', '0', '0', 'L', '30', '30' ...] + + var arr = [] + + do{ + + // Test if we have a path letter + if(SVG.regex.isPathLetter.test(array[0])){ + s = array[0] + array.shift() + // If last letter was a move command and we got no new, it defaults to [L]ine + }else if(s == 'M'){ + s = 'L' + }else if(s == 'm'){ + s = 'l' + } + + // add path letter as first element + seg = [s.toUpperCase()] + + // push all necessary parameters to segment + for(i = 0; i < paramCnt[seg[0]]; ++i){ + seg.push(parseFloat(array.shift())) + } + + // upper case + if(s == seg[0]){ + + if(s == 'M' || s == 'L' || s == 'C' || s == 'Q' || s == 'S' || s == 'T'){ + x = seg[paramCnt[seg[0]]-1] + y = seg[paramCnt[seg[0]]] + }else if(s == 'V'){ + y = seg[1] + }else if(s == 'H'){ + x = seg[1] + }else if(s == 'A'){ + x = seg[6] + y = seg[7] + } + + // lower case + }else{ + + // convert relative to absolute values + if(s == 'm' || s == 'l' || s == 'c' || s == 's' || s == 'q' || s == 't'){ + + seg[1] += x + seg[2] += y + + if(seg[3] != null){ + seg[3] += x + seg[4] += y + } + + if(seg[5] != null){ + seg[5] += x + seg[6] += y + } + + // move pointer + x = seg[paramCnt[seg[0]]-1] + y = seg[paramCnt[seg[0]]] + + }else if(s == 'v'){ + seg[1] += y + y = seg[1] + }else if(s == 'h'){ + seg[1] += x + x = seg[1] + }else if(s == 'a'){ + seg[6] += x + seg[7] += y + x = seg[6] + y = seg[7] + } + + } + + if(seg[0] == 'M'){ + x0 = x + y0 = y + } + + if(seg[0] == 'Z'){ + x = x0 + y = y0 + } + + arr.push(seg) + + }while(array.length) + + return arr + + } + // Get bounding box of path +, bbox: function() { + SVG.parser.path.setAttribute('d', this.toString()) + + return SVG.parser.path.getBBox() + } + +}) + +// Module for unit convertions +SVG.Number = SVG.invent({ + // Initialize + create: function(value, unit) { + // initialize defaults + this.value = 0 + this.unit = unit || '' + + // parse value + if (typeof value === 'number') { + // ensure a valid numeric value + this.value = isNaN(value) ? 0 : !isFinite(value) ? (value < 0 ? -3.4e+38 : +3.4e+38) : value + + } else if (typeof value === 'string') { + unit = value.match(SVG.regex.numberAndUnit) + + if (unit) { + // make value numeric + this.value = parseFloat(unit[1]) + + // normalize + if (unit[5] == '%') + this.value /= 100 + else if (unit[5] == 's') + this.value *= 1000 + + // store unit + this.unit = unit[5] + } + + } else { + if (value instanceof SVG.Number) { + this.value = value.valueOf() + this.unit = value.unit + } + } + + } + // Add methods +, extend: { + // Stringalize + toString: function() { + return ( + this.unit == '%' ? + ~~(this.value * 1e8) / 1e6: + this.unit == 's' ? + this.value / 1e3 : + this.value + ) + this.unit + } + , toJSON: function() { + return this.toString() + } + , // Convert to primitive + valueOf: function() { + return this.value + } + // Add number + , plus: function(number) { + return new SVG.Number(this + new SVG.Number(number), this.unit) + } + // Subtract number + , minus: function(number) { + return this.plus(-new SVG.Number(number)) + } + // Multiply number + , times: function(number) { + return new SVG.Number(this * new SVG.Number(number), this.unit) + } + // Divide number + , divide: function(number) { + return new SVG.Number(this / new SVG.Number(number), this.unit) + } + // Convert to different unit + , to: function(unit) { + var number = new SVG.Number(this) + + if (typeof unit === 'string') + number.unit = unit + + return number + } + // Make number morphable + , morph: function(number) { + this.destination = new SVG.Number(number) + + return this + } + // Get morphed number at given position + , at: function(pos) { + // Make sure a destination is defined + if (!this.destination) return this + + // Generate new morphed number + return new SVG.Number(this.destination) + .minus(this) + .times(pos) + .plus(this) + } + + } +}) + +SVG.Element = SVG.invent({ + // Initialize node + create: function(node) { + // make stroke value accessible dynamically + this._stroke = SVG.defaults.attrs.stroke + + // initialize data object + this.dom = {} + + // create circular reference + if (this.node = node) { + this.type = node.nodeName + this.node.instance = this + + // store current attribute value + this._stroke = node.getAttribute('stroke') || this._stroke + } + } + + // Add class methods +, extend: { + // Move over x-axis + x: function(x) { + return this.attr('x', x) + } + // Move over y-axis + , y: function(y) { + return this.attr('y', y) + } + // Move by center over x-axis + , cx: function(x) { + return x == null ? this.x() + this.width() / 2 : this.x(x - this.width() / 2) + } + // Move by center over y-axis + , cy: function(y) { + return y == null ? this.y() + this.height() / 2 : this.y(y - this.height() / 2) + } + // Move element to given x and y values + , move: function(x, y) { + return this.x(x).y(y) + } + // Move element by its center + , center: function(x, y) { + return this.cx(x).cy(y) + } + // Set width of element + , width: function(width) { + return this.attr('width', width) + } + // Set height of element + , height: function(height) { + return this.attr('height', height) + } + // Set element size to given width and height + , size: function(width, height) { + var p = proportionalSize(this, width, height) + + return this + .width(new SVG.Number(p.width)) + .height(new SVG.Number(p.height)) + } + // Clone element + , clone: function(parent) { + // clone element and assign new id + var clone = assignNewId(this.node.cloneNode(true)) + + // insert the clone in the given parent or after myself + if(parent) parent.add(clone) + else this.after(clone) + + return clone + } + // Remove element + , remove: function() { + if (this.parent()) + this.parent().removeElement(this) + + return this + } + // Replace element + , replace: function(element) { + this.after(element).remove() + + return element + } + // Add element to given container and return self + , addTo: function(parent) { + return parent.put(this) + } + // Add element to given container and return container + , putIn: function(parent) { + return parent.add(this) + } + // Get / set id + , id: function(id) { + return this.attr('id', id) + } + // Checks whether the given point inside the bounding box of the element + , inside: function(x, y) { + var box = this.bbox() + + return x > box.x + && y > box.y + && x < box.x + box.width + && y < box.y + box.height + } + // Show element + , show: function() { + return this.style('display', '') + } + // Hide element + , hide: function() { + return this.style('display', 'none') + } + // Is element visible? + , visible: function() { + return this.style('display') != 'none' + } + // Return id on string conversion + , toString: function() { + return this.attr('id') + } + // Return array of classes on the node + , classes: function() { + var attr = this.attr('class') + + return attr == null ? [] : attr.trim().split(/\s+/) + } + // Return true if class exists on the node, false otherwise + , hasClass: function(name) { + return this.classes().indexOf(name) != -1 + } + // Add class to the node + , addClass: function(name) { + if (!this.hasClass(name)) { + var array = this.classes() + array.push(name) + this.attr('class', array.join(' ')) + } + + return this + } + // Remove class from the node + , removeClass: function(name) { + if (this.hasClass(name)) { + this.attr('class', this.classes().filter(function(c) { + return c != name + }).join(' ')) + } + + return this + } + // Toggle the presence of a class on the node + , toggleClass: function(name) { + return this.hasClass(name) ? this.removeClass(name) : this.addClass(name) + } + // Get referenced element form attribute value + , reference: function(attr) { + return SVG.get(this.attr(attr)) + } + // Returns the parent element instance + , parent: function(type) { + var parent = this + + // check for parent + if(!parent.node.parentNode) return null + + // get parent element + parent = SVG.adopt(parent.node.parentNode) + + if(!type) return parent + + // loop trough ancestors if type is given + while(parent && parent.node instanceof SVGElement){ + if(typeof type === 'string' ? parent.matches(type) : parent instanceof type) return parent + parent = SVG.adopt(parent.node.parentNode) + } + } + // Get parent document + , doc: function() { + return this instanceof SVG.Doc ? this : this.parent(SVG.Doc) + } + // return array of all ancestors of given type up to the root svg + , parents: function(type) { + var parents = [], parent = this + + do{ + parent = parent.parent(type) + if(!parent || !parent.node) break + + parents.push(parent) + } while(parent.parent) + + return parents + } + // matches the element vs a css selector + , matches: function(selector){ + return matches(this.node, selector) + } + // Returns the svg node to call native svg methods on it + , native: function() { + return this.node + } + // Import raw svg + , svg: function(svg) { + // create temporary holder + var well = document.createElement('svg') + + // act as a setter if svg is given + if (svg && this instanceof SVG.Parent) { + // dump raw svg + well.innerHTML = '' + svg.replace(/\n/, '').replace(/<(\w+)([^<]+?)\/>/g, '<$1$2>') + '' + + // transplant nodes + for (var i = 0, il = well.firstChild.childNodes.length; i < il; i++) + this.node.appendChild(well.firstChild.firstChild) + + // otherwise act as a getter + } else { + // create a wrapping svg element in case of partial content + well.appendChild(svg = document.createElement('svg')) + + // write svgjs data to the dom + this.writeDataToDom() + + // insert a copy of this node + svg.appendChild(this.node.cloneNode(true)) + + // return target element + return well.innerHTML.replace(/^/, '').replace(/<\/svg>$/, '') + } + + return this + } + // write svgjs data to the dom + , writeDataToDom: function() { + + // dump variables recursively + if(this.each || this.lines){ + var fn = this.each ? this : this.lines(); + fn.each(function(){ + this.writeDataToDom() + }) + } + + // remove previously set data + this.node.removeAttribute('svgjs:data') + + if(Object.keys(this.dom).length) + this.node.setAttribute('svgjs:data', JSON.stringify(this.dom)) // see #428 + + return this + } + // set given data to the elements data property + , setData: function(o){ + this.dom = o + return this + } + , is: function(obj){ + return is(this, obj) + } + } +}) + +SVG.easing = { + '-': function(pos){return pos} +, '<>':function(pos){return -Math.cos(pos * Math.PI) / 2 + 0.5} +, '>': function(pos){return Math.sin(pos * Math.PI / 2)} +, '<': function(pos){return -Math.cos(pos * Math.PI / 2) + 1} +} + +SVG.morph = function(pos){ + return function(from, to) { + return new SVG.MorphObj(from, to).at(pos) + } +} + +SVG.Situation = SVG.invent({ + + create: function(o){ + this.init = false + this.reversed = false + this.reversing = false + + this.duration = new SVG.Number(o.duration).valueOf() + this.delay = new SVG.Number(o.delay).valueOf() + + this.start = +new Date() + this.delay + this.finish = this.start + this.duration + this.ease = o.ease + + // this.loop is incremented from 0 to this.loops + // it is also incremented when in an infinite loop (when this.loops is true) + this.loop = 0 + this.loops = false + + this.animations = { + // functionToCall: [list of morphable objects] + // e.g. move: [SVG.Number, SVG.Number] + } + + this.attrs = { + // holds all attributes which are not represented from a function svg.js provides + // e.g. someAttr: SVG.Number + } + + this.styles = { + // holds all styles which should be animated + // e.g. fill-color: SVG.Color + } + + this.transforms = [ + // holds all transformations as transformation objects + // e.g. [SVG.Rotate, SVG.Translate, SVG.Matrix] + ] + + this.once = { + // functions to fire at a specific position + // e.g. "0.5": function foo(){} + } + + } + +}) + + +SVG.FX = SVG.invent({ + + create: function(element) { + this._target = element + this.situations = [] + this.active = false + this.situation = null + this.paused = false + this.lastPos = 0 + this.pos = 0 + // The absolute position of an animation is its position in the context of its complete duration (including delay and loops) + // When performing a delay, absPos is below 0 and when performing a loop, its value is above 1 + this.absPos = 0 + this._speed = 1 + } + +, extend: { + + /** + * sets or returns the target of this animation + * @param o object || number In case of Object it holds all parameters. In case of number its the duration of the animation + * @param ease function || string Function which should be used for easing or easing keyword + * @param delay Number indicating the delay before the animation starts + * @return target || this + */ + animate: function(o, ease, delay){ + + if(typeof o == 'object'){ + ease = o.ease + delay = o.delay + o = o.duration + } + + var situation = new SVG.Situation({ + duration: o || 1000, + delay: delay || 0, + ease: SVG.easing[ease || '-'] || ease + }) + + this.queue(situation) + + return this + } + + /** + * sets a delay before the next element of the queue is called + * @param delay Duration of delay in milliseconds + * @return this.target() + */ + , delay: function(delay){ + // The delay is performed by an empty situation with its duration + // attribute set to the duration of the delay + var situation = new SVG.Situation({ + duration: delay, + delay: 0, + ease: SVG.easing['-'] + }) + + return this.queue(situation) + } + + /** + * sets or returns the target of this animation + * @param null || target SVG.Element which should be set as new target + * @return target || this + */ + , target: function(target){ + if(target && target instanceof SVG.Element){ + this._target = target + return this + } + + return this._target + } + + // returns the absolute position at a given time + , timeToAbsPos: function(timestamp){ + return (timestamp - this.situation.start) / (this.situation.duration/this._speed) + } + + // returns the timestamp from a given absolute positon + , absPosToTime: function(absPos){ + return this.situation.duration/this._speed * absPos + this.situation.start + } + + // starts the animationloop + , startAnimFrame: function(){ + this.stopAnimFrame() + this.animationFrame = requestAnimationFrame(function(){ this.step() }.bind(this)) + } + + // cancels the animationframe + , stopAnimFrame: function(){ + cancelAnimationFrame(this.animationFrame) + } + + // kicks off the animation - only does something when the queue is currently not active and at least one situation is set + , start: function(){ + // dont start if already started + if(!this.active && this.situation){ + this.active = true + this.startCurrent() + } + + return this + } + + // start the current situation + , startCurrent: function(){ + this.situation.start = +new Date + this.situation.delay/this._speed + this.situation.finish = this.situation.start + this.situation.duration/this._speed + return this.initAnimations().step() + } + + /** + * adds a function / Situation to the animation queue + * @param fn function / situation to add + * @return this + */ + , queue: function(fn){ + if(typeof fn == 'function' || fn instanceof SVG.Situation) + this.situations.push(fn) + + if(!this.situation) this.situation = this.situations.shift() + + return this + } + + /** + * pulls next element from the queue and execute it + * @return this + */ + , dequeue: function(){ + // stop current animation + this.situation && this.situation.stop && this.situation.stop() + + // get next animation from queue + this.situation = this.situations.shift() + + if(this.situation){ + if(this.situation instanceof SVG.Situation) { + this.startCurrent() + } else { + // If it is not a SVG.Situation, then it is a function, we execute it + this.situation.call(this) + } + } + + return this + } + + // updates all animations to the current state of the element + // this is important when one property could be changed from another property + , initAnimations: function() { + var i + var s = this.situation + + if(s.init) return this + + for(i in s.animations){ + + if(i == 'viewbox'){ + s.animations[i] = this.target().viewbox().morph(s.animations[i]) + }else{ + + // TODO: this is not a clean clone of the array. We may have some unchecked references + s.animations[i].value = (i == 'plot' ? this.target().array().value : this.target()[i]()) + + // sometimes we get back an object and not the real value, fix this + if(s.animations[i].value.value){ + s.animations[i].value = s.animations[i].value.value + } + + if(s.animations[i].relative) + s.animations[i].destination.value = s.animations[i].destination.value + s.animations[i].value + + } + + } + + for(i in s.attrs){ + if(s.attrs[i] instanceof SVG.Color){ + var color = new SVG.Color(this.target().attr(i)) + s.attrs[i].r = color.r + s.attrs[i].g = color.g + s.attrs[i].b = color.b + }else{ + s.attrs[i].value = this.target().attr(i)// + s.attrs[i].value + } + } + + for(i in s.styles){ + s.styles[i].value = this.target().style(i) + } + + s.initialTransformation = this.target().matrixify() + + s.init = true + return this + } + , clearQueue: function(){ + this.situations = [] + return this + } + , clearCurrent: function(){ + this.situation = null + return this + } + /** stops the animation immediately + * @param jumpToEnd A Boolean indicating whether to complete the current animation immediately. + * @param clearQueue A Boolean indicating whether to remove queued animation as well. + * @return this + */ + , stop: function(jumpToEnd, clearQueue){ + if(!this.active) this.start() + + if(clearQueue){ + this.clearQueue() + } + + this.active = false + + if(jumpToEnd && this.situation){ + this.atEnd() + } + + this.stopAnimFrame() + + return this.clearCurrent() + } + + /** resets the element to the state where the current element has started + * @return this + */ + , reset: function(){ + if(this.situation){ + var temp = this.situation + this.stop() + this.situation = temp + this.atStart() + } + return this + } + + // Stop the currently-running animation, remove all queued animations, and complete all animations for the element. + , finish: function(){ + + this.stop(true, false) + + while(this.dequeue().situation && this.stop(true, false)); + + this.clearQueue().clearCurrent() + + return this + } + + // set the internal animation pointer at the start position, before any loops, and updates the visualisation + , atStart: function() { + return this.at(0, true) + } + + // set the internal animation pointer at the end position, after all the loops, and updates the visualisation + , atEnd: function() { + if (this.situation.loops === true) { + // If in a infinite loop, we end the current iteration + return this.at(this.situation.loop+1, true) + } else if(typeof this.situation.loops == 'number') { + // If performing a finite number of loops, we go after all the loops + return this.at(this.situation.loops, true) + } else { + // If no loops, we just go at the end + return this.at(1, true) + } + } + + // set the internal animation pointer to the specified position and updates the visualisation + // if isAbsPos is true, pos is treated as an absolute position + , at: function(pos, isAbsPos){ + var durDivSpd = this.situation.duration/this._speed + + this.absPos = pos + // If pos is not an absolute position, we convert it into one + if (!isAbsPos) { + if (this.situation.reversed) this.absPos = 1 - this.absPos + this.absPos += this.situation.loop + } + + this.situation.start = +new Date - this.absPos * durDivSpd + this.situation.finish = this.situation.start + durDivSpd + + return this.step(true) + } + + /** + * sets or returns the speed of the animations + * @param speed null || Number The new speed of the animations + * @return Number || this + */ + , speed: function(speed){ + if (speed === 0) return this.pause() + + if (speed) { + this._speed = speed + // We use an absolute position here so that speed can affect the delay before the animation + return this.at(this.absPos, true) + } else return this._speed + } + + // Make loopable + , loop: function(times, reverse) { + var c = this.last() + + // store total loops + c.loops = (times != null) ? times : true + c.loop = 0 + + if(reverse) c.reversing = true + return this + } + + // pauses the animation + , pause: function(){ + this.paused = true + this.stopAnimFrame() + + return this + } + + // unpause the animation + , play: function(){ + if(!this.paused) return this + this.paused = false + // We use an absolute position here so that the delay before the animation can be paused + return this.at(this.absPos, true) + } + + /** + * toggle or set the direction of the animation + * true sets direction to backwards while false sets it to forwards + * @param reversed Boolean indicating whether to reverse the animation or not (default: toggle the reverse status) + * @return this + */ + , reverse: function(reversed){ + var c = this.last() + + if(typeof reversed == 'undefined') c.reversed = !c.reversed + else c.reversed = reversed + + return this + } + + + /** + * returns a float from 0-1 indicating the progress of the current animation + * @param eased Boolean indicating whether the returned position should be eased or not + * @return number + */ + , progress: function(easeIt){ + return easeIt ? this.situation.ease(this.pos) : this.pos + } + + /** + * adds a callback function which is called when the current animation is finished + * @param fn Function which should be executed as callback + * @return number + */ + , after: function(fn){ + var c = this.last() + , wrapper = function wrapper(e){ + if(e.detail.situation == c){ + fn.call(this, c) + this.off('finished.fx', wrapper) // prevent memory leak + } + } + + this.target().on('finished.fx', wrapper) + return this + } + + // adds a callback which is called whenever one animation step is performed + , during: function(fn){ + var c = this.last() + , wrapper = function(e){ + if(e.detail.situation == c){ + fn.call(this, e.detail.pos, SVG.morph(e.detail.pos), e.detail.eased, c) + } + } + + // see above + this.target().off('during.fx', wrapper).on('during.fx', wrapper) + + return this.after(function(){ + this.off('during.fx', wrapper) + }) + } + + // calls after ALL animations in the queue are finished + , afterAll: function(fn){ + var wrapper = function wrapper(e){ + fn.call(this) + this.off('allfinished.fx', wrapper) + } + + // see above + this.target().off('allfinished.fx', wrapper).on('allfinished.fx', wrapper) + return this + } + + // calls on every animation step for all animations + , duringAll: function(fn){ + var wrapper = function(e){ + fn.call(this, e.detail.pos, SVG.morph(e.detail.pos), e.detail.eased, e.detail.situation) + } + + this.target().off('during.fx', wrapper).on('during.fx', wrapper) + + return this.afterAll(function(){ + this.off('during.fx', wrapper) + }) + } + + , last: function(){ + return this.situations.length ? this.situations[this.situations.length-1] : this.situation + } + + // adds one property to the animations + , add: function(method, args, type){ + this.last()[type || 'animations'][method] = args + setTimeout(function(){this.start()}.bind(this), 0) + return this + } + + /** perform one step of the animation + * @param ignoreTime Boolean indicating whether to ignore time and use position directly or recalculate position based on time + * @return this + */ + , step: function(ignoreTime){ + + // convert current time to an absolute position + if(!ignoreTime) this.absPos = this.timeToAbsPos(+new Date) + + // This part convert an absolute position to a position + if(this.situation.loops !== false) { + var absPos, absPosInt, lastLoop + + // If the absolute position is below 0, we just treat it as if it was 0 + absPos = Math.max(this.absPos, 0) + absPosInt = Math.floor(absPos) + + if(this.situation.loops === true || absPosInt < this.situation.loops) { + this.pos = absPos - absPosInt + lastLoop = this.situation.loop + this.situation.loop = absPosInt + } else { + this.absPos = this.situation.loops + this.pos = 1 + // The -1 here is because we don't want to toggle reversed when all the loops have been completed + lastLoop = this.situation.loop - 1 + this.situation.loop = this.situation.loops + } + + if(this.situation.reversing) { + // Toggle reversed if an odd number of loops as occured since the last call of step + this.situation.reversed = this.situation.reversed != Boolean((this.situation.loop - lastLoop) % 2) + } + + } else { + // If there are no loop, the absolute position must not be above 1 + this.absPos = Math.min(this.absPos, 1) + this.pos = this.absPos + } + + // while the absolute position can be below 0, the position must not be below 0 + if(this.pos < 0) this.pos = 0 + + if(this.situation.reversed) this.pos = 1 - this.pos + + + // apply easing + var eased = this.situation.ease(this.pos) + + // call once-callbacks + for(var i in this.situation.once){ + if(i > this.lastPos && i <= eased){ + this.situation.once[i].call(this.target(), this.pos, eased) + delete this.situation.once[i] + } + } + + // fire during callback with position, eased position and current situation as parameter + if(this.active) this.target().fire('during', {pos: this.pos, eased: eased, fx: this, situation: this.situation}) + + // the user may call stop or finish in the during callback + // so make sure that we still have a valid situation + if(!this.situation){ + return this + } + + // apply the actual animation to every property + this.eachAt() + + // do final code when situation is finished + if((this.pos == 1 && !this.situation.reversed) || (this.situation.reversed && this.pos == 0)){ + + // stop animation callback + this.stopAnimFrame() + + // fire finished callback with current situation as parameter + this.target().fire('finished', {fx:this, situation: this.situation}) + + if(!this.situations.length){ + this.target().fire('allfinished') + this.target().off('.fx') // there shouldnt be any binding left, but to make sure... + this.active = false + } + + // start next animation + if(this.active) this.dequeue() + else this.clearCurrent() + + }else if(!this.paused && this.active){ + // we continue animating when we are not at the end + this.startAnimFrame() + } + + // save last eased position for once callback triggering + this.lastPos = eased + return this + + } + + // calculates the step for every property and calls block with it + , eachAt: function(){ + var i, at, self = this, target = this.target(), s = this.situation + + // apply animations which can be called trough a method + for(i in s.animations){ + + at = [].concat(s.animations[i]).map(function(el){ + return typeof el !== 'string' && el.at ? el.at(s.ease(self.pos), self.pos) : el + }) + + target[i].apply(target, at) + + } + + // apply animation which has to be applied with attr() + for(i in s.attrs){ + + at = [i].concat(s.attrs[i]).map(function(el){ + return typeof el !== 'string' && el.at ? el.at(s.ease(self.pos), self.pos) : el + }) + + target.attr.apply(target, at) + + } + + // apply animation which has to be applied with style() + for(i in s.styles){ + + at = [i].concat(s.styles[i]).map(function(el){ + return typeof el !== 'string' && el.at ? el.at(s.ease(self.pos), self.pos) : el + }) + + target.style.apply(target, at) + + } + + // animate initialTransformation which has to be chained + if(s.transforms.length){ + + // get initial initialTransformation + at = s.initialTransformation + for(i = 0, len = s.transforms.length; i < len; i++){ + + // get next transformation in chain + var a = s.transforms[i] + + // multiply matrix directly + if(a instanceof SVG.Matrix){ + + if(a.relative){ + at = at.multiply(new SVG.Matrix().morph(a).at(s.ease(this.pos))) + }else{ + at = at.morph(a).at(s.ease(this.pos)) + } + continue + } + + // when transformation is absolute we have to reset the needed transformation first + if(!a.relative) + a.undo(at.extract()) + + // and reapply it after + at = at.multiply(a.at(s.ease(this.pos))) + + } + + // set new matrix on element + target.matrix(at) + } + + return this + + } + + + // adds an once-callback which is called at a specific position and never again + , once: function(pos, fn, isEased){ + + if(!isEased)pos = this.situation.ease(pos) + + this.situation.once[pos] = fn + + return this + } + + } + +, parent: SVG.Element + + // Add method to parent elements +, construct: { + // Get fx module or create a new one, then animate with given duration and ease + animate: function(o, ease, delay) { + return (this.fx || (this.fx = new SVG.FX(this))).animate(o, ease, delay) + } + , delay: function(delay){ + return (this.fx || (this.fx = new SVG.FX(this))).delay(delay) + } + , stop: function(jumpToEnd, clearQueue) { + if (this.fx) + this.fx.stop(jumpToEnd, clearQueue) + + return this + } + , finish: function() { + if (this.fx) + this.fx.finish() + + return this + } + // Pause current animation + , pause: function() { + if (this.fx) + this.fx.pause() + + return this + } + // Play paused current animation + , play: function() { + if (this.fx) + this.fx.play() + + return this + } + // Set/Get the speed of the animations + , speed: function(speed) { + if (this.fx) + if (speed == null) + return this.fx.speed() + else + this.fx.speed(speed) + + return this + } + } + +}) + +// MorphObj is used whenever no morphable object is given +SVG.MorphObj = SVG.invent({ + + create: function(from, to){ + // prepare color for morphing + if(SVG.Color.isColor(to)) return new SVG.Color(from).morph(to) + // prepare number for morphing + if(SVG.regex.numberAndUnit.test(to)) return new SVG.Number(from).morph(to) + + // prepare for plain morphing + this.value = 0 + this.destination = to + } + +, extend: { + at: function(pos, real){ + return real < 1 ? this.value : this.destination + }, + + valueOf: function(){ + return this.value + } + } + +}) + +SVG.extend(SVG.FX, { + // Add animatable attributes + attr: function(a, v, relative) { + // apply attributes individually + if (typeof a == 'object') { + for (var key in a) + this.attr(key, a[key]) + + } else { + // the MorphObj takes care about the right function used + this.add(a, new SVG.MorphObj(null, v), 'attrs') + } + + return this + } + // Add animatable styles +, style: function(s, v) { + if (typeof s == 'object') + for (var key in s) + this.style(key, s[key]) + + else + this.add(s, new SVG.MorphObj(null, v), 'styles') + + return this + } + // Animatable x-axis +, x: function(x, relative) { + if(this.target() instanceof SVG.G){ + this.transform({x:x}, relative) + return this + } + + var num = new SVG.Number().morph(x) + num.relative = relative + return this.add('x', num) + } + // Animatable y-axis +, y: function(y, relative) { + if(this.target() instanceof SVG.G){ + this.transform({y:y}, relative) + return this + } + + var num = new SVG.Number().morph(y) + num.relative = relative + return this.add('y', num) + } + // Animatable center x-axis +, cx: function(x) { + return this.add('cx', new SVG.Number().morph(x)) + } + // Animatable center y-axis +, cy: function(y) { + return this.add('cy', new SVG.Number().morph(y)) + } + // Add animatable move +, move: function(x, y) { + return this.x(x).y(y) + } + // Add animatable center +, center: function(x, y) { + return this.cx(x).cy(y) + } + // Add animatable size +, size: function(width, height) { + if (this.target() instanceof SVG.Text) { + // animate font size for Text elements + this.attr('font-size', width) + + } else { + // animate bbox based size for all other elements + var box + + if(!width || !height){ + box = this.target().bbox() + } + + if(!width){ + width = box.width / box.height * height + } + + if(!height){ + height = box.height / box.width * width + } + + this.add('width' , new SVG.Number().morph(width)) + .add('height', new SVG.Number().morph(height)) + + } + + return this + } + // Add animatable plot +, plot: function(p) { + return this.add('plot', this.target().array().morph(p)) + } + // Add leading method +, leading: function(value) { + return this.target().leading ? + this.add('leading', new SVG.Number().morph(value)) : + this + } + // Add animatable viewbox +, viewbox: function(x, y, width, height) { + if (this.target() instanceof SVG.Container) { + this.add('viewbox', new SVG.ViewBox(x, y, width, height)) + } + + return this + } +, update: function(o) { + if (this.target() instanceof SVG.Stop) { + if (typeof o == 'number' || o instanceof SVG.Number) { + return this.update({ + offset: arguments[0] + , color: arguments[1] + , opacity: arguments[2] + }) + } + + if (o.opacity != null) this.attr('stop-opacity', o.opacity) + if (o.color != null) this.attr('stop-color', o.color) + if (o.offset != null) this.attr('offset', o.offset) + } + + return this + } +}) + +SVG.BBox = SVG.invent({ + // Initialize + create: function(element) { + // get values if element is given + if (element) { + var box + + // yes this is ugly, but Firefox can be a bitch when it comes to elements that are not yet rendered + try { + + // the element is NOT in the dom, throw error + if(!document.documentElement.contains(element.node)) throw new Exception('Element not in the dom') + + // find native bbox + box = element.node.getBBox() + } catch(e) { + if(element instanceof SVG.Shape){ + var clone = element.clone(SVG.parser.draw).show() + box = clone.bbox() + clone.remove() + }else{ + box = { + x: element.node.clientLeft + , y: element.node.clientTop + , width: element.node.clientWidth + , height: element.node.clientHeight + } + } + } + + // plain x and y + this.x = box.x + this.y = box.y + + // plain width and height + this.width = box.width + this.height = box.height + } + + // add center, right and bottom + fullBox(this) + } + + // Define Parent +, parent: SVG.Element + + // Constructor +, construct: { + // Get bounding box + bbox: function() { + return new SVG.BBox(this) + } + } + +}) + +SVG.TBox = SVG.invent({ + // Initialize + create: function(element) { + // get values if element is given + if (element) { + var t = element.ctm().extract() + , box = element.bbox() + + // width and height including transformations + this.width = box.width * t.scaleX + this.height = box.height * t.scaleY + + // x and y including transformations + this.x = box.x + t.x + this.y = box.y + t.y + } + + // add center, right and bottom + fullBox(this) + } + + // Define Parent +, parent: SVG.Element + + // Constructor +, construct: { + // Get transformed bounding box + tbox: function() { + return new SVG.TBox(this) + } + } + +}) + + +SVG.RBox = SVG.invent({ + // Initialize + create: function(element) { + if (element) { + var e = element.doc().parent() + , box = element.node.getBoundingClientRect() + , zoom = 1 + + // get screen offset + this.x = box.left + this.y = box.top + + // subtract parent offset + this.x -= e.offsetLeft + this.y -= e.offsetTop + + while (e = e.offsetParent) { + this.x -= e.offsetLeft + this.y -= e.offsetTop + } + + // calculate cumulative zoom from svg documents + e = element + while (e.parent && (e = e.parent())) { + if (e.viewbox) { + zoom *= e.viewbox().zoom + this.x -= e.x() || 0 + this.y -= e.y() || 0 + } + } + + // recalculate viewbox distortion + this.width = box.width /= zoom + this.height = box.height /= zoom + } + + // add center, right and bottom + fullBox(this) + + // offset by window scroll position, because getBoundingClientRect changes when window is scrolled + this.x += window.pageXOffset + this.y += window.pageYOffset + } + + // define Parent +, parent: SVG.Element + + // Constructor +, construct: { + // Get rect box + rbox: function() { + return new SVG.RBox(this) + } + } + +}) + +// Add universal merge method +;[SVG.BBox, SVG.TBox, SVG.RBox].forEach(function(c) { + + SVG.extend(c, { + // Merge rect box with another, return a new instance + merge: function(box) { + var b = new c() + + // merge boxes + b.x = Math.min(this.x, box.x) + b.y = Math.min(this.y, box.y) + b.width = Math.max(this.x + this.width, box.x + box.width) - b.x + b.height = Math.max(this.y + this.height, box.y + box.height) - b.y + + return fullBox(b) + } + + }) + +}) + +SVG.Matrix = SVG.invent({ + // Initialize + create: function(source) { + var i, base = arrayToMatrix([1, 0, 0, 1, 0, 0]) + + // ensure source as object + source = source instanceof SVG.Element ? + source.matrixify() : + typeof source === 'string' ? + stringToMatrix(source) : + arguments.length == 6 ? + arrayToMatrix([].slice.call(arguments)) : + typeof source === 'object' ? + source : base + + // merge source + for (i = abcdef.length - 1; i >= 0; --i) + this[abcdef[i]] = source && typeof source[abcdef[i]] === 'number' ? + source[abcdef[i]] : base[abcdef[i]] + } + + // Add methods +, extend: { + // Extract individual transformations + extract: function() { + // find delta transform points + var px = deltaTransformPoint(this, 0, 1) + , py = deltaTransformPoint(this, 1, 0) + , skewX = 180 / Math.PI * Math.atan2(px.y, px.x) - 90 + + return { + // translation + x: this.e + , y: this.f + , transformedX:(this.e * Math.cos(skewX * Math.PI / 180) + this.f * Math.sin(skewX * Math.PI / 180)) / Math.sqrt(this.a * this.a + this.b * this.b) + , transformedY:(this.f * Math.cos(skewX * Math.PI / 180) + this.e * Math.sin(-skewX * Math.PI / 180)) / Math.sqrt(this.c * this.c + this.d * this.d) + // skew + , skewX: -skewX + , skewY: 180 / Math.PI * Math.atan2(py.y, py.x) + // scale + , scaleX: Math.sqrt(this.a * this.a + this.b * this.b) + , scaleY: Math.sqrt(this.c * this.c + this.d * this.d) + // rotation + , rotation: skewX + , a: this.a + , b: this.b + , c: this.c + , d: this.d + , e: this.e + , f: this.f + , matrix: new SVG.Matrix(this) + } + } + // Clone matrix + , clone: function() { + return new SVG.Matrix(this) + } + // Morph one matrix into another + , morph: function(matrix) { + // store new destination + this.destination = new SVG.Matrix(matrix) + + return this + } + // Get morphed matrix at a given position + , at: function(pos) { + // make sure a destination is defined + if (!this.destination) return this + + // calculate morphed matrix at a given position + var matrix = new SVG.Matrix({ + a: this.a + (this.destination.a - this.a) * pos + , b: this.b + (this.destination.b - this.b) * pos + , c: this.c + (this.destination.c - this.c) * pos + , d: this.d + (this.destination.d - this.d) * pos + , e: this.e + (this.destination.e - this.e) * pos + , f: this.f + (this.destination.f - this.f) * pos + }) + + // process parametric rotation if present + if (this.param && this.param.to) { + // calculate current parametric position + var param = { + rotation: this.param.from.rotation + (this.param.to.rotation - this.param.from.rotation) * pos + , cx: this.param.from.cx + , cy: this.param.from.cy + } + + // rotate matrix + matrix = matrix.rotate( + (this.param.to.rotation - this.param.from.rotation * 2) * pos + , param.cx + , param.cy + ) + + // store current parametric values + matrix.param = param + } + + return matrix + } + // Multiplies by given matrix + , multiply: function(matrix) { + return new SVG.Matrix(this.native().multiply(parseMatrix(matrix).native())) + } + // Inverses matrix + , inverse: function() { + return new SVG.Matrix(this.native().inverse()) + } + // Translate matrix + , translate: function(x, y) { + return new SVG.Matrix(this.native().translate(x || 0, y || 0)) + } + // Scale matrix + , scale: function(x, y, cx, cy) { + // support uniformal scale + if (arguments.length == 1) { + y = x + } else if (arguments.length == 3) { + cy = cx + cx = y + y = x + } + + return this.around(cx, cy, new SVG.Matrix(x, 0, 0, y, 0, 0)) + } + // Rotate matrix + , rotate: function(r, cx, cy) { + // convert degrees to radians + r = SVG.utils.radians(r) + + return this.around(cx, cy, new SVG.Matrix(Math.cos(r), Math.sin(r), -Math.sin(r), Math.cos(r), 0, 0)) + } + // Flip matrix on x or y, at a given offset + , flip: function(a, o) { + return a == 'x' ? this.scale(-1, 1, o, 0) : this.scale(1, -1, 0, o) + } + // Skew + , skew: function(x, y, cx, cy) { + // support uniformal skew + if (arguments.length == 1) { + y = x + } else if (arguments.length == 3) { + cy = cx + cx = y + y = x + } + + // convert degrees to radians + x = SVG.utils.radians(x) + y = SVG.utils.radians(y) + + return this.around(cx, cy, new SVG.Matrix(1, Math.tan(y), Math.tan(x), 1, 0, 0)) + } + // SkewX + , skewX: function(x, cx, cy) { + return this.skew(x, 0, cx, cy) + } + // SkewY + , skewY: function(y, cx, cy) { + return this.skew(0, y, cx, cy) + } + // Transform around a center point + , around: function(cx, cy, matrix) { + return this + .multiply(new SVG.Matrix(1, 0, 0, 1, cx || 0, cy || 0)) + .multiply(matrix) + .multiply(new SVG.Matrix(1, 0, 0, 1, -cx || 0, -cy || 0)) + } + // Convert to native SVGMatrix + , native: function() { + // create new matrix + var matrix = SVG.parser.native.createSVGMatrix() + + // update with current values + for (var i = abcdef.length - 1; i >= 0; i--) + matrix[abcdef[i]] = this[abcdef[i]] + + return matrix + } + // Convert matrix to string + , toString: function() { + return 'matrix(' + this.a + ',' + this.b + ',' + this.c + ',' + this.d + ',' + this.e + ',' + this.f + ')' + } + } + + // Define parent +, parent: SVG.Element + + // Add parent method +, construct: { + // Get current matrix + ctm: function() { + return new SVG.Matrix(this.node.getCTM()) + }, + // Get current screen matrix + screenCTM: function() { + return new SVG.Matrix(this.node.getScreenCTM()) + } + + } + +}) + +SVG.Point = SVG.invent({ + // Initialize + create: function(x,y) { + var i, source, base = {x:0, y:0} + + // ensure source as object + source = Array.isArray(x) ? + {x:x[0], y:x[1]} : + typeof x === 'object' ? + {x:x.x, y:x.y} : + x != null ? + {x:x, y:(y != null ? y : x)} : base // If y has no value, then x is used has its value + + // merge source + this.x = source.x + this.y = source.y + } + + // Add methods +, extend: { + // Clone point + clone: function() { + return new SVG.Point(this) + } + // Morph one point into another + , morph: function(x, y) { + // store new destination + this.destination = new SVG.Point(x, y) + + return this + } + // Get morphed point at a given position + , at: function(pos) { + // make sure a destination is defined + if (!this.destination) return this + + // calculate morphed matrix at a given position + var point = new SVG.Point({ + x: this.x + (this.destination.x - this.x) * pos + , y: this.y + (this.destination.y - this.y) * pos + }) + + return point + } + // Convert to native SVGPoint + , native: function() { + // create new point + var point = SVG.parser.native.createSVGPoint() + + // update with current values + point.x = this.x + point.y = this.y + + return point + } + // transform point with matrix + , transform: function(matrix) { + return new SVG.Point(this.native().matrixTransform(matrix.native())) + } + + } + +}) + +SVG.extend(SVG.Element, { + + // Get point + point: function(x, y) { + return new SVG.Point(x,y).transform(this.screenCTM().inverse()); + } + +}) + +SVG.extend(SVG.Element, { + // Set svg element attribute + attr: function(a, v, n) { + // act as full getter + if (a == null) { + // get an object of attributes + a = {} + v = this.node.attributes + for (n = v.length - 1; n >= 0; n--) + a[v[n].nodeName] = SVG.regex.isNumber.test(v[n].nodeValue) ? parseFloat(v[n].nodeValue) : v[n].nodeValue + + return a + + } else if (typeof a == 'object') { + // apply every attribute individually if an object is passed + for (v in a) this.attr(v, a[v]) + + } else if (v === null) { + // remove value + this.node.removeAttribute(a) + + } else if (v == null) { + // act as a getter if the first and only argument is not an object + v = this.node.getAttribute(a) + return v == null ? + SVG.defaults.attrs[a] : + SVG.regex.isNumber.test(v) ? + parseFloat(v) : v + + } else { + // BUG FIX: some browsers will render a stroke if a color is given even though stroke width is 0 + if (a == 'stroke-width') + this.attr('stroke', parseFloat(v) > 0 ? this._stroke : null) + else if (a == 'stroke') + this._stroke = v + + // convert image fill and stroke to patterns + if (a == 'fill' || a == 'stroke') { + if (SVG.regex.isImage.test(v)) + v = this.doc().defs().image(v, 0, 0) + + if (v instanceof SVG.Image) + v = this.doc().defs().pattern(0, 0, function() { + this.add(v) + }) + } + + // ensure correct numeric values (also accepts NaN and Infinity) + if (typeof v === 'number') + v = new SVG.Number(v) + + // ensure full hex color + else if (SVG.Color.isColor(v)) + v = new SVG.Color(v) + + // parse array values + else if (Array.isArray(v)) + v = new SVG.Array(v) + + // store parametric transformation values locally + else if (v instanceof SVG.Matrix && v.param) + this.param = v.param + + // if the passed attribute is leading... + if (a == 'leading') { + // ... call the leading method instead + if (this.leading) + this.leading(v) + } else { + // set given attribute on node + typeof n === 'string' ? + this.node.setAttributeNS(n, a, v.toString()) : + this.node.setAttribute(a, v.toString()) + } + + // rebuild if required + if (this.rebuild && (a == 'font-size' || a == 'x')) + this.rebuild(a, v) + } + + return this + } +}) +SVG.extend(SVG.Element, { + // Add transformations + transform: function(o, relative) { + // get target in case of the fx module, otherwise reference this + var target = this + , matrix + + // act as a getter + if (typeof o !== 'object') { + // get current matrix + matrix = new SVG.Matrix(target).extract() + + return typeof o === 'string' ? matrix[o] : matrix + } + + // get current matrix + matrix = new SVG.Matrix(target) + + // ensure relative flag + relative = !!relative || !!o.relative + + // act on matrix + if (o.a != null) { + matrix = relative ? + // relative + matrix.multiply(new SVG.Matrix(o)) : + // absolute + new SVG.Matrix(o) + + // act on rotation + } else if (o.rotation != null) { + // ensure centre point + ensureCentre(o, target) + + // apply transformation + matrix = relative ? + // relative + matrix.rotate(o.rotation, o.cx, o.cy) : + // absolute + matrix.rotate(o.rotation - matrix.extract().rotation, o.cx, o.cy) + + // act on scale + } else if (o.scale != null || o.scaleX != null || o.scaleY != null) { + // ensure centre point + ensureCentre(o, target) + + // ensure scale values on both axes + o.scaleX = o.scale != null ? o.scale : o.scaleX != null ? o.scaleX : 1 + o.scaleY = o.scale != null ? o.scale : o.scaleY != null ? o.scaleY : 1 + + if (!relative) { + // absolute; multiply inversed values + var e = matrix.extract() + o.scaleX = o.scaleX * 1 / e.scaleX + o.scaleY = o.scaleY * 1 / e.scaleY + } + + matrix = matrix.scale(o.scaleX, o.scaleY, o.cx, o.cy) + + // act on skew + } else if (o.skew != null || o.skewX != null || o.skewY != null) { + // ensure centre point + ensureCentre(o, target) + + // ensure skew values on both axes + o.skewX = o.skew != null ? o.skew : o.skewX != null ? o.skewX : 0 + o.skewY = o.skew != null ? o.skew : o.skewY != null ? o.skewY : 0 + + if (!relative) { + // absolute; reset skew values + var e = matrix.extract() + matrix = matrix.multiply(new SVG.Matrix().skew(e.skewX, e.skewY, o.cx, o.cy).inverse()) + } + + matrix = matrix.skew(o.skewX, o.skewY, o.cx, o.cy) + + // act on flip + } else if (o.flip) { + matrix = matrix.flip( + o.flip + , o.offset == null ? target.bbox()['c' + o.flip] : o.offset + ) + + // act on translate + } else if (o.x != null || o.y != null) { + if (relative) { + // relative + matrix = matrix.translate(o.x, o.y) + } else { + // absolute + if (o.x != null) matrix.e = o.x + if (o.y != null) matrix.f = o.y + } + } + + return this.attr('transform', matrix) + } +}) + +SVG.extend(SVG.FX, { + transform: function(o, relative) { + // get target in case of the fx module, otherwise reference this + var target = this.target() + , matrix + + // act as a getter + if (typeof o !== 'object') { + // get current matrix + matrix = new SVG.Matrix(target).extract() + + return typeof o === 'string' ? matrix[o] : matrix + } + + // ensure relative flag + relative = !!relative || !!o.relative + + // act on matrix + if (o.a != null) { + matrix = new SVG.Matrix(o) + + // act on rotation + } else if (o.rotation != null) { + // ensure centre point + ensureCentre(o, target) + + // apply transformation + matrix = new SVG.Rotate(o.rotation, o.cx, o.cy) + + // act on scale + } else if (o.scale != null || o.scaleX != null || o.scaleY != null) { + // ensure centre point + ensureCentre(o, target) + + // ensure scale values on both axes + o.scaleX = o.scale != null ? o.scale : o.scaleX != null ? o.scaleX : 1 + o.scaleY = o.scale != null ? o.scale : o.scaleY != null ? o.scaleY : 1 + + matrix = new SVG.Scale(o.scaleX, o.scaleY, o.cx, o.cy) + + // act on skew + } else if (o.skewX != null || o.skewY != null) { + // ensure centre point + ensureCentre(o, target) + + // ensure skew values on both axes + o.skewX = o.skewX != null ? o.skewX : 0 + o.skewY = o.skewY != null ? o.skewY : 0 + + matrix = new SVG.Skew(o.skewX, o.skewY, o.cx, o.cy) + + // act on flip + } else if (o.flip) { + matrix = new SVG.Matrix().morph(new SVG.Matrix().flip( + o.flip + , o.offset == null ? target.bbox()['c' + o.flip] : o.offset + )) + + // act on translate + } else if (o.x != null || o.y != null) { + matrix = new SVG.Translate(o.x, o.y) + } + + if(!matrix) return this + + matrix.relative = relative + + this.last().transforms.push(matrix) + + setTimeout(function(){this.start()}.bind(this), 0) + + return this + } +}) + +SVG.extend(SVG.Element, { + // Reset all transformations + untransform: function() { + return this.attr('transform', null) + }, + // merge the whole transformation chain into one matrix and returns it + matrixify: function() { + + var matrix = (this.attr('transform') || '') + // split transformations + .split(/\)\s*,?\s*/).slice(0,-1).map(function(str){ + // generate key => value pairs + var kv = str.trim().split('(') + return [kv[0], kv[1].split(SVG.regex.matrixElements).map(function(str){ return parseFloat(str) })] + }) + // calculate every transformation into one matrix + .reduce(function(matrix, transform){ + + if(transform[0] == 'matrix') return matrix.multiply(arrayToMatrix(transform[1])) + return matrix[transform[0]].apply(matrix, transform[1]) + + }, new SVG.Matrix()) + + return matrix + }, + // add an element to another parent without changing the visual representation on the screen + toParent: function(parent) { + if(this == parent) return this + var ctm = this.screenCTM() + var temp = parent.rect(1,1) + var pCtm = temp.screenCTM().inverse() + temp.remove() + + this.addTo(parent).untransform().transform(pCtm.multiply(ctm)) + + return this + }, + // same as above with parent equals root-svg + toDoc: function() { + return this.toParent(this.doc()) + } + +}) + +SVG.Transformation = SVG.invent({ + + create: function(source, inversed){ + + if(arguments.length > 1 && typeof inversed != 'boolean'){ + return this.create([].slice.call(arguments)) + } + + if(typeof source == 'object'){ + for(var i = 0, len = this.arguments.length; i < len; ++i){ + this[this.arguments[i]] = source[this.arguments[i]] + } + } + + if(Array.isArray(source)){ + for(var i = 0, len = this.arguments.length; i < len; ++i){ + this[this.arguments[i]] = source[i] + } + } + + this.inversed = false + + if(inversed === true){ + this.inversed = true + } + + } + +, extend: { + + at: function(pos){ + + var params = [] + + for(var i = 0, len = this.arguments.length; i < len; ++i){ + params.push(this[this.arguments[i]]) + } + + var m = this._undo || new SVG.Matrix() + + m = new SVG.Matrix().morph(SVG.Matrix.prototype[this.method].apply(m, params)).at(pos) + + return this.inversed ? m.inverse() : m + + } + + , undo: function(o){ + for(var i = 0, len = this.arguments.length; i < len; ++i){ + o[this.arguments[i]] = typeof this[this.arguments[i]] == 'undefined' ? 0 : o[this.arguments[i]] + } + + // The method SVG.Matrix.extract which was used before calling this + // method to obtain a value for the parameter o doesn't return a cx and + // a cy so we use the ones that were provided to this object at its creation + o.cx = this.cx + o.cy = this.cy + + this._undo = new SVG[capitalize(this.method)](o, true).at(1) + + return this + } + + } + +}) + +SVG.Translate = SVG.invent({ + + parent: SVG.Matrix +, inherit: SVG.Transformation + +, create: function(source, inversed){ + if(typeof source == 'object') this.constructor.call(this, source, inversed) + else this.constructor.call(this, [].slice.call(arguments)) + } + +, extend: { + arguments: ['transformedX', 'transformedY'] + , method: 'translate' + } + +}) + +SVG.Rotate = SVG.invent({ + + parent: SVG.Matrix +, inherit: SVG.Transformation + +, create: function(source, inversed){ + if(typeof source == 'object') this.constructor.call(this, source, inversed) + else this.constructor.call(this, [].slice.call(arguments)) + } + +, extend: { + arguments: ['rotation', 'cx', 'cy'] + , method: 'rotate' + , at: function(pos){ + var m = new SVG.Matrix().rotate(new SVG.Number().morph(this.rotation - (this._undo ? this._undo.rotation : 0)).at(pos), this.cx, this.cy) + return this.inversed ? m.inverse() : m + } + , undo: function(o){ + this._undo = o + } + } + +}) + +SVG.Scale = SVG.invent({ + + parent: SVG.Matrix +, inherit: SVG.Transformation + +, create: function(source, inversed){ + if(typeof source == 'object') this.constructor.call(this, source, inversed) + else this.constructor.call(this, [].slice.call(arguments)) + } + +, extend: { + arguments: ['scaleX', 'scaleY', 'cx', 'cy'] + , method: 'scale' + } + +}) + +SVG.Skew = SVG.invent({ + + parent: SVG.Matrix +, inherit: SVG.Transformation + +, create: function(source, inversed){ + if(typeof source == 'object') this.constructor.call(this, source, inversed) + else this.constructor.call(this, [].slice.call(arguments)) + } + +, extend: { + arguments: ['skewX', 'skewY', 'cx', 'cy'] + , method: 'skew' + } + +}) + +SVG.extend(SVG.Element, { + // Dynamic style generator + style: function(s, v) { + if (arguments.length == 0) { + // get full style + return this.node.style.cssText || '' + + } else if (arguments.length < 2) { + // apply every style individually if an object is passed + if (typeof s == 'object') { + for (v in s) this.style(v, s[v]) + + } else if (SVG.regex.isCss.test(s)) { + // parse css string + s = s.split(';') + + // apply every definition individually + for (var i = 0; i < s.length; i++) { + v = s[i].split(':') + this.style(v[0].replace(/\s+/g, ''), v[1]) + } + } else { + // act as a getter if the first and only argument is not an object + return this.node.style[camelCase(s)] + } + + } else { + this.node.style[camelCase(s)] = v === null || SVG.regex.isBlank.test(v) ? '' : v + } + + return this + } +}) +SVG.Parent = SVG.invent({ + // Initialize node + create: function(element) { + this.constructor.call(this, element) + } + + // Inherit from +, inherit: SVG.Element + + // Add class methods +, extend: { + // Returns all child elements + children: function() { + return SVG.utils.map(SVG.utils.filterSVGElements(this.node.childNodes), function(node) { + return SVG.adopt(node) + }) + } + // Add given element at a position + , add: function(element, i) { + if (i == null) + this.node.appendChild(element.node) + else if (element.node != this.node.childNodes[i]) + this.node.insertBefore(element.node, this.node.childNodes[i]) + + return this + } + // Basically does the same as `add()` but returns the added element instead + , put: function(element, i) { + this.add(element, i) + return element + } + // Checks if the given element is a child + , has: function(element) { + return this.index(element) >= 0 + } + // Gets index of given element + , index: function(element) { + return [].slice.call(this.node.childNodes).indexOf(element.node) + } + // Get a element at the given index + , get: function(i) { + return SVG.adopt(this.node.childNodes[i]) + } + // Get first child + , first: function() { + return this.get(0) + } + // Get the last child + , last: function() { + return this.get(this.node.childNodes.length - 1) + } + // Iterates over all children and invokes a given block + , each: function(block, deep) { + var i, il + , children = this.children() + + for (i = 0, il = children.length; i < il; i++) { + if (children[i] instanceof SVG.Element) + block.apply(children[i], [i, children]) + + if (deep && (children[i] instanceof SVG.Container)) + children[i].each(block, deep) + } + + return this + } + // Remove a given child + , removeElement: function(element) { + this.node.removeChild(element.node) + + return this + } + // Remove all elements in this container + , clear: function() { + // remove children + while(this.node.hasChildNodes()) + this.node.removeChild(this.node.lastChild) + + // remove defs reference + delete this._defs + + return this + } + , // Get defs + defs: function() { + return this.doc().defs() + } + } + +}) + +SVG.extend(SVG.Parent, { + + ungroup: function(parent, depth) { + if(depth === 0 || this instanceof SVG.Defs) return this + + parent = parent || (this instanceof SVG.Doc ? this : this.parent(SVG.Parent)) + depth = depth || Infinity + + this.each(function(){ + if(this instanceof SVG.Defs) return this + if(this instanceof SVG.Parent) return this.ungroup(parent, depth-1) + return this.toParent(parent) + }) + + this.node.firstChild || this.remove() + + return this + }, + + flatten: function(parent, depth) { + return this.ungroup(parent, depth) + } + +}) +SVG.Container = SVG.invent({ + // Initialize node + create: function(element) { + this.constructor.call(this, element) + } + + // Inherit from +, inherit: SVG.Parent + +}) + +SVG.ViewBox = SVG.invent({ + + create: function(source) { + var i, base = [0, 0, 0, 0] + + var x, y, width, height, box, view, we, he + , wm = 1 // width multiplier + , hm = 1 // height multiplier + , reg = /[+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:e[+-]?\d+)?/gi + + if(source instanceof SVG.Element){ + + we = source + he = source + view = (source.attr('viewBox') || '').match(reg) + box = source.bbox + + // get dimensions of current node + width = new SVG.Number(source.width()) + height = new SVG.Number(source.height()) + + // find nearest non-percentual dimensions + while (width.unit == '%') { + wm *= width.value + width = new SVG.Number(we instanceof SVG.Doc ? we.parent().offsetWidth : we.parent().width()) + we = we.parent() + } + while (height.unit == '%') { + hm *= height.value + height = new SVG.Number(he instanceof SVG.Doc ? he.parent().offsetHeight : he.parent().height()) + he = he.parent() + } + + // ensure defaults + this.x = 0 + this.y = 0 + this.width = width * wm + this.height = height * hm + this.zoom = 1 + + if (view) { + // get width and height from viewbox + x = parseFloat(view[0]) + y = parseFloat(view[1]) + width = parseFloat(view[2]) + height = parseFloat(view[3]) + + // calculate zoom accoring to viewbox + this.zoom = ((this.width / this.height) > (width / height)) ? + this.height / height : + this.width / width + + // calculate real pixel dimensions on parent SVG.Doc element + this.x = x + this.y = y + this.width = width + this.height = height + + } + + }else{ + + // ensure source as object + source = typeof source === 'string' ? + source.match(reg).map(function(el){ return parseFloat(el) }) : + Array.isArray(source) ? + source : + typeof source == 'object' ? + [source.x, source.y, source.width, source.height] : + arguments.length == 4 ? + [].slice.call(arguments) : + base + + this.x = source[0] + this.y = source[1] + this.width = source[2] + this.height = source[3] + } + + + } + +, extend: { + + toString: function() { + return this.x + ' ' + this.y + ' ' + this.width + ' ' + this.height + } + , morph: function(v){ + + var v = arguments.length == 1 ? + [v.x, v.y, v.width, v.height] : + [].slice.call(arguments) + + this.destination = new SVG.ViewBox(v) + + return this + + } + + , at: function(pos) { + + if(!this.destination) return this + + return new SVG.ViewBox([ + this.x + (this.destination.x - this.x) * pos + , this.y + (this.destination.y - this.y) * pos + , this.width + (this.destination.width - this.width) * pos + , this.height + (this.destination.height - this.height) * pos + ]) + + } + + } + + // Define parent +, parent: SVG.Container + + // Add parent method +, construct: { + + // get/set viewbox + viewbox: function(v) { + if (arguments.length == 0) + // act as a getter if there are no arguments + return new SVG.ViewBox(this) + + // otherwise act as a setter + v = arguments.length == 1 ? + [v.x, v.y, v.width, v.height] : + [].slice.call(arguments) + + return this.attr('viewBox', v) + } + + } + +}) +// Add events to elements +;[ 'click' + , 'dblclick' + , 'mousedown' + , 'mouseup' + , 'mouseover' + , 'mouseout' + , 'mousemove' + // , 'mouseenter' -> not supported by IE + // , 'mouseleave' -> not supported by IE + , 'touchstart' + , 'touchmove' + , 'touchleave' + , 'touchend' + , 'touchcancel' ].forEach(function(event) { + + // add event to SVG.Element + SVG.Element.prototype[event] = function(f) { + var self = this + + // bind event to element rather than element node + this.node['on' + event] = typeof f == 'function' ? + function() { return f.apply(self, arguments) } : null + + return this + } + +}) + +// Initialize listeners stack +SVG.listeners = [] +SVG.handlerMap = [] +SVG.listenerId = 0 + +// Add event binder in the SVG namespace +SVG.on = function(node, event, listener, binding) { + // create listener, get object-index + var l = listener.bind(binding || node.instance || node) + , index = (SVG.handlerMap.indexOf(node) + 1 || SVG.handlerMap.push(node)) - 1 + , ev = event.split('.')[0] + , ns = event.split('.')[1] || '*' + + + // ensure valid object + SVG.listeners[index] = SVG.listeners[index] || {} + SVG.listeners[index][ev] = SVG.listeners[index][ev] || {} + SVG.listeners[index][ev][ns] = SVG.listeners[index][ev][ns] || {} + + if(!listener._svgjsListenerId) + listener._svgjsListenerId = ++SVG.listenerId + + // reference listener + SVG.listeners[index][ev][ns][listener._svgjsListenerId] = l + + // add listener + node.addEventListener(ev, l, false) +} + +// Add event unbinder in the SVG namespace +SVG.off = function(node, event, listener) { + var index = SVG.handlerMap.indexOf(node) + , ev = event && event.split('.')[0] + , ns = event && event.split('.')[1] + + if(index == -1) return + + if (listener) { + if(typeof listener == 'function') listener = listener._svgjsListenerId + if(!listener) return + + // remove listener reference + if (SVG.listeners[index][ev] && SVG.listeners[index][ev][ns || '*']) { + // remove listener + node.removeEventListener(ev, SVG.listeners[index][ev][ns || '*'][listener], false) + + delete SVG.listeners[index][ev][ns || '*'][listener] + } + + } else if (ns && ev) { + // remove all listeners for a namespaced event + if (SVG.listeners[index][ev] && SVG.listeners[index][ev][ns]) { + for (listener in SVG.listeners[index][ev][ns]) + SVG.off(node, [ev, ns].join('.'), listener) + + delete SVG.listeners[index][ev][ns] + } + + } else if (ns){ + // remove all listeners for a specific namespace + for(event in SVG.listeners[index]){ + for(namespace in SVG.listeners[index][event]){ + if(ns === namespace){ + SVG.off(node, [event, ns].join('.')) + } + } + } + + } else if (ev) { + // remove all listeners for the event + if (SVG.listeners[index][ev]) { + for (namespace in SVG.listeners[index][ev]) + SVG.off(node, [ev, namespace].join('.')) + + delete SVG.listeners[index][ev] + } + + } else { + // remove all listeners on a given node + for (event in SVG.listeners[index]) + SVG.off(node, event) + + delete SVG.listeners[index] + + } +} + +// +SVG.extend(SVG.Element, { + // Bind given event to listener + on: function(event, listener, binding) { + SVG.on(this.node, event, listener, binding) + + return this + } + // Unbind event from listener +, off: function(event, listener) { + SVG.off(this.node, event, listener) + + return this + } + // Fire given event +, fire: function(event, data) { + + // Dispatch event + if(event instanceof Event){ + this.node.dispatchEvent(event) + }else{ + this.node.dispatchEvent(new CustomEvent(event, {detail:data})) + } + + return this + } +}) + +SVG.Defs = SVG.invent({ + // Initialize node + create: 'defs' + + // Inherit from +, inherit: SVG.Container + +}) +SVG.G = SVG.invent({ + // Initialize node + create: 'g' + + // Inherit from +, inherit: SVG.Container + + // Add class methods +, extend: { + // Move over x-axis + x: function(x) { + return x == null ? this.transform('x') : this.transform({ x: x - this.x() }, true) + } + // Move over y-axis + , y: function(y) { + return y == null ? this.transform('y') : this.transform({ y: y - this.y() }, true) + } + // Move by center over x-axis + , cx: function(x) { + return x == null ? this.gbox().cx : this.x(x - this.gbox().width / 2) + } + // Move by center over y-axis + , cy: function(y) { + return y == null ? this.gbox().cy : this.y(y - this.gbox().height / 2) + } + , gbox: function() { + + var bbox = this.bbox() + , trans = this.transform() + + bbox.x += trans.x + bbox.x2 += trans.x + bbox.cx += trans.x + + bbox.y += trans.y + bbox.y2 += trans.y + bbox.cy += trans.y + + return bbox + } + } + + // Add parent method +, construct: { + // Create a group element + group: function() { + return this.put(new SVG.G) + } + } +}) + +// ### This module adds backward / forward functionality to elements. + +// +SVG.extend(SVG.Element, { + // Get all siblings, including myself + siblings: function() { + return this.parent().children() + } + // Get the curent position siblings +, position: function() { + return this.parent().index(this) + } + // Get the next element (will return null if there is none) +, next: function() { + return this.siblings()[this.position() + 1] + } + // Get the next element (will return null if there is none) +, previous: function() { + return this.siblings()[this.position() - 1] + } + // Send given element one step forward +, forward: function() { + var i = this.position() + 1 + , p = this.parent() + + // move node one step forward + p.removeElement(this).add(this, i) + + // make sure defs node is always at the top + if (p instanceof SVG.Doc) + p.node.appendChild(p.defs().node) + + return this + } + // Send given element one step backward +, backward: function() { + var i = this.position() + + if (i > 0) + this.parent().removeElement(this).add(this, i - 1) + + return this + } + // Send given element all the way to the front +, front: function() { + var p = this.parent() + + // Move node forward + p.node.appendChild(this.node) + + // Make sure defs node is always at the top + if (p instanceof SVG.Doc) + p.node.appendChild(p.defs().node) + + return this + } + // Send given element all the way to the back +, back: function() { + if (this.position() > 0) + this.parent().removeElement(this).add(this, 0) + + return this + } + // Inserts a given element before the targeted element +, before: function(element) { + element.remove() + + var i = this.position() + + this.parent().add(element, i) + + return this + } + // Insters a given element after the targeted element +, after: function(element) { + element.remove() + + var i = this.position() + + this.parent().add(element, i + 1) + + return this + } + +}) +SVG.Mask = SVG.invent({ + // Initialize node + create: function() { + this.constructor.call(this, SVG.create('mask')) + + // keep references to masked elements + this.targets = [] + } + + // Inherit from +, inherit: SVG.Container + + // Add class methods +, extend: { + // Unmask all masked elements and remove itself + remove: function() { + // unmask all targets + for (var i = this.targets.length - 1; i >= 0; i--) + if (this.targets[i]) + this.targets[i].unmask() + this.targets = [] + + // remove mask from parent + this.parent().removeElement(this) + + return this + } + } + + // Add parent method +, construct: { + // Create masking element + mask: function() { + return this.defs().put(new SVG.Mask) + } + } +}) + + +SVG.extend(SVG.Element, { + // Distribute mask to svg element + maskWith: function(element) { + // use given mask or create a new one + this.masker = element instanceof SVG.Mask ? element : this.parent().mask().add(element) + + // store reverence on self in mask + this.masker.targets.push(this) + + // apply mask + return this.attr('mask', 'url("#' + this.masker.attr('id') + '")') + } + // Unmask element +, unmask: function() { + delete this.masker + return this.attr('mask', null) + } + +}) + +SVG.ClipPath = SVG.invent({ + // Initialize node + create: function() { + this.constructor.call(this, SVG.create('clipPath')) + + // keep references to clipped elements + this.targets = [] + } + + // Inherit from +, inherit: SVG.Container + + // Add class methods +, extend: { + // Unclip all clipped elements and remove itself + remove: function() { + // unclip all targets + for (var i = this.targets.length - 1; i >= 0; i--) + if (this.targets[i]) + this.targets[i].unclip() + this.targets = [] + + // remove clipPath from parent + this.parent().removeElement(this) + + return this + } + } + + // Add parent method +, construct: { + // Create clipping element + clip: function() { + return this.defs().put(new SVG.ClipPath) + } + } +}) + +// +SVG.extend(SVG.Element, { + // Distribute clipPath to svg element + clipWith: function(element) { + // use given clip or create a new one + this.clipper = element instanceof SVG.ClipPath ? element : this.parent().clip().add(element) + + // store reverence on self in mask + this.clipper.targets.push(this) + + // apply mask + return this.attr('clip-path', 'url("#' + this.clipper.attr('id') + '")') + } + // Unclip element +, unclip: function() { + delete this.clipper + return this.attr('clip-path', null) + } + +}) +SVG.Gradient = SVG.invent({ + // Initialize node + create: function(type) { + this.constructor.call(this, SVG.create(type + 'Gradient')) + + // store type + this.type = type + } + + // Inherit from +, inherit: SVG.Container + + // Add class methods +, extend: { + // Add a color stop + at: function(offset, color, opacity) { + return this.put(new SVG.Stop).update(offset, color, opacity) + } + // Update gradient + , update: function(block) { + // remove all stops + this.clear() + + // invoke passed block + if (typeof block == 'function') + block.call(this, this) + + return this + } + // Return the fill id + , fill: function() { + return 'url(#' + this.id() + ')' + } + // Alias string convertion to fill + , toString: function() { + return this.fill() + } + // custom attr to handle transform + , attr: function(a, b, c) { + if(a == 'transform') a = 'gradientTransform' + return SVG.Container.prototype.attr.call(this, a, b, c) + } + } + + // Add parent method +, construct: { + // Create gradient element in defs + gradient: function(type, block) { + return this.defs().gradient(type, block) + } + } +}) + +// Add animatable methods to both gradient and fx module +SVG.extend(SVG.Gradient, SVG.FX, { + // From position + from: function(x, y) { + return (this._target || this).type == 'radial' ? + this.attr({ fx: new SVG.Number(x), fy: new SVG.Number(y) }) : + this.attr({ x1: new SVG.Number(x), y1: new SVG.Number(y) }) + } + // To position +, to: function(x, y) { + return (this._target || this).type == 'radial' ? + this.attr({ cx: new SVG.Number(x), cy: new SVG.Number(y) }) : + this.attr({ x2: new SVG.Number(x), y2: new SVG.Number(y) }) + } +}) + +// Base gradient generation +SVG.extend(SVG.Defs, { + // define gradient + gradient: function(type, block) { + return this.put(new SVG.Gradient(type)).update(block) + } + +}) + +SVG.Stop = SVG.invent({ + // Initialize node + create: 'stop' + + // Inherit from +, inherit: SVG.Element + + // Add class methods +, extend: { + // add color stops + update: function(o) { + if (typeof o == 'number' || o instanceof SVG.Number) { + o = { + offset: arguments[0] + , color: arguments[1] + , opacity: arguments[2] + } + } + + // set attributes + if (o.opacity != null) this.attr('stop-opacity', o.opacity) + if (o.color != null) this.attr('stop-color', o.color) + if (o.offset != null) this.attr('offset', new SVG.Number(o.offset)) + + return this + } + } + +}) + +SVG.Pattern = SVG.invent({ + // Initialize node + create: 'pattern' + + // Inherit from +, inherit: SVG.Container + + // Add class methods +, extend: { + // Return the fill id + fill: function() { + return 'url(#' + this.id() + ')' + } + // Update pattern by rebuilding + , update: function(block) { + // remove content + this.clear() + + // invoke passed block + if (typeof block == 'function') + block.call(this, this) + + return this + } + // Alias string convertion to fill + , toString: function() { + return this.fill() + } + // custom attr to handle transform + , attr: function(a, b, c) { + if(a == 'transform') a = 'patternTransform' + return SVG.Container.prototype.attr.call(this, a, b, c) + } + + } + + // Add parent method +, construct: { + // Create pattern element in defs + pattern: function(width, height, block) { + return this.defs().pattern(width, height, block) + } + } +}) + +SVG.extend(SVG.Defs, { + // Define gradient + pattern: function(width, height, block) { + return this.put(new SVG.Pattern).update(block).attr({ + x: 0 + , y: 0 + , width: width + , height: height + , patternUnits: 'userSpaceOnUse' + }) + } + +}) +SVG.Doc = SVG.invent({ + // Initialize node + create: function(element) { + if (element) { + // ensure the presence of a dom element + element = typeof element == 'string' ? + document.getElementById(element) : + element + + // If the target is an svg element, use that element as the main wrapper. + // This allows svg.js to work with svg documents as well. + if (element.nodeName == 'svg') { + this.constructor.call(this, element) + } else { + this.constructor.call(this, SVG.create('svg')) + element.appendChild(this.node) + this.size('100%', '100%') + } + + // set svg element attributes and ensure defs node + this.namespace().defs() + } + } + + // Inherit from +, inherit: SVG.Container + + // Add class methods +, extend: { + // Add namespaces + namespace: function() { + return this + .attr({ xmlns: SVG.ns, version: '1.1' }) + .attr('xmlns:xlink', SVG.xlink, SVG.xmlns) + .attr('xmlns:svgjs', SVG.svgjs, SVG.xmlns) + } + // Creates and returns defs element + , defs: function() { + if (!this._defs) { + var defs + + // Find or create a defs element in this instance + if (defs = this.node.getElementsByTagName('defs')[0]) + this._defs = SVG.adopt(defs) + else + this._defs = new SVG.Defs + + // Make sure the defs node is at the end of the stack + this.node.appendChild(this._defs.node) + } + + return this._defs + } + // custom parent method + , parent: function() { + return this.node.parentNode.nodeName == '#document' ? null : this.node.parentNode + } + // Fix for possible sub-pixel offset. See: + // https://bugzilla.mozilla.org/show_bug.cgi?id=608812 + , spof: function(spof) { + var pos = this.node.getScreenCTM() + + if (pos) + this + .style('left', (-pos.e % 1) + 'px') + .style('top', (-pos.f % 1) + 'px') + + return this + } + + // Removes the doc from the DOM + , remove: function() { + if(this.parent()) { + this.parent().removeChild(this.node); + } + + return this; + } + } + +}) + +SVG.Shape = SVG.invent({ + // Initialize node + create: function(element) { + this.constructor.call(this, element) + } + + // Inherit from +, inherit: SVG.Element + +}) + +SVG.Bare = SVG.invent({ + // Initialize + create: function(element, inherit) { + // construct element + this.constructor.call(this, SVG.create(element)) + + // inherit custom methods + if (inherit) + for (var method in inherit.prototype) + if (typeof inherit.prototype[method] === 'function') + this[method] = inherit.prototype[method] + } + + // Inherit from +, inherit: SVG.Element + + // Add methods +, extend: { + // Insert some plain text + words: function(text) { + // remove contents + while (this.node.hasChildNodes()) + this.node.removeChild(this.node.lastChild) + + // create text node + this.node.appendChild(document.createTextNode(text)) + + return this + } + } +}) + + +SVG.extend(SVG.Parent, { + // Create an element that is not described by SVG.js + element: function(element, inherit) { + return this.put(new SVG.Bare(element, inherit)) + } + // Add symbol element +, symbol: function() { + return this.defs().element('symbol', SVG.Container) + } + +}) +SVG.Use = SVG.invent({ + // Initialize node + create: 'use' + + // Inherit from +, inherit: SVG.Shape + + // Add class methods +, extend: { + // Use element as a reference + element: function(element, file) { + // Set lined element + return this.attr('href', (file || '') + '#' + element, SVG.xlink) + } + } + + // Add parent method +, construct: { + // Create a use element + use: function(element, file) { + return this.put(new SVG.Use).element(element, file) + } + } +}) +SVG.Rect = SVG.invent({ + // Initialize node + create: 'rect' + + // Inherit from +, inherit: SVG.Shape + + // Add parent method +, construct: { + // Create a rect element + rect: function(width, height) { + return this.put(new SVG.Rect()).size(width, height) + } + } +}) +SVG.Circle = SVG.invent({ + // Initialize node + create: 'circle' + + // Inherit from +, inherit: SVG.Shape + + // Add parent method +, construct: { + // Create circle element, based on ellipse + circle: function(size) { + return this.put(new SVG.Circle).rx(new SVG.Number(size).divide(2)).move(0, 0) + } + } +}) + +SVG.extend(SVG.Circle, SVG.FX, { + // Radius x value + rx: function(rx) { + return this.attr('r', rx) + } + // Alias radius x value +, ry: function(ry) { + return this.rx(ry) + } +}) + +SVG.Ellipse = SVG.invent({ + // Initialize node + create: 'ellipse' + + // Inherit from +, inherit: SVG.Shape + + // Add parent method +, construct: { + // Create an ellipse + ellipse: function(width, height) { + return this.put(new SVG.Ellipse).size(width, height).move(0, 0) + } + } +}) + +SVG.extend(SVG.Ellipse, SVG.Rect, SVG.FX, { + // Radius x value + rx: function(rx) { + return this.attr('rx', rx) + } + // Radius y value +, ry: function(ry) { + return this.attr('ry', ry) + } +}) + +// Add common method +SVG.extend(SVG.Circle, SVG.Ellipse, { + // Move over x-axis + x: function(x) { + return x == null ? this.cx() - this.rx() : this.cx(x + this.rx()) + } + // Move over y-axis + , y: function(y) { + return y == null ? this.cy() - this.ry() : this.cy(y + this.ry()) + } + // Move by center over x-axis + , cx: function(x) { + return x == null ? this.attr('cx') : this.attr('cx', x) + } + // Move by center over y-axis + , cy: function(y) { + return y == null ? this.attr('cy') : this.attr('cy', y) + } + // Set width of element + , width: function(width) { + return width == null ? this.rx() * 2 : this.rx(new SVG.Number(width).divide(2)) + } + // Set height of element + , height: function(height) { + return height == null ? this.ry() * 2 : this.ry(new SVG.Number(height).divide(2)) + } + // Custom size function + , size: function(width, height) { + var p = proportionalSize(this, width, height) + + return this + .rx(new SVG.Number(p.width).divide(2)) + .ry(new SVG.Number(p.height).divide(2)) + } +}) +SVG.Line = SVG.invent({ + // Initialize node + create: 'line' + + // Inherit from +, inherit: SVG.Shape + + // Add class methods +, extend: { + // Get array + array: function() { + return new SVG.PointArray([ + [ this.attr('x1'), this.attr('y1') ] + , [ this.attr('x2'), this.attr('y2') ] + ]) + } + // Overwrite native plot() method + , plot: function(x1, y1, x2, y2) { + if (typeof y1 !== 'undefined') + x1 = { x1: x1, y1: y1, x2: x2, y2: y2 } + else + x1 = new SVG.PointArray(x1).toLine() + + return this.attr(x1) + } + // Move by left top corner + , move: function(x, y) { + return this.attr(this.array().move(x, y).toLine()) + } + // Set element size to given width and height + , size: function(width, height) { + var p = proportionalSize(this, width, height) + + return this.attr(this.array().size(p.width, p.height).toLine()) + } + } + + // Add parent method +, construct: { + // Create a line element + line: function(x1, y1, x2, y2) { + return this.put(new SVG.Line).plot(x1, y1, x2, y2) + } + } +}) + +SVG.Polyline = SVG.invent({ + // Initialize node + create: 'polyline' + + // Inherit from +, inherit: SVG.Shape + + // Add parent method +, construct: { + // Create a wrapped polyline element + polyline: function(p) { + return this.put(new SVG.Polyline).plot(p) + } + } +}) + +SVG.Polygon = SVG.invent({ + // Initialize node + create: 'polygon' + + // Inherit from +, inherit: SVG.Shape + + // Add parent method +, construct: { + // Create a wrapped polygon element + polygon: function(p) { + return this.put(new SVG.Polygon).plot(p) + } + } +}) + +// Add polygon-specific functions +SVG.extend(SVG.Polyline, SVG.Polygon, { + // Get array + array: function() { + return this._array || (this._array = new SVG.PointArray(this.attr('points'))) + } + // Plot new path +, plot: function(p) { + return this.attr('points', (this._array = new SVG.PointArray(p))) + } + // Move by left top corner +, move: function(x, y) { + return this.attr('points', this.array().move(x, y)) + } + // Set element size to given width and height +, size: function(width, height) { + var p = proportionalSize(this, width, height) + + return this.attr('points', this.array().size(p.width, p.height)) + } + +}) +// unify all point to point elements +SVG.extend(SVG.Line, SVG.Polyline, SVG.Polygon, { + // Define morphable array + morphArray: SVG.PointArray + // Move by left top corner over x-axis +, x: function(x) { + return x == null ? this.bbox().x : this.move(x, this.bbox().y) + } + // Move by left top corner over y-axis +, y: function(y) { + return y == null ? this.bbox().y : this.move(this.bbox().x, y) + } + // Set width of element +, width: function(width) { + var b = this.bbox() + + return width == null ? b.width : this.size(width, b.height) + } + // Set height of element +, height: function(height) { + var b = this.bbox() + + return height == null ? b.height : this.size(b.width, height) + } +}) +SVG.Path = SVG.invent({ + // Initialize node + create: 'path' + + // Inherit from +, inherit: SVG.Shape + + // Add class methods +, extend: { + // Define morphable array + morphArray: SVG.PathArray + // Get array + , array: function() { + return this._array || (this._array = new SVG.PathArray(this.attr('d'))) + } + // Plot new poly points + , plot: function(p) { + return this.attr('d', (this._array = new SVG.PathArray(p))) + } + // Move by left top corner + , move: function(x, y) { + return this.attr('d', this.array().move(x, y)) + } + // Move by left top corner over x-axis + , x: function(x) { + return x == null ? this.bbox().x : this.move(x, this.bbox().y) + } + // Move by left top corner over y-axis + , y: function(y) { + return y == null ? this.bbox().y : this.move(this.bbox().x, y) + } + // Set element size to given width and height + , size: function(width, height) { + var p = proportionalSize(this, width, height) + + return this.attr('d', this.array().size(p.width, p.height)) + } + // Set width of element + , width: function(width) { + return width == null ? this.bbox().width : this.size(width, this.bbox().height) + } + // Set height of element + , height: function(height) { + return height == null ? this.bbox().height : this.size(this.bbox().width, height) + } + + } + + // Add parent method +, construct: { + // Create a wrapped path element + path: function(d) { + return this.put(new SVG.Path).plot(d) + } + } +}) +SVG.Image = SVG.invent({ + // Initialize node + create: 'image' + + // Inherit from +, inherit: SVG.Shape + + // Add class methods +, extend: { + // (re)load image + load: function(url) { + if (!url) return this + + var self = this + , img = document.createElement('img') + + // preload image + img.onload = function() { + var p = self.parent(SVG.Pattern) + + if(p === null) return + + // ensure image size + if (self.width() == 0 && self.height() == 0) + self.size(img.width, img.height) + + // ensure pattern size if not set + if (p && p.width() == 0 && p.height() == 0) + p.size(self.width(), self.height()) + + // callback + if (typeof self._loaded === 'function') + self._loaded.call(self, { + width: img.width + , height: img.height + , ratio: img.width / img.height + , url: url + }) + } + + img.onerror = function(e){ + if (typeof self._error === 'function'){ + self._error.call(self, e) + } + } + + return this.attr('href', (img.src = this.src = url), SVG.xlink) + } + // Add loaded callback + , loaded: function(loaded) { + this._loaded = loaded + return this + } + + , error: function(error) { + this._error = error + return this + } + } + + // Add parent method +, construct: { + // create image element, load image and set its size + image: function(source, width, height) { + return this.put(new SVG.Image).load(source).size(width || 0, height || width || 0) + } + } + +}) +SVG.Text = SVG.invent({ + // Initialize node + create: function() { + this.constructor.call(this, SVG.create('text')) + + this.dom.leading = new SVG.Number(1.3) // store leading value for rebuilding + this._rebuild = true // enable automatic updating of dy values + this._build = false // disable build mode for adding multiple lines + + // set default font + this.attr('font-family', SVG.defaults.attrs['font-family']) + } + + // Inherit from +, inherit: SVG.Shape + + // Add class methods +, extend: { + // Move over x-axis + x: function(x) { + // act as getter + if (x == null) + return this.attr('x') + + // move lines as well if no textPath is present + if (!this.textPath) + this.lines().each(function() { if (this.dom.newLined) this.x(x) }) + + return this.attr('x', x) + } + // Move over y-axis + , y: function(y) { + var oy = this.attr('y') + , o = typeof oy === 'number' ? oy - this.bbox().y : 0 + + // act as getter + if (y == null) + return typeof oy === 'number' ? oy - o : oy + + return this.attr('y', typeof y === 'number' ? y + o : y) + } + // Move center over x-axis + , cx: function(x) { + return x == null ? this.bbox().cx : this.x(x - this.bbox().width / 2) + } + // Move center over y-axis + , cy: function(y) { + return y == null ? this.bbox().cy : this.y(y - this.bbox().height / 2) + } + // Set the text content + , text: function(text) { + // act as getter + if (typeof text === 'undefined'){ + var text = '' + var children = this.node.childNodes + for(var i = 0, len = children.length; i < len; ++i){ + + // add newline if its not the first child and newLined is set to true + if(i != 0 && children[i].nodeType != 3 && SVG.adopt(children[i]).dom.newLined == true){ + text += '\n' + } + + // add content of this node + text += children[i].textContent + } + + return text + } + + // remove existing content + this.clear().build(true) + + if (typeof text === 'function') { + // call block + text.call(this, this) + + } else { + // store text and make sure text is not blank + text = text.split('\n') + + // build new lines + for (var i = 0, il = text.length; i < il; i++) + this.tspan(text[i]).newLine() + } + + // disable build mode and rebuild lines + return this.build(false).rebuild() + } + // Set font size + , size: function(size) { + return this.attr('font-size', size).rebuild() + } + // Set / get leading + , leading: function(value) { + // act as getter + if (value == null) + return this.dom.leading + + // act as setter + this.dom.leading = new SVG.Number(value) + + return this.rebuild() + } + // Get all the first level lines + , lines: function() { + var node = (this.textPath && this.textPath() || this).node + + // filter tspans and map them to SVG.js instances + var lines = SVG.utils.map(SVG.utils.filterSVGElements(node.childNodes), function(el){ + return SVG.adopt(el) + }) + + // return an instance of SVG.set + return new SVG.Set(lines) + } + // Rebuild appearance type + , rebuild: function(rebuild) { + // store new rebuild flag if given + if (typeof rebuild == 'boolean') + this._rebuild = rebuild + + // define position of all lines + if (this._rebuild) { + var self = this + , blankLineOffset = 0 + , dy = this.dom.leading * new SVG.Number(this.attr('font-size')) + + this.lines().each(function() { + if (this.dom.newLined) { + if (!this.textPath) + this.attr('x', self.attr('x')) + + if(this.text() == '\n') { + blankLineOffset += dy + }else{ + this.attr('dy', dy + blankLineOffset) + blankLineOffset = 0 + } + } + }) + + this.fire('rebuild') + } + + return this + } + // Enable / disable build mode + , build: function(build) { + this._build = !!build + return this + } + // overwrite method from parent to set data properly + , setData: function(o){ + this.dom = o + this.dom.leading = new SVG.Number(o.leading || 1.3) + return this + } + } + + // Add parent method +, construct: { + // Create text element + text: function(text) { + return this.put(new SVG.Text).text(text) + } + // Create plain text element + , plain: function(text) { + return this.put(new SVG.Text).plain(text) + } + } + +}) + +SVG.Tspan = SVG.invent({ + // Initialize node + create: 'tspan' + + // Inherit from +, inherit: SVG.Shape + + // Add class methods +, extend: { + // Set text content + text: function(text) { + if(text == null) return this.node.textContent + (this.dom.newLined ? '\n' : '') + + typeof text === 'function' ? text.call(this, this) : this.plain(text) + + return this + } + // Shortcut dx + , dx: function(dx) { + return this.attr('dx', dx) + } + // Shortcut dy + , dy: function(dy) { + return this.attr('dy', dy) + } + // Create new line + , newLine: function() { + // fetch text parent + var t = this.parent(SVG.Text) + + // mark new line + this.dom.newLined = true + + // apply new hy¡n + return this.dy(t.dom.leading * t.attr('font-size')).attr('x', t.x()) + } + } + +}) + +SVG.extend(SVG.Text, SVG.Tspan, { + // Create plain text node + plain: function(text) { + // clear if build mode is disabled + if (this._build === false) + this.clear() + + // create text node + this.node.appendChild(document.createTextNode(text)) + + return this + } + // Create a tspan +, tspan: function(text) { + var node = (this.textPath && this.textPath() || this).node + , tspan = new SVG.Tspan + + // clear if build mode is disabled + if (this._build === false) + this.clear() + + // add new tspan + node.appendChild(tspan.node) + + return tspan.text(text) + } + // Clear all lines +, clear: function() { + var node = (this.textPath && this.textPath() || this).node + + // remove existing child nodes + while (node.hasChildNodes()) + node.removeChild(node.lastChild) + + return this + } + // Get length of text element +, length: function() { + return this.node.getComputedTextLength() + } +}) + +SVG.TextPath = SVG.invent({ + // Initialize node + create: 'textPath' + + // Inherit from +, inherit: SVG.Parent + + // Define parent class +, parent: SVG.Text + + // Add parent method +, construct: { + // Create path for text to run on + path: function(d) { + // create textPath element + var path = new SVG.TextPath + , track = this.doc().defs().path(d) + + // move lines to textpath + while (this.node.hasChildNodes()) + path.node.appendChild(this.node.firstChild) + + // add textPath element as child node + this.node.appendChild(path.node) + + // link textPath to path and add content + path.attr('href', '#' + track, SVG.xlink) + + return this + } + // Plot path if any + , plot: function(d) { + var track = this.track() + + if (track) + track.plot(d) + + return this + } + // Get the path track element + , track: function() { + var path = this.textPath() + + if (path) + return path.reference('href') + } + // Get the textPath child + , textPath: function() { + if (this.node.firstChild && this.node.firstChild.nodeName == 'textPath') + return SVG.adopt(this.node.firstChild) + } + } +}) +SVG.Nested = SVG.invent({ + // Initialize node + create: function() { + this.constructor.call(this, SVG.create('svg')) + + this.style('overflow', 'visible') + } + + // Inherit from +, inherit: SVG.Container + + // Add parent method +, construct: { + // Create nested svg document + nested: function() { + return this.put(new SVG.Nested) + } + } +}) +SVG.A = SVG.invent({ + // Initialize node + create: 'a' + + // Inherit from +, inherit: SVG.Container + + // Add class methods +, extend: { + // Link url + to: function(url) { + return this.attr('href', url, SVG.xlink) + } + // Link show attribute + , show: function(target) { + return this.attr('show', target, SVG.xlink) + } + // Link target attribute + , target: function(target) { + return this.attr('target', target) + } + } + + // Add parent method +, construct: { + // Create a hyperlink element + link: function(url) { + return this.put(new SVG.A).to(url) + } + } +}) + +SVG.extend(SVG.Element, { + // Create a hyperlink element + linkTo: function(url) { + var link = new SVG.A + + if (typeof url == 'function') + url.call(link, link) + else + link.to(url) + + return this.parent().put(link).put(this) + } + +}) +SVG.Marker = SVG.invent({ + // Initialize node + create: 'marker' + + // Inherit from +, inherit: SVG.Container + + // Add class methods +, extend: { + // Set width of element + width: function(width) { + return this.attr('markerWidth', width) + } + // Set height of element + , height: function(height) { + return this.attr('markerHeight', height) + } + // Set marker refX and refY + , ref: function(x, y) { + return this.attr('refX', x).attr('refY', y) + } + // Update marker + , update: function(block) { + // remove all content + this.clear() + + // invoke passed block + if (typeof block == 'function') + block.call(this, this) + + return this + } + // Return the fill id + , toString: function() { + return 'url(#' + this.id() + ')' + } + } + + // Add parent method +, construct: { + marker: function(width, height, block) { + // Create marker element in defs + return this.defs().marker(width, height, block) + } + } + +}) + +SVG.extend(SVG.Defs, { + // Create marker + marker: function(width, height, block) { + // Set default viewbox to match the width and height, set ref to cx and cy and set orient to auto + return this.put(new SVG.Marker) + .size(width, height) + .ref(width / 2, height / 2) + .viewbox(0, 0, width, height) + .attr('orient', 'auto') + .update(block) + } + +}) + +SVG.extend(SVG.Line, SVG.Polyline, SVG.Polygon, SVG.Path, { + // Create and attach markers + marker: function(marker, width, height, block) { + var attr = ['marker'] + + // Build attribute name + if (marker != 'all') attr.push(marker) + attr = attr.join('-') + + // Set marker attribute + marker = arguments[1] instanceof SVG.Marker ? + arguments[1] : + this.doc().marker(width, height, block) + + return this.attr(attr, marker) + } + +}) +// Define list of available attributes for stroke and fill +var sugar = { + stroke: ['color', 'width', 'opacity', 'linecap', 'linejoin', 'miterlimit', 'dasharray', 'dashoffset'] +, fill: ['color', 'opacity', 'rule'] +, prefix: function(t, a) { + return a == 'color' ? t : t + '-' + a + } +} + +// Add sugar for fill and stroke +;['fill', 'stroke'].forEach(function(m) { + var i, extension = {} + + extension[m] = function(o) { + if (typeof o == 'undefined') + return this + if (typeof o == 'string' || SVG.Color.isRgb(o) || (o && typeof o.fill === 'function')) + this.attr(m, o) + + else + // set all attributes from sugar.fill and sugar.stroke list + for (i = sugar[m].length - 1; i >= 0; i--) + if (o[sugar[m][i]] != null) + this.attr(sugar.prefix(m, sugar[m][i]), o[sugar[m][i]]) + + return this + } + + SVG.extend(SVG.Element, SVG.FX, extension) + +}) + +SVG.extend(SVG.Element, SVG.FX, { + // Map rotation to transform + rotate: function(d, cx, cy) { + return this.transform({ rotation: d, cx: cx, cy: cy }) + } + // Map skew to transform +, skew: function(x, y, cx, cy) { + return arguments.length == 1 || arguments.length == 3 ? + this.transform({ skew: x, cx: y, cy: cx }) : + this.transform({ skewX: x, skewY: y, cx: cx, cy: cy }) + } + // Map scale to transform +, scale: function(x, y, cx, cy) { + return arguments.length == 1 || arguments.length == 3 ? + this.transform({ scale: x, cx: y, cy: cx }) : + this.transform({ scaleX: x, scaleY: y, cx: cx, cy: cy }) + } + // Map translate to transform +, translate: function(x, y) { + return this.transform({ x: x, y: y }) + } + // Map flip to transform +, flip: function(a, o) { + return this.transform({ flip: a, offset: o }) + } + // Map matrix to transform +, matrix: function(m) { + return this.attr('transform', new SVG.Matrix(m)) + } + // Opacity +, opacity: function(value) { + return this.attr('opacity', value) + } + // Relative move over x axis +, dx: function(x) { + return this.x((this instanceof SVG.FX ? 0 : this.x()) + x, true) + } + // Relative move over y axis +, dy: function(y) { + return this.y((this instanceof SVG.FX ? 0 : this.y()) + y, true) + } + // Relative move over x and y axes +, dmove: function(x, y) { + return this.dx(x).dy(y) + } +}) + +SVG.extend(SVG.Rect, SVG.Ellipse, SVG.Circle, SVG.Gradient, SVG.FX, { + // Add x and y radius + radius: function(x, y) { + var type = (this._target || this).type; + return type == 'radial' || type == 'circle' ? + this.attr('r', new SVG.Number(x)) : + this.rx(x).ry(y == null ? x : y) + } +}) + +SVG.extend(SVG.Path, { + // Get path length + length: function() { + return this.node.getTotalLength() + } + // Get point at length +, pointAt: function(length) { + return this.node.getPointAtLength(length) + } +}) + +SVG.extend(SVG.Parent, SVG.Text, SVG.FX, { + // Set font + font: function(o) { + for (var k in o) + k == 'leading' ? + this.leading(o[k]) : + k == 'anchor' ? + this.attr('text-anchor', o[k]) : + k == 'size' || k == 'family' || k == 'weight' || k == 'stretch' || k == 'variant' || k == 'style' ? + this.attr('font-'+ k, o[k]) : + this.attr(k, o[k]) + + return this + } +}) + +SVG.Set = SVG.invent({ + // Initialize + create: function(members) { + // Set initial state + Array.isArray(members) ? this.members = members : this.clear() + } + + // Add class methods +, extend: { + // Add element to set + add: function() { + var i, il, elements = [].slice.call(arguments) + + for (i = 0, il = elements.length; i < il; i++) + this.members.push(elements[i]) + + return this + } + // Remove element from set + , remove: function(element) { + var i = this.index(element) + + // remove given child + if (i > -1) + this.members.splice(i, 1) + + return this + } + // Iterate over all members + , each: function(block) { + for (var i = 0, il = this.members.length; i < il; i++) + block.apply(this.members[i], [i, this.members]) + + return this + } + // Restore to defaults + , clear: function() { + // initialize store + this.members = [] + + return this + } + // Get the length of a set + , length: function() { + return this.members.length + } + // Checks if a given element is present in set + , has: function(element) { + return this.index(element) >= 0 + } + // retuns index of given element in set + , index: function(element) { + return this.members.indexOf(element) + } + // Get member at given index + , get: function(i) { + return this.members[i] + } + // Get first member + , first: function() { + return this.get(0) + } + // Get last member + , last: function() { + return this.get(this.members.length - 1) + } + // Default value + , valueOf: function() { + return this.members + } + // Get the bounding box of all members included or empty box if set has no items + , bbox: function(){ + var box = new SVG.BBox() + + // return an empty box of there are no members + if (this.members.length == 0) + return box + + // get the first rbox and update the target bbox + var rbox = this.members[0].rbox() + box.x = rbox.x + box.y = rbox.y + box.width = rbox.width + box.height = rbox.height + + this.each(function() { + // user rbox for correct position and visual representation + box = box.merge(this.rbox()) + }) + + return box + } + } + + // Add parent method +, construct: { + // Create a new set + set: function(members) { + return new SVG.Set(members) + } + } +}) + +SVG.FX.Set = SVG.invent({ + // Initialize node + create: function(set) { + // store reference to set + this.set = set + } + +}) + +// Alias methods +SVG.Set.inherit = function() { + var m + , methods = [] + + // gather shape methods + for(var m in SVG.Shape.prototype) + if (typeof SVG.Shape.prototype[m] == 'function' && typeof SVG.Set.prototype[m] != 'function') + methods.push(m) + + // apply shape aliasses + methods.forEach(function(method) { + SVG.Set.prototype[method] = function() { + for (var i = 0, il = this.members.length; i < il; i++) + if (this.members[i] && typeof this.members[i][method] == 'function') + this.members[i][method].apply(this.members[i], arguments) + + return method == 'animate' ? (this.fx || (this.fx = new SVG.FX.Set(this))) : this + } + }) + + // clear methods for the next round + methods = [] + + // gather fx methods + for(var m in SVG.FX.prototype) + if (typeof SVG.FX.prototype[m] == 'function' && typeof SVG.FX.Set.prototype[m] != 'function') + methods.push(m) + + // apply fx aliasses + methods.forEach(function(method) { + SVG.FX.Set.prototype[method] = function() { + for (var i = 0, il = this.set.members.length; i < il; i++) + this.set.members[i].fx[method].apply(this.set.members[i].fx, arguments) + + return this + } + }) +} + + + + +SVG.extend(SVG.Element, { + // Store data values on svg nodes + data: function(a, v, r) { + if (typeof a == 'object') { + for (v in a) + this.data(v, a[v]) + + } else if (arguments.length < 2) { + try { + return JSON.parse(this.attr('data-' + a)) + } catch(e) { + return this.attr('data-' + a) + } + + } else { + this.attr( + 'data-' + a + , v === null ? + null : + r === true || typeof v === 'string' || typeof v === 'number' ? + v : + JSON.stringify(v) + ) + } + + return this + } +}) +SVG.extend(SVG.Element, { + // Remember arbitrary data + remember: function(k, v) { + // remember every item in an object individually + if (typeof arguments[0] == 'object') + for (var v in k) + this.remember(v, k[v]) + + // retrieve memory + else if (arguments.length == 1) + return this.memory()[k] + + // store memory + else + this.memory()[k] = v + + return this + } + + // Erase a given memory +, forget: function() { + if (arguments.length == 0) + this._memory = {} + else + for (var i = arguments.length - 1; i >= 0; i--) + delete this.memory()[arguments[i]] + + return this + } + + // Initialize or return local memory object +, memory: function() { + return this._memory || (this._memory = {}) + } + +}) +// Method for getting an element by id +SVG.get = function(id) { + var node = document.getElementById(idFromReference(id) || id) + return SVG.adopt(node) +} + +// Select elements by query string +SVG.select = function(query, parent) { + return new SVG.Set( + SVG.utils.map((parent || document).querySelectorAll(query), function(node) { + return SVG.adopt(node) + }) + ) +} + +SVG.extend(SVG.Parent, { + // Scoped select method + select: function(query) { + return SVG.select(query, this.node) + } + +}) +function is(el, obj){ + return el instanceof obj +} + +// tests if a given selector matches an element +function matches(el, selector) { + return (el.matches || el.matchesSelector || el.msMatchesSelector || el.mozMatchesSelector || el.webkitMatchesSelector || el.oMatchesSelector).call(el, selector); +} + +// Convert dash-separated-string to camelCase +function camelCase(s) { + return s.toLowerCase().replace(/-(.)/g, function(m, g) { + return g.toUpperCase() + }) +} + +// Capitalize first letter of a string +function capitalize(s) { + return s.charAt(0).toUpperCase() + s.slice(1) +} + +// Ensure to six-based hex +function fullHex(hex) { + return hex.length == 4 ? + [ '#', + hex.substring(1, 2), hex.substring(1, 2) + , hex.substring(2, 3), hex.substring(2, 3) + , hex.substring(3, 4), hex.substring(3, 4) + ].join('') : hex +} + +// Component to hex value +function compToHex(comp) { + var hex = comp.toString(16) + return hex.length == 1 ? '0' + hex : hex +} + +// Calculate proportional width and height values when necessary +function proportionalSize(element, width, height) { + if (width == null || height == null) { + var box = element.bbox() + + if (width == null) + width = box.width / box.height * height + else if (height == null) + height = box.height / box.width * width + } + + return { + width: width + , height: height + } +} + +// Delta transform point +function deltaTransformPoint(matrix, x, y) { + return { + x: x * matrix.a + y * matrix.c + 0 + , y: x * matrix.b + y * matrix.d + 0 + } +} + +// Map matrix array to object +function arrayToMatrix(a) { + return { a: a[0], b: a[1], c: a[2], d: a[3], e: a[4], f: a[5] } +} + +// Parse matrix if required +function parseMatrix(matrix) { + if (!(matrix instanceof SVG.Matrix)) + matrix = new SVG.Matrix(matrix) + + return matrix +} + +// Add centre point to transform object +function ensureCentre(o, target) { + o.cx = o.cx == null ? target.bbox().cx : o.cx + o.cy = o.cy == null ? target.bbox().cy : o.cy +} + +// Convert string to matrix +function stringToMatrix(source) { + // remove matrix wrapper and split to individual numbers + source = source + .replace(SVG.regex.whitespace, '') + .replace(SVG.regex.matrix, '') + .split(SVG.regex.matrixElements) + + // convert string values to floats and convert to a matrix-formatted object + return arrayToMatrix( + SVG.utils.map(source, function(n) { + return parseFloat(n) + }) + ) +} + +// Calculate position according to from and to +function at(o, pos) { + // number recalculation (don't bother converting to SVG.Number for performance reasons) + return typeof o.from == 'number' ? + o.from + (o.to - o.from) * pos : + + // instance recalculation + o instanceof SVG.Color || o instanceof SVG.Number || o instanceof SVG.Matrix ? o.at(pos) : + + // for all other values wait until pos has reached 1 to return the final value + pos < 1 ? o.from : o.to +} + +// PathArray Helpers +function arrayToString(a) { + for (var i = 0, il = a.length, s = ''; i < il; i++) { + s += a[i][0] + + if (a[i][1] != null) { + s += a[i][1] + + if (a[i][2] != null) { + s += ' ' + s += a[i][2] + + if (a[i][3] != null) { + s += ' ' + s += a[i][3] + s += ' ' + s += a[i][4] + + if (a[i][5] != null) { + s += ' ' + s += a[i][5] + s += ' ' + s += a[i][6] + + if (a[i][7] != null) { + s += ' ' + s += a[i][7] + } + } + } + } + } + } + + return s + ' ' +} + +// Deep new id assignment +function assignNewId(node) { + // do the same for SVG child nodes as well + for (var i = node.childNodes.length - 1; i >= 0; i--) + if (node.childNodes[i] instanceof SVGElement) + assignNewId(node.childNodes[i]) + + return SVG.adopt(node).id(SVG.eid(node.nodeName)) +} + +// Add more bounding box properties +function fullBox(b) { + if (b.x == null) { + b.x = 0 + b.y = 0 + b.width = 0 + b.height = 0 + } + + b.w = b.width + b.h = b.height + b.x2 = b.x + b.width + b.y2 = b.y + b.height + b.cx = b.x + b.width / 2 + b.cy = b.y + b.height / 2 + + return b +} + +// Get id from reference string +function idFromReference(url) { + var m = url.toString().match(SVG.regex.reference) + + if (m) return m[1] +} + +// Create matrix array for looping +var abcdef = 'abcdef'.split('') +// Add CustomEvent to IE9 and IE10 +if (typeof CustomEvent !== 'function') { + // Code from: https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent + var CustomEvent = function(event, options) { + options = options || { bubbles: false, cancelable: false, detail: undefined } + var e = document.createEvent('CustomEvent') + e.initCustomEvent(event, options.bubbles, options.cancelable, options.detail) + return e + } + + CustomEvent.prototype = window.Event.prototype + + window.CustomEvent = CustomEvent +} + +// requestAnimationFrame / cancelAnimationFrame Polyfill with fallback based on Paul Irish +(function(w) { + var lastTime = 0 + var vendors = ['moz', 'webkit'] + + for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) { + w.requestAnimationFrame = w[vendors[x] + 'RequestAnimationFrame'] + w.cancelAnimationFrame = w[vendors[x] + 'CancelAnimationFrame'] || + w[vendors[x] + 'CancelRequestAnimationFrame'] + } + + w.requestAnimationFrame = w.requestAnimationFrame || + function(callback) { + var currTime = new Date().getTime() + var timeToCall = Math.max(0, 16 - (currTime - lastTime)) + + var id = w.setTimeout(function() { + callback(currTime + timeToCall) + }, timeToCall) + + lastTime = currTime + timeToCall + return id + } + + w.cancelAnimationFrame = w.cancelAnimationFrame || w.clearTimeout; + +}(window)) + +return SVG + +})); \ No newline at end of file diff --git a/easy_gantt/assets/javascripts/easy_gantt/loader.js b/easy_gantt/assets/javascripts/easy_gantt/loader.js new file mode 100644 index 0000000..712a714 --- /dev/null +++ b/easy_gantt/assets/javascripts/easy_gantt/loader.js @@ -0,0 +1,278 @@ +/* loader.js */ +/* global ysy */ +window.ysy = window.ysy || {}; +ysy.data = ysy.data || {}; +ysy.data.loader = ysy.data.loader || {}; +$.extend(ysy.data.loader, { + /* + * this object is responsible for downloading and preparing data from server + */ + _name: "Loader", + loaded: false, + inited: false, + issueIdListMap: {}, + _onChange: [], + init: function () { + var settings = ysy.settings; + var data = ysy.data; + if (settings.paths.rootPath.substr(-1) !== "/") { + settings.paths.rootPath += "/"; + } + if (!settings.project) { + settings.global = true; + } + if (settings.project) { + settings.projectID = parseInt(settings.project.id); + } + settings.zoom = new ysy.data.Data(); + settings.zoom.init({zoom: data.storage.getSavedZoom() || ysy.settings.defaultZoom || "day", _name: "Zoom"}); + settings.controls = new ysy.data.Data(); + settings.controls.init({controls: true, _name: "Task controls"}); + settings.baseline = new ysy.data.Data(); + settings.baseline.init({open: false, _name: "Baselines"}); + settings.critical = new ysy.data.Data(); + settings.critical.init({open: false, active: false, _name: "Critical path"}); + settings.addTask = new ysy.data.Data(); + settings.addTask.init({open: false, type: "issue", _name: "Add Task"}); + settings.resource = new ysy.data.Data(); + settings.resource.init({open: false, _name: "Resource Management"}); + settings.sumRow = new ysy.data.Data(); + settings.sumRow.init({_name: "SumRow"}); + settings.sample = new data.Data(); + data.limits = new data.Data(); + data.limits.init({_name: "Limits", openings: {}}); + data.relations = new data.Array().init({_name: "RelationArray"}); + data.issues = new data.Array().init({_name: "IssueArray"}); + data.milestones = new data.Array().init({_name: "MilestoneArray"}); + data.projects = new data.Array().init({_name: "ProjectArray"}); + data.baselines = new data.Array().init({_name: "BaselineArray"}); + ysy.view.patch(); + ysy.proManager.patch(); + settings.sample.init(); + this.inited = true; + }, + load: function () { + // second part of program initialization + this.loaded = false; + //this.projects=new ysy.data.Array; + //var data=ysy.availableProjects; + ysy.log.debug("load()", "load"); + if (ysy.settings.sample.active) { + ysy.pro.sample.loadSample(ysy.settings.sample.active); + } else { + ysy.gateway.loadGanttdata( + $.proxy(this._handleMainGantt, this), + function () { + ysy.log.error("Error: Unable to load data"); + } + ); + } + }, + loadSubEntity: function (type, id) { + if (type === "project") { + return this.loadProject(id); + } + }, + register: function (func, ctx) { + this._onChange.push({func: func, ctx: ctx}); + }, + /** + * + * @param {Array.<{id:int}>} array + * @param {Array.} oldIds + * @return {Array.<{id:int}>} + */ + reorderArray: function (array, oldIds) { + var newArray = []; + var arrayPointer = 0; + for (var i = 0; i < array.length; i++) { + if (oldIds.length === i || oldIds[i] !== array[i].id) { + if (i > 0) { + newArray = array.slice(0, i); + arrayPointer = i; + oldIds = oldIds.slice(i); + } + break; + } + } + var banned = []; + for (i = 0; i < oldIds.length; i++) { + for (var j = arrayPointer; j < array.length; j++) { + if (array[j].id === oldIds[i]) { + newArray.push(array[j]); + banned.push(array[j]); + break; + } + } + } + for (i = arrayPointer; i < array.length; i++) { + if (banned.indexOf(array[i]) > -1) continue; + newArray.push(array[i]); + } + return newArray; + }, + _fireChanges: function (who, reason) { + for (var i = 0; i < this._onChange.length; i++) { + var ctx = this._onChange[i].ctx; + if (!ctx || ctx.deleted) { + this._onChange.splice(i, 1); + continue; + } + //this.onChangeNew[i].func(); + ysy.log.log("-- changes to " + (ctx.name ? ctx.name : ctx._name) + " widget"); + $.proxy(this._onChange[i].func, ctx)(); + } + }, + _handleMainGantt: function (data) { + if (!data.easy_gantt_data) return; + var json = data.easy_gantt_data; + ysy.log.debug("_handleGantt()", "load"); + // -- LIMITS -- + //ysy.data.limits.set({ // TODO + // start_date: moment(json.start_date, "YYYY-MM-DD"), + // end_date: moment(json.end_date, "YYYY-MM-DD") + //}); + // -- COLUMNS -- + ysy.data.columns = json.columns; + // ARRAY INITIALIZATION + // -- RELATIONS -- + ysy.data.relations.clear(); + // -- ISSUES -- + ysy.data.issues.clear(); + // -- MILESTONES -- + ysy.data.milestones.clear(); + // -- PROJECTS -- + ysy.data.projects.clear(); + // ARRAY FILLING + // -- PROJECTS -- + this._loadProjects(json.projects); + // -- ISSUES -- + this._loadIssues(json.issues, "root"); + // -- MILESTONES -- + this._loadMilestones(json.versions); // after issue loading because of shared milestones + // -- RELATIONS -- + this._loadRelations(json.relations); + + ysy.log.debug("data loaded", "load"); + ysy.log.message("JSON loaded"); + this._fireChanges(); + ysy.history.clear(); + this.loaded = true; + }, + _loadIssues: function (json, rootId) { + if (!json) return; + if (rootId) { + if (this.issueIdListMap[rootId]) { + json = ysy.data.loader.reorderArray(json, this.issueIdListMap[rootId]); + } + this.issueIdListMap[rootId] = json.map(function (item) { + return item.id; + }); + } + var issues = ysy.data.issues; + for (var i = 0; i < json.length; i++) { + var issue = new ysy.data.Issue(); + issue.init(json[i]); + issues.pushSilent(issue); + } + issues._fireChanges(this, "load"); + }, + _loadRelations: function (json) { + if (!json) return; + var relations = ysy.data.relations; + var allowedTypes = { + precedes: true, + finish_to_finish: true, + start_to_start: true, + start_to_finish: true + }; + for (var i = 0; i < json.length; i++) { + // TODO enable other relation types + if (allowedTypes[json[i].type]) { + var rela = new ysy.data.Relation(); + } else { + rela = new ysy.data.SimpleRelation(); + } + rela.init(json[i]); + relations.pushSilent(rela); + } + relations._fireChanges(this, "load"); + }, + _loadMilestones: function (json) { + if (!json) return; + var milestones = ysy.data.milestones; + for (var i = 0; i < json.length; i++) { + var mile = new ysy.data.Milestone(); + mile.init(json[i]); + milestones.pushSilent(mile); + + var issues = mile.getIssues(); + var projectIds = {}; + for (var j = 0; j < issues.length; j++) { + projectIds[issues[j].project_id] = true; + } + delete projectIds[mile.project_id]; + var sharedForIds = Object.getOwnPropertyNames(projectIds); + if (sharedForIds.length === 0) continue; + var realProjectId = json[i].project_id; + var realProjectName = json[i].project_name; + delete json[i].project_id; + for (j = 0; j < sharedForIds.length; j++) { + var sharedMile = new ysy.data.SharedMilestone(); + $.extend(sharedMile, { + project_id: parseInt(sharedForIds[j]), + real_project_id: realProjectId, + real_project_name: realProjectName, + real_milestone: milestones.getByID(mile.id) + }); + sharedMile.init(json[i]); + milestones.pushSilent(sharedMile); + } + } + milestones._fireChanges(this, "load"); + }, + _loadProjects: function (json) { + if (!json) return; + var projects = ysy.data.projects; + //var main_id = ysy.settings.projectID; + for (var i = 0; i < json.length; i++) { + //if (json[i].id === main_id) continue; + var project = new ysy.data.Project(); + project.init(json[i]); + projects.pushSilent(project); + } + projects._fireChanges(this, "load"); + var openings = ysy.data.limits.openings; + for (var id in openings) { + if (!openings.hasOwnProperty(id)) continue; + if (ysy.main.startsWith(id, "p")) { + var realId = id.substring(1); + project = projects.getByID(realId); + if (!project) continue; + if (!project.needLoad) continue; + project.needLoad = false; + this.loadProject(realId); + } else if (this.openIssuesOfProject && ysy.main.startsWith(id, "s")) { + realId = id.substring(1); + if (!openings["p" + realId]) continue; + this.openIssuesOfProject(realId); + } + } + }, + _loadHolidays: function (json) { + if (!json) return; + ysy.settings.holidays = json; + ysy.view.initNonworkingDays(); + }, + loadProject: function () { + } + +}); +if (!ysy.gateway) ysy.gateway = {}; +$.extend(ysy.gateway, { + loadGanttdata: function (callback, fail) { + $.getJSON(ysy.settings.paths.mainGantt) + .done(callback) + .fail(fail); + } +}); diff --git a/easy_gantt/assets/javascripts/easy_gantt/logger.js b/easy_gantt/assets/javascripts/easy_gantt/logger.js new file mode 100644 index 0000000..fe2c435 --- /dev/null +++ b/easy_gantt/assets/javascripts/easy_gantt/logger.js @@ -0,0 +1,92 @@ +/* logger.js */ +/* global ysy */ +window.ysy = window.ysy || {}; +ysy.log = { + logLevel: 2, + mainDebug: "", + debugTypes: [ + // "refresher", + // "critical", + // "canvas_bg", + // "set", + // "baseline", + // "baseline_render", + // "inline", + // "print", + // "move_task", + // "add_task", + // "add_task_marker", + // "taskModal", + // "date_format", + // "date_helper", + // "date", + // "tooltip", + // "send", + // "load", + // "supersend", + // "scroll", + // "scrollRender", + // "grid_resize", + // "task_drag", + // "asc", + // "task_push", + // "link_render", + // "outer", + // "sort", + // "link_config", + // "link_drag", + // "empty_field", + // "task_drag_milestone", + // "widget_destroy", + // "resource", + // "summer", + "nothing" + ], + log: function (text) { + if (this.logLevel >= 4) { + this.print(text); + } + }, + message: function (text) { + if (this.logLevel >= 3) { + this.print(text); + } + }, + debug: function (text, type) { + if (type) { + if (this.mainDebug === type) { + this.print(text, "debug"); + return; + } + for (var i = 0; i < this.debugTypes.length; i++) { + if (this.debugTypes[i] === type) { + this.print(text, type === this.mainDebug ? "debug" : null); + return; + } + } + } else { + this.print(text, "debug"); + } + }, + warning: function (text) { + if (this.logLevel >= 2) { + this.print(text, "warning"); + } + }, + error: function (text) { + if (this.logLevel >= 1) { + this.print(text, "error"); + } + }, + print: function (text, type) { + if (type === "error") { + console.error(text); + } else if (type === "warning") { + console.warn(text); + } else if (type === "debug") { + console.debug(text); + } else { + console.log(text); + } + } +}; \ No newline at end of file diff --git a/easy_gantt/assets/javascripts/easy_gantt/main.js b/easy_gantt/assets/javascripts/easy_gantt/main.js new file mode 100644 index 0000000..7df2f74 --- /dev/null +++ b/easy_gantt/assets/javascripts/easy_gantt/main.js @@ -0,0 +1,15 @@ +/* main.js */ +/* global ysy */ +window.ysy = window.ysy || {}; +ysy.main = ysy.main || {}; +ysy.initGantt = function () { + $("p.nodata").remove(); + ysy.data.loader.init(); + ysy.data.loader.load(); + ysy.data.storage.init(); + if (!ysy.settings.easyRedmine) { + moment.locale(ysy.settings.language || "en"); + } + ysy.view.start(); + //ysy.main.start(); +}; diff --git a/easy_gantt/assets/javascripts/easy_gantt/panel_widget.js b/easy_gantt/assets/javascripts/easy_gantt/panel_widget.js new file mode 100644 index 0000000..6469384 --- /dev/null +++ b/easy_gantt/assets/javascripts/easy_gantt/panel_widget.js @@ -0,0 +1,470 @@ +/* panel_widget.js */ +/* global ysy */ +window.ysy = window.ysy || {}; +ysy.view = ysy.view || {}; + +ysy.view.Toolbars = function () { + ysy.view.Widget.call(this); +}; +ysy.main.extender(ysy.view.Widget, ysy.view.Toolbars, { + name: "ToolbarsWidget", + template: "", + childTargets: { + "SuperPanelWidget": "#supertop_panel", + "BottomPanelWidget": "#gantt_footer_buttons", + "BaselinePanelWidget": "#baseline_panel", + "CriticalPanelWidget": "#critical_panel", + "AddTaskPanelWidget": "#add_task_panel", + "LegendWidget": "#easy_gantt_footer_legend", + "ToolPanelWidget": "#easy_gantt_tool_panel", + "CollapsorsWidget": "#gantt_cont", + "AffixWidget": "#easy_gantt_menu" + }, + _updateChildren: function () { + if (this.children.length > 0) { + return; + } + if (ysy.view.SuperPanel) { + var superpanel = new ysy.view.SuperPanel(); + superpanel.init(ysy.settings.sample); + this.children.push(superpanel); + } + + var toppanel = new ysy.view.AllButtons(); + toppanel.init(); + this.children.push(toppanel); + + ysy.proManager.fireEvent("initToolbar", this); + + var legend = new ysy.view.Legend(); + legend.init(null); + this.children.push(legend); + + var collapsors = new ysy.view.Collapsors(); + collapsors.init(null); + ysy.view.collapsors = collapsors; + this.children.push(collapsors); + + if (window.affix || !ysy.settings.easyRedmine) { + var affix = new ysy.view.Affix(); + ysy.view.affix = affix; + affix.init(); + this.children.push(affix); + } else { + ysy.view.affix = { + requestRepaint: function () { + } + }; + } + + }, + _repaintCore: function () { + for (var i = 0; i < this.children.length; i++) { + var child = this.children[i]; + this.setChildTarget(child, i); + child.repaint(true); + } + }, + setChildTarget: function (child/*, i*/) { + if (this.childTargets[child.name]) { + child.$target = this.$target.find(this.childTargets[child.name]); + } + } +}); +//############################################################################################# +ysy.view.AllButtons = function () { + ysy.view.Widget.call(this); +}; +ysy.main.extender(ysy.view.Widget, ysy.view.AllButtons, { + name: "AllButtonsWidget", + templateName: "AllButtons", + extendees: { + test: { + func: function () { + ysy.test.run(); + }, on: true, + hid: true + }, + back: { + bind: function () { + this.model = ysy.history; + }, + func: function () { + ysy.history.revert(); + }, + isDisabled: function () { + return ysy.history.isEmpty(); + } + + }, + save: { + bind: function () { + this.model = ysy.history; + this.sample = ysy.settings.sample; + this._register(this.sample); + }, + func: function () { + if (ysy.settings.sample.active) { + ysy.data.loader.load(); + return; + } + var $content =$(".easy-content-page"); + var height = $content.height(); + $content.css({"height": height}); + if (this.timeoutSubscription){ + window.clearTimeout(this.timeoutSubscription); + } + this.timeoutSubscription = window.setTimeout(function(){ + $content.css({"height": ""}); + },5000); + ysy.data.save(); + }, + specialRepaint: function () { + var button_labels = ysy.view.getLabel("buttons"); + if (ysy.settings.sample.active) { + var label = button_labels.button_reload; + } else { + label = button_labels.button_save; + } + this.$target.children("icon-label").html(label); + }, + //isHidden:function(){return ysy.settings.sample.active;}, + isDisabled: function () { + return this.model.isEmpty() + }, + timeoutSubscription: 0 + }, + day_zoom: { + value: "day", + bind: function () { + this.model = ysy.settings.zoom; + }, + func: function () { + if (ysy.settings.zoom.setSilent("zoom", this.value)) ysy.settings.zoom._fireChanges(this, this.value); + }, + isOn: function () { + return ysy.settings.zoom.zoom === this.value; + } + }, + week_zoom: { + value: "week", + bind: function () { + this.model = ysy.settings.zoom; + }, + func: function () { + if (ysy.settings.zoom.setSilent("zoom", this.value)) ysy.settings.zoom._fireChanges(this, this.value); + }, + isOn: function () { + return ysy.settings.zoom.zoom === this.value; + } + }, + month_zoom: { + value: "month", + bind: function () { + this.model = ysy.settings.zoom; + }, + func: function () { + if (ysy.settings.zoom.setSilent("zoom", this.value)) ysy.settings.zoom._fireChanges(this, this.value); + }, + isOn: function () { + return ysy.settings.zoom.zoom === this.value; + } + }, + quarter_zoom: { + value: "quarter", + bind: function () { + this.model = ysy.settings.zoom; + }, + func: function () { + if (ysy.settings.zoom.setSilent("zoom", this.value)) ysy.settings.zoom._fireChanges(this, this.value); + }, + isOn: function () { + return ysy.settings.zoom.zoom === this.value; + } + }, + year_zoom: { + value: "year", + bind: function () { + this.model = ysy.settings.zoom; + }, + func: function () { + if (ysy.settings.zoom.setSilent("zoom", this.value)) ysy.settings.zoom._fireChanges(this, this.value); + }, + isOn: function () { + return ysy.settings.zoom.zoom === this.value; + } + }, + + task_control: { + bind: function () { + this.model = ysy.settings.controls; + }, + func: function () { + ysy.settings.controls.setSilent("controls", !this.isOn()); + ysy.settings.controls._fireChanges(this, !this.isOn()); + //this.on=!$(".gantt_bars_area").toggleClass("no_task_controls").hasClass("no_task_controls"); + $(".gantt_bars_area").toggleClass("no_task_controls"); + this.requestRepaint(); + }, + isOn: function () { + return ysy.settings.controls.controls; + }, + isHidden: function () { + // return !ysy.settings.permissions.allowed("edit_easy_gantt", "edit_issues"); + return false; + } + }, + resource_help: {}, + add_task_help: {}, + baseline_help: {}, + critical_help: {}, + print: { + func: function () { + return ysy.pro.print.directPrint(this); + }, + isOn: function () { + return ysy.pro.print.printPreparing; + }, + forceRepaint:function () { + this.requestRepaint(); + this.repaint(); + } + }, + jump_today: { + func: function () { + gantt.showDate(moment()); + } + } + }, + _updateChildren: function () { + var children = []; + this.$target = $("#content"); + //var spans=this.$target.children("span"); + for (var elid in this.extendees) { + if (!this.extendees.hasOwnProperty(elid)) continue; + var extendee = this.extendees[elid]; + var button; + if (extendee.widget) { + button = new extendee.widget(); + } else { + button = new ysy.view.Button(); + } + $.extend(button, extendee, {elid: elid}); + if (!this.getChildTarget(button, elid).length) continue; + button.init(); + children.push(button); + } + this.children = children; + }, + out: function () { + //return {buttons:this.child_array}; + }, + _repaintCore: function () { + for (var i = 0; i < this.children.length; i++) { + var child = this.children[i]; + this.setChildTarget(child, i); + child.repaint(true); + } + }, + setChildTarget: function (child /*,i*/) { + child.$target = this.getChildTarget(child); + }, + getChildTarget: function (child, elid) { + if (!elid) elid = child.elid; + return this.$target.find("#" + child.elementPrefix + elid); + } +}); +//############################################################################## +ysy.view.Button = function () { + ysy.view.Widget.call(this); + this.on = false; + this.disabled = false; + this.func = function () { + var div = $(this.$target).next('div'); + var x = div.clone().attr({"id": div[0].id + "_popup"}).appendTo($("body")); + showModal(x[0].id); + //var template=ysy.view.getTemplate("easy_unimplemented"); + //var rendered=Mustache.render(template, {modal: ysy.view.getLabel("soon_"+this.elid)}); + //$("#ajax-modal").html(rendered); // REPAINT + //window.showModal("ajax-modal"); + } +}; +ysy.main.extender(ysy.view.Widget, ysy.view.Button, { + name: "ButtonWidget", + templateName: "Button", + elementPrefix: "button_", + _replace: true, + init: function () { + this.name = (this.elid || this.id) + this.name; + this.name = this.name.charAt(0).toUpperCase() + this.name.slice(1); + if (this.bind) { + this.bind(); + } + if (this.model) { + this._register(this.model); + } + //this.tideFunctionality(); + return this; + }, + tideFunctionality: function () { + if (this.func && !this.isDisabled() && (!this.$target.is("a") || this.$target.attr("href") === "javascript:void(0)")) { + this.$target.off("click").on("click", $.proxy(this.func, this)); + } + }, + isHidden: function () { + return this.hid; + }, + _repaintCore: function () { + var target = this.$target; + var hidden = this.isHidden(); + target.toggle(!hidden); + if (hidden) { + if (this.specialRepaint) { + this.specialRepaint(hidden); + } + return; + } + if (this.isDisabled()) { + target.addClass("disabled"); + target.removeClass("active"); + } else { + target.removeClass("disabled"); + if (this.isOn()) { + target.addClass("active"); + } else { + target.removeClass("active"); + } + } + if (this.specialRepaint) { + this.specialRepaint(); + } + this.tideFunctionality(); + }, + isOn: function () { + return this.on; + }, + isDisabled: function () { + return this.disabled; + } +}); +//############################################################################## +ysy.view.Select = function () { + ysy.view.Button.call(this); +}; +ysy.main.extender(ysy.view.Button, ysy.view.Select, { + name: "SelectWidget", + templateName: "Select", + elementPrefix: "select_", + _repaintCore: function () { + var target = this.$target; + var hidden = this.isHidden(); + target.toggle(!hidden); + if (hidden) { + if (this.specialRepaint) { + this.specialRepaint(hidden); + } + return; + } + target.prop('disabled', this.isDisabled()); + target.val(this.modelValue()); + if (this.specialRepaint) { + this.specialRepaint(); + } + this.tideFunctionality(); + }, + tideFunctionality: function () { + if (this.func && !this.isDisabled()) { + this.$target.off("change").on("change", $.proxy(this.func, this)); + } + }, + modelValue: function () { + return ""; + } +}); +//############################################################################## +ysy.view.CheckBox = function () { + ysy.view.Button.call(this); +}; +ysy.main.extender(ysy.view.Button, ysy.view.CheckBox, { + name: "CheckBoxWidget", + elementPrefix: "checkbox_", + _repaintCore: function () { + var target = this.$target; + var hidden = this.isHidden(); + target.toggle(!hidden); + if (hidden) { + if (this.specialRepaint) { + this.specialRepaint(hidden); + } + return; + } + target.prop('disabled', this.isDisabled()); + target.prop('checked', this.isOn()); + if (this.specialRepaint) { + this.specialRepaint(); + } + this.tideFunctionality(); + }, + tideFunctionality: function () { + if (this.func && !this.isDisabled()) { + this.$target.off("change").on("change", $.proxy(this.func, this)); + } + } +}); +//#################################################### +ysy.view.Legend = function () { + ysy.view.Widget.call(this); +}; +ysy.main.extender(ysy.view.Widget, ysy.view.Legend, { + name: "LegendWidget", + templateName: "legend", + _postInit: function () { + }, + out: function () { + return null; + //return {text: "Legend for EasyGantt"}; + } +}); +//################################################### +ysy.view.Affix = function () { + ysy.view.Widget.call(this); + this.offset = 0; +}; +ysy.main.extender(ysy.view.Widget, ysy.view.Affix, { + name: "AffixWidget", + init: function () { + this.$document = $(document); + this.$superPanel = $("#supertop_panel"); + this.$cont = $("#gantt_cont"); + this.$document.on("scroll", $.proxy(this.requestRepaint, this)); + $(window).on("resize", $.proxy(this.requestRepaint, this)); + if (ysy.settings.easyRedmine) { + this.offset += $("#top-menu").outerHeight(); + } + this.bottomFixed = false; + //this._updateChildren(); + }, + _repaintCore: function () { + var top = this.$document.scrollTop() + this.offset - this.$superPanel.offset().top - this.$superPanel.outerHeight(); + top = Math.max(Math.floor(top), 0); + this.setPosition(top); + var box = this.$cont[0].getBoundingClientRect(); + + if (box.bottom > window.innerHeight) { + if (!this.bottomFixed) { + // this.$cont.find(".gantt_hor_scroll").css({position: "fixed", top: (window.innerHeight - 15) + "px"}); + this.$cont.find(".gantt_hor_scroll").css({position: "fixed", bottom: "0"}); + this.bottomFixed = true; + } + } else { + if (this.bottomFixed) { + this.$cont.find(".gantt_hor_scroll").css({position: "relative", bottom: ""}); + this.bottomFixed = false; + } + } + }, + setPosition: function (top) { + this.$target.css({transform: "translate(0, " + top + "px)"}); + this.$cont.find(".gantt_grid_scale, .gantt_task_scale").css({transform: "translate(0, " + (top - 1) + "px)"}); + } +}); diff --git a/easy_gantt/assets/javascripts/easy_gantt/print.js b/easy_gantt/assets/javascripts/easy_gantt/print.js new file mode 100644 index 0000000..ee279c3 --- /dev/null +++ b/easy_gantt/assets/javascripts/easy_gantt/print.js @@ -0,0 +1,277 @@ +/* print.js */ +/* global ysy */ +window.ysy = window.ysy || {}; +ysy.pro = ysy.pro || {}; +ysy.pro.print = { + printPrepared: false, + printPreparing: false, + patch: function () { + var self = this; + var mediaQueryList = window.matchMedia('print'); + mediaQueryList.addListener(function (mql) { + if (mql.matches) { + // self.beforePrint(); + } else { + self.afterPrint(); + } + }); + // window.onbeforeprint = $.proxy(this.beforePrint, this); + window.onafterprint = $.proxy(this.afterPrint, this); + + window.easyModel = window.easyModel || {}; + window.easyModel.print = window.easyModel.print || {}; + window.easyModel.print.functions = window.easyModel.print.functions || []; + + window.easyModel.print.functions.push(this.printToTemplate); + }, + directPrint: function (buttonWidget) { + var self = this; + self.printPreparing = true; + buttonWidget.forceRepaint(); + setTimeout(function () { + self.beforePrint(); + self.printPreparing = false; + buttonWidget.forceRepaint(); + window.print(); + // self.afterPrint(); + }, 30); + }, + beforePrint: function (stripWidth) { + var self = ysy.pro.print; + if (self.printPrepared) return; + var $wrapper2 = $("#wrapper2"); + var $wrapper3 = $("#wrapper3"); + if (ysy.view.affix.setPosition) { + ysy.view.affix.setPosition(0); + } + $("#print_area").remove(); + var $print = $(''); + + // if (ysy.settings.project) { + // var headerText = '

' + ysy.settings.project.name + '

' + // + '

 - ' + // + (ysy.settings.resource.open ? ysy.settings.labels.titles.title_rm : ysy.settings.labels.titles.easyGantt) + // + '

'; + // } else { + // headerText = '

' + // + (ysy.settings.resource.open ? ysy.settings.labels.titles.title_rm : ysy.settings.labels.titles.easyGantt) + // + '

'; + // } + // var $headerCont = $('
'); + // $headerCont.html(headerText); + + var $gantt = $('
'); + $print.append($gantt); + var fullWidth = gantt._tasks.full_width; + stripWidth = stripWidth || 490; + var $grid = self.cloneGrid(); + // $print.prepend($headerCont); + $gantt.append($grid); + var gridWidth = $grid.outerWidth(); + for (var p = -gridWidth; p < fullWidth; p += stripWidth) { + $gantt.append(self.createStrip(p < 0 ? 0 : p, Math.min(p + stripWidth, fullWidth))); + //p -= 2; + } + // $(".gantt-print__strip, .gantt-print__grid").css("margin-top", $headerCont.height() + 5); + $wrapper3.hide(); + $(".gantt_hor_scroll").hide(); + $wrapper2.append($print); + $("body").addClass("gantt-print__body"); + self.printPrepared = true; + + }, + afterPrint: function () { + setTimeout(function () { + if (!ysy.pro.print.printPrepared) return; + $("body").removeClass("gantt-print__body"); + $("#print_area").remove(); + + $("#wrapper3").show(); + $("#content, #sidebar").removeClass("fake-responsive"); + + gantt._scroll_resize(); + ysy.pro.print.printPrepared = false; + }, 100); + }, + printToTemplate: function () { + var printFit = $("#easy_gantt_print_fit_checkbox").is(":checked"); + ysy.pro.print.beforePrint(printFit ? Infinity : undefined); + + var width = $(".gantt_container").width(); + var content = $("#print_area").html() + ysy.view.templates.printIncludes; + // TODO add easy_gantt_pro.css + // TODO add easy_gantt_resources.css + + if (printFit) { + content = '
' + content + '
'; + } + + // window.easyModel.print.tokens['easy_gantt_current_base64'] = $.base64.encode(content); + window.easyModel.print.tokens['easy_gantt_current'] = content; + window.easyModel.print.setWidth(width); + ysy.pro.print.afterPrint(); + }, + cloneGrid: function () { + var $gantt_cont = $("#gantt_cont"); + var $grid = $gantt_cont.find(".gantt_grid").clone().addClass("gantt-print__grid"); + $grid.find("a").each(function () { + var $this = $(this); + $this.parent().append('' + $this.text() + ''); + $this.remove(); + }); + var $gridScale = $grid.find(".gantt_grid_scale"); + $gridScale.css({height: $gridScale.height() + 1 + "px", transform: "none"}); + return $grid; + }, + createStrip: function (start, end) { + if (end <= start) return null; + var $gantt_cont = $("#gantt_cont"); + var $gantt_task = $gantt_cont.find(".gantt_task"); + var $strip = $('
'); + // SCALE LINE + $strip.append(this.cloneScales($gantt_task, start, end)); + + // DATA AREA + var $gantt_data_area = $gantt_cont.find(".gantt_data_area"); + var $data = $('
').css({ + height: $gantt_data_area.height() + "px", + width: (end - start) + "px" + }); + // BACKGROUND + $data.append(this.cloneSvgBackground($gantt_data_area, start, end)); + + // TASKS + $data.append(this.cloneTasks($gantt_data_area, start, end)); + // LINKS + $data.append(this.cloneLinks($gantt_data_area, start, end)); + + $strip.append($data); + + return $strip; + }, + cloneScales: function ($source, start, end) { + var $scale = $( + '
'); + var lines = $source.find(".gantt_scale_line"); + for (var l = 0; l < lines.length; l++) { + var oldLine = $(lines[l]); + var cells = oldLine.find(".gantt_scale_cell"); + var $line = $('
'); + $line[0].style.height = lines[l].style.height; + $line[0].style.lineHeight = lines[l].style.lineHeight; + //$line.style.height=oldLine.style.height; + //$line.style.lineHeight=oldLine.style.lineHeight; + //$line.height(oldLine.height()); + var leftPointer = 0; + var first = false; + for (var i = 0; i < cells.length; i++) { + var oldCell = $(cells[i]); + var width = oldCell.outerWidth(); + if (leftPointer < end && leftPointer + width > start) { + var $cell = oldCell.clone(); + $line.append($cell); + if (first === false) { + first = true; + $cell.css("margin-left", (leftPointer - start) + "px"); + } + } + leftPointer += width; + } + $line.width(leftPointer); + + $scale.append($line); + } + return $scale; + }, + cloneSvgBackground: function ($source, startX, endX) { + var $background = $('
'); + var svg = SVG($background[0]); + $(svg.node).css({position: "absolute"}); + var itemIds = gantt._order; + var items = itemIds.map(function (itemId) { + return gantt._pull[itemId]; + }); + + + var cfg = gantt._tasks; + var fullHeight = itemIds.length * cfg.height; + var colWidth = cfg.col_width; + var countX = cfg.count; + var countY = items.length; + var endCountX = Math.ceil(endX / colWidth); + var startCountX = Math.floor(startX / colWidth); + if (endCountX > countX) { + endCountX = countX; + } + gantt._backgroundRenderer._render_bg_canvas(svg, items, { + fromX: startCountX, + toX: endCountX, + fromY: 0, + toY: countY + }); + svg.node.style.left = (cfg.left[startCountX] - startX) + "px"; + return $background.height(fullHeight); + }, + cloneLinks: function ($source, start, end) { + var $gantt_links_area = $source.find(".gantt_links_area"); + var $links = $gantt_links_area + .clone() + .addClass("gantt-print__links-area") + .css('left', -start + 'px'); + $links.find(".gantt_task_link > div").filter(function () { + var left = parseInt(this.style.left); + var width = parseInt(this.style.width || 10); + //ysy.log.debug("start: " + (left + width) + "<" + start + + // " end: " + left + ">" + end + + // " filtered = " + (left > end || left + width < start)); + return (left > end || left + width < start); + }).remove(); + return $links; + }, + cloneTasks: function ($source, start, end) { + var $gantt_bars_area = $source.find(".gantt_bars_area"); + var $tasks = $($gantt_bars_area[0].cloneNode(false)) + .addClass("gantt-print__bars-area") + .css('left', -start + 'px'); + var taskArray = $gantt_bars_area.children(); + for (var i = 0; i < taskArray.length; i++) { + var task = taskArray[i]; + var left = parseInt(task.style.left) - 50; + var width = task.offsetWidth + 300; + // ysy.log.debug(JSON.stringify({text: $(task).text(), start: (left + task.offsetWidth) + "<" + start, end: left + ">" + end})); + if (left >= end || left + width <= start) continue; + $tasks.append($(task).clone()); + } + var sourceCanvases = $gantt_bars_area.find("canvas"); + if (sourceCanvases.length > 0) { + var clonedCanvases = $tasks.find("canvas"); + for (i = 0; i < clonedCanvases.length; i++) { + clonedCanvases[i].getContext('2d').drawImage(sourceCanvases[i], 0, 0); + } + } + return $tasks; + } +}; +//###################################################################################################################### +ysy.pro = ysy.pro || {}; +ysy.pro.pdfPrint = ysy.pro.pdfPrint || {}; +$.extend(ysy.pro.pdfPrint, { + beats: 0, + targetBeat: 2, + patch: function () { + if (!ysy.settings.pdfPrint) return; + ysy.data.loader.register(function () { + ysy.view.onRepaint.push($.proxy(this.repaint, this)); + }, this); + }, + repaint: function () { + if (this.beats > this.targetBeat) return; + // skip first few renders + if (this.beats === this.targetBeat) { + gantt._backgroundRenderer.switchFullRender(true); + gantt._unset_sizes(); + // window.print(); + } + this.beats++; + } +}); diff --git a/easy_gantt/assets/javascripts/easy_gantt/pro_manager.js b/easy_gantt/assets/javascripts/easy_gantt/pro_manager.js new file mode 100644 index 0000000..7cb039a --- /dev/null +++ b/easy_gantt/assets/javascripts/easy_gantt/pro_manager.js @@ -0,0 +1,72 @@ +/* pro_manager.js */ +/* global ysy */ +window.ysy = window.ysy || {}; +ysy.proManager = ysy.proManager || {}; +ysy.pro = ysy.pro || {}; +$.extend(ysy.proManager, { + proFunctionsMap: {}, + name:"proManager", + patch: function () { + window.ysy = window.ysy || {}; + ysy.settings = ysy.settings || {}; + for (var key in ysy.pro) { + if (!ysy.pro.hasOwnProperty(key)) continue; + if (ysy.pro[key].patch) { + ysy.pro[key].patch(); + } + } + }, + forEachPro: function (wrapperFunc, event) { + var proFunctions = this.proFunctionsMap[event]; + if (!proFunctions) return; + for (var i = 0; i < proFunctions.length; i++) { + wrapperFunc.call(this, proFunctions[i]); + } + }, + fireEvent: function (event) { + var proFunctions = this.proFunctionsMap[event]; + if (!proFunctions) return; + var slicedArgs = Array.prototype.slice.call(arguments, 1); + for (var i = 0; i < proFunctions.length; i++) { + proFunctions[i].apply(this, slicedArgs); + } + }, + register: function (event, func) { + if (!func) throw "missing call function"; + var eventList = this.proFunctionsMap[event]; + if (!eventList) this.proFunctionsMap[event] = eventList = []; + for (var i = 0; i < eventList.length; i++) { + if (eventList[i] === func) { + return; + } + } + eventList.push(func); + }, + unregister: function (event, func) { + var eventList = this.proFunctionsMap[event]; + if (!eventList) return; + for (var i = 0; i < eventList.length; i++) { + if (eventList[i] === func) { + eventList.splice(i, 1); + return; + } + } + }, + showHelp: function () { + var div = $(this).next(); + var x = div.clone().attr({"id": div[0].id + "_popup"}).appendTo($("body")); + showModal(x[0].id); + }, + closeAll: function (except) { + this.forEachPro(function (func) { + if (except.close !== func) func.call(this,except); + }, "close"); + }, + eventFilterTask: function (id, task) { + var ret = true; + this.forEachPro(function (func) { + if (ret) ret = func(id, task); + }, "filterTask"); + return ret; + } +}); diff --git a/easy_gantt/assets/javascripts/easy_gantt/problem_finder.js b/easy_gantt/assets/javascripts/easy_gantt/problem_finder.js new file mode 100644 index 0000000..2ad0f36 --- /dev/null +++ b/easy_gantt/assets/javascripts/easy_gantt/problem_finder.js @@ -0,0 +1,172 @@ +/* problem_finder.js */ +/* global ysy */ +window.ysy = window.ysy || {}; +ysy.pro = ysy.pro || {}; +ysy.pro.problemFinder = $.extend(ysy.pro.problemFinder, { + patch: function () { + var setting = new ysy.data.Data(); + setting.init({ + _name: "Problem finder", + opened: false, + issueProblems: [], + relationProblems: [] + }); + ysy.settings.problemFinder = setting; + ysy.data.issues.childRegister(function () { + ysy.pro.problemFinder.recalculateProblemsInIssues.call(this); + ysy.pro.problemFinder.recalculateProblemsInRelations.call(this); + }, setting); + ysy.data.relations.childRegister(this.recalculateProblemsInRelations, setting); + ysy.data.issues.register(this.recalculateProblemsInIssues, setting); + ysy.data.projects.register(this.recalculateProblemsInIssues, setting); + ysy.data.relations.register(this.recalculateProblemsInRelations, setting); + ysy.proManager.register("close", this.close); + + ysy.view.AllButtons.prototype.extendees.problem_finder = { + widget: ysy.view.ProblemFinder, + bind: function () { + this.model = setting; + }, + func: function () { + this.model.setSilent({opened: !this.model.opened}); + this.model._fireChanges(this, "open problem list"); + }, + isOn: function () { + return this.model.opened; + }, + isHidden: function () { + return this.problemCount() === 0; + } + }; + + }, + recalculateProblemsInIssues: function () { + var problems = []; + var array = ysy.data.issues.getArray(); + if (ysy.settings.projectProgress) { + array = array.concat(ysy.data.projects.getArray()); + } + for (var i = 0; i < array.length; i++) { + var entity = array[i]; + var entityProblems = entity.getProblems(); + if (!entityProblems) continue; + for (var j = 0; j < entityProblems.length; j++) { + problems.push({ + entity: entity, + text: entityProblems[j] + }) + } + } + this.issueProblems = problems; + this._fireChanges(this, "issues problems recalculated"); + }, + recalculateProblemsInRelations: function () { + var problems = []; + var array = ysy.data.relations.getArray(); + for (var i = 0; +i < array.length; i++) { + var entity = array[i]; + var entityProblems = entity.getProblems(); + if (!entityProblems) continue; + for (var j = 0; j < entityProblems.length; j++) { + problems.push({ + relation: entity, + text: entityProblems[j] + }) + } + } + this.relationProblems = problems; + this._fireChanges(this, "relations problems recalculated"); + }, + close: function (who) { + ysy.settings.problemFinder.setSilent({opened: false}); + ysy.settings.problemFinder._fireChanges(who, "event close"); + }, + scrollToEntity: function (entityId) { + var task = gantt._pull[entityId]; + if (!task) return; + gantt.selectTask(entityId); + gantt.showTask(entityId); + } +}); +ysy.view.ProblemFinder = function () { + ysy.view.Widget.call(this); + this.listIsHidden = true; +}; +ysy.main.extender(ysy.view.Button, ysy.view.ProblemFinder, { + templateName: "ProblemFinder", + elementPrefix: "button_", + outerClickBind: false, + specialRepaint: function (hidden) { + if (hidden && this.listIsHidden) return; + var $problemList = $("#gantt_problem_list"); + if (hidden) { + $problemList.hide(); + this.listIsHidden = true; + return; + } + var rendered = Mustache.render(ysy.view.getTemplate(this.templateName), { + count: this.problemCount() + }); + this.$target.html(rendered); + if (this.model.opened) { + if (!$problemList.length) { + var $button = $("#button_problem_finder"); + var offset = $button.position().top; + var viewHeight = Math.min($(window).height() - 105 - offset, $("#easy_gantt").height() - 95); + $problemList = $('
').insertAfter($button) + .css({maxHeight: viewHeight + "px"}) + } + this.bindOuterClick(); + rendered = Mustache.render(ysy.view.getTemplate(this.templateName + "List"), this.out()); + $problemList.html(rendered).show(); + this.listIsHidden = false; + } else { + $problemList.hide(); + this.listIsHidden = true; + } + }, + out: function () { + var issueProblems = []; + var relationProblems = []; + var problems = this.model.issueProblems; + for (var i = 0; i < problems.length; i++) { + var problem = problems[i]; + issueProblems.push({ + isProject: problem.entity.isProject, + name: problem.entity.name, + text: problem.text, + entityId: problem.entity.getID() + }); + } + var issues = ysy.data.issues; + problems = this.model.relationProblems; + for (i = 0; i < problems.length; i++) { + problem = problems[i]; + var source = issues.getByID(problem.relation.source_id); + var target = issues.getByID(problem.relation.target_id); + relationProblems.push({ + sourceName: (source ? source.name : "#" + problem.relation.source_id), + targetName: (target ? target.name : "#" + problem.relation.target_id), + text: problem.text, + entityId: problem.relation.target_id + }); + } + return {"entities": issueProblems, "relations": relationProblems}; + + }, + problemCount: function () { + return this.model.issueProblems.length + this.model.relationProblems.length; + }, + bindOuterClick: function () { + if (this.outerClickBind) return; + var self = this; + $(document).on("click.problem_finder", function (e) { + //is inside ProblemList + if (e && $(e.target).closest("#gantt_problem_list").length) return; + $(document).off("click.problem_finder"); + self.outerClickBind = false; + ysy.pro.problemFinder.close(self); + }); + this.outerClickBind = true; + } +}); diff --git a/easy_gantt/assets/javascripts/easy_gantt/sample.js b/easy_gantt/assets/javascripts/easy_gantt/sample.js new file mode 100644 index 0000000..26fed3a --- /dev/null +++ b/easy_gantt/assets/javascripts/easy_gantt/sample.js @@ -0,0 +1,141 @@ +window.ysy = window.ysy || {}; +ysy.pro = ysy.pro || {}; +ysy.pro.sample = { + patch: function () { + var setting = ysy.settings.sample; + $.extend(ysy.view.AllButtons.prototype.extendees, { + sample: { + bind: function () { + this.model = ysy.settings.sample; + }, + func: function () { + if (ysy.data.loader.loaded) { + this.model.toggle(); + ysy.data.loader.load(); + } + }, + isOn: function () { + return this.model.active; + } + //icon:"zoom-in icon-day" + } + }); + + $.extend(setting, { + init: function () { + if (ysy.settings.easyRedmine || this.isViewed()) { + this.prevented = true; + } + this.active = this.getSampleVersion(); + }, + getSampleVersion: function (turnOn) { + if (ysy.settings.global) return "global"; + if (turnOn === false) return 0; + if (turnOn === true) return 1; + return this.prevented ? 0 : 1; + }, + toggle: function (turnOn) { + if (turnOn === undefined) { + turnOn = !this.active; + } + this.setSilent("active", this.getSampleVersion(turnOn)); + this._fireChanges(this, "toggle"); + }, + storageKey: "sample_viewed", + setViewed: function () { + ysy.data.storage.savePersistentData(this.storageKey, true); + }, + isViewed: function () { + return ysy.data.storage.getPersistentData(this.storageKey); + } + }); + }, + _loadSampleData: function (data) { + if (!data.easy_gantt_data) return; + var json = data.easy_gantt_data; + var projects = json.projects; + for (var i = 0; i < projects.length; i++) { + projects[i].needLoad = false; + projects[i].permissions = {editable: true}; + } + var issues = json.issues; + for (i = 0; i < issues.length; i++) { + issues[i].permissions = {editable: true}; + } + var versions = json.versions; + for (i = 0; i < versions.length; i++) { + versions[i].permissions = {editable: true}; + } + ysy.data.loader._handleMainGantt(data); + }, + loadSample:function (sampleVersion) { + ysy.gateway.polymorficGetJSON( + ysy.settings.paths.sample_data.replace("{{version}}", sampleVersion), null, + $.proxy(this._loadSampleData, this), + function () { + ysy.log.error("Error: Example data fetch failed"); + } + ); + } +}; +//############################################################################## +ysy.view.SuperPanel = function () { + ysy.view.Widget.call(this); +}; +ysy.main.extender(ysy.view.Widget, ysy.view.SuperPanel, { + name: "SuperPanelWidget", + templateName: "SuperPanel", + _repaintCore: function () { + if (!this.template) { + var templ = ysy.view.getTemplate(this.templateName); + if (templ) { + this.template = templ; + } else { + return true; + } + } + var rendered = Mustache.render(this.template, this.out()); // REPAINT + var $easygantt = $("#easy_gantt"); + $easygantt.find(".flash").remove(); + this.$target = $(rendered); + $easygantt.prepend(this.$target); + //window.showFlashMessage("notice",rendered); + this.tideFunctionality(); + }, + out: function () { + var obj, label; + var free = !!ysy.settings.sample.getSampleVersion(false); + if (free) { + label = ysy.view.getLabel("sample_global_free_text"); + obj = {global_free: true}; + } else { + label = ysy.view.getLabel("sample_text"); + obj = {}; + } + return $.extend({}, {text: label}, {sample: this.model.active}, obj); + }, + tideFunctionality: function () { + this.$target.find("#sample_close_button").click($.proxy(function () { + if (ysy.data.loader.loaded) { + this.model.setViewed(); + this.model.setSilent("active", false); + this.model._fireChanges(this, "toggle"); + ysy.data.loader.load(); + } + }, this)); + this.$target.find("#sample_video_button").click($.proxy(function () { + if (ysy.settings.global) { + var template = ysy.view.getTemplate("video_modal_global"); + } else { + template = ysy.view.getTemplate("video_modal"); + } + var $modal = ysy.main.getModal("video-modal", "850px"); + $modal.html(template); // REPAINT + $modal.off("dialogclose"); + window.showModal("video-modal", 850); + $modal.on("dialogclose", function () { + $modal.empty(); + }); + })); + } +}); \ No newline at end of file diff --git a/easy_gantt/assets/javascripts/easy_gantt/saver.js b/easy_gantt/assets/javascripts/easy_gantt/saver.js new file mode 100644 index 0000000..ca9960c --- /dev/null +++ b/easy_gantt/assets/javascripts/easy_gantt/saver.js @@ -0,0 +1,529 @@ +/* saver.js */ +/* global ysy */ +window.ysy = window.ysy || {}; +if (!ysy.gateway) ysy.gateway = {}; +$.extend(ysy.gateway, { + requestsGroups: {}, + temp: { + retry: false, + superSuccess: null, + superFail: null, + fails: 0 + }, + sendIssue: function (method, issueID, data, callback) { + var priority = 3; + if (method === "REPAIR") { + method = "PUT"; + priority = 9; + } + var urlTemplate = ysy.settings.paths["issue" + method]; + if (!urlTemplate) return; + //var url=urlTemplate+(method==="POST"?"":"/"+issueID)+".json"; + var url = urlTemplate.replace(":issueID", issueID); + //var url = Mustache.render(urlTemplate, {issueID: issueID, apiKey: this.getApiKey()}); + this.prepare({ + priority: priority, + method: method, + url: url, + data: data, + callback: callback, + issueID: issueID + }); + }, + sendRelation: function (method, rela, data, callback) { + var urlTemplate = ysy.settings.paths["relation" + method]; + if (!urlTemplate) return; + //var url = Mustache.render(urlTemplate, $.extend(this.getBasicParams(), { + // relaID: rela.id, + // sourceID: rela.source_id + //})); + var url = urlTemplate.replace(":issueID", rela.source_id).replace(":projectID", rela.getSource().project_id).replace(":relaID", rela.id); + var priorities = {DELETE: 1, POST: 6, PUT: 6}; + this.prepare({ + priority: priorities[method], + method: method, + url: url, + data: data, + callback: callback, + relation: rela + }); + }, + sendMilestone: function (method, mile, data, callback) { + var urlTemplate = ysy.settings.paths["version" + method]; + if (!urlTemplate) return; + //var url = Mustache.render(urlTemplate, $.extend(this.getBasicParams(), {versionID: mile.id})); + var url = urlTemplate.replace(":versionID", mile.id).replace(":projectID", mile.project_id); + this.prepare({priority: 2, method: method, url: url, data: data, callback: callback, milestone: mile}); + }, + sendProject: function (method, project, data, callback) { + var urlTemplate = ysy.settings.paths["project" + method]; + if (!urlTemplate) return; + var url = urlTemplate.replace(":projectID", project.id); + this.prepare({priority: 10, method: method, url: url, data: data, callback: callback, project: project}); + }, + prepare: function (packet) { + if (!this.requestsGroups) { + this.requestsGroups = {}; + } + var priority = packet.priority; + if (!this.requestsGroups[priority]) { + this.requestsGroups[priority] = []; + } + this.requestsGroups[priority].push(packet); + ysy.log.debug("prepared " + packet.method + " " + packet.url, "supersend"); + }, + send: function (request) { + ysy.log.debug(request.method + " " + request.url + " " + JSON.stringify(request.data), "send"); + var xhr = $.ajax({ + url: request.url, + type: request.method, + dataType: "text", + data: request.data + }); + xhr.done(function (message) { + if (request.callback) { + request.callback(message); + } + var temp = ysy.gateway.temp; + temp.successes++; + request.passed = true; + temp.retry = true; + return true; + }).fail(function (response) { + var temp = ysy.gateway.temp; + temp.allOk = false; + temp.fails++; + if (response.responseText) { + try { + var json = JSON.parse(response.responseText); + if (json.errors.length) { + request.errorMessage = json.errors[0]; + } + } catch (e) { + request.errorMessage = response.responseText; + } + } + request.errorStatus = response.statusText; + }).always(ysy.gateway._process); + //pending.push(xhr); + return xhr; + }, + fireSend: function (success, fail) { + var requestList = []; + var keys = Object.getOwnPropertyNames(this.requestsGroups); + if (keys.length === 0) { + success(); + return; + } + var sortedKeys = []; + for (var i = 0; i < keys.length; i++) { + sortedKeys.push(parseFloat(keys[i])); + } + sortedKeys.sort(); + + for (i = 0; i < sortedKeys.length; i++) { + requestList = requestList.concat(this.requestsGroups[sortedKeys[i]]); + } + ysy.log.debug("_fireSend() ", "supersend"); + this.temp = { + requestList: requestList, + superSuccess: success, + superFail: fail, + retry: true + }; + this._fireRetry(); + }, + _fireRetry: function () { + var temp = ysy.gateway.temp; + if (temp.allOk) { + ysy.log.debug("superSuccess triggered", "supersend"); + if (temp.superSuccess) { + temp.superSuccess(); + } + this.requestsGroups = {}; + return; + } + if (!temp.retry) { + ysy.log.debug("superFail triggered", "supersend"); + if (temp.superFail) { + temp.superFail(this.gatherErrors()); + } + this.requestsGroups = {}; + return; + } + ysy.log.debug("_fireRetry() ", "supersend"); + $.extend(temp, { + pointer: 0, + fails: 0, + successes: 0, + retry: false, + allOk: true + }); + this._fireOne(); + + }, + _fireOne: function () { + var temp = this.temp; + if (temp.pointer >= temp.requestList.length) { + this._fireRetry(); + return; + } + var req = temp.requestList[temp.pointer]; + temp.pointer++; + if (req && !req.passed) { + this.send(req); + } else { + this._fireOne(); + } + }, + _process: function (hrx) { + var temp = ysy.gateway.temp; + ysy.log.debug("AJAX _process (p=" + temp.pointer + ",s=" + temp.successes + ",f=" + temp.fails + ")", "supersend"); + ysy.gateway._fireOne(); + }, + gatherErrors: function () { + var errors = []; + var temp = this.temp; + var reqList = temp.requestList; + if (!reqList || reqList.length === 0)return; + for (var j = 0; j < reqList.length; j++) { + var request = reqList[j]; + if (request.passed) continue; + var error, name, entityType = null; + if (request.issueID || (request.data && request.data.issue)) { + entityType = "issue"; + var issue = ysy.data.issues.getByID(request.issueID); + if (issue) { + name = '"' + issue.name + '"'; + } else if (request.data && request.data.issue) { + name = '"' + request.data.issue.subject + '"'; + } else { + name = "#" + request.issueID; + } + } else if (request.project) { + entityType = "project"; + name = '"' + request.project.name + '"'; + } else if (request.milestone) { + entityType = "milestone"; + name = '"' + request.milestone.name + '"'; + } else if (request.relation) { + entityType = "relation"; + var source = request.relation.getSource(); + var target = request.relation.getTarget(); + name = '"' + source.name + '" - "' + target.name + '"'; + } + if (entityType) { + error = ysy.settings.labels.gateway.entitySaveFailed.replace("%{entityName}", name).replace("%{entityType}", ysy.settings.labels.types[entityType]); + } else { + error = request.method + " " + request.url.substr(0, request.url.indexOf('?')) + " - " + request.errorStatus; + } + if (request.errorMessage) { + error += ": " + request.errorMessage; + } else if (entityType) { + error += ": " + request.errorStatus; + } + errors.push(error); + } + return errors; + }, + polymorficGet: function (url, data, callback, fail) { + if (!url) return; + $.get(url, data) + .done(callback) + .fail(fail); + }, + polymorficGetJSON: function (url, data, callback, fail) { + if (!url) return; + $.getJSON(url, data) + .done(callback) + .fail(fail); + }, + polymorficPostJSON: function (url, data, callback, fail) { + if (!url) return; + $.ajax({ + url: url, + type: "POST", + data: data, + dataType: "json" + }).done(callback).fail(fail); + }, + polymorficPost: function (url, obj, data, callback, fail) { + if (!url) return; + $.ajax({ + url: url, + type: "POST", + data: data, + dataType: "text" + }).done(callback).fail(fail); + }, + polymorficPut: function (url, obj, data, callback, fail) { + if (!url) return; + $.ajax({ + url: url, + type: "PUT", + data: JSON.stringify(data), + contentType: "application/json", + dataType: "text" + }).done(callback).fail(fail); + }, + polymorficDelete: function (url, obj, callback, fail) { + if (!url) return; + $.ajax({ + url: url, + type: "DELETE", + dataType: "json" + }).done(callback).fail(fail); + } + + +}); + +ysy.data = ysy.data || {}; +ysy.data.save = function () { + ysy.data.saver.sendIssues(); + ysy.data.saver.sendRelations(); + ysy.data.saver.sendMilestones(); + ysy.data.saver.sendProjects(); + ysy.gateway.fireSend( + ysy.data.saver.afterSaveSuccess, + ysy.data.saver.afterSaveFail, + true + ); +}; +ysy.data.saver = { + _name:"Saver", + sendIssues: function () { + var j, data; + var issues = ysy.data.issues.array; + for (j = 0; j < issues.length; j++) { + var issue = issues[j]; + if (!issue._changed) continue; + if (issue._deleted && issue._created) continue; + if (issue._deleted) { + ysy.gateway.sendIssue("DELETE", issue.id, null, callbackBuilder(issue)); + } else if (issue._created) { + data = {issue: {}}; + for (var key in issue) { + if (!issue.hasOwnProperty(key)) continue; + if (ysy.main.startsWith(key, "_"))continue; + data.issue[key] = issue[key]; + } + delete data.issue["name"]; + delete data.issue["id"]; + delete data.issue["end_date"]; + $.extend(data.issue, { + subject: issue.name, + start_date: issue.start_date ? issue.start_date.format("YYYY-MM-DD") : undefined, + due_date: issue.end_date ? issue.end_date.format("YYYY-MM-DD") : undefined + }); + var parents = ysy.data.saver.constructParentData(issue); + $.extend(data.issue, parents); + ysy.gateway.sendIssue("POST", null, data, this.callbackBuilder(issue)); + //ysy.log.error("Issue "+issue.id+" cannot be created - not implemented"); + } else { + data = {}; + for (key in issue) { + if (!issue.hasOwnProperty(key)) continue; + if (ysy.main.startsWith(key, "_"))continue; + data[key] = issue[key]; + } + delete data["end_date"]; + delete data["columns"]; + $.extend(data, { + start_date: issue.start_date ? issue.start_date.format("YYYY-MM-DD") : undefined, + due_date: issue.end_date ? issue.end_date.format("YYYY-MM-DD") : undefined + }); + // parents = ysy.data.saver.constructParentData(issue); + // $.extend(data, parents); + ysy.proManager.fireEvent("beforeSaveIssue", data); + data = issue.getDiff(data); + if (data === null) { + this.callbackBuilder(issue)(); + continue; + } + ysy.gateway.sendIssue("PUT", issue.id, {issue: data}, this.callbackBuilder(issue)); + //console.log("Issue "+issue.id); + } + } + }, + sendRelations: function () { + var j, data; + var relas = ysy.data.relations.array; + var repairedIssues = {}; + for (j = 0; j < relas.length; j++) { + var rela = relas[j]; + if (ysy.settings.fixedRelations) { + rela.makeDelayFixedForSave(); + } + if (!rela._changed) continue; + if (rela._deleted && rela._created) continue; + //var callback=$.proxy(function(){this._changed=false;},rela); + if (rela._deleted) { + ysy.gateway.sendRelation("DELETE", rela, null, this.callbackBuilder(rela)); + } else { + //if(rela.delay>0){data.relation.delay=rela.delay;} + if (rela._created) { + data = { + relation: { + issue_to_id: rela.target_id, + relation_type: rela.type, + delay: rela.delay + }, + project_id: rela.getTarget().project_id + }; + ysy.gateway.sendRelation("POST", rela, data, this.callbackBuilder(rela)); + } else { + data = {delay: rela.delay}; + ysy.gateway.sendRelation("PUT", rela, data, this.callbackBuilder(rela)); + } + var targetIssue = rela.getTarget(); + repairedIssues[targetIssue.id] = targetIssue; + } + } + for (var id in repairedIssues) { + if (!repairedIssues.hasOwnProperty(id)) continue; + targetIssue = repairedIssues[id]; + var targetData = { + issue: { + start_date: targetIssue.start_date ? targetIssue.start_date.format("YYYY-MM-DD") : undefined, + due_date: targetIssue.end_date ? targetIssue.end_date.format("YYYY-MM-DD") : undefined + } + }; + ysy.gateway.sendIssue("REPAIR", targetIssue.id, targetData, null); + } + }, + sendMilestones: function () { + var j, data; + var miles = ysy.data.milestones.array; + for (j = 0; j < miles.length; j++) { + var mile = miles[j]; + if (!mile._changed) continue; + if (mile._deleted && mile._created) continue; + //var callback=$.proxy(function(){this._changed=false;},mile); + if (mile._deleted) { + ysy.gateway.sendMilestone("DELETE", mile, null, this.callbackBuilder(mile)); + } else if (mile._created) { + data = { + version: { + name: mile.name, + //status: the status of the version in: open (default), locked, closed + //sharing: the version sharing in: none (default), descendants, hierarchy, tree, system + description: mile.description, + due_date: mile.start_date.format("YYYY-MM-DD") + } + }; + ysy.gateway.sendMilestone("POST", mile, data, this.callbackBuilder(mile)); + //ysy.log.error("Milestone "+mile.id+" cannot be created - not implemented"); + //ysy.gateway.sendIssue("POST",issue.id,null,callback); + //console.log("Issue "+issue.id+" created"); + } else { + data = { + version: { + due_date: mile.start_date.format("YYYY-MM-DD") + } + }; + ysy.gateway.sendMilestone("PUT", mile, data, this.callbackBuilder(mile)); + } + } + }, + sendProjects: function () { + }, + callbackBuilder: function (item) { + return function (response) { + var message = (item.name || item._name); + //dhtmlx.message(message,"success"); + ysy.log.debug(message + " sended", "send"); + if (response) { + try { + var parsedResponse = JSON.parse(response); + } catch (e) { + } + if (parsedResponse) { + var model = parsedResponse.issue + || parsedResponse.project + || parsedResponse.version + || parsedResponse.relation; + if (model) { + item.setSilent({ + id: model.id, + _created: false + }); + item._fireChanges(ysy.data.saver, "afterPOST set"); + } + } + } + item._changed = false; + } + }, + constructParentData: function (issue) { + var data = { + parent_issue_id: null, + fixed_version_id: null, + project_id: issue.project_id + }; + var parent; + if (issue.parent_issue_id) { + data.parent_issue_id = issue.parent_issue_id; + parent = ysy.data.issues.getByID(issue.parent_issue_id); + if (parent) { + data.fixed_version_id = parent.fixed_version_id; + //data.project_id = parent.project_id; + } + } else if (issue.fixed_version_id) { + data.fixed_version_id = issue.fixed_version_id; + //parent = ysy.data.milestones.getByID(issue.fixed_version_id); + //if (parent) { + // data.project_id = parent.project_id; + //} + } + return data; + }, + afterSaveSuccess: function () { + dhtmlx.message(ysy.view.getLabel("gateway", "allSended"), "notice", 2000); + ysy.data.loader.load(); + }, + afterSaveFail: function (errors) { + var string = "

" + ysy.view.getLabel("gateway", "sendFailed") + "

    "; + for (var i = 0; i < errors.length; i++) { + string += "
  • " + errors[i] + "
  • "; + } + dhtmlx.message(string + "
", "error",5000); + //for(var i=0;i -1) + this.requestRepaint(); + }, + invalidateIssues: function () { + if (this.summer && (!this.summer.entities || this.summer.entities.indexOf("issues") > -1)) + this.requestRepaint(); + }, + getSubScale: function (zoom) { + var summer = ysy.pro.sumRow.summers[ysy.settings.sumRow.summer]; + if (!summer) return; + ysy.log.debug("getSubScale for " + summer.title, "summer"); + this.summer = summer; + this.updateTemper(); + var temper = this.temper; + // var max = Math.max.apply(null, values); + // var min = Math.min.apply(null, values); + var template = this.nopeFormatter; + if (temper.values) { + if (summer.formatter) { + template = function (date) { + var index = -temper.minDate.diff(date, zoom); + if (index < 0 || index > temper.valuesLength) return ""; + return summer.formatter(temper.values[index], temper.widths[index]); + } + } else { + template = function (date) { + var index = -temper.minDate.diff(date, zoom); + if (index < 0 || index > temper.valuesLength) return ""; + return temper.values[index]; + } + } + } + return { + step: 1, + className: "gantt-sum-row", + unit: zoom, + template: template + }; + }, + getSummerFunction: function (zoom) { + return zoom === "day" ? this.summer.day : this.summer.week; + }, + getEntities: function () { + if (!this.summer) return null; + var types = this.summer.entities || ["issues"]; + var entities = []; + for (var i = 0; i < types.length; i++) { + entities = entities.concat(ysy.data[types[i]].getArray()); + } + return entities; + }, + _repaintCore: function () { + if (!ysy.settings.sumRow.active) return; + ysy.log.debug("sumRow repaintCore", "summer"); + var summer = ysy.pro.sumRow.summers[ysy.settings.sumRow.summer]; + if (!summer) return; + this.summer = summer; + this.updateTemper(this.calculateSums(ysy.settings.zoom.zoom)); + var $target = $(".gantt_scale_line.gantt-sum-row"); + var config = gantt.config; + + var resize = gantt._get_resize_options(); + var avail_width = resize.x ? Math.max(config.autosize_min_width, 0) : gantt.$task.offsetWidth; + + var cfgs = gantt._scale_helpers.prepareConfigs([this.getSubScale(ysy.settings.zoom.zoom)], config.min_column_width, avail_width, config.scale_height - 1); + var html = gantt._prepare_scale_html(cfgs[0]); + $target.html(html); + }, + updateTemper: function (values) { + if (values) { + this.temper.values = values; + this.temper.valuesLength = values.length; + } + //this.temper.fullRange = moment(gantt._max_date).diff(gantt._min_date, ysy.settings.zoom.zoom); + this.temper.minDate = moment(gantt._min_date); + this.temper.widths = gantt._tasks.width; + }, + calculateSums: function (unit) { + var summerFunction = this.getSummerFunction(unit); + var values = []; + var mover = moment(gantt._min_date); + var maxDate = moment(gantt._max_date); + var issues = this.getEntities(); + // var lastValue = 0; + if (unit === "day") { + while (mover.isBefore(maxDate)) { + var count = 0; + for (var i = 0; i < issues.length; i++) { + count += summerFunction(mover, issues[i]); + } + // if (summer.cumulative) { + // count += lastValue; + // } + values.push(count); + // lastValue = count; + mover.add(1, unit); + } + } else { + var nextMover = moment(mover).add(1, unit); + while (mover.isBefore(maxDate)) { + count = 0; + for (i = 0; i < issues.length; i++) { + count += summerFunction(mover, nextMover, issues[i]); + } + // if (summer.cumulative) { + // count += lastValue; + // } + values.push(count); + // lastValue = count; + mover.add(2, unit); + var tempMover = mover; + mover = nextMover; + nextMover = tempMover; + } + } + return values; + } +}); \ No newline at end of file diff --git a/easy_gantt/assets/javascripts/easy_gantt/toolpanel.js b/easy_gantt/assets/javascripts/easy_gantt/toolpanel.js new file mode 100644 index 0000000..d2e09e3 --- /dev/null +++ b/easy_gantt/assets/javascripts/easy_gantt/toolpanel.js @@ -0,0 +1,91 @@ +/* toolpanel.js */ +/* global ysy */ +window.ysy = window.ysy || {}; +ysy.pro = ysy.pro || {}; +ysy.pro.toolPanel = ysy.pro.toolPanel || {}; +$.extend(ysy.pro.toolPanel, { + _name: "ToolPanel", + extendees: [], + initToolbar: function (ctx) { + var toolPanel = new ysy.view.ToolPanel(); + toolPanel.init(ysy.settings.toolPanel); + ctx.children.push(toolPanel); + }, + patch: function () { + var toolSetting = ysy.settings.toolPanel = new ysy.data.Data(); + toolSetting.init({ + _name: "ToolPanel", buttonIds: [], buttons: {}, + registerButtonSilent: function (button) { + if (button.id === undefined) throw("Missing id for button"); + this.buttons[button.id] = button; + this.buttonIds.push(button.id); + } + }); + + ysy.proManager.register("initToolbar", this.initToolbar); + + for (var i = 0; i < this.extendees.length; i++) { + var button = this.extendees[i]; + if (button.isRemoved && button.isRemoved()) continue; + toolSetting.registerButtonSilent(button); + } + toolSetting._fireChanges(this, "delayed registerButton"); + delete this.extendees; + }, + registerButton: function (button) { + if (button.isRemoved && button.isRemoved()) return; + if (ysy.settings.toolPanel) { + ysy.settings.toolPanel.registerButtonSilent(button); + ysy.settings.toolPanel._fireChanges(this, "direct registerButton"); + } else { + this.extendees.push(button); + } + } +}); + +ysy.view.ToolPanel = function () { + ysy.view.Widget.call(this); +}; +ysy.main.extender(ysy.view.Widget, ysy.view.ToolPanel, { + name: "ToolPanelWidget", + templateName: "ToolButtons", + + _postInit: function () { + var $toolPanel = $("#easy_gantt_tool_panel"); + $toolPanel.find("a:not([href])").attr("href", "javascript:void(0)"); + $toolPanel.find("li > *").hide(); + }, + _updateChildren: function () { + var model = this.model; + var children = []; + // this.$target = $("#content"); + for (var i = 0; i < model.buttonIds.length; i++) { + var elid = model.buttonIds[i]; + var extendee = model.buttons[elid]; + // if (!this.getChildTarget(extendee).length) continue; + var button; + if (extendee.widget) { + button = new extendee.widget(); + } else { + button = new ysy.view.Button(); + } + $.extend(button, extendee); + button.init(); + children.push(button); + } + this.children = children; + }, + _repaintCore: function () { + for (var i = 0; i < this.children.length; i++) { + var child = this.children[i]; + this.setChildTarget(child, i); + child.repaint(true); + } + }, + setChildTarget: function (child /*,i*/) { + var selector = "#" + child.elementPrefix + child.id; + var target = this.$target.find(selector); + if (target.length === 0) throw("element #" + child.elementPrefix + child.id + " missing"); + child.$target = target; + } +}); diff --git a/easy_gantt/assets/javascripts/easy_gantt/tooltip.js b/easy_gantt/assets/javascripts/easy_gantt/tooltip.js new file mode 100644 index 0000000..e35ffc3 --- /dev/null +++ b/easy_gantt/assets/javascripts/easy_gantt/tooltip.js @@ -0,0 +1,217 @@ +/* tooltip.js */ +/* global ysy */ +window.ysy = window.ysy || {}; +ysy.view = ysy.view || {}; +ysy.view.tooltip = $.extend(ysy.view.tooltip, { + instance: null, + show: function (className, event, template, out) { + if (!this.instance) { + this.instance = new ysy.view.ToolTip().init(); + } + return this.instance.set(className, event, template, out); + }, + hide: function () { + if (this.instance) + return this.instance.hide(); + return false; + }, + changePos: function (event) { + if (this.instance) + return this.instance.changePos(event); + } +}); +ysy.view.taskTooltip = $.extend(ysy.view.taskTooltip, { + timeout: 0, + timeoutTime: 1000, + phase: 1, + /** + * phase 1 mouse is out + * phase 2 mouse on, start timer + * phase 3 tooltip display + */ + taskTooltipInit: function () { + var self = this; + /** + * phase 2 mouse on, start timer 1s + */ + $("#content") + .on("mouseenter", ".gantt_task_content, .gantt_task_progress, .gantt-task-tooltip-area", function (e) { + if (self.phase !== 1) return; + ysy.log.debug("mouseenter", "tooltip"); + if (e.buttons !== 0) return; + self.phase = 2; + // ysy.log.debug("e.which = "+e.which+" e.button = "+ e.button+" e.buttons = "+ e.buttons); + self.bindHiders(e.target); + self.updatePos(e); + }); + + }, + bindHiders: function (target) { + target.addEventListener("mouseleave", this.hideTooltip); + target.addEventListener("mousedown", this.hideTooltip); + target.addEventListener("mousemove", this.updatePos); + }, + hideTooltip: function (event) { + var self = ysy.view.taskTooltip; + /** + * set phase 1 mouse out + */ + self.phase = 1; + self.lastPos = null; + if (self.timeout) { + clearTimeout(self.timeout); + } + if (ysy.view.tooltip.hide()) { + event.target.removeEventListener("mouseleave", this.hideTooltip); + event.target.removeEventListener("mousedown", this.hideTooltip); + event.target.removeEventListener("mousemove", this.updatePos); + } + }, + /** + * @param {MouseEvent} event + */ + updatePos: function (event) { + var self = ysy.view.taskTooltip; + if (self.phase === 1) return; + + var changed = false; + if (self.lastPos) { + if (Math.abs(self.lastPos.clientX - event.clientX) > 5) { + self.lastPos.clientX = event.clientX; + changed = true; + } + if (Math.abs(self.lastPos.clientY - event.clientY) > 5) { + self.lastPos.clientY = event.clientY; + changed = true; + } + } else { + self.lastPos = {clientX: event.clientX, clientY: event.clientY}; + changed = true; + } + if (self.phase === 3 && changed) { + self.hideTooltip(event); + return; + } + self.lastPos.target = event.target; + if (changed) { + if (self.timeout) { + window.clearTimeout(self.timeout); + } + self.timeout = window.setTimeout(function () { + self.showTaskTooltip(self.lastPos) + }, self.timeoutTime); + } + }, + showTaskTooltip: function (event) { + var self = this; + /** + * phase 3 tooltip display + */ + if (self.phase !== 2) return; + var task = gantt._pull[gantt.locate(event)]; + if (!task) return; + self.phase = 3; + if (event.target.parentElement.parentElement === null) { + self.phase = 1; + return; + } + var taskPos = $(event.target).offset(); + return ysy.view.tooltip.show("gantt-task-tooltip", + {clientX: event.clientX, clientY: event.clientY, top: taskPos.top + gantt.config.row_height}, + ysy.view.templates.TaskTooltip, + self.taskTooltipOut(task)); + }, + + taskTooltipOut: function (task) { + var issue = task.widget.model; + var problemList = issue.getProblems(); + var columns = []; + if (issue.milestone) { + if (issue.isShared) { + columns = [{ + name: "shared-from", + label: "Shared from project", + value: ysy.main.escapeText(issue.real_project_name) + }] + } + return { + name: issue.name, + end_date: issue.start_date.format(gantt.config.date_format), + columns: columns + }; + } + var columnHeads = gantt.config.columns; + var banned = ["subject", "start_date", "end_date", "due_date"]; + for (var i = 0; i < columnHeads.length; i++) { + var columnHead = columnHeads[i]; + if (banned.indexOf(columnHead.name) < 0) { + var html = columnHead.template(task); + if (!html) continue; + if (html.indexOf("<") === 0) { + html = $(html).html(); + } + columns.push({name: columnHead.name, label: columnHead.label, value: html}); + } + } + if (issue.fixed_version_id) { + var milestone = ysy.data.milestones.getByID(issue.fixed_version_id); + } + return { + name: issue.name, + start_date: issue.start_date ? issue.start_date.format(gantt.config.date_format) : moment(null), + end_date: issue.end_date ? issue.end_date.format(gantt.config.date_format) : moment(null), + milestone: milestone, + columns: columns, + problems: problemList + }; + } +}); +ysy.view.ToolTip = function () { + ysy.view.Widget.call(this); +}; +ysy.main.extender(ysy.view.Widget, ysy.view.ToolTip, { + name: "ToolTip", + init: function () { + var $target = this.$target = $("").appendTo("#content"); + $target.on("mouseleave", function () { + $target[0].style.display = "none"; + }); + ysy.view.onRepaint.push($.proxy(this.repaint, this)); + return this; + }, + set: function (className, event, template, out) { + this.className = className || this.className; + this.event = event || this.event; + this.template = template || this.template; + this.outed = out; + this.repaintRequested = true; + return this.$target; + }, + hide: function () { + this.$target[0].style.display = "none";//.hide(); + return true; + }, + changePos: function (event) { + var left = event.clientX; + var top = event.top; + if (event.clientY + this.elementHeight > window.innerHeight) { + top -= this.elementHeight + gantt.config.row_height + 4; + } + if (event.clientX + this.elementWidth > window.innerWidth) { + left -= this.elementWidth; + } + this.$target[0].style.cssText = "display: block; left: " + left + "px; top: " + top + "px"; + }, + _repaintCore: function () { + this.$target.html(Mustache.render(this.template, this.outed)); // REPAINT + this.$target[0].style.display = "block"; + this.$target[0].className = 'gantt-tooltip ' + this.className; + this.elementWidth = this.$target.outerWidth(); + this.elementHeight = this.$target.outerHeight(); + var event = this.event; + if (event) { + this.changePos(event); + } + return false; + } +}); diff --git a/easy_gantt/assets/javascripts/easy_gantt/utils.js b/easy_gantt/assets/javascripts/easy_gantt/utils.js new file mode 100644 index 0000000..f3bc60d --- /dev/null +++ b/easy_gantt/assets/javascripts/easy_gantt/utils.js @@ -0,0 +1,119 @@ +/* utils.js */ +/* global ysy */ +window.ysy = window.ysy || {}; +ysy.main = ysy.main || {}; +$.extend(ysy.main, { + extender: function (parent, child, proto) { + function ProtoCreator() { + } + ProtoCreator.prototype = parent.prototype; + child.prototype = new ProtoCreator(); + child.prototype.constructor = child; + $.extend(child.prototype, proto); + }, + getModal: function (id, width) { + var $target = $("#" + id); + if ($target.length === 0) { + $target = $("
"); + $target.dialog({ + width: width, + appendTo: document.body, + modal: true, + resizable: false, + dialogClass: 'modal' + }); + $target.dialog("close"); + } + return $target; + }, + startsWith: function (text, char) { + if (text.startsWith) { + return text.startsWith(char); + } + return text.toString().charAt(0) === char; + }, + isSameMoment: function (date1, date2) { + if (!moment.isMoment(date1)) return false; + if (!moment.isMoment(date2)) return false; + return date1.isSame(date2); + }, + /** + * Utility function for measuring performance of some code + * @example + * var perf = createPerformanceMeter("myFunction"); + * perf("part 1"); + * perf("part 2"); + * perf.whole(); + * @param {String} groupName + * @return {Function} + */ + createPerformanceMeter: function (groupName) { + var lastTime = window.performance.now(); + var silence = false; + var initTime = lastTime; + var func = function (/** @param {String} name*/ name) { + if (silence) return; + var nowTime = window.performance.now(); + var nameString = groupName + " " + name + ": "; + nameString = nameString.substr(0, 30); + var diffString = " " + (nowTime - lastTime).toFixed(3); + diffString = diffString.substr(diffString.length - 10); + console.debug(nameString + diffString + " ms"); + lastTime = nowTime; + }; + func.whole = function () { + if (silence) return; + var nowTime = window.performance.now(); + var nameString = groupName + ": "; + nameString = nameString.substr(0, 30); + var diffString = " " + (nowTime - initTime).toFixed(3); + diffString = diffString.substr(diffString.length - 10); + console.debug(nameString + diffString + " ms"); + }; + func.silence = function (verbose) { + silence = !verbose; + }; + return func; + }, + /** + * + * @param {Array.<{name:String,value:String}>} formData + * @return {Object} + */ + formToJson: function (formData) { + var result = {}; + var prolong = function (result, split, value) { + var key = split.shift(); + if (key === "") { + result.push(value); + } else { + if (split.length > 0) { + var next = split[0]; + if (!result[key]) { + if (next === "") { + result[key] = []; + } else { + result[key] = {}; + } + } + prolong(result[key], split, value); + } else { + result[key] = value; + } + } + }; + for (var i = 0; i < formData.length; i++) { + var split = formData[i].name.split(/]\[|\[|]/); + if (split.length > 1) { + split.pop(); + } + prolong(result, split, formData[i].value); + } + return result; + }, + escapeText: function (text) { + var tmp = document.createElement('div'); + tmp.appendChild(document.createTextNode(text)); + return tmp.innerHTML; + } +}); \ No newline at end of file diff --git a/easy_gantt/assets/javascripts/easy_gantt/view.js b/easy_gantt/assets/javascripts/easy_gantt/view.js new file mode 100644 index 0000000..97b1d77 --- /dev/null +++ b/easy_gantt/assets/javascripts/easy_gantt/view.js @@ -0,0 +1,48 @@ +/* view.js */ +/* global ysy */ +window.ysy = window.ysy || {}; +ysy.view = ysy.view || {}; +$.extend(ysy.view, { + onRepaint: [], + patch: function () { + this.applyGanttRewritePatch(); + this.addGanttAddons(); + this.applyGanttPatch(); + if (!window.initEasyAutocomplete) { + window.initEasyAutocomplete = function () { + }; + } + if (ysy.settings.easyRedmine && $("#content").children(".easy-content-page").length === 0) { + $("#easy_gantt").addClass("easy-content-page"); + } + }, + start: function () { + this.labels = ysy.settings.labels; + var main = new ysy.view.Main(); + main.init(ysy.data.projects); + this.anim(); + }, + anim: function () { + var view = ysy.view; + for (var i = 0; i < view.onRepaint.length; i++) { + view.onRepaint[i](); + } + //requestAnimFrame($.proxy(this.anim, this)); + requestAnimFrame(view.anim); + }, + getTemplate: function (name) { + return this.templates[name]; + }, + getLabel: function () { + var temp = this.labels; + for (var i = 0; i < arguments.length; i++) { + var arg = arguments[i]; + if (temp[arg]) { + temp = temp[arg]; + } else { + return temp; + } + } + return temp; + } +}); diff --git a/easy_gantt/assets/javascripts/easy_gantt/widget.js b/easy_gantt/assets/javascripts/easy_gantt/widget.js new file mode 100644 index 0000000..77c618a --- /dev/null +++ b/easy_gantt/assets/javascripts/easy_gantt/widget.js @@ -0,0 +1,266 @@ +/* widget.js */ +/* global ysy */ +window.ysy = window.ysy || {}; +ysy.view = ysy.view || {}; +ysy.view.Widget = function () { + /* + *Widget class is a base class for all other Widgets. + *It implement basic repaint, init and _register functions, which in most cases dont have to changed. + */ + //this.template = null; // nutne zakomentovat, aby to + this.$target = null; + this.parent = null; + this.children = []; + this.repaintRequested = true; + this.keepPaintedState = false; + this.deleted = false; + this.regs = [null, null, null, null]; +}; +ysy.view.Widget.prototype = { + name: "Widget", + init: function (modl) { + if (modl instanceof Array) { + ysy.log.error("Array Model"); + } + if (this.model) { + this.model.unregister(this); + } + if (arguments.length > 1) { + for (var i = 1; i < arguments.length; i++) { + if (!arguments[i]) continue; + this._register(arguments[i], i); + } + } + if (modl) { + this.model = modl; + this._register(modl); + } + this._updateChildren(); + this._postInit(); + return this; + }, + requestRepaint: function () { + this.repaintRequested = true; + }, + _updateChildren: function () { + }, + _postInit: function () { + }, + _register: function (model, pos) { + //if (model === undefined) { + // ysy.log.error("no model for register in "+this.name); + //} + if (!model) return; + if (pos) { + if (this.regs[pos]) { + this.regs[pos].unregister(this); + } + this.regs[pos] = model; + } + model.register(function () { + this._updateChildren(); + this.requestRepaint(); + }, this); + }, + out: function () { + + }, + repaint: function (force) { + if (this.hidden) { + this.repaintRequested = true; + return; + } + if (this.keepPaintedState) { + this.onNoRepaint(); + return; + } + if (this.repaintRequested || force) { + ysy.log.log("--- RepaintCore in " + this.name); + this.repaintRequested = !!this._repaintCore(); + } else { + this.onNoRepaint(); + for (var i = 0; i < this.children.length; i++) { + this.children[i].repaint(); + } + } + }, + _repaintCore: function () { + if (!this.template) { + var templ = ysy.view.getTemplate(this.templateName); + if (templ) { + this.template = templ; + } else { + return true; + } + } + if (this.$target === null) { + throw "Target is null for " + this.templateName; + } + this.$target.html(Mustache.render(this.template, this.out() || {})); // REPAINT + this.tideFunctionality(); // TIDE FUNCTIONALITY + for (var i = 0; i < this.children.length; i++) { + var child = this.children[i]; + this.setChildTarget(child, i); // SET CHILD TARGET + child.repaint(true); // CHILD REPAINT + } + return false; + }, + onNoRepaint: function () { + + }, + tideFunctionality: function () { + + }, + setChildTarget: function (child, i) { + + }, + destroy: function () { + this.deleted = true; + }, + _getChildByID: function (id) { + for (var i = 0; i < this.children.length; i++) { + if (this.children[i].id === id) { + return this.children[i]; + } + } + return null; + }, + _getChildByName: function (name) { + for (var i = 0; i < this.children.length; i++) { + if (this.children[i].name === name) { + return this.children[i]; + } + } + return null; + } +}; +ysy.view.Main = function () { + ysy.view.Widget.call(this); + this.name = "MainWidget"; +}; +ysy.main.extender(ysy.view.Widget, ysy.view.Main, { + init: function (mod) { + this.$target = $("#easy_gantt"); + this.model = mod; + ysy.view.onRepaint.push($.proxy(this.repaint, this)); + this._register(null); + this._updateChildren(); + ysy.view.mainWidget = this; + }, + _updateChildren: function () { + if (this.children.length > 0) { + return; + } + var toolbars = new ysy.view.Toolbars(); + toolbars.init(); + toolbars.$target = $("#content"); + this.children.push(toolbars); + + var mainGantt = new ysy.view.Gantt(); + mainGantt.$target = $("#gantt_cont")[0]; + mainGantt.init(/*ysy.data.limits,*//*ysy.data.loader,*/ ysy.data.baselines); + this.children.push(mainGantt); + }, + _repaintCore: function () { + for (var i = 0; i < this.children.length; i++) { + var child = this.children[i]; + this.setChildTarget(child, i); + child.repaint(true); + } + }, + setChildTarget: function (child/*, i*/) { + //if (this.childTargets[child.name]) { + // child.$target = this.$target.find(this.childTargets[child.name]); + //} + //if (child.name === "AllButtonsWidget") { + // child.$target = $("#content"); + //} + } +}); +//############################################################################## + +//############################################################################## + +//############################################################################## +ysy.view.LinkPopup = function () { + ysy.view.Widget.call(this); +}; +ysy.main.extender(ysy.view.Widget, ysy.view.LinkPopup, { + name: "PopupWidget", + templateName: "LinkConfigPopup", + init: function (model, dhtml) { + if (this.model) { + this.model.unregister(this); + } + this.model = model; + this.dhtml = dhtml; + this._register(model); + return this; + }, + requestRepaint: function () { + ysy.log.debug("Popup requestRepaint()", "link_config"); + //this.$target.hide(); + }, + out: function () { + ysy.log.debug("Popup out()", "link_config"); + var delayLabels = ysy.view.getLabel("delay"); + var buttonLabels = ysy.view.getLabel("buttons"); + return { + readonly:this.dhtml.readonly, + title: delayLabels.title, + delay: this.dhtml.delay, + label_delay: delayLabels.label, + button_delete: buttonLabels.button_delete, + button_submit: buttonLabels.button_submit, + minimal:ysy.settings.workDayDelays?-1:"" + }; + }, + tideFunctionality: function () { + var model = this.model; + var dhtml = this.dhtml; + var $target = this.$target; + var sourceIssue = gantt.getTask(dhtml.source); + var targetIssue = gantt.getTask(dhtml.target); + if (!ysy.settings.easyRedmine) $target.addClass("redmine"); + var close = function () { + ysy.log.debug("close link popup", "link_config"); + hideModal(); + }; + $target.find("#link_delete").on("click", function () { + model.remove(); + close(); + }); + $target.keyup(function (event) { + if (event.keyCode == 13) { + $("#link_close").click(); + } + }); + $target.find("#link_close").on("click", function () { + var delay = parseInt($target.find("#link_delay_input").val()); + if (!isNaN(delay) && delay < -1 ){ + delay = -1; + showFlashMessage("warning", Mustache.render(ysy.settings.labels.warnings.change_link_length, { + source_task: sourceIssue.text, + target_task: targetIssue.text, + minimum_link_length: -1 + }), 15000); + } + close(); + if (isNaN(delay) || delay === dhtml.delay) return; + if(ysy.settings.workDayDelays && delay < -1) return; + dhtml.delay = delay || 0; + dhtml.widget.update(dhtml); + }); + $target.find("#link_fix_actual").on("click", function () { + var delay = model.getActDelay(); + close(); + dhtml.delay = delay || 0; + dhtml.widget.update(dhtml); + }); + $target.find("#link_remove_delay").on("click", function () { + close(); + dhtml.delay = 0; + dhtml.widget.update(dhtml); + }); + } +}); diff --git a/easy_gantt/assets/stylesheets/easy_gantt/easy_gantt.css b/easy_gantt/assets/stylesheets/easy_gantt/easy_gantt.css new file mode 100644 index 0000000..6b1d6c3 --- /dev/null +++ b/easy_gantt/assets/stylesheets/easy_gantt/easy_gantt.css @@ -0,0 +1,3295 @@ +/* +@license + +dhtmlxGantt v.3.2.1 Stardard +This software is covered by GPL license. You also can obtain Commercial or Enterprise license to use it in non-GPL project - please contact sales@dhtmlx.com. Usage without proper license is prohibited. + +(c) Dinamenta, UAB. +*/ + +body { + -webkit-print-color-adjust: exact !important; +} +.gridHoverStyle, +.gridSelection, +.timelineSelection { + background-color: #fff3a1 +} +.gantt_grid_scale .gantt_grid_head_cell { + color: #a6a6a6; + border-top: none!important; + /*border-right: none!important*/ +} +/*.gantt_grid_data .gantt_cell { + border-right: none; + color: #454545 +}*/ +.gantt_task_link .gantt_link_arrow_right { + border-width: 6px; + margin-top: -3px +} +.gantt_task_link .gantt_link_arrow_left { + border-width: 6px; + margin-left: -6px; + margin-top: -3px +} +.gantt_task_link .gantt_link_arrow_down, +.gantt_task_link .gantt_link_arrow_top { + border-width: 6px +} +.gantt_task_line .gantt_task_progress_drag { + bottom: -4px; + height: 16px; + margin-left: -8px; + width: 16px +} +.chartHeaderBg { + background-color: #fff +} +.gantt_task .gantt_task_scale .gantt_scale_cell { + color: #a6a6a6; + border-right: 1px solid #ebebeb +} + +/*.gantt_row.gantt_project-type,*/ +/*.gantt_row.odd.gantt_project-type {*/ +/*background-color: #edffef*/ +/*}*/ +.gantt_task_row.gantt_project-type, +.gantt_task_row.odd.gantt_project-type { + background-color: #f5fff6 +} +.gantt_task_line.gantt_project-type { + background-color: #65c16f; + border: 1px solid #3c9445 +} +.gantt_task_line.gantt_project-type .gantt_task_progress { + background-color: #46ad51 +} +.buttonBg { + background: #fff +} +.gantt_cal_light .gantt_btn_set { + margin: 5px 10px +} +.gantt_btn_set.gantt_cancel_btn_set { + background: #fff; + color: #454545; + border: 1px solid #cecece +} +.gantt_btn_set.gantt_save_btn_set { + background: #3db9d3; + text-shadow: 0 -1px 0 #248a9f; + color: #fff +} +.gantt_btn_set.gantt_delete_btn_set { + background: #ec8e00; + text-shadow: 0 -1px 0 #a60; + color: #fff +} +.gantt_cal_light_wide { + padding-left: 0!important; + padding-right: 0!important +} +.gantt_cal_light_wide .gantt_cal_larea { + border-left: none!important; + border-right: none!important +} +.dhtmlx_popup_button.dhtmlx_ok_button { + background: #3db9d3; + text-shadow: 0 -1px 0 #248a9f; + color: #fff; + font-weight: 700; + border-width: 0 +} +.dhtmlx_popup_button.dhtmlx_cancel_button { + font-weight: 700; + color: #454544 +} +.gantt_qi_big_icon.icon_edit { + color: #454545; + background: #fff +} +.gantt_qi_big_icon.icon_delete { + text-shadow: 0 -1px 0 #a60; + background: #ec8e00; + color: #fff; + border-width: 0 +} +.gantt_container { + font-family: Arial; + font-size: 13px; + border: 1px solid #cecece; + position: relative; + white-space: nowrap +} +.gantt_grid { + border-right: 1px solid #cecece +} +.gantt_task_scroll { + overflow-x: scroll +} +.gantt_task { + position: relative +} +.gantt_grid, +.gantt_task { + overflow-x: hidden; + overflow-y: hidden; + display: inline-block; + vertical-align: top +} +.gantt_grid_scale, +.gantt_task_scale { + color: #6b6b6b; + font-size: 12px; + border-bottom: 1px solid #cecece; + background-color: #fff +} +.gantt_scale_line { + box-sizing: border-box; + -moz-box-sizing: border-box; + border-top: 1px solid #cecece +} +.gantt_scale_line:first-child { + border-top: none +} +.gantt_grid_head_cell { + display: inline-block; + vertical-align: top; + border-right: 1px solid #cecece; + text-align: center; + position: relative; + cursor: default; + height: 100%; + -moz-user-select: -moz-none; + -webkit-user-select: none; + -user-select: none; + -ms-user-select: none; + overflow: hidden +} +.gantt_scale_line { + clear: both +} +.gantt_grid_data { + width: 100%; + overflow: hidden +} +.gantt_row { + position: relative; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + -moz-user-select: -moz-none +} +.gantt_add, +.gantt_grid_head_add { + width: 100%; + height: 100%; + background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoV2luZG93cykiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6NTQ3MjMyMENDNkI0MTFFMjk4MTI5QTg3MDhFNDVDQTkiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6NTQ3MjMyMERDNkI0MTFFMjk4MTI5QTg3MDhFNDVDQTkiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo1NDcyMzIwQUM2QjQxMUUyOTgxMjlBODcwOEU0NUNBOSIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo1NDcyMzIwQkM2QjQxMUUyOTgxMjlBODcwOEU0NUNBOSIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PshZT8UAAABbSURBVHjaYrTdeZmBEsCER+4wEP+H4sPkGGCDg020ARR7gb4GIAcYDKMDdPnDyAbYkGG5DVW9cIQMvUdBBAuUY4vDz8iAcZinA2zgCHqAYQMseAywJcYFAAEGAM+UFGuohFczAAAAAElFTkSuQmCC); + background-position: center center; + background-repeat: no-repeat; + cursor: pointer; + position: relative; + -moz-opacity: .3; + opacity: .3 +} +.gantt_grid_head_cell.gantt_grid_head_add { + -moz-opacity: .6; + opacity: .6; + top: 0 +} +.gantt_grid_head_cell.gantt_grid_head_add:hover { + -moz-opacity: 1; + opacity: 1 +} +@media screen { + .gantt_grid_data .gantt_row.odd:hover, + .gantt_grid_data .gantt_row:hover { + background-color: #fff3a1 + } +} +.gantt_grid_data .gantt_row.odd:hover .gantt_add, +.gantt_grid_data .gantt_row:hover .gantt_add { + -moz-opacity: 1; + opacity: 1 +} +.gantt_row, +.gantt_task_row { + border-bottom: 1px solid #ebebeb; + /*background-color: #fff*/ +} +.gantt_row.odd, +.gantt_task_row.odd { + /*background-color: #fff*/ +} +.gantt_cell, +.gantt_grid_head_cell, +.gantt_row, +.gantt_scale_cell, +.gantt_task_cell, +.gantt_task_row { + box-sizing: border-box; + -moz-box-sizing: border-box +} +.gantt_grid_head_cell, +.gantt_scale_cell { + line-height: inherit +} +.gantt_grid .gantt_grid_resize_wrap { + cursor: col-resize; + position: absolute; + width: 13px; + z-index: 1 +} +.gantt_grid_resize_wrap .gantt_grid_resize { + background-color: #cecece; + width: 1px; + margin: 0 auto +} +.gantt_drag_marker.gantt_grid_resize_area { + background-color: rgba(231, 231, 231, .5); + border-left: 1px solid #cecece; + border-right: 1px solid #cecece; + height: 100%; + width: 100%; + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; + box-sizing: border-box +} +.gantt_cell { + display: inline-block; + vertical-align: top; + border-right: 1px solid #ebebeb; + padding-left: 6px; + padding-right: 6px; + height: 100%; + overflow: hidden; + white-space: nowrap; + font-size: 13px +} +.gantt_grid_data .gantt_last_cell, +.gantt_grid_scale .gantt_last_cell, +.gantt_task_bg .gantt_last_cell, +.gantt_task_scale .gantt_last_cell { + border-right-width: 0 +} +.gantt_task_bg { + overflow: hidden +} +.gantt_scale_cell { + display: inline-block; + white-space: nowrap; + overflow: hidden; + border-right: 1px solid #cecece; + text-align: center; + height: 100% +} +.gantt_task_cell { + display: inline-block; + height: 100%; + border-right: 1px solid #ebebeb +} +.gantt_ver_scroll { + width: 0; + background-color: transparent; + height: 1px; + overflow-x: hidden; + overflow-y: scroll; + display: none; + position: absolute; + right: 0 +} +.gantt_ver_scroll>div { + width: 1px; + height: 1px +} +.gantt_hor_scroll { + height: 0; + background-color: transparent; + width: 100%; + clear: both; + overflow-x: scroll; + overflow-y: hidden; + display: none +} +.gantt_hor_scroll>div { + width: 5000px; + height: 1px +} +.gantt_tree_indent { + width: 15px; + /*height: 100%;*/ + display: inline-block +} +.gantt_tree_content, +.gantt_tree_icon { + vertical-align: top +} +.gantt_tree_icon { + width: 28px; + height: 100%; + display: inline-block; + background-repeat: no-repeat; + background-position: center center +} +.gantt_tree_content { + height: 100%; + display: inline-block +} +.gantt_tree_icon.gantt_open { + background-image: url(data:image/gif;base64,R0lGODlhEgASALMJAMrKyt3d3ejp6d7f3+/v75aWlvf39////wAAAP///wAAAAAAAAAAAAAAAAAAAAAAACH/C1hNUCBEYXRhWE1QPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS4wLWMwNjAgNjEuMTM0Nzc3LCAyMDEwLzAyLzEyLTE3OjMyOjAwICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOnhtcE1NPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvbW0vIiB4bWxuczpzdFJlZj0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL3NUeXBlL1Jlc291cmNlUmVmIyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ1M1IFdpbmRvd3MiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6M0I5RTczQjVDMDdBMTFFMTgxRjc4Mzk4M0Q3MjVFQzAiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6M0I5RTczQjZDMDdBMTFFMTgxRjc4Mzk4M0Q3MjVFQzAiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDozQjlFNzNCM0MwN0ExMUUxODFGNzgzOTgzRDcyNUVDMCIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDozQjlFNzNCNEMwN0ExMUUxODFGNzgzOTgzRDcyNUVDMCIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PgH//v38+/r5+Pf29fTz8vHw7+7t7Ovq6ejn5uXk4+Lh4N/e3dzb2tnY19bV1NPS0dDPzs3My8rJyMfGxcTDwsHAv769vLu6ubi3trW0s7KxsK+urayrqqmop6alpKOioaCfnp2cm5qZmJeWlZSTkpGQj46NjIuKiYiHhoWEg4KBgH9+fXx7enl4d3Z1dHNycXBvbm1sa2ppaGdmZWRjYmFgX15dXFtaWVhXVlVUU1JRUE9OTUxLSklIR0ZFRENCQUA/Pj08Ozo5ODc2NTQzMjEwLy4tLCsqKSgnJiUkIyIhIB8eHRwbGhkYFxYVFBMSERAPDg0MCwoJCAcGBQQDAgEAACH5BAEAAAkALAAAAAASABIAAARJMMlJq704661B+SAIXAVhnKhBFKSZnmuLImhslXPN3ibi+6pdBXc4IIpB2YkGE1IKAoL0ICUInJNCYMDtDgJYiScUGnHO6LQkAgA7); + width: 18px; + cursor: pointer +} +.gantt_tree_icon.gantt_close { + background-image: url(data:image/gif;base64,R0lGODlhEgASALMJAMrKyt3d3ejp6d7f3+/v75aWlvf39wAAAP///////wAAAAAAAAAAAAAAAAAAAAAAACH/C1hNUCBEYXRhWE1QPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS4wLWMwNjAgNjEuMTM0Nzc3LCAyMDEwLzAyLzEyLTE3OjMyOjAwICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOnhtcE1NPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvbW0vIiB4bWxuczpzdFJlZj0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL3NUeXBlL1Jlc291cmNlUmVmIyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ1M1IFdpbmRvd3MiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6MzY0QzNGM0VDMDdBMTFFMUE3MDlCNUM2QjU1NDA5RjgiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6MzY0QzNGM0ZDMDdBMTFFMUE3MDlCNUM2QjU1NDA5RjgiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDozNjRDM0YzQ0MwN0ExMUUxQTcwOUI1QzZCNTU0MDlGOCIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDozNjRDM0YzREMwN0ExMUUxQTcwOUI1QzZCNTU0MDlGOCIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PgH//v38+/r5+Pf29fTz8vHw7+7t7Ovq6ejn5uXk4+Lh4N/e3dzb2tnY19bV1NPS0dDPzs3My8rJyMfGxcTDwsHAv769vLu6ubi3trW0s7KxsK+urayrqqmop6alpKOioaCfnp2cm5qZmJeWlZSTkpGQj46NjIuKiYiHhoWEg4KBgH9+fXx7enl4d3Z1dHNycXBvbm1sa2ppaGdmZWRjYmFgX15dXFtaWVhXVlVUU1JRUE9OTUxLSklIR0ZFRENCQUA/Pj08Ozo5ODc2NTQzMjEwLy4tLCsqKSgnJiUkIyIhIB8eHRwbGhkYFxYVFBMSERAPDg0MCwoJCAcGBQQDAgEAACH5BAEAAAkALAAAAAASABIAAARDMMlJq704661B+SAIXAVhnKhBFKSZnmv7wqxVzmpd3Uff5zKEUAi0uV4xm4DAbBIEOkohMKhaB4HoxBMKjTjgsFgSAQA7); + width: 18px; + cursor: pointer +} +.gantt_tree_icon.gantt_blank { + width: 18px +} +.gantt_tree_icon.gantt_folder_open { + background-image: url(data:image/gif;base64,R0lGODlhEgASAJECAJeXl7Gvrf///wAAACH/C1hNUCBEYXRhWE1QPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS4wLWMwNjAgNjEuMTM0Nzc3LCAyMDEwLzAyLzEyLTE3OjMyOjAwICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOnhtcE1NPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvbW0vIiB4bWxuczpzdFJlZj0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL3NUeXBlL1Jlc291cmNlUmVmIyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ1M1IFdpbmRvd3MiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6NTdDRDM3QzVDMDZEMTFFMUJGMzhFMDhCN0RGRjBGQ0YiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6NTdDRDM3QzZDMDZEMTFFMUJGMzhFMDhCN0RGRjBGQ0YiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo1N0NEMzdDM0MwNkQxMUUxQkYzOEUwOEI3REZGMEZDRiIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo1N0NEMzdDNEMwNkQxMUUxQkYzOEUwOEI3REZGMEZDRiIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PgH//v38+/r5+Pf29fTz8vHw7+7t7Ovq6ejn5uXk4+Lh4N/e3dzb2tnY19bV1NPS0dDPzs3My8rJyMfGxcTDwsHAv769vLu6ubi3trW0s7KxsK+urayrqqmop6alpKOioaCfnp2cm5qZmJeWlZSTkpGQj46NjIuKiYiHhoWEg4KBgH9+fXx7enl4d3Z1dHNycXBvbm1sa2ppaGdmZWRjYmFgX15dXFtaWVhXVlVUU1JRUE9OTUxLSklIR0ZFRENCQUA/Pj08Ozo5ODc2NTQzMjEwLy4tLCsqKSgnJiUkIyIhIB8eHRwbGhkYFxYVFBMSERAPDg0MCwoJCAcGBQQDAgEAACH5BAEAAAIALAAAAAASABIAAAIzlI+pywcPm3mhWgkCsjBOvVkimElG9ZlCBlXd+2XjjLKg5GqoeZXqvsOQXK/ijUZTKVUFADs=) +} +.gantt_tree_icon.gantt_folder_closed { + background-image: url(data:image/gif;base64,R0lGODlhEgASAJECAJeXl7Gvrf///wAAACH/C1hNUCBEYXRhWE1QPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS4wLWMwNjAgNjEuMTM0Nzc3LCAyMDEwLzAyLzEyLTE3OjMyOjAwICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOnhtcE1NPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvbW0vIiB4bWxuczpzdFJlZj0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL3NUeXBlL1Jlc291cmNlUmVmIyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ1M1IFdpbmRvd3MiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6NTAyMTU1RTNDMDZEMTFFMUJGNzZCRThBRkFCRjg4MTIiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6NTAyMTU1RTRDMDZEMTFFMUJGNzZCRThBRkFCRjg4MTIiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo1MDIxNTVFMUMwNkQxMUUxQkY3NkJFOEFGQUJGODgxMiIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo1MDIxNTVFMkMwNkQxMUUxQkY3NkJFOEFGQUJGODgxMiIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PgH//v38+/r5+Pf29fTz8vHw7+7t7Ovq6ejn5uXk4+Lh4N/e3dzb2tnY19bV1NPS0dDPzs3My8rJyMfGxcTDwsHAv769vLu6ubi3trW0s7KxsK+urayrqqmop6alpKOioaCfnp2cm5qZmJeWlZSTkpGQj46NjIuKiYiHhoWEg4KBgH9+fXx7enl4d3Z1dHNycXBvbm1sa2ppaGdmZWRjYmFgX15dXFtaWVhXVlVUU1JRUE9OTUxLSklIR0ZFRENCQUA/Pj08Ozo5ODc2NTQzMjEwLy4tLCsqKSgnJiUkIyIhIB8eHRwbGhkYFxYVFBMSERAPDg0MCwoJCAcGBQQDAgEAACH5BAEAAAIALAAAAAASABIAAAIwlI+pywcPm3mhWgkCsjBOvVkimElG9ZlCuYIY6TYs+6bmHDO4igfdD3GNhheV0VQAADs=) +} +.gantt_tree_icon.gantt_file { + background-image: url(data:image/gif;base64,R0lGODlhEgASAJECAJeXl7Gvrf///wAAACH/C1hNUCBEYXRhWE1QPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS4wLWMwNjAgNjEuMTM0Nzc3LCAyMDEwLzAyLzEyLTE3OjMyOjAwICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOnhtcE1NPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvbW0vIiB4bWxuczpzdFJlZj0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL3NUeXBlL1Jlc291cmNlUmVmIyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ1M1IFdpbmRvd3MiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6NzkxQzI4RjZDMDZEMTFFMTgwRjhBQURDQzI3NDU3QUEiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6NzkxQzI4RjdDMDZEMTFFMTgwRjhBQURDQzI3NDU3QUEiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo3OTFDMjhGNEMwNkQxMUUxODBGOEFBRENDMjc0NTdBQSIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo3OTFDMjhGNUMwNkQxMUUxODBGOEFBRENDMjc0NTdBQSIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PgH//v38+/r5+Pf29fTz8vHw7+7t7Ovq6ejn5uXk4+Lh4N/e3dzb2tnY19bV1NPS0dDPzs3My8rJyMfGxcTDwsHAv769vLu6ubi3trW0s7KxsK+urayrqqmop6alpKOioaCfnp2cm5qZmJeWlZSTkpGQj46NjIuKiYiHhoWEg4KBgH9+fXx7enl4d3Z1dHNycXBvbm1sa2ppaGdmZWRjYmFgX15dXFtaWVhXVlVUU1JRUE9OTUxLSklIR0ZFRENCQUA/Pj08Ozo5ODc2NTQzMjEwLy4tLCsqKSgnJiUkIyIhIB8eHRwbGhkYFxYVFBMSERAPDg0MCwoJCAcGBQQDAgEAACH5BAEAAAIALAAAAAASABIAAAIylI+pwN16QJiUQiFThRlJm3RRFYSlR5qXMKmXaMDuuMoyOi8n/e6xn8NMHETgh5RaKQsAOw==) +} +.gantt_grid_head_cell .gantt_sort { + position: absolute; + right: 5px; + top: 8px; + width: 7px; + height: 13px; + background-repeat: no-repeat; + background-position: center center +} +.gantt_grid_head_cell .gantt_sort.gantt_asc { + background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAcAAAANCAYAAABlyXS1AAAARUlEQVR4nGNgQAKGxib/GbABkIS7b8B/DAUwCRiGK0CXwFBAb1DfP/U/LszwHwi2X7qFgUEArBtdAVwCBmAKMCSQFSDzAWXXaOHsXeqkAAAAAElFTkSuQmCC) +} +.gantt_grid_head_cell .gantt_sort.gantt_desc { + background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAcAAAANCAYAAABlyXS1AAAARUlEQVR42mNgQAL1/VP/M2ADIIntF2/9x1AAlrh0C47hCmA60DFYwX88gIFGwNDY5D8uDFbg7hvwHx2jmIBTAlkB0e4BAEjlaNtBWJPnAAAAAElFTkSuQmCC) +} +.gantt_inserted, +.gantt_updated { + font-weight: 700 +} +.gantt_deleted { + text-decoration: line-through +} +.gantt_invalid { + background-color: #FFE0E0 +} +.gantt_error { + color: red +} +.gantt_status { + right: 1px; + padding: 5px 10px; + background: rgba(155, 155, 155, .1); + position: absolute; + top: 1px; + -webkit-transition: opacity .2s; + transition: opacity .2s; + opacity: 0 +} +.gantt_status.gantt_status_visible { + opacity: 1 +} +#gantt_ajax_dots span { + -webkit-transition: opacity .2s; + transition: opacity .2s; + background-repeat: no-repeat; + opacity: 0 +} +#gantt_ajax_dots span.gantt_dot_visible { + opacity: 1 +} +.dhtmlx_message_area { + position: fixed; + right: 5px; + width: 250px; + z-index: 1000 +} +.dhtmlx-message { // HOSEK +min-width: 120px; + font-family: Arial; + z-index: 10000; + margin: 5px 5px 10px; + -webkit-transition: all .5s ease; + -moz-transition: all .5s ease; + -o-transition: all .5s ease; + transition: all .5s ease; + font-size: 14px; + color: #000; + box-shadow: 3px 3px 3px rgba(0, 0, 0, .07); + padding: 0; + background-color: #FFF; + border-radius: 3px; + border: 1px solid #fff +} +.dhtmlx-message.hidden { // HOSEK +height: 0; + padding: 0; + border-width: 0; + margin: 0; + overflow: hidden +} +.dhtmlx_modal_box { + overflow: hidden; + display: inline-block; + min-width: 250px; + width: 250px; + text-align: center; + position: fixed; + z-index: 20000; + box-shadow: 3px 3px 3px rgba(0, 0, 0, .07); + font-family: Arial; + border-radius: 6px; + border: 1px solid #cecece; + background: #fff +} +.dhtmlx_popup_title { + border-top-left-radius: 6px; + border-top-right-radius: 6px; + border-width: 0 +} +.dhtmlx_button, +.dhtmlx_popup_button { + border: 1px solid #cecece; + height: 30px; + line-height: 30px; + display: inline-block; + margin: 0 5px; + border-radius: 4px; + background: #fff +} +.dhtmlx-message, /* HOSEK*/ +.dhtmlx_button, +.dhtmlx_popup_button { + user-select: none; + -webkit-user-select: none; + -moz-user-select: -moz-none; + -ms-user-select: none; + cursor: pointer +} +.dhtmlx_popup_text { + overflow: hidden +} +.dhtmlx_popup_controls { + border-radius: 6px; + padding: 10px +} +.dhtmlx_popup_button { + min-width: 100px +} +div.dhx_modal_cover { + background-color: #000; + cursor: default; + filter: alpha(opacity=20); + opacity: .2; + position: fixed; + z-index: 19999; + left: 0; + top: 0; + width: 100%; + height: 100%; + border: none; + zoom: 1 +} +.dhtmlx-message img, /* HOSEK */ +.dhtmlx_modal_box img { + float: left; + margin-right: 20px +} +.dhtmlx-alert-error, +.dhtmlx-confirm-error { + border: 1px solid red +} +.dhtmlx_button input, +.dhtmlx_popup_button div { + border-radius: 4px; + font-size: 14px; + -moz-box-sizing: content-box; + box-sizing: content-box; + padding: 0; + margin: 0; + vertical-align: top +} +.dhtmlx_popup_title { + color: #fff; + text-shadow: 1px 1px #000; + height: 40px; + line-height: 40px; + font-size: 20px +} +.dhtmlx_popup_text { + margin: 15px 15px 5px; + font-size: 14px; + color: #000; + min-height: 30px; + border-radius: 6px +} + +/* HOSEK V */ +/*.dhtmlx-error, +.dhtmlx-info { + font-size: 14px; + color: #000; + box-shadow: 3px 3px 3px rgba(0, 0, 0, .07); + padding: 0; + background-color: #FFF; + border-radius: 3px; + border: 1px solid #fff +}*/ +.dhtmlx-message div { + padding: 5px 10px; + border-radius: 3px; +} +.dhtmlx-info div { + background-color: #fff; + border: 1px solid #cecece; +} +.dhtmlx-error { + background-color: #d81b1b; + border: 1px solid #ff3c3c; +} +.dhtmlx-error div { + background-color: #d81b1b; + border: 1px solid #940000; + color: #FFF; +} +.dhtmlx-success { + background-color: #2dff2d; + border: 1px solid #6CFF6C; +} +.dhtmlx-success div { + background-color: #2dff2d; + border: 1px solid #1B991B; + /*color: #FFF*/ +} +/* HOSEK A */ +.gantt_data_area div, +.gantt_grid div { + -ms-touch-action: none; + -webkit-tap-highlight-color: transparent +} +.gantt_data_area { + position: relative; + overflow-x: hidden; + overflow-y: hidden; + -moz-user-select: -moz-none; + -webkit-user-select: none; + -ms-user-select: none; + user-select: none; +} +.gantt_links_area { + position: absolute; + left: 0; + top: 0 +} +.gantt_side_content, +.gantt_task_content, +.gantt_task_progress { + line-height: inherit; + overflow: hidden; + height: 100% +} +.gantt_task_content { + font-size: 12px; + /*color: #fff;*/ + width: 100%; + top: 0; + position: absolute; + white-space: nowrap; + text-align: center +} +.gantt_task_progress { + text-align: center; + z-index: 0; + background: #299cb4 +} +.gantt_task_line { + -webkit-border-radius: 2px; + -moz-border-radius: 2px; + border-radius: 2px; + position: absolute; + -moz-box-sizing: border-box; + box-sizing: border-box; + background-color: #3db9d3; + border: 1px solid #2898b0; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + -moz-user-select: -moz-none +} +.gantt_task_line.gantt_drag_move div { + cursor: move +} +.gantt_touch_move, +.gantt_touch_progress .gantt_touch_resize { + -moz-transform: scale(1.02, 1.1); + -o-transform: scale(1.02, 1.1); + -webkit-transform: scale(1.02, 1.1); + transform: scale(1.02, 1.1); + -moz-transform-origin: 50%; + -o-transform-origin: 50%; + -webkit-transform-origin: 50%; + transform-origin: 50% +} +.gantt_touch_progress .gantt_task_progress_drag, +.gantt_touch_resize .gantt_task_drag { + -moz-transform: scaleY(1.3); + -o-transform: scaleY(1.3); + -webkit-transform: scaleY(1.3); + transform: scaleY(1.3); + -moz-transform-origin: 50%; + -o-transform-origin: 50%; + -webkit-transform-origin: 50%; + transform-origin: 50% +} +.gantt_side_content { + position: absolute; + white-space: nowrap; + /*color: #6e6e6e;*/ + bottom: 1px; + /*font-size: 14px*/ +} +.gantt_side_content.gantt_left { + right: 100%; + padding-right: 15px +} +.gantt_side_content.gantt_right { + left: 100%; + padding-left: 15px +} +.gantt_side_content.gantt_link_crossing { + /*bottom: 8.75px*/ +} +.gantt_link_arrow, +.gantt_task_link .gantt_line_wrapper { + position: absolute; + cursor: pointer +} +.gantt_line_wrapper div { + /*background-color: #ffa011;*/ + border: 1px solid #ffa011; +} +.gantt_task_link:hover .gantt_line_wrapper div { + box-shadow: 0 0 5px 0 #ffa011 +} +.gantt_task_link div.gantt_link_arrow { + background-color: transparent; + border-style: solid; + width: 0; + height: 0 +} +.gantt_link_control { + position: absolute; + width: 13px; + top: 0 +} +.gantt_link_control div { + display: none; + cursor: pointer; + box-sizing: border-box; + position: relative; + top: 50%; + margin-top: -7.5px; + vertical-align: middle; + border: 1px solid #929292; + -webkit-border-radius: 6.5px; + -moz-border-radius: 6.5px; + border-radius: 6.5px; + height: 13px; + width: 13px; + background-color: #f0f0f0 +} +.gantt_link_control div:hover { + background-color: #fff +} +.gantt_link_control.task_left { + left: -13px +} +.gantt_link_control.task_right { + right: -13px +} +.gantt_link_target .gantt_link_control div, +.gantt_task_line.gantt_selected .gantt_link_control div, +.gantt_task_line:hover .gantt_link_control div { + display: block +} +.gantt_link_source, +.gantt_link_target { + box-shadow: 0 0 3px #3db9d3 +} +.gantt_link_target.link_finish_allow, +.gantt_link_target.link_start_allow { + box-shadow: 0 0 3px #ffbf5e +} +.gantt_link_target.link_finish_deny, +.gantt_link_target.link_start_deny { + box-shadow: 0 0 3px #e87e7b +} +.link_finish_allow .gantt_link_control.task_right div, +.link_start_allow .gantt_link_control.task_left div { + background-color: #ffbf5e; + border-color: #ffa011 +} +.link_finish_deny .gantt_link_control.task_right div, +.link_start_deny .gantt_link_control.task_left div { + background-color: #e87e7b; + border-color: #dd3e3a +} +.gantt_link_arrow_right { + border-width: 4px 0 4px 6px; + border-top-color: transparent!important; + border-right-color: transparent!important; + border-bottom-color: transparent!important; + border-color: #ffa011; + margin-top: -1px +} +.gantt_link_arrow_left { + border-width: 4px 6px 4px 0; + margin-top: -1px; + border-top-color: transparent!important; + border-color: #ffa011; + border-bottom-color: transparent!important; + border-left-color: transparent!important +} +.gantt_link_arrow_top { + border-width: 0 4px 6px; + border-color: #ffa011; + border-top-color: transparent!important; + border-right-color: transparent!important; + border-left-color: transparent!important +} +.gantt_link_arrow_down { + border-width: 4px 6px 0 4px; + border-color: #ffa011; + border-right-color: transparent!important; + border-bottom-color: transparent!important; + border-left-color: transparent!important +} +.gantt_task_drag, +.gantt_task_progress_drag { + cursor: w-resize; + height: 100%; + display: none; + position: absolute +} +.gantt_task_line.gantt_selected .gantt_task_drag, +.gantt_task_line.gantt_selected .gantt_task_progress_drag, +.gantt_task_line:hover .gantt_task_drag, +.gantt_task_line:hover .gantt_task_progress_drag { + display: block +} +.gantt_task_drag { + width: 6px; + background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAYAAAACCAYAAAB7Xa1eAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3QYDDjkw3UJvAwAAABRJREFUCNdj/P//PwM2wASl/6PTAKrrBf4+lD8LAAAAAElFTkSuQmCC); + z-index: 1; + top: 0 +} +.gantt_task_drag.task_left { + left: 0 +} +.gantt_task_drag.task_right { + right: 0 +} +.gantt_task_progress_drag { + height: 8px; + width: 8px; + bottom: -4px; + margin-left: -4px; + background-position: bottom; + background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAALCAYAAAB24g05AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoV2luZG93cykiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6MkY3Rjk0RUVDMkYzMTFFMkI1OThEQTA3ODU0OTkzMEEiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6MkY3Rjk0RUZDMkYzMTFFMkI1OThEQTA3ODU0OTkzMEEiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDoyRjdGOTRFQ0MyRjMxMUUyQjU5OERBMDc4NTQ5OTMwQSIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDoyRjdGOTRFREMyRjMxMUUyQjU5OERBMDc4NTQ5OTMwQSIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PobPBzIAAADkSURBVHjaYpk2bRoDDsAExL1QdjEQ/8OmiAWHZk4gXqymqhQM4ty6fU8OSMUA8XdiDBAB4k0a6iqWRga6EKcwMQXduHlnL5DpB8Rv0J2JDFSA+JiOtgZcMwiA2CAxkBxUDVYDLEAKgIpV9XQ0MZwFEgPJAZnHoWpRDAgC4n2W5saiQKfjClQGkBxQDciL+6B6wAbkA/EqJwdrTkUFOQZCAKQGpBbIXA3SCzJggo+XK7OEuBgDsQCkFqgHrBfsBT5eHgZSAUwP2IBfv36TbABMDygdtK1Zv6UESLORaAbIhG6AAAMAKN8wE24DXWcAAAAASUVORK5CYII=); + background-repeat: no-repeat; + z-index: 2 +} +.gantt_link_tooltip { + box-shadow: 3px 3px 3px #888; + background-color: #fff; + border-left: 1px dotted #cecece; + border-top: 1px dotted #cecece; + font-family: Tahoma; + font-size: 8pt; + color: #444; + padding: 6px; + line-height: 20px +} +.gantt_link_direction { + height: 0; + border: 0 #ffa011; + border-bottom-style: dashed; + border-bottom-width: 2px; + transform-origin: 0 0; + -ms-transform-origin: 0 0; + -webkit-transform-origin: 0 0; + z-index: 2; + margin-left: 1px; + position: absolute +} +.gantt_grid_data .gantt_row.gantt_selected, +.gantt_grid_data .gantt_row.odd.gantt_selected, +.gantt_task_row.gantt_selected { + background-color: #fff3a1 +} +.gantt_task_row.gantt_selected .gantt_task_cell { + border-right-color: #ffec6e +} +.gantt_task_line.gantt_selected { + box-shadow: 0 0 5px #299cb4 +} +.gantt_task_line.gantt_project-type.gantt_selected { + box-shadow: 0 0 5px #46ad51 +} +.gantt_task_line.gantt_milestone-type { + visibility: hidden; + background-color: #d33daf; + border: 0 solid #61164f; + box-sizing: content-box; + -moz-box-sizing: content-box +} +.gantt_task_line.gantt_milestone-type div { + visibility: visible +} +.gantt_task_line.gantt_milestone-type .gantt_task_content { + background: inherit; + border: inherit; + border-width: 1px; + border-radius: inherit; + box-sizing: border-box; + -moz-box-sizing: border-box; + -webkit-transform: rotate(45deg); + -moz-transform: rotate(45deg); + -ms-transform: rotate(45deg); + -o-transform: rotate(45deg); + transform: rotate(45deg) +} +.gantt_task_line.gantt_task_inline_color { + border-color: #999 +} +.gantt_task_line.gantt_task_inline_color .gantt_task_progress { + background-color: #363636; + opacity: .2 +} +.gantt_task_line.gantt_task_inline_color.gantt_project-type.gantt_selected, +.gantt_task_line.gantt_task_inline_color.gantt_selected { + box-shadow: 0 0 5px #999 +} +.gantt_task_link.gantt_link_inline_color:hover .gantt_line_wrapper div { + box-shadow: 0 0 5px 0 #999 +} +.gantt_critical_task { + background-color: #e63030; + border-color: #9d3a3a +} +.gantt_critical_task .gantt_task_progress { + background-color: rgba(0, 0, 0, .4) +} +.gantt_critical_link .gantt_line_wrapper>div { + background-color: #e63030 +} +.gantt_critical_link .gantt_link_arrow { + border-color: #e63030 +} +.gantt_unselectable, +.gantt_unselectable div { + -webkit-user-select: none; + -moz-user-select: none; + -moz-user-select: -moz-none; + -ms-user-select: none; +} +.gantt_cal_light { + -webkit-tap-highlight-color: transparent; + background: #fff; + border-radius: 6px; + font-family: Arial; + border: 1px solid #cecece; + color: #6b6b6b; + font-size: 12px; + position: absolute; + z-index: 10001; + width: 550px; + height: 250px; + box-shadow: 3px 3px 3px rgba(0, 0, 0, .07) +} +.gantt_cal_light select { + font-family: Arial; + border: 1px solid #cecece; + font-size: 13px; + padding: 2px; + margin: 0 +} +.gantt_cal_ltitle { + padding: 7px 10px; + overflow: hidden; + white-space: nowrap; + -webkit-border-radius: 6px 6px 0 0; + -moz-border-radius-topleft: 6px; + -moz-border-radius-bottomleft: 0; + -moz-border-radius-topright: 6px; + -moz-border-radius-bottomright: 0; + border-radius: 6px 6px 0 0 +} +.gantt_cal_ltitle span { + white-space: nowrap +} +.gantt_cal_lsection { + color: #727272; + font-weight: 700; + padding: 12px 0 5px 10px +} +.gantt_cal_lsection .gantt_fullday { + float: right; + margin-right: 5px; + font-size: 12px; + font-weight: 400; + line-height: 20px; + vertical-align: top; + cursor: pointer +} +.gantt_cal_lsection { + font-size: 13px +} +.gantt_cal_ltext { + padding: 2px 10px; + overflow: hidden +} +.gantt_cal_ltext textarea { + overflow: auto; + font-family: Arial; + font-size: 13px; + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; + box-sizing: border-box; + border: 1px solid #cecece; + height: 100%; + width: 100%; + outline: 0!important; + resize: none +} +.gantt_time { + font-weight: 700 +} +.gantt_cal_light .gantt_title { + padding-left: 10px +} +.gantt_cal_larea { + border: 1px solid #cecece; + border-left: none; + border-right: none; + background-color: #fff; + overflow: hidden; + height: 1px +} +.gantt_btn_set { + margin: 10px 7px 5px 10px; + padding: 5px 15px 5px 10px; + float: left; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + border-radius: 4px; + border-width: 0; + border-color: #cecece; + border-style: solid; + height: 32px; + font-weight: 700; + background: #fff; + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; + box-sizing: border-box; + cursor: pointer +} +.gantt_btn_set div { + float: left; + font-size: 13px; + height: 22px; + line-height: 22px; + background-repeat: no-repeat; + vertical-align: middle +} +.gantt_save_btn { + background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoV2luZG93cykiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6MTk1OUU5RDFDMzA0MTFFMkExMUZBQTdDNDAzOUE5RjMiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6MTk1OUU5RDJDMzA0MTFFMkExMUZBQTdDNDAzOUE5RjMiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDoxOTU5RTlDRkMzMDQxMUUyQTExRkFBN0M0MDM5QTlGMyIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDoxOTU5RTlEMEMzMDQxMUUyQTExRkFBN0M0MDM5QTlGMyIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PjDroXYAAAEXSURBVHjaYvz//z8DJYCRUgPIAUxAbAnEHiAHMIBcQCwGaRYXF3e6evXqoffv39/dv38/CymaGSUkJBzv3LlzCsj///fv3wdAihkkIQnEvkAshU8zLy+v7a1bt06ANP/79+87kDIAy505cybq06dPr3p7ezuwGQLTfOPGjWP/ESAZLg8kPKBO+g01RBJNszWyZqC6uSgWgIg/f/4shxnS2dnZBjMEqNkSFGBImi8CKTYMA4BYCGjIczRDHC5dunQQSfN7IKWI4UUkjjdMMdCwnw8ePLjwHxV4Yw1gZA5Q47z/2EELzhhCE+ABGvIQWSeQvwcU38QaAML2wHj+C/X3MyAlijeB4ZBoBOIPQGxJKIVSnBsBAgwABddBclWfcZUAAAAASUVORK5CYII=); + margin-top: 2px; + width: 21px +} +.gantt_cancel_btn { + margin-top: 2px; + background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoV2luZG93cykiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6MDkzMDA3MzlDMzA0MTFFMjg2QTVFMzFEQzgwRkJERDYiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6MDkzMDA3M0FDMzA0MTFFMjg2QTVFMzFEQzgwRkJERDYiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDowOTMwMDczN0MzMDQxMUUyODZBNUUzMURDODBGQkRENiIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDowOTMwMDczOEMzMDQxMUUyODZBNUUzMURDODBGQkRENiIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PmYuYOUAAAEdSURBVHjaYvz//z8DJYAFXWDlypU8QKoIiD2A2AwqfAqIdwBxX3h4+Bdk9YzILgBqtgdS84FYEYeF94E4EWjIQZgAE5LmQCB1AKoZZKMPEAtAMYh9GSp3AKjWD8UFQAEhIPshEIOc3wHENUBb/qJ57SyQMoJyPwKxElDNO1gYFEE17wMKVmIJlzNQzeegrjaA6qmBecEbSvfh0GwMxGeBhoPoemQ9MAO0kEIbl2YTqPAFKK2IbMB3AjabYIkRZmQD7kNpMyI0G0PpO8gGbIUFJj7NQDk2INWIrIcJKfBAKcwJqvkcDs0TgFgXGo19KCkRmpDWQdWDEk0NUoCBoq0FqhkE/IEWbKJKUmZEz43QzFSKIzN1481M5ACAAAMAlfl/lCwRpagAAAAASUVORK5CYII=); + width: 20px +} +.gantt_delete_btn { + background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoV2luZG93cykiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6MjFENzI3NUNDMzA0MTFFMjhBNjJGQTc3MUIyQzYzNEYiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6MjFENzI3NURDMzA0MTFFMjhBNjJGQTc3MUIyQzYzNEYiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDoyMUQ3Mjc1QUMzMDQxMUUyOEE2MkZBNzcxQjJDNjM0RiIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDoyMUQ3Mjc1QkMzMDQxMUUyOEE2MkZBNzcxQjJDNjM0RiIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PmUD0gAAAABvSURBVHjaYvz//z8DIyMjAxYQicReji4J0ofKQNP8HwmgGQbXB8IsWGwDSSwDuioKjY9uBthVjFAXYHUGAQA2kYmBUoAUBpGk0LAwgBvwH+YX4mkwptgLowYMRgOITUyYKRFIN/wnDjQgJySAAAMApryKzL8wjfUAAAAASUVORK5CYII=); + margin-top: 2px; + width: 20px +} +.gantt_cal_cover { + width: 100%; + height: 100%; + position: absolute; + z-index: 10000; + top: 0; + left: 0; + background-color: #000; + opacity: .1; + filter: alpha(opacity=10) +} +.gantt_custom_button { + padding: 0 3px; + font-family: Arial; + font-size: 13px; + font-weight: 400; + margin-right: 10px; + margin-top: -5px; + cursor: pointer; + float: right; + height: 21px; + width: 90px; + border: 1px solid #CECECE; + text-align: center; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + -ms-border-radius: 4px; + -o-border-radius: 4px; + border-radius: 4px +} +.gantt_custom_button div { + cursor: pointer; + float: none; + height: 21px; + line-height: 21px; + vertical-align: middle +} +.gantt_custom_button div:first-child { + display: none +} +.gantt_cal_light_wide { + width: 580px; + padding: 2px 4px +} +.gantt_cal_light_wide .gantt_cal_larea { + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; + box-sizing: border-box; + border: 1px solid #cecece +} +.gantt_cal_light_wide .gantt_cal_lsection { + border: 0; + float: left; + text-align: right; + width: 80px; + height: 20px; + padding: 5px 10px 0 0 +} +.gantt_cal_light_wide .gantt_wrap_section { + position: relative; + padding: 10px 0; + overflow: hidden; + border-bottom: 1px solid #ebebeb +} +.gantt_cal_light_wide .gantt_section_time { + overflow: hidden; + padding-top: 2px!important; + padding-right: 0; + height: 20px!important +} +.gantt_cal_light_wide .gantt_cal_ltext { + padding-right: 0 +} +.gantt_cal_light_wide .gantt_cal_larea { + padding: 0 10px; + width: 100% +} +.gantt_cal_light_wide .gantt_section_time { + background: 0 0 +} +.gantt_cal_light_wide .gantt_cal_checkbox label { + padding-left: 0 +} +.gantt_cal_light_wide .gantt_cal_lsection .gantt_fullday { + float: none; + margin-right: 0; + font-weight: 700; + cursor: pointer +} +.gantt_cal_light_wide .gantt_custom_button { + position: absolute; + top: 0; + right: 0; + margin-top: 2px +} +.gantt_cal_light_wide .gantt_repeat_right { + margin-right: 55px +} +.gantt_cal_light_wide.gantt_cal_light_full { + width: 738px +} +.gantt_cal_wide_checkbox input { + margin-top: 8px; + margin-left: 14px +} +.gantt_cal_light input { + font-size: 13px +} +.gantt_section_time { + background-color: #fff; + white-space: nowrap; + padding: 5px 10px; + padding-top: 2px!important +} +.gantt_section_time .gantt_time_selects { + float: left; + height: 25px +} +.gantt_section_time .gantt_time_selects select { + height: 23px; + padding: 2px; + border: 1px solid #cecece +} +.gantt_duration { + width: 100px; + height: 23px; + float: left; + white-space: nowrap; + margin-left: 20px; + line-height: 23px +} +.gantt_duration .gantt_duration_dec, +.gantt_duration .gantt_duration_inc, +.gantt_duration .gantt_duration_value { + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; + box-sizing: border-box; + text-align: center; + vertical-align: top; + height: 100%; + border: 1px solid #cecece +} +.gantt_duration .gantt_duration_value { + width: 40px; + padding: 3px 4px; + border-left-width: 0; + border-right-width: 0 +} +.gantt_duration .gantt_duration_dec, +.gantt_duration .gantt_duration_inc { + width: 20px; + padding: 1px 1px 3px; + background: #fff +} +.gantt_duration .gantt_duration_dec { + -moz-border-top-left-radius: 4px; + -moz-border-bottom-left-radius: 4px; + -webkit-border-top-left-radius: 4px; + -webkit-border-bottom-left-radius: 4px; + border-top-left-radius: 4px; + border-bottom-left-radius: 4px +} +.gantt_duration .gantt_duration_inc { + margin-right: 4px; + -moz-border-top-right-radius: 4px; + -moz-border-bottom-right-radius: 4px; + -webkit-border-top-right-radius: 4px; + -webkit-border-bottom-right-radius: 4px; + border-top-right-radius: 4px; + border-bottom-right-radius: 4px +} +.gantt_cal_quick_info { + border: 1px solid #cecece; + border-radius: 6px; + position: absolute; + z-index: 300; + box-shadow: 3px 3px 3px rgba(0, 0, 0, .07); + background-color: #fff; + width: 300px; + transition: left .5s ease, right .5s; + -moz-transition: left .5s ease, right .5s; + -webkit-transition: left .5s ease, right .5s; + -o-transition: left .5s ease, right .5s +} +.gantt_no_animate { + transition: none; + -moz-transition: none; + -webkit-transition: none; + -o-transition: none +} +.gantt_cal_quick_info.gantt_qi_left .gantt_qi_big_icon { + float: right +} +.gantt_cal_qi_title { + -webkit-border-radius: 6px 6px 0 0; + -moz-border-radius-topleft: 6px; + -moz-border-radius-bottomleft: 0; + -moz-border-radius-topright: 6px; + -moz-border-radius-bottomright: 0; + border-radius: 6px 6px 0 0; + padding: 5px 0 8px 12px; + color: #454545; + background-color: #fff; + border-bottom: 1px solid #cecece +} +.gantt_cal_qi_tdate { + font-size: 14px; + font-weight: 700 +} +.gantt_cal_qi_tcontent { + font-size: 13px +} +.gantt_cal_qi_content { + padding: 16px 8px; + font-size: 13px; + color: #454545; + overflow: hidden +} +.gantt_cal_qi_controls { + -webkit-border-radius: 0 0 6px 6px; + -moz-border-radius-topleft: 0; + -moz-border-radius-bottomleft: 6px; + -moz-border-radius-topright: 0; + -moz-border-radius-bottomright: 6px; + border-radius: 0 0 6px 6px; + padding-left: 7px +} +#top-menu .easy-gantt.icon-stats .icon-svg { + stroke: orange; +} +.gantt_cal_qi_controls .gantt_menu_icon { + margin-top: 6px; + background-repeat: no-repeat +} +.gantt_cal_qi_controls .gantt_menu_icon.icon_edit { + width: 20px; + background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAA7EAAAOxAGVKw4bAAAAB3RJTUUH3QYFCjI5ZQj5bAAAAFNJREFUOMvt0zEOACAIA0DkwTymH8bJTRTKZGJXyaWEKPKTCQAH4Ls37cItcDUzsxHNDLZNhCq7Gt1wh9ErV7EjyGAhyGLphlnsClWuS32rn0czAV+vNGrM/LBtAAAAAElFTkSuQmCC) +} +.gantt_cal_qi_controls .gantt_menu_icon.icon_delete { + width: 20px; + background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoV2luZG93cykiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6MjFENzI3NUNDMzA0MTFFMjhBNjJGQTc3MUIyQzYzNEYiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6MjFENzI3NURDMzA0MTFFMjhBNjJGQTc3MUIyQzYzNEYiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDoyMUQ3Mjc1QUMzMDQxMUUyOEE2MkZBNzcxQjJDNjM0RiIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDoyMUQ3Mjc1QkMzMDQxMUUyOEE2MkZBNzcxQjJDNjM0RiIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PmUD0gAAAABvSURBVHjaYvz//z8DIyMjAxYQicReji4J0ofKQNP8HwmgGQbXB8IsWGwDSSwDuioKjY9uBthVjFAXYHUGAQA2kYmBUoAUBpGk0LAwgBvwH+YX4mkwptgLowYMRgOITUyYKRFIN/wnDjQgJySAAAMApryKzL8wjfUAAAAASUVORK5CYII=) +} +.gantt_qi_big_icon { + font-size: 13px; + border-radius: 4px; + font-weight: 700; + background: #fff; + margin: 5px 9px 8px 0; + min-width: 60px; + line-height: 32px; + vertical-align: middle; + padding: 0 10px 0 5px; + cursor: pointer; + border: 1px solid #cecece +} +.gantt_cal_qi_controls div { + float: left; + height: 32px; + text-align: center; + line-height: 32px +} +.gantt_tooltip { + box-shadow: 3px 3px 3px rgba(0, 0, 0, .07); + background-color: #fff; + border-left: 1px solid rgba(0, 0, 0, .07); + border-top: 1px solid rgba(0, 0, 0, .07); + font-family: Arial; + font-size: 8pt; + color: #454545; + padding: 10px; + position: absolute; + z-index: 50 +} +.gantt_marker { + height: 100%; + width: 2px; + top: 0; + position: absolute; + text-align: center; + background-color: rgba(255, 0, 0, .4); + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; + box-sizing: border-box +} +.gantt_marker .gantt_marker_content { + padding: 5px; + background: inherit; + color: #fff; + position: absolute; + font-size: 12px; + line-height: 12px; + opacity: .8 +} +.gantt_marker_area { + position: absolute; + top: 0; + left: 0 +} +.gantt_noselect { + -moz-user-select: -moz-none; + -webkit-user-select: none; + -ms-user-select: none; + user-select: none +} +.gantt_drag_marker { + position: absolute; + font-family: Arial; + font-size: 13px +} +.gantt_drag_marker .gantt_tree_icon.gantt_blank, +.gantt_drag_marker .gantt_tree_icon.gantt_close, +.gantt_drag_marker .gantt_tree_icon.gantt_open, +.gantt_drag_marker .gantt_tree_indent { + display: none +} +.gantt_drag_marker, +.gantt_drag_marker .gantt_row.odd { + background-color: #fff +} +.gantt_drag_marker .gantt_row { + border-left: 1px solid #d2d2d2; + border-top: 1px solid #d2d2d2 +} +.gantt_drag_marker .gantt_cell { + border-color: #d2d2d2 +} +.gantt_row.gantt_over, +.gantt_task_row.gantt_over { + background-color: #0070fe +} +.gantt_row.gantt_transparent .gantt_cell { + opacity: .7 +} +.gantt_task_row.gantt_transparent { + background-color: #f8fdfd +} +.dhtmlx_popup_button.dhtmlx_delete_button { + background: #3db9d3; + text-shadow: 0 -1px 0 #248a9f; + color: #fff; + font-weight: 700; + border-width: 0 +} +/* + + + +*/ + +@media print { + .content-title { + display: none; + } + + @page { + size: landscape + } + + .gantt-grid-checkbox-cont { + display: none; + } +} + +.gantt_container { + display: inline-block; +} + +#gantt_cont { + position: relative; + z-index: 0; +} + +#easy_gantt .weekend { + background-color: #F7F7F7; +} + +#easy_gantt .first-date { + position: relative; +} + +#easy_gantt .first-date:after { + content: ""; + position: absolute; + display: block; + left: -1px; + top: 0; + bottom: 0; + border-left: 1px solid #628DB6; +} + +#easy_gantt { + position: relative; +} + +.gantt_task_content { + overflow: visible; + /*color:black;*/ +} + +.no_task_controls .gantt_link_control div { + display: none !important; +} + +.gantt-fresh .gantt_link_control div { + display: none !important; +} + +.no_task_controls .gantt_task_drag, .no_task_controls .gantt_task_progress_drag { + display: none !important; +} + +#gantt_link_dialog { + border: 1px #000 solid; + background-color: #fff; + width: 200px; + position: absolute; + padding: 10px; + z-index: 2; +} + +#gantt_link_dialog input[type="number"] { + width: 38px; +} + +.gantt-tooltip { + position: absolute; + display: none; + background-color: white; + -webkit-box-shadow: 0 0 4px 2px rgba(232, 232, 232, 1); + -moz-box-shadow: 0 0 4px 2px rgba(232, 232, 232, 1); + box-shadow: 0 0 4px 2px rgba(232, 232, 232, 1); + padding: 20px; + z-index: 10; +} + +.gantt-tooltip-header { + font-size: 1em; + padding-bottom: 10px; + border-bottom: 1px solid rgba(0, 0, 0, 0.2); + margin: 0 0 10px; +} + +.gantt-tooltip-label { + font-size: 0.89em; + color: #a0a0a0; +} + +.gantt-tooltip-problem { + color: #d94838; + font-size: smaller; +} + +#easy_gantt.gantt .gantt_task_line.overdue { + background-color: #d94838; +} + +#easy_gantt.easy .gantt_task_line, #easy_gantt.easy .gantt_row { + padding: 0; + border-width: 0 0 1px 0; +} + +.gantt_task_progress { + /*background-color: rgba(0, 0, 0, 0.3);*/ + margin-left: 1px; +} + +#link_popup_button_cont { + margin-top: 35px; +} + +#easy_gantt a.disabled:hover { + text-decoration: none; + cursor: not-allowed; +} + +.milestone-type .gantt_cell { + border-right: 0; +} + +.gantt-legend-color-square { + border: 1px solid transparent; + margin-left: 10px; +} + +.gantt-color-square, .gantt-legend-color-square { + width: 15px; + height: 15px; + display: inline-block; + vertical-align: middle; + border: 1px solid transparent; +} + +.easy-gantt-legend-symbol { + margin-left: 10px; +} + +.gantt_cell { + padding: 0; +} + +.gantt_grid { + border-right-width: 2px; +} + +.gantt_row .gantt_drag_handle:before { + width: 18px; + height: 100%; + font-size: 20px; + font-weight: bold; +} + +.gantt_row:hover .gantt_drag_handle:before { + content: '\21F5'; + opacity: 0.5; +} + +.gantt_row .gantt_drag_handle:hover:before { + opacity: 1; + content: '\21F5'; +} + +.gantt_row.gantt_drag_to_allowed { + background-color: #FFEB59; +} + +.gantt_row.gantt_drag_hover { + background-color: #eeeeee; +} + +.gantt_row.gantt_drag_to_allowed.gantt_drag_hover { + background-color: #FBCF00; +} + +/*#sample_cont.flash {*/ +/*background-image: none;*/ +/*}*/ +.gantt-supertop-panel { + position: relative; +} + +#easy_gantt .gantt-sample-flash { + font-size: 14px; +} + +.gantt-menu { + z-index: 1; + position: relative; + background-color: #ffffff; + padding: 5px 0; +} + +.gantt-footer-menu { + padding: 5px 0; +} + +/*.easy-gantt-menu .action-buttons-with-submenu {*/ +/*display: inline;*/ +/*}*/ +.gantt-modal-video { + margin-left: 10px; + margin-top: 5px; +} + +.gantt_drag_marker { + z-index: 1; + pointer-events: none; +} + +.gantt_grid_column_resize_wrap, .gantt_grid_columns_resize_wrap { + top: 0; + height: 100%; + cursor: col-resize; + position: absolute; + /*background-color: red;*/ + width: 12px +} + +/*.gantt_task_line.parent{*/ +/*background-color: yellow;*/ +/*}*/ +.gantt-sample-close-button { + background-color: #cd0a0a; + color: white; + width: auto; + height: auto; + float: none; + display: inline; + margin-left: 25px; + padding: 8px 10px 8px 25px; + background-position-x: 5px; +} + +/* Easy Redmine < 2016 */ +#easy_gantt.easy .push-left { + float: left; +} + +#easy_gantt.easy .push-right { + float: right; + text-align: right +} + +/*#easy_gantt.easy .gantt-menu p[id^="button_"], #easy_gantt.easy .gantt-menu div[id^="button_"] {*/ +/*display: inline-block;*/ +/*}*/ +.gantt_grid_data div.empty-type div { + display: none; +} + +.gantt_bars_area div.empty-type { + display: none; +} + +.gantt_task_line.planned { + z-index: 1; +} + +.gantt_link_tooltip.gantt_link_deny { + background-color: #ff6666; +} + +@media print { + #easy_servicebar { + display: none; + } + + #easy_gantt_menu { + display: none; + } + + #easy_gantt_footer { + display: none; + } + + .flash { + display: none; + } +} + +.gantt_grid_superitem { + font-weight: bold; + /*font-style: italic;*/ + height: 100%; + width: 100%; + vertical-align: top; +} + +.gantt_grid_scale, .gantt_task_scale { + background-color: #ffffff; + z-index: 1; + position: relative; + border-top: 1px solid #cecece; + transform: translate(0, -1px); +} + +.gantt_grid_scale .gantt_grid_head_cell, .gantt_task .gantt_task_scale .gantt_scale_cell { + color: rgba(72, 72, 72, 0.8); +} + +/*.gantt_scale_cell{*/ +/*overflow: visible;*/ +/*}*/ + +.gantt-footer-legend { + display: inline-block; + float: right; + margin-top: 20px; +} + +.gantt-legend-symbol { + font-size: 15px; +} + +#easy_gantt input.wrong { + background-color: #cd0a0a; +} + +.gantt-reload-model-error { + color: #cd0a0a; +} + +.gantt_task_line { + background-color: inherit !important; + border: none !important; + box-shadow: none !important; +} + +.gantt_task_line.gantt_selected .gantt_task_content { + box-shadow: none !important; +} + +.gantt_task_line.closed .gantt_task_ticks, +.gantt_task_line.closed .gantt_task_content { + background-color: rgba(200, 200, 200, 0.2); + border-color: rgba(200, 200, 200, 1); +} + +.gantt_link_point { + border-color: rgba(217, 72, 56, 1) !important; + background-color: rgba(217, 72, 56, 1) !important; +} + +.gantt_task_drag { + background: radial-gradient(rgba(217, 72, 56, 1) 25%, transparent 10%) 1px 1px; + background-size: 4px 4px; +} + +.gantt_task_progress_drag { + border-bottom: 10px solid rgba(217, 72, 56, 1); +} + +.gantt_line_wrapper div, .gantt_link_arrow { + border-color: rgba(255, 125, 30, 1); +} + +.gantt-relation-simple > .gantt_line_wrapper div, .gantt-relation-simple > .gantt_link_arrow { + border-color: rgba(30, 120, 255, 1); +} + +.gantt-relation-simple:hover > .gantt_line_wrapper div { + box-shadow: 0 0 5px 0 rgba(30, 120, 255, 1); +} +.gantt-relation-unlocked > .gantt_line_wrapper div, .gantt-relation-unlocked > .gantt_link_arrow { + border-color: rgba(51, 141, 71, 1); +} + +.gantt-relation-unlocked:hover > .gantt_line_wrapper div { + box-shadow: 0 0 5px 0 rgba(255, 125, 30, 1); +} + +#easy_gantt .wrong .gantt_line_wrapper div { + border-color: rgba(217, 72, 56, 1) !important; +} + +#easy_gantt .wrong .gantt_link_arrow { + border-color: rgba(217, 72, 56, 1) !important; + border-top-color: transparent !important; + border-right-color: transparent !important; + border-bottom-color: transparent !important; +} + +.gantt_task_progress_drag { + border-bottom: 10px solid rgba(217, 72, 56, 1); +} + +#easy_gantt .gantt_link_arrow_right { + border-top-color: transparent !important; + border-right-color: transparent !important; + border-bottom-color: transparent !important; +} + +.gantt_task_progress_drag { + width: 0 !important; + height: 0 !important; + background: none !important; + border-left: 6.5px solid transparent; + border-right: 6.5px solid transparent; + border-bottom-width: 10px; + border-bottom-style: solid; + margin-left: -6.5px !important; +} + +.gantt_task_drag { + background-size: 4px 4px; +} + +.gantt_link_control.task_left { + left: -6.5px; + width: 6.5px; + border-radius: 6.5px 0 0 6.5px; +} + +.gantt_link_control.task_right { + right: -6.5px; + width: 6.5px; + border-radius: 0 6.5px 6.5px 0; +} + +.gantt_link_control.task_left .gantt_link_point { + width: 6.5px; + border-radius: 6.5px 0 0 6.5px; +} + +.gantt_link_control.task_right .gantt_link_point { + width: 6.5px; + border-radius: 0 6.5px 6.5px 0; +} + +@media print { + body { + margin: 0; + } + + .gantt_sort { + display: none; + } +} + +.gantt-grid-header-collapse-buttons { + position: absolute; + left: 5px; + bottom: 2px; +} + +.gantt-grid-header-collapse-buttons a { + font-size: 18px; + color: #a6a6a6; + font-weight: bold; + font-family: monospace; + display: inline-block; +} + +.gantt-grid-header-collapse-buttons a.active { + font-size: 18px; + color: #d94838 !important; + background-color: transparent !important; + border: none !important; +} + +.gantt-grid-header-collapse-buttons a:hover { + font-size: 18px; + color: rgba(72, 72, 72, 1); + text-decoration: none; + background-color: #eeeeee !important; + box-shadow: 0 0 15px 5px #eeeeee; + border-radius: 11px; +} + +.gantt-grid-checkbox-cont br { + display: none; +} + +@media screen { + .gantt-icon-parent-issue:before { + content: '⥐' + } + + .gantt-icon-milestone:before { + content: '♦'; + } +} + +.gantt_task_line.gantt_parent_task-subtype .gantt_task_content { + height: 50%; + -moz-border-radius-bottomleft: 0; + -moz-border-radius-bottomright: 0; +} + +.gantt_task_line.gantt_parent_task-subtype .gantt_task_progress { + height: 50%; +} + +.gantt_task_ticks { + height: 0; + margin-top: -1px; + border: 10px solid rgba(51, 141, 71, 1); + border-top-width: 0; + border-bottom-color: transparent !important; + background-color: transparent !important; +} + +.gantt-milestone-icon { + transform: scale(0.65) rotate(45deg); + width: 16px !important; + height: 16px !important; + background-color: rgba(62, 91, 118, 1); + border: 2px solid rgba(62, 91, 118, 1); +} + +.gantt_row:hover .gantt-bullet-hover-hide { + display: none; +} + +.gantt-sum-row-small { + font-size: 8px; +} + +.gantt-sum-row-negative { + color: red; +} + +#easy_gantt.easy #easy_gantt_menu { + z-index: 2; + overflow: visible; +} + +#easy_gantt .contextual.settings a { + z-index: 1; + border: none; + padding-right: 0; + opacity: 0.6; +} + +#easy_gantt.easy .contextual.settings a { + background: none; +} + +#easy_gantt .contextual.settings a:hover { + opacity: 1; +} + +#easy_gantt.easy .contextual.settings a:hover { + background: none; +} + +.gantt-grid-header-multi { + line-height: normal; + display: inline-block; + white-space: normal; +} + +.gantt_task_relation_stop { + position: absolute; + width: 8px; + top: 0; + height: 100%; + border: 2px solid rgba(255, 120, 0, 0.5); + box-sizing: border-box; +} + +.gantt_task_relation_stop_left { + border-right-color: transparent; +} + +.gantt_task_relation_stop_right { + border-left-color: transparent; + -webkit-transform: translate(-100%); + -moz-transform: translate(-100%); + -ms-transform: translate(-100%); + -o-transform: translate(-100%); + transform: translate(-100%); +} + +.gantt-menu-sub-panel { + margin-top: 3px; +} + +.gantt_task .gantt-sum-row .gantt_scale_cell { + font-weight: bold; + color: #260080; +} + +.gantt-menu-problems-count.gantt-with-problems { + background-color: red; + padding: 1px; + border: 1px solid #b0b0b0; + border-radius: 4px; +} + +.gantt-menu-problems-list { + overflow: auto; + position: absolute; + z-index: 2; + max-width: 600px; + margin-top: 5px; + border: 2px solid #cecece; + background-color: white; + right: 0; + text-align: left; +} + +.gantt-menu-problems-list ol { + margin: 5px 5px 5px -5px; +} + +.gantt-menu-problems-list li { + border-bottom: 1px solid #d9d9d9; + white-space: nowrap; + padding: 5px 25px 5px 5px; +} + +.gantt-menu-problems-list ol, +.gantt-menu-problems-list a { + color: #4b5561; +} + +.gantt-menu-problems-reason { + color: red; +} + +.gantt-task-bar-line { + position: absolute; + left: 0; + top: 0; + overflow: hidden +} + +@media print { + /* Because of gaps between cells in header */ + .gantt_scale_line.gantt-print__scale-line{ + font-size: 0; + } + .gantt_scale_line.gantt-print__scale-line .gantt_scale_cell{ + font-size: 12px; + } +} +/*----------------------------------------*/ +/* Vendors */ +/*----------------------------------------*/ +/*----------------------------------------*/ +/* Mixins & Placeholders */ +/*----------------------------------------*/ +/* line 88, plugins/easyproject/easy_plugins/easy_extensions/assets/../../../../easyproject/easy_plugins/easy_extensions/assets/stylesheets/scss/mixins/css3.scss */ +div#easy_gantt.easy .button.active, .easy .easy-gantt__menu-group--tooltiped ul li > .menu-children, .easy .easy-gantt__menu-group--tooltiped ul { + -webkit-border-radius: 2px; + -moz-border-radius: 2px; + border-radius: 2px; +} + +/* line 97, plugins/easyproject/easy_plugins/easy_extensions/assets/../../../../easyproject/easy_plugins/easy_extensions/assets/stylesheets/scss/mixins/css3.scss */ +.easy-gantt__menu .gantt-menu-problems-count.gantt-with-problems { + -webkit-border-radius: 5000px; + -moz-border-radius: 5000px; + border-radius: 5000px; +} + +/* line 118, plugins/easyproject/easy_plugins/easy_extensions/assets/../../../../easyproject/easy_plugins/easy_extensions/assets/stylesheets/scss/mixins/css3.scss */ +.easy .easy-gantt__menu-group--tooltiped ul li > .menu-children, .easy .easy-gantt__menu-group--tooltiped ul { + -webkit-box-shadow: 0px 0px 5px 0 rgba(0, 0, 0, 0.125); + -moz-box-shadow: 0px 0px 5px 0 rgba(0, 0, 0, 0.125); + box-shadow: 0px 0px 5px 0 rgba(0, 0, 0, 0.125); +} + +/* line 130, plugins/easyproject/easy_plugins/easy_extensions/assets/../../../../easyproject/easy_plugins/easy_extensions/assets/stylesheets/scss/mixins/css3.scss */ +div#easy_gantt.easy .active.button { + -webkit-box-shadow: inset 0px 1px 10px 0px rgba(0, 0, 0, 0.125); + -moz-box-shadow: inset 0px 1px 10px 0px rgba(0, 0, 0, 0.125); + box-shadow: inset 0px 1px 10px 0px rgba(0, 0, 0, 0.125); +} + +/* line 305, plugins/easyproject/easy_plugins/easy_extensions/assets/../../../../easyproject/easy_plugins/easy_extensions/assets/stylesheets/scss/mixins/css3.scss */ +.easy-gantt__menu { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} + +/* line 502, plugins/easyproject/easy_plugins/easy_extensions/assets/../../../../easyproject/easy_plugins/easy_extensions/assets/stylesheets/scss/mixins/css3.scss */ +.easy-gantt__menu { + display: -webkit-box; + display: -moz-box; + display: -ms-flexbox; + display: -webkit-flex; + display: flex; +} + +/* line 524, plugins/easyproject/easy_plugins/easy_extensions/assets/../../../../easyproject/easy_plugins/easy_extensions/assets/stylesheets/scss/mixins/css3.scss */ +.easy-gantt__menu { + -webkit-justify-content: space-between; + -ms-justify-content: space-between; + justify-content: space-between; +} + +/* line 551, plugins/easyproject/easy_plugins/easy_extensions/assets/../../../../easyproject/easy_plugins/easy_extensions/assets/stylesheets/scss/mixins/css3.scss */ +.easy-gantt__menu { + -webkit-flex-wrap: wrap; + -ms-flex-wrap: wrap; + flex-wrap: wrap; +} + +/* line 15, plugins/easyproject/easy_plugins/easy_extensions/assets/../../../../easyproject/easy_plugins/easy_extensions/assets/stylesheets/scss/mixins/typography.scss */ +.easy .easy-gantt__menu-group--tooltiped ul li { + font-family: "Open Sans", sans-serif; + font-size: 13px; + line-height: 1.5; + font-weight: normal; + color: #42321a; +} + +@media only screen and (min-resolution: 100dpi) { + /* line 15, plugins/easyproject/easy_plugins/easy_extensions/assets/../../../../easyproject/easy_plugins/easy_extensions/assets/stylesheets/scss/mixins/typography.scss */ + .easy .easy-gantt__menu-group--tooltiped ul li { + font-size: 14px; + } +} + +@media only screen and (max-width: 960px) { + /* line 15, plugins/easyproject/easy_plugins/easy_extensions/assets/../../../../easyproject/easy_plugins/easy_extensions/assets/stylesheets/scss/mixins/typography.scss */ + .easy .easy-gantt__menu-group--tooltiped ul li { + font-size: 11px; + } +} + +@media only screen and (max-width: 960px) and (min-resolution: 100dpi) { + /* line 15, plugins/easyproject/easy_plugins/easy_extensions/assets/../../../../easyproject/easy_plugins/easy_extensions/assets/stylesheets/scss/mixins/typography.scss */ + .easy .easy-gantt__menu-group--tooltiped ul li { + font-size: 13px; + } +} + +@media only screen and (min-width: 1401px) { + /* line 15, plugins/easyproject/easy_plugins/easy_extensions/assets/../../../../easyproject/easy_plugins/easy_extensions/assets/stylesheets/scss/mixins/typography.scss */ + .easy .easy-gantt__menu-group--tooltiped ul li { + font-size: 14px; + } +} + +@media only screen and (min-width: 1401px) and (min-resolution: 100dpi) { + /* line 15, plugins/easyproject/easy_plugins/easy_extensions/assets/../../../../easyproject/easy_plugins/easy_extensions/assets/stylesheets/scss/mixins/typography.scss */ + .easy .easy-gantt__menu-group--tooltiped ul li { + font-size: 16px; + } +} + +@media only screen and (min-width: 1901px) { + /* line 15, plugins/easyproject/easy_plugins/easy_extensions/assets/../../../../easyproject/easy_plugins/easy_extensions/assets/stylesheets/scss/mixins/typography.scss */ + .easy .easy-gantt__menu-group--tooltiped ul li { + font-size: 15px; + } +} + +@media only screen and (min-width: 1901px) and (min-resolution: 100dpi) { + /* line 15, plugins/easyproject/easy_plugins/easy_extensions/assets/../../../../easyproject/easy_plugins/easy_extensions/assets/stylesheets/scss/mixins/typography.scss */ + .easy .easy-gantt__menu-group--tooltiped ul li { + font-size: 17px; + } +} + +/* line 98, plugins/easyproject/easy_plugins/easy_extensions/assets/../../../../easyproject/easy_plugins/easy_extensions/assets/stylesheets/scss/mixins/typography.scss */ +.easy .easy-gantt__menu-group--tooltiped ul li a { + -ms-word-break: break-all; + word-break: break-word; + -webkit-hyphens: auto; + -moz-hyphens: auto; + -ms-hyphens: auto; + hyphens: auto; +} + +/* line 1, plugins/easyproject/easy_plugins/easy_extensions/assets/../../../../easyproject/easy_plugins/easy_extensions/assets/stylesheets/scss/mixins/icons.scss */ +.easy .easy-gantt__menu-group--tooltiped ul li a.submenu:after, .easy-gantt__icon:before { + speak: none; + font-weight: normal; + font-style: normal; + text-decoration: none; + -webkit-font-smoothing: antialiased; + display: inline-block; + width: auto; + height: auto; + background-position: 0% 0%; + background-repeat: repeat; + background-image: none; + vertical-align: baseline; +} + +/* line 21, plugins/easyproject/easy_plugins/easy_extensions/assets/../../../../easyproject/easy_plugins/easy_extensions/assets/stylesheets/scss/mixins/icons.scss */ +.easy .easy-gantt__menu-group--tooltiped ul li a.submenu:after, .easy-gantt__icon:before { + font-family: "EasyIcons"; +} + +/* line 24, plugins/easyproject/easy_plugins/easy_extensions/assets/../../../../easyproject/easy_plugins/easy_extensions/assets/stylesheets/scss/mixins/icons.scss */ +.easy-gantt__icon { + position: relative; + background-repeat: no-repeat; + background-image: none !important; +} + +/* line 2, plugins/easyproject/easy_plugins/easy_extensions/assets/../../../../easyproject/easy_plugins/easy_extensions/assets/stylesheets/scss/mixins/pseudo.scss */ +.easy .easy-gantt__menu-group--tooltiped ul li > .menu-children:after, .easy .easy-gantt__menu-group--tooltiped ul li > .menu-children:before { + content: ""; + display: block; + z-index: 1; +} + +/* line 7, plugins/easyproject/easy_plugins/easy_extensions/assets/../../../../easyproject/easy_plugins/easy_extensions/assets/stylesheets/scss/mixins/pseudo.scss */ +.easy .easy-gantt__menu-group--tooltiped ul li > .menu-children { + position: relative; +} + +/* line 12, plugins/easyproject/easy_plugins/easy_extensions/assets/../../../../easyproject/easy_plugins/easy_extensions/assets/stylesheets/scss/mixins/pseudo.scss */ +.easy .easy-gantt__menu-group--tooltiped ul li > .menu-children:after { + position: absolute; +} + +/* line 19, plugins/easyproject/easy_plugins/easy_extensions/assets/../../../../easyproject/easy_plugins/easy_extensions/assets/stylesheets/scss/mixins/pseudo.scss */ +.easy .easy-gantt__menu-group--tooltiped ul li > .menu-children:before { + position: absolute; +} + +/* line 16, plugins/easyproject/easy_plugins/easy_extensions/assets/../../../../easyproject/easy_plugins/easy_extensions/assets/stylesheets/scss/mixins/buttons.scss */ +.splitcontent .contextual div#easy_gantt.easy .button.active, div#easy_gantt.easy .splitcontent .contextual .button.active { + padding: 2.5px 5px !important; + font-size: 0.89em; +} + +/* line 19, plugins/easyproject/easy_plugins/easy_extensions/assets/../../../../easyproject/easy_plugins/easy_extensions/assets/stylesheets/scss/mixins/buttons.scss */ +.splitcontent .contextual div#easy_gantt.easy .icon.button.active, div#easy_gantt.easy .splitcontent .contextual .icon.button.active { + padding-left: 30px !important; +} + +/* line 21, plugins/easyproject/easy_plugins/easy_extensions/assets/../../../../easyproject/easy_plugins/easy_extensions/assets/stylesheets/scss/mixins/buttons.scss */ +.splitcontent .contextual div#easy_gantt.easy .icon.button.active:before, div#easy_gantt.easy .splitcontent .contextual .icon.button.active:before { + width: 30px !important; + font-size: 1.1em; +} + +/* line 26, plugins/easyproject/easy_plugins/easy_extensions/assets/../../../../easyproject/easy_plugins/easy_extensions/assets/stylesheets/scss/mixins/buttons.scss */ +#sidebar .splitcontent .contextual div#easy_gantt.easy .button.active, .splitcontent .contextual div#easy_gantt.easy #sidebar .button.active, #sidebar div#easy_gantt.easy .splitcontent .contextual .button.active, div#easy_gantt.easy .splitcontent .contextual #sidebar .button.active, #easy_grid_sidebar .splitcontent .contextual div#easy_gantt.easy .button.active, .splitcontent .contextual div#easy_gantt.easy #easy_grid_sidebar .button.active, #easy_grid_sidebar div#easy_gantt.easy .splitcontent .contextual .button.active, div#easy_gantt.easy .splitcontent .contextual #easy_grid_sidebar .button.active { + display: inline-block !important; +} + +/* line 46, plugins/easyproject/easy_plugins/easy_extensions/assets/../../../../easyproject/easy_plugins/easy_extensions/assets/stylesheets/scss/mixins/buttons.scss */ +div#easy_gantt.easy .icon.button.active:before { + position: absolute; + left: 0; + width: 30px; + text-align: center; + font-size: 1.2em; + line-height: 1; + color: inherit; + top: 50%; + margin-top: -0.5em; +} + +/* line 58, plugins/easyproject/easy_plugins/easy_extensions/assets/../../../../easyproject/easy_plugins/easy_extensions/assets/stylesheets/scss/mixins/buttons.scss */ +#sidebar div#easy_gantt.easy .icon.button.active, div#easy_gantt.easy #sidebar .icon.button.active, #easy_grid_sidebar div#easy_gantt.easy .icon.button.active, div#easy_gantt.easy #easy_grid_sidebar .icon.button.active { + padding-left: 40px !important; +} + +/* line 60, plugins/easyproject/easy_plugins/easy_extensions/assets/../../../../easyproject/easy_plugins/easy_extensions/assets/stylesheets/scss/mixins/buttons.scss */ +#sidebar div#easy_gantt.easy .icon.button.active:before, div#easy_gantt.easy #sidebar .icon.button.active:before, #easy_grid_sidebar div#easy_gantt.easy .icon.button.active:before, div#easy_gantt.easy #easy_grid_sidebar .icon.button.active:before { + width: 40px !important; +} + +/* line 64, plugins/easyproject/easy_plugins/easy_extensions/assets/../../../../easyproject/easy_plugins/easy_extensions/assets/stylesheets/scss/mixins/buttons.scss */ +div#easy_gantt.easy .button.active { + font-size: 0.89em; + font-weight: bold; + display: inline-block; + border: 1px solid transparent; + padding: 5px 10px; + text-align: left; + cursor: pointer; + position: relative; + vertical-align: middle; +} + +/* line 75, plugins/easyproject/easy_plugins/easy_extensions/assets/../../../../easyproject/easy_plugins/easy_extensions/assets/stylesheets/scss/mixins/buttons.scss */ +div#easy_gantt.easy .icon.button.active { + padding-left: 30px; +} + +/* line 82, plugins/easyproject/easy_plugins/easy_extensions/assets/../../../../easyproject/easy_plugins/easy_extensions/assets/stylesheets/scss/mixins/buttons.scss */ +#sidebar div#easy_gantt.easy .button.active, div#easy_gantt.easy #sidebar .button.active, #easy_grid_sidebar div#easy_gantt.easy .button.active, div#easy_gantt.easy #easy_grid_sidebar .button.active { + display: block; + position: relative !important; + z-index: 2; + padding-left: 20px; + margin-bottom: 3px; +} + +@media only screen and (min-width: 641px) { + /* line 93, plugins/easyproject/easy_plugins/easy_extensions/assets/../../../../easyproject/easy_plugins/easy_extensions/assets/stylesheets/scss/mixins/buttons.scss */ + .nosidebar #sidebar div#easy_gantt.easy .button.active:hover, div#easy_gantt.easy .nosidebar #sidebar .button.active:hover, .nosidebar #easy_grid_sidebar div#easy_gantt.easy .button.active:hover, div#easy_gantt.easy .nosidebar #easy_grid_sidebar .button.active:hover { + -webkit-animation: sidebar-buttons-slide-left 0.25s forwards; + -moz-animation: sidebar-buttons-slide-left 0.25s forwards; + -ms-animation: sidebar-buttons-slide-left 0.25s forwards; + -o-animation: sidebar-buttons-slide-left 0.25s forwards; + animation: sidebar-buttons-slide-left 0.25s forwards; + } +} + +/* line 99, plugins/easyproject/easy_plugins/easy_extensions/assets/../../../../easyproject/easy_plugins/easy_extensions/assets/stylesheets/scss/mixins/buttons.scss */ +.form-actions div#easy_gantt.easy .button.active, div#easy_gantt.easy .form-actions .button.active { + margin: 3px 0; +} + +@-webkit-keyframes sidebar-buttons-slide-left { + from { + -webkit-transform: translate(0, 0); + -moz-transform: translate(0, 0); + -ms-transform: translate(0, 0); + -o-transform: translate(0, 0); + transform: translate(0, 0); + } + to { + -webkit-transform: translate(-200px, 0); + -moz-transform: translate(-200px, 0); + -ms-transform: translate(-200px, 0); + -o-transform: translate(-200px, 0); + transform: translate(-200px, 0); + } +} + +@-moz-keyframes sidebar-buttons-slide-left { + from { + -webkit-transform: translate(0, 0); + -moz-transform: translate(0, 0); + -ms-transform: translate(0, 0); + -o-transform: translate(0, 0); + transform: translate(0, 0); + } + to { + -webkit-transform: translate(-200px, 0); + -moz-transform: translate(-200px, 0); + -ms-transform: translate(-200px, 0); + -o-transform: translate(-200px, 0); + transform: translate(-200px, 0); + } +} + +@-ms-keyframes sidebar-buttons-slide-left { + /* line 110, plugins/easyproject/easy_plugins/easy_extensions/assets/../../../../easyproject/easy_plugins/easy_extensions/assets/stylesheets/scss/mixins/buttons.scss */ + from { + -webkit-transform: translate(0, 0); + -moz-transform: translate(0, 0); + -ms-transform: translate(0, 0); + -o-transform: translate(0, 0); + transform: translate(0, 0); + } + /* line 113, plugins/easyproject/easy_plugins/easy_extensions/assets/../../../../easyproject/easy_plugins/easy_extensions/assets/stylesheets/scss/mixins/buttons.scss */ + to { + -webkit-transform: translate(-200px, 0); + -moz-transform: translate(-200px, 0); + -ms-transform: translate(-200px, 0); + -o-transform: translate(-200px, 0); + transform: translate(-200px, 0); + } +} + +@keyframes sidebar-buttons-slide-left { + from { + -webkit-transform: translate(0, 0); + -moz-transform: translate(0, 0); + -ms-transform: translate(0, 0); + -o-transform: translate(0, 0); + transform: translate(0, 0); + } + to { + -webkit-transform: translate(-200px, 0); + -moz-transform: translate(-200px, 0); + -ms-transform: translate(-200px, 0); + -o-transform: translate(-200px, 0); + transform: translate(-200px, 0); + } +} + +/* line 152, plugins/easyproject/easy_plugins/easy_extensions/assets/../../../../easyproject/easy_plugins/easy_extensions/assets/stylesheets/scss/mixins/buttons.scss */ +div#easy_gantt.easy .button.active { + line-height: 18px; + min-height: 18px; + background: #007aad; + border-color: #004461; + color: #ffffff; +} + +/* line 126, plugins/easyproject/easy_plugins/easy_extensions/assets/../../../../easyproject/easy_plugins/easy_extensions/assets/stylesheets/scss/mixins/buttons.scss */ +div#easy_gantt.easy .button.active:hover { + color: #ffffff; + text-decoration: none; + background: #006894; +} + +/* line 131, plugins/easyproject/easy_plugins/easy_extensions/assets/../../../../easyproject/easy_plugins/easy_extensions/assets/stylesheets/scss/mixins/buttons.scss */ +div#easy_gantt.easy .selected.button.active { + background: #006894; + color: rgba(255, 255, 255, 0.75); +} + +/* line 135, plugins/easyproject/easy_plugins/easy_extensions/assets/../../../../easyproject/easy_plugins/easy_extensions/assets/stylesheets/scss/mixins/buttons.scss */ +.ui-widget-content div#easy_gantt.easy .button.active, div#easy_gantt.easy .ui-widget-content .button.active { + color: #ffffff; +} + +/* line 183, plugins/easyproject/easy_plugins/easy_extensions/assets/../../../../easyproject/easy_plugins/easy_extensions/assets/stylesheets/scss/mixins/forms.scss */ +.easy .easy-gantt__menu-group--tooltiped ul li a.submenu:after { + position: absolute; + right: 1px; + top: 1px; + bottom: 1px; + font-size: 1em; + line-height: 2.25; + margin: 0; + padding: 0; + display: block; + background: none; + border: none; + min-width: 20px; + text-align: center; +} + +/* line 197, plugins/easyproject/easy_plugins/easy_extensions/assets/../../../../easyproject/easy_plugins/easy_extensions/assets/stylesheets/scss/mixins/forms.scss */ +.easy .easy-gantt__menu-group--tooltiped ul li a.submenu:after { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; +} + +/* line 204, plugins/easyproject/easy_plugins/easy_extensions/assets/../../../../easyproject/easy_plugins/easy_extensions/assets/stylesheets/scss/mixins/forms.scss */ +.easy .easy-gantt__menu-group--tooltiped ul li a.submenu:after span { + display: none; +} + +/* line 124, plugins/easyproject/easy_plugins/easy_extensions/assets/../../../../easyproject/easy_plugins/easy_extensions/assets/stylesheets/scss/mixins/pseudo.scss */ +.easy .easy-gantt__menu-group--tooltiped ul li > .menu-children:before { + left: -5px; + right: auto; + top: 6px; + border-top: 5px solid transparent; + border-bottom: 5px solid transparent; + border-right: 5px solid #dfccaf; + border-left: none; +} + +/* line 130, plugins/easyproject/easy_plugins/easy_extensions/assets/../../../../easyproject/easy_plugins/easy_extensions/assets/stylesheets/scss/mixins/pseudo.scss */ +.easy .easy-gantt__menu-group--tooltiped ul li > .menu-children:after { + left: -4px; + right: auto; + top: 6px; + border-top: 5px solid transparent; + border-bottom: 5px solid transparent; + border-right: 5px solid #ffffff; + border-left: none; +} + +/* line 32, plugins/easyproject/easy_plugins/easy_extensions/assets/../../../../easyproject/easy_plugins/easy_extensions/assets/stylesheets/scss/mixins/tooltips.scss */ +.easy .easy-gantt__menu-group--tooltiped ul li > .menu-children, .easy .easy-gantt__menu-group--tooltiped ul { + position: absolute; + background-color: #ffffff; + border: 1px solid #dfccaf; + color: #42321a; + padding: 0.5em; + font-size: 11.57px; + line-height: 1; + margin-top: -0.25em; + white-space: pre; +} + +/* line 42, plugins/easyproject/easy_plugins/easy_extensions/assets/../../../../easyproject/easy_plugins/easy_extensions/assets/stylesheets/scss/mixins/tooltips.scss */ +.easy .easy-gantt__menu-group--tooltiped ul a { + color: #42321a; + text-decoration: underline; +} + +/* line 46, plugins/easyproject/easy_plugins/easy_extensions/assets/../../../../easyproject/easy_plugins/easy_extensions/assets/stylesheets/scss/mixins/tooltips.scss */ +.input-append .easy .easy-gantt__menu-group--tooltiped ul li > .menu-children, .easy .easy-gantt__menu-group--tooltiped ul .input-append li > .menu-children, .input-append .easy .easy-gantt__menu-group--tooltiped ul, .easy .easy-gantt__menu-group--tooltiped .input-append ul { + font-weight: normal; + margin-top: 1px; +} + +/* line 1, plugins/easyproject/easy_plugins/easy_extensions/assets/../../../../easyproject/easy_plugins/easy_extensions/assets/stylesheets/scss/mixins/menus.scss */ +.easy .easy-gantt__menu-group--tooltiped ul li { + padding: 0; + padding-left: 30px; + border: 1px solid transparent; + border-left: none; + border-right: none; + position: relative; + line-height: 1.25; +} + +/* line 10, plugins/easyproject/easy_plugins/easy_extensions/assets/../../../../easyproject/easy_plugins/easy_extensions/assets/stylesheets/scss/mixins/menus.scss */ +.easy .easy-gantt__menu-group--tooltiped ul li:hover { + border-color: #fcf1f0 !important; + background: #fcf1f0 !important; + z-index: 1; +} + +/* line 15, plugins/easyproject/easy_plugins/easy_extensions/assets/../../../../easyproject/easy_plugins/easy_extensions/assets/stylesheets/scss/mixins/menus.scss */ +.easy .easy-gantt__menu-group--tooltiped ul li a { + padding: 5px 10px; + padding-left: 40px; + margin-left: -30px; + display: block; + text-decoration: none; +} + +/* line 22, plugins/easyproject/easy_plugins/easy_extensions/assets/../../../../easyproject/easy_plugins/easy_extensions/assets/stylesheets/scss/mixins/menus.scss */ +.easy .easy-gantt__menu-group--tooltiped ul li a.active { + color: #e50026; + background: none; + border: none; +} + +/* line 26, plugins/easyproject/easy_plugins/easy_extensions/assets/../../../../easyproject/easy_plugins/easy_extensions/assets/stylesheets/scss/mixins/menus.scss */ +.easy .easy-gantt__menu-group--tooltiped ul li a.active:before { + color: #e50026; +} + +/* line 30, plugins/easyproject/easy_plugins/easy_extensions/assets/../../../../easyproject/easy_plugins/easy_extensions/assets/stylesheets/scss/mixins/menus.scss */ +.easy .easy-gantt__menu-group--tooltiped ul li a:before { + position: absolute; + left: 0; + width: 30px; + text-align: center; + padding-top: 1px; + color: #42321a; +} + +/* line 38, plugins/easyproject/easy_plugins/easy_extensions/assets/../../../../easyproject/easy_plugins/easy_extensions/assets/stylesheets/scss/mixins/menus.scss */ +.easy .easy-gantt__menu-group--tooltiped ul li a.submenu { + padding-right: 0 !important; + background: none !important; + position: relative; +} + +/* line 42, plugins/easyproject/easy_plugins/easy_extensions/assets/../../../../easyproject/easy_plugins/easy_extensions/assets/stylesheets/scss/mixins/menus.scss */ +.easy .easy-gantt__menu-group--tooltiped ul li a.submenu:after { + content: "\005d" !important; + left: auto; + font-size: 10px; + text-align: left; + line-height: 1.9; +} + +/* line 51, plugins/easyproject/easy_plugins/easy_extensions/assets/../../../../easyproject/easy_plugins/easy_extensions/assets/stylesheets/scss/mixins/menus.scss */ +.easy .easy-gantt__menu-group--tooltiped ul li a ~ span { + font-size: 0.89em; +} + +/* line 56, plugins/easyproject/easy_plugins/easy_extensions/assets/../../../../easyproject/easy_plugins/easy_extensions/assets/stylesheets/scss/mixins/menus.scss */ +.easy .easy-gantt__menu-group--tooltiped ul { + z-index: 1000; + margin: 0; + list-style: none; + font-size: 1.125em; + min-width: 200px; + padding: 5px 0; + white-space: normal !important; +} + +/* line 66, plugins/easyproject/easy_plugins/easy_extensions/assets/../../../../easyproject/easy_plugins/easy_extensions/assets/stylesheets/scss/mixins/menus.scss */ +.easy .easy-gantt__menu-group--tooltiped ul:after { + content: ''; + position: absolute; + top: 0; + bottom: 0; + right: auto; + left: 30px; + border-left: 1px solid #eee3d4; + z-index: 0; +} + +/* line 78, plugins/easyproject/easy_plugins/easy_extensions/assets/../../../../easyproject/easy_plugins/easy_extensions/assets/stylesheets/scss/mixins/menus.scss */ +.easy .easy-gantt__menu-group--tooltiped ul li > .menu-children { + display: none; + white-space: normal; + top: 6px; + left: 99%; + width: 150px; +} + +/* line 86, plugins/easyproject/easy_plugins/easy_extensions/assets/../../../../easyproject/easy_plugins/easy_extensions/assets/stylesheets/scss/mixins/menus.scss */ +.easy .easy-gantt__menu-group--tooltiped ul li > .menu-children > li + li { + border-top: 1px dashed #eee3d4; +} + +/* line 89, plugins/easyproject/easy_plugins/easy_extensions/assets/../../../../easyproject/easy_plugins/easy_extensions/assets/stylesheets/scss/mixins/menus.scss */ +.easy .easy-gantt__menu-group--tooltiped ul li > .menu-children > li a { + text-decoration: none; + display: block; + padding: 0.5em; +} + +/* line 93, plugins/easyproject/easy_plugins/easy_extensions/assets/../../../../easyproject/easy_plugins/easy_extensions/assets/stylesheets/scss/mixins/menus.scss */ +.easy .easy-gantt__menu-group--tooltiped ul li > .menu-children > li a:hover { + text-decoration: underline; +} + +/* line 96, plugins/easyproject/easy_plugins/easy_extensions/assets/../../../../easyproject/easy_plugins/easy_extensions/assets/stylesheets/scss/mixins/menus.scss */ +.easy .easy-gantt__menu-group--tooltiped ul li > .menu-children > li a:before { + color: #4ebf67; + text-decoration: none; + margin-right: 5px; +} + +/* line 105, plugins/easyproject/easy_plugins/easy_extensions/assets/../../../../easyproject/easy_plugins/easy_extensions/assets/stylesheets/scss/mixins/menus.scss */ +.easy .easy-gantt__menu-group--tooltiped ul li:hover > .menu-children { + display: inline-block; +} + +/* line 111, plugins/easyproject/easy_plugins/easy_extensions/assets/../../../../easyproject/easy_plugins/easy_extensions/assets/stylesheets/scss/mixins/menus.scss */ +.easy .easy-gantt__menu-group--tooltiped { + position: relative; +} + +/* line 17, plugins/easyproject/easy_plugins/easy_extensions/assets/../../../../easyproject/easy_plugins/easy_extensions/assets/stylesheets/scss/mixins/material/material__elevation.scss */ +.gantt_task_scale, .gantt_grid_scale { + -webkit-box-shadow: material__elevation--shadow_bottom(1), material__elevation--shadow_top(1); + -moz-box-shadow: material__elevation--shadow_bottom(1), material__elevation--shadow_top(1); + box-shadow: material__elevation--shadow_bottom(1), material__elevation--shadow_top(1); +} + +/* line 20, plugins/easyproject/easy_plugins/easy_extensions/assets/../../../../easyproject/easy_plugins/easy_extensions/assets/stylesheets/scss/mixins/material/material__elevation.scss */ +.easy-gantt__menu .gantt-menu-problems-list { + -webkit-box-shadow: material__elevation--shadow_bottom(2), material__elevation--shadow_top(2); + -moz-box-shadow: material__elevation--shadow_bottom(2), material__elevation--shadow_top(2); + box-shadow: material__elevation--shadow_bottom(2), material__elevation--shadow_top(2); +} + +/* line 26, plugins/easyproject/easy_plugins/easy_extensions/assets/../../../../easyproject/easy_plugins/easy_extensions/assets/stylesheets/scss/mixins/material/material__elevation.scss */ +.easy .easy-gantt__menu-group--tooltiped ul { + -webkit-box-shadow: material__elevation--shadow_bottom(4), material__elevation--shadow_top(4); + -moz-box-shadow: material__elevation--shadow_bottom(4), material__elevation--shadow_top(4); + box-shadow: material__elevation--shadow_bottom(4), material__elevation--shadow_top(4); +} + +/* line 10, plugins/easy_gantt/assets/stylesheets/easy_gantt/sass/_gantt_icons.scss */ +.easy-gantt__icon:before { + font-family: "Material Icons", sans-serif; +} + +/* line 14, plugins/easy_gantt/assets/stylesheets/easy_gantt/sass/_gantt_icons.scss */ +.easy-gantt__icon--open:before { + content: "\e147"; +} + +/* line 14, plugins/easy_gantt/assets/stylesheets/easy_gantt/sass/_gantt_icons.scss */ +.easy-gantt__icon--close:before { + content: "\e15c"; +} + +/* line 25, plugins/easy_gantt/assets/stylesheets/easy_gantt/sass/_gantt_icons.scss */ +.gantt_tree_icon { + margin-right: 7px; +} + +/* line 29, plugins/easy_gantt/assets/stylesheets/easy_gantt/sass/_gantt_icons.scss */ +.gantt_tree_icon.gantt_folder_open { + background-image: none; +} + +/* line 31, plugins/easy_gantt/assets/stylesheets/easy_gantt/sass/_gantt_icons.scss */ +.gantt_tree_icon.gantt_folder_open:before { + content: url(data:image/gif;base64,R0lGODlhEgASAJECAJeXl7Gvrf///wAAACH/C1hNUCBEYXRhWE1QPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS4wLWMwNjAgNjEuMTM0Nzc3LCAyMDEwLzAyLzEyLTE3OjMyOjAwICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOnhtcE1NPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvbW0vIiB4bWxuczpzdFJlZj0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL3NUeXBlL1Jlc291cmNlUmVmIyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ1M1IFdpbmRvd3MiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6NTdDRDM3QzVDMDZEMTFFMUJGMzhFMDhCN0RGRjBGQ0YiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6NTdDRDM3QzZDMDZEMTFFMUJGMzhFMDhCN0RGRjBGQ0YiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo1N0NEMzdDM0MwNkQxMUUxQkYzOEUwOEI3REZGMEZDRiIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo1N0NEMzdDNEMwNkQxMUUxQkYzOEUwOEI3REZGMEZDRiIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PgH//v38+/r5+Pf29fTz8vHw7+7t7Ovq6ejn5uXk4+Lh4N/e3dzb2tnY19bV1NPS0dDPzs3My8rJyMfGxcTDwsHAv769vLu6ubi3trW0s7KxsK+urayrqqmop6alpKOioaCfnp2cm5qZmJeWlZSTkpGQj46NjIuKiYiHhoWEg4KBgH9+fXx7enl4d3Z1dHNycXBvbm1sa2ppaGdmZWRjYmFgX15dXFtaWVhXVlVUU1JRUE9OTUxLSklIR0ZFRENCQUA/Pj08Ozo5ODc2NTQzMjEwLy4tLCsqKSgnJiUkIyIhIB8eHRwbGhkYFxYVFBMSERAPDg0MCwoJCAcGBQQDAgEAACH5BAEAAAIALAAAAAASABIAAAIzlI+pywcPm3mhWgkCsjBOvVkimElG9ZlCBlXd+2XjjLKg5GqoeZXqvsOQXK/ijUZTKVUFADs=); +} + +/* line 29, plugins/easy_gantt/assets/stylesheets/easy_gantt/sass/_gantt_icons.scss */ +.gantt_tree_icon.gantt_folder_close { + background-image: none; +} + +/* line 31, plugins/easy_gantt/assets/stylesheets/easy_gantt/sass/_gantt_icons.scss */ +.gantt_tree_icon.gantt_folder_close:before { + content: url(data:image/gif;base64,R0lGODlhEgASAJECAJeXl7Gvrf///wAAACH/C1hNUCBEYXRhWE1QPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS4wLWMwNjAgNjEuMTM0Nzc3LCAyMDEwLzAyLzEyLTE3OjMyOjAwICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOnhtcE1NPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvbW0vIiB4bWxuczpzdFJlZj0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL3NUeXBlL1Jlc291cmNlUmVmIyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ1M1IFdpbmRvd3MiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6NTAyMTU1RTNDMDZEMTFFMUJGNzZCRThBRkFCRjg4MTIiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6NTAyMTU1RTRDMDZEMTFFMUJGNzZCRThBRkFCRjg4MTIiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo1MDIxNTVFMUMwNkQxMUUxQkY3NkJFOEFGQUJGODgxMiIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo1MDIxNTVFMkMwNkQxMUUxQkY3NkJFOEFGQUJGODgxMiIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PgH//v38+/r5+Pf29fTz8vHw7+7t7Ovq6ejn5uXk4+Lh4N/e3dzb2tnY19bV1NPS0dDPzs3My8rJyMfGxcTDwsHAv769vLu6ubi3trW0s7KxsK+urayrqqmop6alpKOioaCfnp2cm5qZmJeWlZSTkpGQj46NjIuKiYiHhoWEg4KBgH9+fXx7enl4d3Z1dHNycXBvbm1sa2ppaGdmZWRjYmFgX15dXFtaWVhXVlVUU1JRUE9OTUxLSklIR0ZFRENCQUA/Pj08Ozo5ODc2NTQzMjEwLy4tLCsqKSgnJiUkIyIhIB8eHRwbGhkYFxYVFBMSERAPDg0MCwoJCAcGBQQDAgEAACH5BAEAAAIALAAAAAASABIAAAIwlI+pywcPm3mhWgkCsjBOvVkimElG9ZlCuYIY6TYs+6bmHDO4igfdD3GNhheV0VQAADs=); +} + +/* line 38, plugins/easy_gantt/assets/stylesheets/easy_gantt/sass/_gantt_icons.scss */ +.gantt_tree_icon.gantt_folder_open:before, .gantt_tree_icon.gantt_folder_close:before { + vertical-align: sub; +} + +/* line 43, plugins/easy_gantt/assets/stylesheets/easy_gantt/sass/_gantt_icons.scss */ +.gantt_tree_icon.gantt_open, .gantt_tree_icon.gantt_close { + margin-right: 0; + margin-left: 7px; +} + +/* line 16, plugins/easy_gantt/assets/stylesheets/easy_gantt/sass/_gantt_redmine.scss */ +.redmine .button-important { + color: #fff; +} + +/* line 20, plugins/easy_gantt/assets/stylesheets/easy_gantt/sass/_gantt_redmine.scss */ +.redmine .gantt-menu-button { + display: inline-block; + border-radius: 2px; + border: 1px solid #e4e4e4; + padding: 8px 16px; +} + +/* line 27, plugins/easy_gantt/assets/stylesheets/easy_gantt/sass/_gantt_redmine.scss */ +.redmine .gantt-menu-button.button-positive { + background-color: #4ebf67; + border-color: #338d47; + color: #ffffff; +} + +/* line 33, plugins/easy_gantt/assets/stylesheets/easy_gantt/sass/_gantt_redmine.scss */ +.redmine .gantt-menu-button.active, .redmine #easy_gantt.redmine .gantt_button.active { + background-color: #628DB6; + border-color: #3E5B76; +} + +/* line 37, plugins/easy_gantt/assets/stylesheets/easy_gantt/sass/_gantt_redmine.scss */ +.redmine .problem-finder { + border: none; + padding-right: 0; +} + +/* line 42, plugins/easy_gantt/assets/stylesheets/easy_gantt/sass/_gantt_redmine.scss */ +.redmine .gantt-menu-button.icon, .redmine .gantt_button.icon { + background-position-x: 5px; +} + +/* line 51, plugins/easy_gantt/assets/stylesheets/easy_gantt/sass/_gantt_redmine.scss */ +.redmine .icon-youtube { + padding-left: 28px; +} + +/* line 54, plugins/easy_gantt/assets/stylesheets/easy_gantt/sass/_gantt_redmine.scss */ +.redmine .icon.icon-filter { + background-position-x: 9px; + background-position-y: 7px; +} + +/* line 58, plugins/easy_gantt/assets/stylesheets/easy_gantt/sass/_gantt_redmine.scss */ +.redmine .easy-gantt__menu { + margin: 0 0 1px; + padding: 5px 0 5px 0; + background-color: white; +} + +/* line 63, plugins/easy_gantt/assets/stylesheets/easy_gantt/sass/_gantt_redmine.scss */ +.redmine #gantt_cont { + /* width: auto !important; */ + margin: 0; +} + +/* line 67, plugins/easy_gantt/assets/stylesheets/easy_gantt/sass/_gantt_redmine.scss */ +.redmine .gantt_tree_icon { + width: 20px; +} + +/* line 69, plugins/easy_gantt/assets/stylesheets/easy_gantt/sass/_gantt_redmine.scss */ +.redmine .gantt_tree_icon:before { + vertical-align: sub; +} + +/* line 74, plugins/easy_gantt/assets/stylesheets/easy_gantt/sass/_gantt_redmine.scss */ +.redmine a.active { + color: #fff; +} + +/* line 77, plugins/easy_gantt/assets/stylesheets/easy_gantt/sass/_gantt_redmine.scss */ +.redmine a.disabled { + opacity: 0.25; + color: #484848; + font-weight: lighter; +} + +/* line 82, plugins/easy_gantt/assets/stylesheets/easy_gantt/sass/_gantt_redmine.scss */ +.redmine #link_delay_input { + width: auto; +} + +/* line 85, plugins/easy_gantt/assets/stylesheets/easy_gantt/sass/_gantt_redmine.scss */ +.redmine .push-left { + float: left; +} + +/* line 89, plugins/easy_gantt/assets/stylesheets/easy_gantt/sass/_gantt_redmine.scss */ +.redmine .push-right { + float: right; + text-align: right; +} + +/* line 94, plugins/easy_gantt/assets/stylesheets/easy_gantt/sass/_gantt_redmine.scss */ +.redmine .gantt-footer p { + text-align: center; +} + +/* line 97, plugins/easy_gantt/assets/stylesheets/easy_gantt/sass/_gantt_redmine.scss */ +.redmine #button_close_all_projects { + width: 18px; + height: 25px; + padding: 0; + vertical-align: bottom; + background-position: 0 50%; + background-repeat: no-repeat; +} + +/* line 106, plugins/easy_gantt/assets/stylesheets/easy_gantt/sass/_gantt_redmine.scss */ +.redmine .overdue { + color: #e50026; +} + +/* line 109, plugins/easy_gantt/assets/stylesheets/easy_gantt/sass/_gantt_redmine.scss */ +.redmine .contextual { + text-align: right; +} + +/* line 112, plugins/easy_gantt/assets/stylesheets/easy_gantt/sass/_gantt_redmine.scss */ +.redmine .easy-gantt__menu-group--tooltiped { + position: relative; +} + +/* line 114, plugins/easy_gantt/assets/stylesheets/easy_gantt/sass/_gantt_redmine.scss */ +.redmine .easy-gantt__menu-group--tooltiped ul { + position: absolute; + right: 0; + background-color: white; + border: 1px solid #cccccc; + padding: 5px; + padding-left: 0; + min-width: 180px; + display: none; +} + + + +/* line 132, plugins/easy_gantt/assets/stylesheets/easy_gantt/sass/_gantt_redmine.scss */ +.redmine .easy-gantt__menu-group--tooltiped ul li { + display: block; +} + +/* line 135, plugins/easy_gantt/assets/stylesheets/easy_gantt/sass/_gantt_redmine.scss */ +.redmine .easy-gantt__menu-group--tooltiped ul .gantt-menu-button { + border: none; + padding: 5px 5px 5px 5px; + background-color: transparent; +} + +/* line 141, plugins/easy_gantt/assets/stylesheets/easy_gantt/sass/_gantt_redmine.scss */ +.redmine .easy-gantt__menu-group--tooltiped:hover ul { + display: block; +} + +@media print { + /* line 146, plugins/easy_gantt/assets/stylesheets/easy_gantt/sass/_gantt_redmine.scss */ + .redmine .gantt_task_progress { + border-right: 1px solid #4ebf67; + box-sizing: border-box; + margin-top: 1px; + margin-bottom: 1px; + height: calc(100% - 2px); + } + /* line 153, plugins/easy_gantt/assets/stylesheets/easy_gantt/sass/_gantt_redmine.scss */ + .redmine .wrong .gantt_task_progress { + border-color: #e50026; + } + /* line 156, plugins/easy_gantt/assets/stylesheets/easy_gantt/sass/_gantt_redmine.scss */ + .redmine .gantt_project-type .gantt_task_progress { + border-color: #009ee0; + } + /* line 159, plugins/easy_gantt/assets/stylesheets/easy_gantt/sass/_gantt_redmine.scss */ + .redmine .odd { + background: none; + } +} + +/* line 16, plugins/easy_gantt/assets/stylesheets/easy_gantt/sass/_gantt_print.scss */ +.gantt-print__header-logo { + vertical-align: middle; + display: inline-block; +} + +/* line 20, plugins/easy_gantt/assets/stylesheets/easy_gantt/sass/_gantt_print.scss */ +.gantt-print__header-header { + display: inline-block; + margin: 0; +} + +/* line 24, plugins/easy_gantt/assets/stylesheets/easy_gantt/sass/_gantt_print.scss */ +.gantt-print__header-text { + display: inline-block; + vertical-align: bottom; + margin-left: 20px; +} + +/* line 31, plugins/easy_gantt/assets/stylesheets/easy_gantt/sass/_gantt_print.scss */ +.gantt-print__area { + background-color: white; +} + +/* line 35, plugins/easy_gantt/assets/stylesheets/easy_gantt/sass/_gantt_print.scss */ +.gantt-print__strip { + border: 1px solid #cecece; + overflow: hidden; + white-space: nowrap; + /*page-break-before:always;*/ + /*page-break-inside: avoid;*/ + break-inside: avoid; + margin: 10px 0; + margin-left: -1px; + display: inline-block; + background-color: #ffffff; + border-left: 0; +} + +/* line 40, plugins/easy_gantt/assets/stylesheets/easy_gantt/sass/_gantt_print.scss */ +.gantt-print__bg-canvas { + display: block; + /*page-break-before:avoid;*/ +} + +/* line 45, plugins/easy_gantt/assets/stylesheets/easy_gantt/sass/_gantt_print.scss */ +.gantt-print__bg { + position: absolute; + /*page-break-before:avoid;*/ +} + +/* line 50, plugins/easy_gantt/assets/stylesheets/easy_gantt/sass/_gantt_print.scss */ +.gantt-print__data-area { + /*page-break-before:avoid;*/ +} + +/* line 54, plugins/easy_gantt/assets/stylesheets/easy_gantt/sass/_gantt_print.scss */ +.gantt-print__bars-area { + position: relative; + top: 0; +} + +/* line 59, plugins/easy_gantt/assets/stylesheets/easy_gantt/sass/_gantt_print.scss */ +.gantt-print__scale { + transform: none; + border-top: 0; +} + +/* line 64, plugins/easy_gantt/assets/stylesheets/easy_gantt/sass/_gantt_print.scss */ +.gantt-print__grid { + border: 1px solid #cecece; + overflow: hidden; + white-space: nowrap; + /*page-break-before:always;*/ + /*page-break-inside: avoid;*/ + break-inside: avoid; + margin: 10px 0; + margin-left: -1px; + display: inline-block; + background-color: #ffffff; + border-right: 2px solid #cecece; + margin-right: 1px; +} + +/* line 68, plugins/easy_gantt/assets/stylesheets/easy_gantt/sass/_gantt_print.scss */ +.gantt-print__grid .gantt_grid_scale { + margin-top: -2px; +} + +/* line 71, plugins/easy_gantt/assets/stylesheets/easy_gantt/sass/_gantt_print.scss */ +.gantt-print__grid .gantt-grid-header-collapse-buttons, +.gantt-print__grid .gantt_sort { + display: none; +} + +/* line 77, plugins/easy_gantt/assets/stylesheets/easy_gantt/sass/_gantt_print.scss */ +.gantt-print__template--nowrap { + white-space: nowrap; +} + +/* line 5, plugins/easy_gantt/assets/stylesheets/easy_gantt/sass/_gantt_main.scss */ +div#easy_gantt.easy #easy_gantt_menu { + padding-bottom: 7px; +} + +/* line 8, plugins/easy_gantt/assets/stylesheets/easy_gantt/sass/_gantt_main.scss */ +div#easy_gantt.easy #easy_gantt_menu p { + margin-bottom: 0; +} + +/* line 12, plugins/easy_gantt/assets/stylesheets/easy_gantt/sass/_gantt_main.scss */ +div#easy_gantt.easy #gantt_footer_buttons { + float: left; + margin-top: 10px; +} + +/* line 15, plugins/easy_gantt/assets/stylesheets/easy_gantt/sass/_gantt_main.scss */ +div#easy_gantt.easy #gantt_footer_buttons + p { + float: right; + margin-top: 10px; +} + +/* line 25, plugins/easy_gantt/assets/stylesheets/easy_gantt/sass/_gantt_main.scss */ +#button_jump_today { + padding-right: 0; + padding-left: 0; +} + +/* line 30, plugins/easy_gantt/assets/stylesheets/easy_gantt/sass/_gantt_main.scss */ +.easy-gantt__menu { + user-select: none; + position: relative; + z-index: 1; + background-color: #fdfbf9; + padding: 20px; + margin: 0 -20px; +} + +/* line 42, plugins/easy_gantt/assets/stylesheets/easy_gantt/sass/_gantt_main.scss */ +.easy-gantt__menu:after { + display: none; +} + +/* line 45, plugins/easy_gantt/assets/stylesheets/easy_gantt/sass/_gantt_main.scss */ +.easy-gantt__menu-item { + display: inline-block; + text-align: left; +} + +/* line 48, plugins/easy_gantt/assets/stylesheets/easy_gantt/sass/_gantt_main.scss */ +.easy-gantt__menu-item .active { + color: #e50026 !important; +} + +/* line 50, plugins/easy_gantt/assets/stylesheets/easy_gantt/sass/_gantt_main.scss */ +.easy-gantt__menu-item .active:before { + color: #e50026; +} + +/* line 55, plugins/easy_gantt/assets/stylesheets/easy_gantt/sass/_gantt_main.scss */ +.easy-gantt__menu-group { + display: inline-block; +} + +/* line 57, plugins/easy_gantt/assets/stylesheets/easy_gantt/sass/_gantt_main.scss */ +.easy-gantt__menu-group ul { + margin: 0; +} + +/* line 62, plugins/easy_gantt/assets/stylesheets/easy_gantt/sass/_gantt_main.scss */ +.easy .easy-gantt__menu-group--tooltiped > ul { + display: none; + right: 0; +} + +/* line 67, plugins/easy_gantt/assets/stylesheets/easy_gantt/sass/_gantt_main.scss */ +.easy .easy-gantt__menu-group--tooltiped:hover > ul { + display: block; +} + +/* line 73, plugins/easy_gantt/assets/stylesheets/easy_gantt/sass/_gantt_main.scss */ +.easy .easy-gantt__menu .problem-finder { + font-size: 0.89em; + color: #42321a; +} + +/* line 76, plugins/easy_gantt/assets/stylesheets/easy_gantt/sass/_gantt_main.scss */ +.easy .easy-gantt__menu .problem-finder.active { + background: none; + border: none; + color: #42321a; +} + +/* line 81, plugins/easy_gantt/assets/stylesheets/easy_gantt/sass/_gantt_main.scss */ +.easy .easy-gantt__menu .problem-finder:hover { + text-decoration: none; +} + +/* line 85, plugins/easy_gantt/assets/stylesheets/easy_gantt/sass/_gantt_main.scss */ +.easy-gantt__menu .gantt-menu-problems-list { + border-width: 1px !important; +} + +/* line 89, plugins/easy_gantt/assets/stylesheets/easy_gantt/sass/_gantt_main.scss */ +.easy-gantt__menu .gantt-menu-problems-count.gantt-with-problems { + font-weight: bold; + background: #e50026; + border: 1px solid #cc0022; + text-align: center; + width: 15px; + line-height: 15px; + display: inline-block; + color: white; + padding: 2.5px; + margin-right: 10px; +} + +/* line 106, plugins/easy_gantt/assets/stylesheets/easy_gantt/sass/_gantt_main.scss */ +.gantt-menu-sub-panel { + width: 100%; +} + +/* line 108, plugins/easy_gantt/assets/stylesheets/easy_gantt/sass/_gantt_main.scss */ +.gantt-menu-sub-panel > span { + display: none !important; +} + +/* line 113, plugins/easy_gantt/assets/stylesheets/easy_gantt/sass/_gantt_main.scss */ +#button_problem_finder.active { + color: #e50026 !important; + background: none !important; + border: none !important; +} + +/* line 119, plugins/easy_gantt/assets/stylesheets/easy_gantt/sass/_gantt_main.scss */ +.gantt-menu-problems-reason { + color: #e50026 !important; +} + +/* line 123, plugins/easy_gantt/assets/stylesheets/easy_gantt/sass/_gantt_main.scss */ +.gantt-grid-milestone-bullet { + margin-top: 4px !important; + margin-left: 5px; + background: #42321a !important; + border: none !important; +} + +/* line 128, plugins/easy_gantt/assets/stylesheets/easy_gantt/sass/_gantt_main.scss */ +.gantt-milestone-shared .gantt-grid-milestone-bullet { + background: #d94838 !important; +} + +/* line 133, plugins/easy_gantt/assets/stylesheets/easy_gantt/sass/_gantt_main.scss */ +.gantt_subtask_arrow:before { + content: '\21B3'; + opacity: .75; +} + +/* line 138, plugins/easy_gantt/assets/stylesheets/easy_gantt/sass/_gantt_main.scss */ +.gantt_cell + .gantt_cell, .gantt_side_content { + color: #42321a !important; +} + +/* line 142, plugins/easy_gantt/assets/stylesheets/easy_gantt/sass/_gantt_main.scss */ +.gantt_grid .gantt_grid_head_cell, .gantt_grid_scale, #gantt_grid, .gantt_scale_cel, .gantt_task_scale { + border-color: #eee3d4 !important; +} + +/* line 146, plugins/easy_gantt/assets/stylesheets/easy_gantt/sass/_gantt_main.scss */ +.gantt_grid { + border-right-width: 1px !important; +} + +/* line 154, plugins/easy_gantt/assets/stylesheets/easy_gantt/sass/_gantt_main.scss */ +.gantt_grid_data .gantt_row:hover, .gantt_grid_data .gantt_row.odd:hover { + background: rgba(229, 0, 38, 0.25); +} + +/* line 158, plugins/easy_gantt/assets/stylesheets/easy_gantt/sass/_gantt_main.scss */ +.gantt_grid_data .gantt_row.closed { + background-color: rgba(223, 204, 175, 0.5); + border-color: #dfccaf; +} + +/* line 161, plugins/easy_gantt/assets/stylesheets/easy_gantt/sass/_gantt_main.scss */ +.gantt_grid_data .gantt_row.closed .gantt_tree_content { + text-decoration: line-through; +} + +/* line 168, plugins/easy_gantt/assets/stylesheets/easy_gantt/sass/_gantt_main.scss */ +.gantt_task_scale, .gantt_grid_scale { + z-index: 2 !important; + margin-top: -1px; +} + +/* line 174, plugins/easy_gantt/assets/stylesheets/easy_gantt/sass/_gantt_main.scss */ +#gantt_cont { + width: auto !important; + margin: 0 -21px; + background: #ffffff; +} + +/* line 182, plugins/easy_gantt/assets/stylesheets/easy_gantt/sass/_gantt_main.scss */ +.easy .gantt_tree_indent { + width: 20px; +} + +/* line 185, plugins/easy_gantt/assets/stylesheets/easy_gantt/sass/_gantt_main.scss */ +.easy .gantt_tree_icon { + width: 20px; + text-align: center; +} + +/* line 192, plugins/easy_gantt/assets/stylesheets/easy_gantt/sass/_gantt_main.scss */ +div.gantt_task_progress { + position: relative; + z-index: 1 !important; +} + +/* line 199, plugins/easy_gantt/assets/stylesheets/easy_gantt/sass/_gantt_main.scss */ +div.gantt_task_line.gantt_parent_task-subtype .gantt_task_progress, div.gantt_task_line.gantt_parent_task-subtype .gantt_task_content { + height: 100%; +} + +/* line 203, plugins/easy_gantt/assets/stylesheets/easy_gantt/sass/_gantt_main.scss */ +div.gantt_task_line .gantt_task_content { + border-radius: 2px; + border: 1px solid; + box-sizing: border-box; +} + +/* line 208, plugins/easy_gantt/assets/stylesheets/easy_gantt/sass/_gantt_main.scss */ +div.gantt_task_line .gantt_task_content, +div.gantt_task_line .gantt_task_ticks { + background-color: rgba(78, 191, 103, 0.1); + border-color: #4ebf67; +} + +/* line 213, plugins/easy_gantt/assets/stylesheets/easy_gantt/sass/_gantt_main.scss */ +div.gantt_task_line .gantt_task_progress { + background-color: rgba(78, 191, 103, 0.5); +} + +/* line 217, plugins/easy_gantt/assets/stylesheets/easy_gantt/sass/_gantt_main.scss */ +div.gantt_task_line.wrong .gantt_task_ticks, +div.gantt_task_line.wrong .gantt_task_content { + background-color: rgba(229, 0, 38, 0.1); + border-color: #e50026; +} + +/* line 222, plugins/easy_gantt/assets/stylesheets/easy_gantt/sass/_gantt_main.scss */ +div.gantt_task_line.wrong .gantt_task_progress { + background-color: rgba(229, 0, 38, 0.5); +} + +/* line 227, plugins/easy_gantt/assets/stylesheets/easy_gantt/sass/_gantt_main.scss */ +div.gantt_task_line.closed .gantt_task_ticks, +div.gantt_task_line.closed .gantt_task_content { + background-color: rgba(223, 204, 175, 0.25); + border-color: #dfccaf; +} + +/* line 232, plugins/easy_gantt/assets/stylesheets/easy_gantt/sass/_gantt_main.scss */ +div.gantt_task_line.closed .gantt_task_progress { + background-color: rgba(223, 204, 175, 0.5); +} + +/* line 237, plugins/easy_gantt/assets/stylesheets/easy_gantt/sass/_gantt_main.scss */ +div.gantt_task_line.gantt_project-type .gantt_task_content { + background-color: rgba(0, 158, 224, 0.25); + border-color: #009ee0; +} + +/* line 241, plugins/easy_gantt/assets/stylesheets/easy_gantt/sass/_gantt_main.scss */ +div.gantt_task_line.gantt_project-type div.gantt_task_progress { + background-color: rgba(0, 158, 224, 0.5); +} + +/* line 245, plugins/easy_gantt/assets/stylesheets/easy_gantt/sass/_gantt_main.scss */ +div.gantt_task_line.gantt_project-type.wrong .gantt_task_content { + background-color: rgba(172, 40, 85, 0.25); +} + +/* line 248, plugins/easy_gantt/assets/stylesheets/easy_gantt/sass/_gantt_main.scss */ +div.gantt_task_line.gantt_project-type.wrong div.gantt_task_progress { + background-color: rgba(172, 40, 85, 0.5); +} + +/* line 254, plugins/easy_gantt/assets/stylesheets/easy_gantt/sass/_gantt_main.scss */ +div.gantt_task_line.gantt_milestone-type .gantt_task_content { + -webkit-transform: scale(0.65) rotate(45deg); + -moz-transform: scale(0.65) rotate(45deg); + -ms-transform: scale(0.65) rotate(45deg); + -o-transform: scale(0.65) rotate(45deg); + transform: scale(0.65) rotate(45deg); + background-color: #42321a; + border-color: #42321a; +} + +/* line 260, plugins/easy_gantt/assets/stylesheets/easy_gantt/sass/_gantt_main.scss */ +div.gantt_task_line.gantt_milestone-type.gantt-milestone-shared .gantt_task_content { + -webkit-transform: scale(0.65) rotate(45deg); + -moz-transform: scale(0.65) rotate(45deg); + -ms-transform: scale(0.65) rotate(45deg); + -o-transform: scale(0.65) rotate(45deg); + transform: scale(0.65) rotate(45deg); + background-color: #d94838; + border-color: #d94838; +} + +/* line 268, plugins/easy_gantt/assets/stylesheets/easy_gantt/sass/_gantt_main.scss */ +div.gantt_task_line.critical .gantt_task_ticks, +div.gantt_task_line.critical .gantt_task_content { + background-color: rgba(200, 0, 255, 0.5); + border-color: #c800ff; +} + +/* line 273, plugins/easy_gantt/assets/stylesheets/easy_gantt/sass/_gantt_main.scss */ +div.gantt_task_line.critical .gantt_task_progress { + background-color: #c800ff; +} + +/* line 279, plugins/easy_gantt/assets/stylesheets/easy_gantt/sass/_gantt_main.scss */ +.gantt_hor_scroll { + z-index: 1; +} + +@font-face{ + font-family: "Material Icons"; + src: url("/plugin_assets/easy_gantt/EasyMaterialIcons-Regular.woff2") format("woff2"); + font-weight: 400; + font-style: normal; +} diff --git a/easy_gantt/assets/stylesheets/easy_gantt/modal_editor.css b/easy_gantt/assets/stylesheets/easy_gantt/modal_editor.css new file mode 100644 index 0000000..f063d1a --- /dev/null +++ b/easy_gantt/assets/stylesheets/easy_gantt/modal_editor.css @@ -0,0 +1,63 @@ +/* + a missing css for issue modals +*/ + +#form-modal .tabs {height: 2.6em; margin-bottom:1.2em; position:relative; overflow:hidden;} +#form-modal .tabs ul {margin:0; position:absolute; bottom:0; padding-left:0.5em; min-width: 2000px; width: 100%; border-bottom: 1px solid #bbbbbb;} +#form-modal .tabs ul li { + float:left; + list-style-type:none; + white-space:nowrap; + margin-right:4px; + position:relative; + margin-bottom:-1px; +} +#form-modal .tabs ul li a{ + display:block; + font-size: 0.9em; + text-decoration:none; + line-height:1.3em; + padding:4px 6px 4px 6px; + border: 1px solid #ccc; + border-bottom: 1px solid #bbbbbb; + color:#999; + font-weight:bold; + border-top-left-radius:3px; + border-top-right-radius:3px; +} + +#form-modal .tabs ul li a:hover { + color:#777; + text-decoration:none; +} + +#form-modal .tabs ul li a.selected { + background-color: #fff; + border: 1px solid #bbbbbb; + border-bottom: 1px solid #fff; + color:#444; +} + +#form-modal .tabs ul li a.selected:hover {background-color: #fff;} + +#form-modal div.tabs-buttons { position:absolute; right: 0; width: 54px; height: 24px; background: white; bottom: 0; border-bottom: 1px solid #bbbbbb; } + +#form-modal .jstTabs.tabs { + margin-bottom: -1px; +} +#form-modal .jstTabs.tabs ul {border-bottom:0;} +#form-modal .jstTabs.tabs li { + height: 42px; +} +#form-modal .jstTabs.tabs li:before{ + content: ''; + display: inline-block; + vertical-align: middle; + height: 100%; +} +#form-modal .jstTabs.tabs li a { + display: inline-block; + vertical-align: bottom; + line-height: 19px; +border-bottom: 1px solid transparent; +} \ No newline at end of file diff --git a/easy_gantt/config/locales/ar.yml b/easy_gantt/config/locales/ar.yml new file mode 100644 index 0000000..b93c21a --- /dev/null +++ b/easy_gantt/config/locales/ar.yml @@ -0,0 +1,2 @@ +--- +ar: diff --git a/easy_gantt/config/locales/cs.yml b/easy_gantt/config/locales/cs.yml new file mode 100644 index 0000000..2acd316 --- /dev/null +++ b/easy_gantt/config/locales/cs.yml @@ -0,0 +1,183 @@ +--- +cs: + button_print: Tisk + button_project_menu_easy_gantt: Easy Gantt + button_top_menu_easy_gantt: Globální Gantt + button_use_actual_delay: Použít skutečné zpoždění + easy_gantt_toolbar: + day: Dny + month: Měsíce + week: Týdny + easy_gantt: + button: + close_all: Zavřít vše + close_all_parent_issues: Zavřít všechny rodičovské úkoly + create_baseline: Baseline + critical_path: Kritická cesta + day_zoom: Dny + delayed_project_filter: Filtrovat zpožděné projekty + jump_today: Přejít na dnešek + load_sample_data: Nahrát zkušební data + month_zoom: Měsíce + moth_zoom: Měsíce + print_fit: Přizpůsobit stránce + problem_finder: Problémy + reload: Znovu načíst (uložit) + remove_delay: Odstranit zpoždění + resource_management: Vytížení zdrojů + tool_panel: Nástroje + week_zoom: Týdny + critical_path: + disabled: Vypnuto + last: Poslední + last_text: Úkoly, které by se neměly odkládat + longest: Nejdelší + longest_text: Zobrazit nejdelší sled úkolů + errors: + duplicate_link: Není možné vytvořit zdvojenou vazbu + fresh_milestone: Není možné přidat úkol na neuložený milník. Nejprve uložte + mílník. + link_target_new: Nelze vytvořit vazbu na neuložený úkol. Nejprve uložte změny. + link_target_readonly: Cílový úkol pouze pro čtení + loop_link: Není možné vytvořit cyklickou vazbu + no_rest_api: Easy Gantt potřebuje mít zapnuté REST API. Zapněte ho v Administraci + -> Nastavení -> API -> Zapnout službu REST + overdue: má skončit v budoucnu, nebo být již uzavřen + progress_date_overdue: se opožduje za plánem o %{days} dní + overmile: musí končit %{effective_date}, aby dodržel milník + short_delay: mělo by být delší o %{diff} dnů + too_short: není dost dlouhý pro zbývajících %{rest} hodin odhadovaného času + unsaved_parent: Nelze přidat úkol k neuloženému rodiči. Nejprve uložte změny. + unsupported_link_type: Nepodporovaný typ vazby + gateway: + entity_save_failed: "%{entityType} %{entityName} se nepodařilo uložit kvůli + chybě" + send_failed: 'Následující požadavky se nepodařilo odeslat:' + label_pro_upgrade: Upgradovat na PRO verzi + link_dir: + link_end: jako předchůdce + link_start: jako následovník + popup: + add_task: + heading: Přidat úkol + text: "Tato funkce je dostupná pouze v Easy Gantt PRO \r\nUpgradovat + na PRO verzi" + baseline: + heading: Baseline + text: "Tato funkce je dostupná pouze v Easy Gantt PRO \r\nUpgradovat + na PRO verzi" + critical: + heading: Kritická cesta + text: "Tato funkce je dostupná pouze v Easy Gantt PRO \r\nUpgradovat + na PRO verzi" + resource: + heading: Vytížení zdrojů + text: Vytížení zdrojů je dostupné v + Easy Project + reload_modal: + label_errors: Chyby + text_reload_appeal: Chcete ignorovat neuložené položky a znovu načíst data ze + serveru? + title: Gantt se nepodařilo správně uložit + sample_global_free: + text: Zkušební data nelze uzavřít. Gantt nad všemi projekty je k dispozici pouze + ve verzi PRO. + video: + text: Zde si můžete prohlédnout většinu funkcí Ganttu nad všemi projekty + title: Ukázkové video Ganttu nad všemi projekty + video_id: EiiqBrrY4m4 + sample_video: + text: Zde můžete vidět většinu funkcí modulu Easy Gantt + title: Ukázkové video + sample: + active_label: Jsou načtena testovací data! + close_label: Zavřít toto okno a načíst skutečná data + header: Jsou načtena zkušební data! + text: Připravili jsme zkušební data, abyste vyzkoušeli všechny funkce + Easy Ganttu bez obav. Koukněte se také na video průvodce ukazujícího + užitečné triky. Zavřením tohoto okna načtete skutečná data. + video_id: UHgqfsrD59Q + video_label: Spustit video návod + video: + text: Zde můžete vidět většinu funkcí modulu Easy Gantt + title: Spustit video návod + video_id: UHgqfsrD59Q + text_blocker_milestone: Milník blokuje posunutí úkolu za toto datum + text_blocker_move_pre: Úkol obsahuje vazbu, která blokuje posunutí úkolu za toto + datum + title: + button_test: Testovací tlačítko (pouze pro testování) + day_zoom: Dny + jump_today: Přejít na dnešek + month_zoom: Měsíce + week_zoom: Týdny + easy_query: + name: + easy_gantt_easy_issue_query: Easy Gantt + easy_gantt_easy_project_query: Globální Easy Gantt + error_easy_gantt_view_permission: Nemáte právo vidět Easy Gantt + error_epm_easy_gantt_already_active: Easy gantt je na této stránce již aktivní + errors: + duplicate_link: Cannot create duplicate link + fresh_milestone: Cannot add task to non-saved milestone. Save the milestone first. + loop_link: Cannot create looped link + overmile: Task should end before its milestone + too_short: Task is too short for its estimated hours count + field_easy_gantt_default_zoom: Výchozí přiblížení + field_easy_gantt_show_holidays: Zobrazit svátky + field_easy_gantt_show_project_progress: Zobrazit pokrok na projektu + field_easy_gantt_show_task_soonest_start: Zobrazit nejdřívější začátek + field_keep_link_delay_in_drag: Zachovat zpoždění vazby během přesunu + field_relation: Vazba + gantt_grid_head_subject: Předmět + heading_delay_popup: Nastavte zpoždění úkolu ve dnech + heading_demo_feature_popup: Již brzy... + heading_easy_gantts_issues: Easy Gantt Free + label_easy_gantt: Easy Gantt + label_easy_gantt_critical_path: Kritická cesta + label_easy_gantt_settings: Nastavení Ganttu + label_filter_group_easy_gantt_easy_issue_query: Úkolová pole + label_finish_to_finish: Konec na konec + label_parent_issue_plural: Rodičovské úkoly + label_pro_upgrade: Upgrade to PRO version + label_start_to_finish: Začátek na konec + label_start_to_start: Začátek na začátek + link_easy_gantt_plugin: https://www.easyredmine.com/easy-gantt-upgrade-to-pro + permission_edit_easy_gantt: Upravit Easy Gantt & Vytížení zdrojů na projektu + permission_edit_global_easy_gantt: Upravit globální Easy Gantt & Vytížení zdrojů + permission_edit_personal_easy_gantt: Upravit osobní Easy Gantt & Vytížení zdrojů + permission_view_easy_gantt: Zobrazit Easy Gantt & Vytížení zdrojů na projektu + permission_view_global_easy_gantt: Zobrazit globální Easy Gantt & Vytížení zdrojů + permission_view_personal_easy_gantt: Zobrazit osobní Easy Gantt & Vytížení zdrojů + project_default_page: + easy_gantt: Easy Gantt + project_module_easy_gantt: Easy Gantt + sample_global_free: + text: Sample data cannot be closed. Gantt over all projects is available only + in PRO version + video: + text: Here you can see most of features of Gantt over all projects + title: Sample video of Gantt over all projects + video_id: UHgqfsrD59Q + sample: + close_label: Close this window and load real project data + header: Sample data are loaded! + text: We have prepared some sample data for you to try all Gantt features + with no stress. We also want to encourage you to watch a video guide + showing useful tweaks. Closing this window will load real project data. + video_label: Watch video tutorial + video: + text: Here you can see most of features of this Gantt module + title: Sample video + video_id: UHgqfsrD59Q + text_demo_feature_popup: Tato funkce bude brzy dostupná. + text_easy_gantt_footer: Redmine Gantt powered by Easy + text_easy_gantt_keep_link_delay_in_drag: Zachovat zpoždění vazby, když je úkol přetažen + zpět + text_easy_gantt_show_holidays: Zobrazit aktuální dovolenou uživatelů v Ganttu (funguje + pouze v Easy Redmine) + text_easy_gantt_show_project_progress: Zobrazit procentuální dokončení na liště + projektu (načítání může být pomalé) + text_easy_gantt_show_task_soonest_start: Ukázat nejnižší platná data pro úkoly definovaná + vazbami nebo rodičem + title_easy_gantt_settings: Easy Gantt diff --git a/easy_gantt/config/locales/da.yml b/easy_gantt/config/locales/da.yml new file mode 100644 index 0000000..d3dcd59 --- /dev/null +++ b/easy_gantt/config/locales/da.yml @@ -0,0 +1,2 @@ +--- +da: diff --git a/easy_gantt/config/locales/de.yml b/easy_gantt/config/locales/de.yml new file mode 100644 index 0000000..8a0e45a --- /dev/null +++ b/easy_gantt/config/locales/de.yml @@ -0,0 +1,194 @@ +--- +de: + button_print: Drucken + button_project_menu_easy_gantt: Easy Gantt + button_top_menu_easy_gantt: Projekte Gantt + button_use_actual_delay: Aktuelle Verzögerung übernehmen + easy_gantt_toolbar: + day: Days + month: Months + week: Weeks + easy_gantt: + buton_create_baseline: Baselines + button_critical_path: Critical path + button_resource_management: Ressourcenmanagement + button: + close_all: Alle schließen + close_all_parent_issues: Alle übergeordneten Aufgaben schliessen + create_baseline: Basispläne + critical_path: Kritischer Weg + day_zoom: Tage + delayed_project_filter: Verspätete Projekte filtern + jump_today: Zum heutigen Tag überspringen + load_sample_data: Beispieldaten laden + month_zoom: Monate + print_fit: Der Seite anpassen + problem_finder: Probleme + reload: Neu laden (speichern) + remove_delay: Die Verzögerung entfernen + resource_management: Ressourcenmanagement + tool_panel: Werkzeuge + week_zoom: Wochen + critical_path: + disabled: Deaktiviert + last: Letzte + last_text: Die Aufgaben, die ohne Verspätung erfüllt werden sollen + longest: Längste + longest_text: Die längste Sequenz anzeigen + error_overmile: Task should end before its milestone + error_too_short: Task is too short for its estimated hours count + errors: + duplicate_link: Das Duplikat des Links kann nicht erstellt werden + fresh_milestone: "Die Aufgabe kann nicht zu dem nicht gespeicherten Meilenstein + hinzugefügt werden.\r\nBitte speichern Sie erst den Meilenstein." + link_target_new: Ein Link zur nicht abgespeicherte Aufgabe kann nicht erstellt + werden. Bitte die Veränderungen erst speichern. + link_target_readonly: Nur-lese-Modus die Zielaufgabe + loop_link: Der geloopter Link kann nicht erstellt werden. + no_rest_api: Easy Gantt braucht REST API aktiviert. Bitte aktivieren Sie es + in der Administration -> Einstellungen-> API -> Aktivieren REST Web Service + overdue: Soll in der Zukunft enden oder soll bereits geschlossen werden + overmile: Soll am %{effective_date} enden, um den Meilenstein einzuhalten + short_delay: Soll länger sein als %{diff} Tage + too_short: enthält nicht genug Tage für %{rest} Stunden der abgeschätzten Zeit. + unsaved_parent: "Die Aufgabe kann nicht zur nicht gespeicherten übergeordneten + Aufgabe hinzugefügt werden.\r\nBitte erst die Änderungen speichern." + unsupported_link_type: Der Link Typ wird nicht unterstützt + gateway: + entity_save_failed: "%{entityType} %{entityName} kann nicht gespeichert werden. + Ein Fehler ist aufgetreten." + send_failed: Die Anfrage ist fehlgeschlagen + label_pro_upgrade: Auf die Pro Version upgraden + link_dir: + link_end: wie vorangegangen + link_start: als nächstes + popup: + add_task: + heading: Eine Funktion der Aufgabe hinzufügen + text: "Neue Funktion für eine/einen Aufgabe/Meilenstein ist ausschließlich + verfügbar für Easy Gantt PRO Mitglieder\r\nCheck + for update!" + baseline: + heading: Funktion für Basispläne + text: "Funktionen für Basisplan ist ausschließlich verfügbar für Easy Gantt + PRO Mitglieder\r\nCheck + for update!" + critical: + heading: Funktion für den kritischen Weg + text: "Funktion für den kritischen Weg ist ausschließlich verfügbar für Easy + Gantt PRO Mitglieder\r\nCheck + for update!" + resource: + heading: Ressourcenmanager Funktion + text: Ressourcenmanagement Funktion steht Ihnen hier zur Verfügung + Easy Redmine + reload_modal: + label_errors: Fehler + text_reload_appeal: Möchten Sie nicht gespeicherte Elemente ignorieren und die + Daten vom Server neuladen? + title: Gantt wurde nicht ordnungsgemäß gespeichert + sample_global_free: + text: Beispieldaten können nicht geschlossen werden. Gantt über alle Projekte + ist nur in der PRO Version verfügrbar + video: + text: Hier bekommen Sie ein Übersicht über die meisten Funktionen des Gantt + über alle Projekte + title: Beispielvideo für Gantt über alle Projekte + video_id: EiiqBrrY4m4 + sample: + close_label: Das Fenster schließen und die echten Projektdaten laden + header: Beispieldaten sind hochgeladen + text: "Wir haben einige Beispieldaten für Sie vorbereitet, um alle Easy Gantt + Funktionen ohne Stress testen zu können. Wir möchten Sie auch ermutigen, sich + die Video-Anleitung über Optimierungsmethoden anzuschauen.\r\nSchließen Sie + dieses Fenster, um das Laden der echten Projektdaten auszulösen." + video_label: Die Video-Anleitung anschauen + video: + text: Hier können Sie die meisten der Funktionen für dieses Gantt Modul sehen + title: Beispielvideo + video_id: UHgqfsrD59Q + soon: + add_task: + heading: Add Issue feature + text: This feature is comming soon. Check + the availability here! + baseline: + heading: Baselines feature + text: Baselines feature is comming soon. Check + the availability here!" + resource: + heading: Resource manager feature + text: Resource Management feature is available just in + Easy Redmine + text_blocker_milestone: Der Meilenstein blockiert das Verschieben der Aufgabe + außerhalb des Datums + text_blocker_move_pre: Die Aufgabe hat eine Beziehung, die das Verschieben der + Aufgabe außerhalb des Datums verhindert. + title: + button_test: Test Taste (nur zum Testen) + day_zoom: In den täglichen Zeitplan zoomen + jump_today: springe zum heutigen Tag + month_zoom: In den monatlichen Zeitplan zoomen + print_fit: Skalieren Sie das Gantt auf eine Seite + week_zoom: In den wöchentlichen Zeitplan zoomen + easy_printable_templates_categories: + easy_gantt: Easy Gantt + easy_query: + name: + easy_gantt_easy_issue_query: Easy Gantt + easy_gantt_easy_project_query: Globales Easy Gantt + error_easy_gantt_view_permission: Sie haben nicht die Berechtigung, Easy Gantt zu + sehen + error_epm_easy_gantt_already_active: Easy Gantt ist auf der Seite bereits aktiv + field_easy_gantt_default_zoom: Standard Zoom + field_easy_gantt_relation_delay_in_workdays: Relationsverzögerungen in Arbeitstagen + field_easy_gantt_show_holidays: Feiertage anzeigen + field_easy_gantt_show_project_progress: Den Projektfortschritt anzeigen + field_easy_gantt_show_task_soonest_start: Den frühestmöglichen Starttermin anzeigen + field_keep_link_delay_in_drag: Konstante Link-Verzögerung beim Ziehen (Drag) + field_relation: Beziehung + heading_delay_popup: Definition der Verzögerung in Tagen + heading_demo_feature_popup: Coming soon + heading_easy_gantts_issues: Aufgaben Gantt + label_easy_gantt: Easy Gantt + label_easy_gantt_critical_path: Kritischer Weg + label_easy_gantt_settings: Gantt Einstellungen + label_filter_group_easy_gantt_easy_issue_query: Aufgabenfelder + label_finish_to_finish: Ende zu Ende + label_parent_issue_plural: Probleme der übergeordneten Aufgaben + label_start_to_finish: Start zu Ende + label_start_to_start: Start zu Start + link_easy_gantt_footer: https://www.easyredmine.com/redmine-gantt-plugin + link_easy_gantt_plugin: https://www.easyredmine.com/easy-gantt-upgrade-to-pro + permission_edit_easy_gantt: Easy Gantt & Ressourcenmanagement in dem Projekt bearbeiten + permission_edit_global_easy_gantt: Easy Gantt & Ressourcenmanagement in dem Prokjekt + global bearbeiten + permission_edit_personal_easy_gantt: Persönliche Easy Gantt & Resource management + bearbeiten + permission_view_easy_gantt: Easy Gantt & Resourcenmanagement in dem Prokjekt sehen + permission_view_global_easy_gantt: Easy Gantt & Ressourcenmanagement in dem Prokjekt + global sehen + permission_view_personal_easy_gantt: Persönliche Easy Gantt & Ressourcenmanagement + sehen + project_default_page: + easy_gantt: Easy Gantt + project_module_easy_gantt: Easy Gantt + text_demo_feature_popup: This feature will be available soon. + text_easy_gantt_footer: Redmine Gantt powered by Easy + text_easy_gantt_keep_link_delay_in_drag: Die Verzögerung der Beziehung konstant + beibehalten während die Aufgabe nach hinten gezogen wird + text_easy_gantt_print_easy_gantt_current: Aktuelles Easy Gantt anzeigen (funktioniert + nur von einer Seite, wo Easy Gantt geladen wird) + text_easy_gantt_relation_delay_in_workdays: Rechnen Sie nicht Nichtarbeitstage in + der Relationsverzögerung mit. + text_easy_gantt_show_holidays: Die aktuellen Feiertage des Benutzers im Gantt- Diagram + anzeigen (Die Funktion ist nur in Easy Redmine verfügbar) + text_easy_gantt_show_project_progress: Den Fortschritt in Prozent auf dem Balken + des Projekts anzeigen (kann die Ladezeit beeinträchtigen) + text_easy_gantt_show_task_soonest_start: Die kleinsten zulässigen Daten, die durch + Beziehungen oder übergeordnete Aufgaben definiert sind, für die Aufgabe anzeigen + title_easy_gantt_settings: Easy Gantt diff --git a/easy_gantt/config/locales/en-AU.yml b/easy_gantt/config/locales/en-AU.yml new file mode 100644 index 0000000..56b80b3 --- /dev/null +++ b/easy_gantt/config/locales/en-AU.yml @@ -0,0 +1,2 @@ +--- +en-AU: diff --git a/easy_gantt/config/locales/en-GB.yml b/easy_gantt/config/locales/en-GB.yml new file mode 100644 index 0000000..a32c228 --- /dev/null +++ b/easy_gantt/config/locales/en-GB.yml @@ -0,0 +1,2 @@ +--- +en-GB: diff --git a/easy_gantt/config/locales/en-US.yml b/easy_gantt/config/locales/en-US.yml new file mode 100644 index 0000000..54b3be8 --- /dev/null +++ b/easy_gantt/config/locales/en-US.yml @@ -0,0 +1,2 @@ +--- +en-US: diff --git a/easy_gantt/config/locales/en.yml b/easy_gantt/config/locales/en.yml new file mode 100644 index 0000000..b1f1115 --- /dev/null +++ b/easy_gantt/config/locales/en.yml @@ -0,0 +1,162 @@ +--- +en: + button_print: Print + button_project_menu_easy_gantt: Easy Gantt + button_top_menu_easy_gantt: Projects Gantt + button_use_actual_delay: Use actual delay + easy_gantt: + button: + close_all: Close all + close_all_parent_issues: Close all parent tasks + create_baseline: Baselines + critical_path: Critical path + day_zoom: Days + delayed_project_filter: Filter Delayed Projects + jump_today: Jump to today + load_sample_data: Load sample data + month_zoom: Months + print_fit: Fit to page + problem_finder: Problems + quarter_zoom: Quarters + reload: Reload (save) + remove_delay: Remove delay + resource_management: Resource management + tool_panel: Tools + week_zoom: Weeks + year_zoom: Years + critical_path: + disabled: Disabled + last: Last + last_text: Tasks which should not be delayed + longest: Longest + longest_text: Show longest sequence of tasks + errors: + duplicate_link: Cannot create duplicate link + fresh_milestone: Cannot add task to non-saved milestone. Save the milestone + first. + link_target_new: Cannot create link to unsaved task. Save the changes first. + link_target_readonly: Read-only target task + loop_link: Cannot create looped link + no_rest_api: Easy Gantt needs REST API enabled. Turn it on in Administration + -> Settings -> API -> Enable REST web service + overdue: should end in future or should be closed already + progress_date_overdue: is behind schedule by %{days} days + overmile: should end on %{effective_date} in order to keep milestone + short_delay: should be longer by %{diff} days + too_short: does not contain enough days for %{rest} hours of estimated time + unsaved_parent: Cannot add task to non-saved parent. Save the changes first. + unsupported_link_type: Unsupported link type + change_link_length: The delay between {{source_task}} and {{target_task}} has been changed to a minimum length of {{minimum_link_length}} + gateway: + entity_save_failed: "%{entityType} %{entityName} failed to save due to error" + send_failed: Request failed + label_pro_upgrade: Upgrade to PRO version + link_dir: + link_end: as preceding + link_start: as following + popup: + add_task: + heading: Add task feature + text: "New task/milestone feature is available only in Easy Gantt PRO \r\nCheck for + update!" + baseline: + heading: Baselines feature + text: "Baselines feature is available only in Easy Gantt PRO \r\nCheck + for update!" + critical: + heading: Critical path feature + text: "Critical path feature is available only in Easy Gantt PRO \r\nCheck + for update!" + resource: + heading: Resource manager feature + text: Resource management feature is available only in + Easy Redmine + reload_modal: + label_errors: Errors + text_reload_appeal: Do you want to ignore unsaved items and reload data from + server? + title: Gantt failed to save properly + sample_global_free: + text: Sample data cannot be closed. Gantt over all projects is available only + in PRO version + video: + text: Here you can see most of features of Gantt over all projects + title: Sample video of Gantt over all projects + video_id: EiiqBrrY4m4 + sample: + close_label: Close this window and load real project data + header: Sample data are loaded! + text: We have prepared some sample data for you to try all Easy Gantt + features with no stress. We also want to encourage you to watch a + video guide showing useful tweaks. Closing this window will load real project + data. + video_label: Watch video tutorial + video: + text: Here you can see most of features of this Gantt module + title: Sample video + video_id: UHgqfsrD59Q + text_blocker_milestone: Milestone is blocking task to be moved beyond this date + text_blocker_move_pre: Task has relation that blocks moving task beyond this date + text_blocker_move_end: At least one following task will be postponed if moved after this date + title: + button_test: Test button (only for testing) + day_zoom: Zoom to day scale + jump_today: Display timeline at today + month_zoom: Zoom to month scale + print_fit: Scale the gantt to one page + quarter_zoom: Zoom to quarter scale + week_zoom: Zoom to week scale + year_zoom: Zoom to year scale + easy_printable_templates_categories: + easy_gantt: Easy Gantt + easy_query: + name: + easy_gantt_easy_issue_query: Easy Gantt + easy_gantt_easy_project_query: Global Easy Gantt + error_easy_gantt_view_permission: You do not have permission to see Easy Gantt + error_epm_easy_gantt_already_active: Easy gantt on the page is already active + field_easy_gantt_default_zoom: Default zoom + field_easy_gantt_relation_delay_in_workdays: Relation delays in workdays + field_easy_gantt_show_holidays: Show holidays + field_easy_gantt_show_project_progress: Show project progress + field_easy_gantt_show_task_soonest_start: Show soonest start + field_relation: Relation + heading_delay_popup: Define delay in days + heading_demo_feature_popup: Coming soon + heading_easy_gantts_issues: Easy Gantt Free + label_easy_gantt: Easy Gantt + label_easy_gantt_critical_path: Critical path + label_easy_gantt_settings: Gantt settings + label_filter_group_easy_gantt_easy_issue_query: Task fields + label_finish_to_finish: Finish to finish + label_parent_issue_plural: Parent tasks + label_start_to_finish: Start to finish + label_start_to_start: Start to start + link_easy_gantt_plugin: https://www.easyredmine.com/easy-gantt-upgrade-to-pro + permission_edit_easy_gantt: Edit Easy Gantt & Resource management on a project + permission_edit_global_easy_gantt: Edit global Easy Gantt & Resource management + permission_edit_personal_easy_gantt: Edit personal Easy Gantt & Resource management + permission_view_easy_gantt: View Easy Gantt & Resource management on a project + permission_view_global_easy_gantt: View global Easy Gantt & Resource management + permission_view_personal_easy_gantt: View personal Easy Gantt & Resource management + project_default_page: + easy_gantt: Easy Gantt + project_module_easy_gantt: Easy Gantt + text_demo_feature_popup: This feature will be available soon. + text_easy_gantt_footer: Redmine Gantt powered by Easy + text_easy_gantt_print_easy_gantt_current: Show current Easy Gantt (works only from + page where is easy gantt loaded) + text_easy_gantt_relation_delay_in_workdays: Do not count non-working days in relation + delay + text_easy_gantt_show_holidays: Show current user holidays on gantt (works only in + Easy Redmine) + text_easy_gantt_show_project_progress: Show completed percent on project's bar (may + be slow to load) + text_easy_gantt_show_task_soonest_start: Show lowest valid dates for tasks defined + by relations or parent + title_easy_gantt_settings: Easy Gantt + field_easy_gantt_fixed_delay: Fixed delay + text_easy_gantt_fixed_delay: Keep relation delay fixed when task is dragging (works only on easy gantt) + label_easy_gantt_recalculate_fixed_delay: recalculate fixed delay + notice_easy_gantt_fixed_delay_recalculated: Fixed delay on all relations was recalculated diff --git a/easy_gantt/config/locales/es.yml b/easy_gantt/config/locales/es.yml new file mode 100644 index 0000000..40ad8fa --- /dev/null +++ b/easy_gantt/config/locales/es.yml @@ -0,0 +1,163 @@ +--- +es: + button_print: Imprimir + button_project_menu_easy_gantt: Easy Gantt + button_top_menu_easy_gantt: Proyectos Gantt + button_use_actual_delay: Emplear retraso exacto + easy_gantt: + button: + close_all: Cerrar todo + close_all_parent_issues: Cerrar todas las tareas matrices + create_baseline: Referencias + critical_path: Ruta crítica + day_zoom: Días + delayed_project_filter: Filtrar los proyectos atrasados + jump_today: Saltar a hoy + load_sample_data: Cargar datos de muestra + month_zoom: Meses + print_fit: Ajustar a la página + problem_finder: Problemas + reload: Recargar (guardar) + remove_delay: Eliminar retraso + resource_management: Gestión de los recursos + tool_panel: Herramientas + week_zoom: Semanas + critical_path: + disabled: Deshabilitada + last: Última + last_text: Tareas que no deberían retrasarse + longest: Más largas + longest_text: Mostrar la secuencia de tareas más larga + errors: + duplicate_link: No se puede crear enlace duplicado + fresh_milestone: No se puede añadir tarea a un objetivo no guardado. Primero, + guarda el objetivo. + link_target_new: No se puede crear enlace a la tarea sin guardar. Primero, guarda + los cambios. + link_target_readonly: Tarea objetivo solo de lectura + loop_link: No se puede crear enlace con vínculo + no_rest_api: Easy Gantt requiere que REST API esté habilitado. Actívalo en Administración + -> Ajustes -> API -> Activar servicio web REST + overdue: debería terminar en el futuro o debería haberse cerrado ya + overmile: debería terminar en %{effective_date} para poder mantener el objetivo + short_delay: debe ser más extenso en %{diff} días + too_short: no contiene días suficientes para %{rest} horas de tiempo estimado + unsaved_parent: No se puede añadir tarea a una matriz sin guardar. Primero, + guarda los cambios. + unsupported_link_type: Tipo de enlace no compatible + gateway: + entity_save_failed: "%{entityType} %{entityName} no se ha guardado debido a + un error" + send_failed: Solicitud fallida + label_pro_upgrade: Actualizar a la versión PRO + link_dir: + link_end: como el anterior + link_start: como el siguiente + popup: + add_task: + heading: Añadir característica de tarea + text: La nueva característica de tarea/objetivo está disponible únicamente + en Easy Gantt PRO ¡Marcar + para actualizar! + baseline: + heading: Característica de referencias + text: La característica de la referencia está disponible únicamente en Easy + Gantt PRO ¡Marcar + para actualizar! + critical: + heading: Característica de ruta crítica + text: La característica de la ruta crítica está disponible únicamente en Easy + Gantt PRO Marcar + para actualizar + resource: + heading: Característica de gestor de recursos + text: La característica de gestor de recursos está disponible únicamente en + Easy Redmine + reload_modal: + label_errors: Errores + text_reload_appeal: "¿Quieres ignorar los objetos no guardados y cargar de nuevo + los datos del servidor?" + title: Gantt no ha podido guardar correctamente + sample_global_free: + text: No se pueden cerrar los datos de muestra. Gantt en todos los proyectos + está disponible únicamente en la versión PRO + video: + text: Aquí podrás ver la mayoría de las características de Gantt en todos + los proyectos + title: Vídeo de muestra de Gant en todos los proyectos + video_id: EiiqBrrY4m4 + sample: + close_label: Cerrar esta ventana y cargar los datos reales del proyecto + header: "¡Se han cargado los datos de muestra!" + text: Hemos preparado unos datos de muestra para que pruebes todas las + características de Easy Gantt sin estrés. También nos gustaría animarte + a que visualices un vídeo de guía que muestra ajustes útiles. Al cerrar esta + ventana se cargarán los datos reales del proyecto. + video_label: Ver el vídeo tutorial + video: + text: Aquí podrás ver la mayoría de las características de este módulo de + Gantt + title: Vídeo de muestra + video_id: UHgqfsrD59Q + text_blocker_milestone: El objetivo no permite que la tarea se desplace después + de esta fecha + text_blocker_move_pre: La tarea tiene una relación que bloquea su desplazamiento + a una fecha posterior + title: + button_test: Botón de prueba (sólo para pruebas) + day_zoom: Ampliar a escala de día + jump_today: Mostrar calendario en hoy + month_zoom: Ampliar a escala de mes + print_fit: Ajustar gantt a una página + week_zoom: Ampliar a escala de semana + easy_printable_templates_categories: + easy_gantt: Easy Gantt + easy_query: + name: + easy_gantt_easy_issue_query: Easy Gantt + easy_gantt_easy_project_query: Global Easy Gantt + error_easy_gantt_view_permission: No tienes permiso para ver Easy Gantt + error_epm_easy_gantt_already_active: Ya está activo Easy Gantt en la página + field_easy_gantt_default_zoom: Zoom por defecto + field_easy_gantt_relation_delay_in_workdays: Retrasos de relación en días hábiles + field_easy_gantt_show_holidays: Mostrar vacaciones + field_easy_gantt_show_project_progress: Mostrar el progreso del proyecto + field_easy_gantt_show_task_soonest_start: Mostrar el inicio más próximo + field_keep_link_delay_in_drag: Retraso constante del enlace durante el arrastre + field_relation: Relación + heading_delay_popup: Definir retraso en días + heading_demo_feature_popup: Muy pronto + heading_easy_gantts_issues: Easy Gantt gratis + label_easy_gantt: Easy Gantt + label_easy_gantt_critical_path: Senda crítica + label_easy_gantt_settings: Ajustes de Gantt + label_filter_group_easy_gantt_easy_issue_query: Campos de tarea + label_finish_to_finish: Fin a fin + label_parent_issue_plural: Problemas de matriz + label_start_to_finish: Inicio a fin + label_start_to_start: Inicio a inicio + link_easy_gantt_plugin: https://www.easyredmine.com/actualizar-easy-gantt-a-pro + permission_edit_easy_gantt: Editar Easy Gantt y gestión de recursos en un proyecto + permission_edit_global_easy_gantt: Editar Easy Gantt y gestión de recursos global + permission_edit_personal_easy_gantt: Editar Easy Gantt y gestión de recursos personal + permission_view_easy_gantt: Ver Easy Gantt y gestión de recursos en un proyecto + permission_view_global_easy_gantt: Ver Easy Gantt y gestión de recursos global + permission_view_personal_easy_gantt: Ver Easy Gantt y gestión de recursos personal + project_default_page: + easy_gantt: Easy Gantt + project_module_easy_gantt: Easy Gantt + text_demo_feature_popup: Esta característica está disponible pronto. + text_easy_gantt_footer: Redmine Gantt respaldado por Easy + text_easy_gantt_keep_link_delay_in_drag: Mantener constante la relación de retraso + mientras se arrastra la tarea + text_easy_gantt_print_easy_gantt_current: Mostrar Easy Gantt actual (funciona sola + desde la página en la que se carga easy gantt) + text_easy_gantt_relation_delay_in_workdays: No contar días no hábiles en el retraso + de relación + text_easy_gantt_show_holidays: Mostrar las vacaciones del usuario actual en gantt + (funciona sólo en Easy Redmine) + text_easy_gantt_show_project_progress: Mostrar porcentaje completado en la barra + del proyecto (podría tardar al cargar) + text_easy_gantt_show_task_soonest_start: Mostrar las fechas válidas más bajas para + tareas definidas por relaciones o matriz + title_easy_gantt_settings: Easy Gantt diff --git a/easy_gantt/config/locales/fi.yml b/easy_gantt/config/locales/fi.yml new file mode 100644 index 0000000..e173d18 --- /dev/null +++ b/easy_gantt/config/locales/fi.yml @@ -0,0 +1,2 @@ +--- +fi: diff --git a/easy_gantt/config/locales/fr.yml b/easy_gantt/config/locales/fr.yml new file mode 100644 index 0000000..ae08ce6 --- /dev/null +++ b/easy_gantt/config/locales/fr.yml @@ -0,0 +1,163 @@ +--- +fr: + button_print: Imprimer + button_project_menu_easy_gantt: Easy Gantt + button_top_menu_easy_gantt: Easy Gantt + button_use_actual_delay: Utilisez le délai réel + easy_gantt_toolbar: + day: Jours + month: Mois + week: Semaines + easy_gantt: + button: + close_all: Fermer tout + close_all_parent_issues: Fermer toutes les tâches parents + create_baseline: Lignes de base + critical_path: Chemin critique + day_zoom: Jours + delayed_project_filter: Filtrer les projets retardés + jump_today: Aller à aujourd'hui + load_sample_data: Charger données d'échantillon + month_zoom: Mois + print_fit: Ajuster à la page + problem_finder: Problèmes + reload: Recharger (sauvegarder) + remove_delay: Éliminer délai + resource_management: Gestion des resources + tool_panel: Outils + week_zoom: Semaines + critical_path: + disabled: Désactivé + last: Dernier + last_text: Tâches qui ne doivent pas être retardées + longest: La plus longue + longest_text: Afficher la séquence de tâches la plus longue + errors: + duplicate_link: Cannot create duplicate link + fresh_milestone: Cannot add task to non-saved milestone. Save the milestone + first. + link_target_new: Vous ne pouvez pas créer un lien vers la tâche non-sauvegardée. + Commencez par enregistrer les modifications. + link_target_readonly: Tâche cible en lecture seule + loop_link: Cannot create looped link + no_rest_api: Easy Gantt doit activer REST API. Allumez-le dans Administration + - > Paramètres -> API - > Activer REST web service + overdue: devrait se terminer à l'avenir ou devrait être déjà fermé + overmile: La tâche doit être terminée avant la date d'échéance du jalon + short_delay: Devrait être plus long de %{diff} jours + too_short: Tâche est trop courte pour le nombre d'heures estimés + unsaved_parent: Impossible d'ajouter la tâche au parent non-enregistré. Commencez + par enregistrez les modifications. + unsupported_link_type: Type de lien non reconnu + gateway: + entity_save_failed: "%{entityType} %{entityName} n'a pas pu sauvegarder due + à une erreur" + send_failed: Request failed + label_pro_upgrade: Upgrade to PRO version + link_dir: + link_end: as preceding + link_start: as following + popup: + add_task: + heading: Ajouter la tâche fonctionnalité + text: Cette fonctionnalité est à venir bientôt. Check + the availability here! + baseline: + heading: Lignes de base disposent + text: Baselines fonctionnalité bientôt disponible. Check + the availability here! + critical: + heading: Fonctionnalité du chemin critique + text: "Fonctionnalité du chemin critique sera disponible bientôt.\r\nCheck + the availability here!" + resource: + heading: Fonctionnalité de gestion de ressources + text: Fonctionnalité de gestion de ressources est disponible + Easy Redmine + reload_modal: + label_errors: Erreurs + text_reload_appeal: Voulez-vous ignorer des éléments non enregistrés et recharger + les données à partir du serveur ? + title: Gantt n'a pas réussi à enregistrer correctement + sample_global_free: + text: Sample data cannot be closed. Gantt over all projects is available only + in PRO version + video: + text: Here you can see most of features of Gantt over all projects + title: Sample video of Gantt over all projects + video_id: EiiqBrrY4m4 + sample: + close_label: Close this window and load real project data + header: Sample data are loaded! + text: We have prepared some sample data for you to try all Gantt features + with no stress. We also want to encourage you to watch a video guide + showing useful tweaks. Closing this window will load real project data. + video_label: Watch video tutorial + video: + text: Here you can see most of features of this Gantt module + title: Sample video + video_id: UHgqfsrD59Q + text_blocker_milestone: Un jalon empêche la tâche d'être déplacée au-delà de cette + date + text_blocker_move_pre: La tâche a une relation qui bloque le déplacement de la + tâche au-delà de cette date + title: + button_test: Test button (only for testing) + day_zoom: Zoom to day scale + jump_today: Display timeline at today + month_zoom: Zoom to month scale + print_fit: Réduire le diagramme de Gantt à une page + week_zoom: Zoom to week scale + easy_printable_templates_categories: + easy_gantt: Easy Gantt + easy_query: + name: + easy_gantt_easy_issue_query: Easy Gantt + easy_gantt_easy_project_query: Global Easy Gantt + error_easy_gantt_view_permission: Vous n'êtes pas autorisé à voir Easy Gantt + error_epm_easy_gantt_already_active: Easy gantt sur la page est déjà actif + field_easy_gantt_default_zoom: Zoom par défaut + field_easy_gantt_relation_delay_in_workdays: Délais des relations en journées de + travail + field_easy_gantt_show_holidays: Afficher les vacances + field_easy_gantt_show_project_progress: Afficher les progrès du projet + field_easy_gantt_show_task_soonest_start: Afficher le démarrage le plus tôt + field_keep_link_delay_in_drag: Délai de lien constant pendant glissement + field_relation: Relation + heading_delay_popup: Définir délai en jours + heading_demo_feature_popup: À venir + heading_easy_gantts_issues: Easy Gantt Free + label_easy_gantt: Easy Gantt + label_easy_gantt_critical_path: Chemin critique + label_easy_gantt_settings: Paramètres de Gantt + label_filter_group_easy_gantt_easy_issue_query: Champs de tâches + label_finish_to_finish: Terminer pour terminer + label_parent_issue_plural: Problèmes parents + label_start_to_finish: Début à la fin + label_start_to_start: Démarrer pour démarrer + link_easy_gantt_plugin: https://www.easyredmine.com/easy-gantt-upgrade-to-pro + permission_edit_easy_gantt: Éditer Easy Gantt et gestion des ressources sur un projet + permission_edit_global_easy_gantt: Éditer Easy Gantt & Resource management global + permission_edit_personal_easy_gantt: Éditer Easy Gantt & Gestion des ressources + personnel + permission_view_easy_gantt: Voir Easy Gantt et Gestion des ressources sur un projet + permission_view_global_easy_gantt: Voir Easy Gantt & Resource management global + permission_view_personal_easy_gantt: Voir Easy Gantt & Resource management personnel + project_default_page: + easy_gantt: Easy Gantt + project_module_easy_gantt: Easy Gantt + text_demo_feature_popup: Cette fonctionnalité sera disponible bientôt. + text_easy_gantt_footer: Redmine Gantt powered by Easy + text_easy_gantt_keep_link_delay_in_drag: Maintient le délai de relation constant + pendant que la tâche est déplacée en arrière + text_easy_gantt_print_easy_gantt_current: Afficher l'actuel Easy Gantt (fonctionne + uniquement à partir de la page sur laquelle easy gantt est chargé) + text_easy_gantt_relation_delay_in_workdays: Ne pas compter les journées non-ouvrables + en délai de relation + text_easy_gantt_show_holidays: Afficher les vacances de l'utilisateur actuel sur + Gantt ( fonctionne uniquement dans Easy Redmine) + text_easy_gantt_show_project_progress: Afficher le pourcentage terminé sur la barre + de projet (peut être lent à charger) + text_easy_gantt_show_task_soonest_start: Afficher les dates de validité les plus + basses pour les tâches définies par des relations ou parent + title_easy_gantt_settings: Easy Gantt diff --git a/easy_gantt/config/locales/he.yml b/easy_gantt/config/locales/he.yml new file mode 100644 index 0000000..de35edf --- /dev/null +++ b/easy_gantt/config/locales/he.yml @@ -0,0 +1,2 @@ +--- +he: diff --git a/easy_gantt/config/locales/hr.yml b/easy_gantt/config/locales/hr.yml new file mode 100644 index 0000000..8ecf54a --- /dev/null +++ b/easy_gantt/config/locales/hr.yml @@ -0,0 +1,8 @@ +--- +hr: + button_print: Ispiši + button_use_actual_delay: Koristi stvarno kašnjeje + easy_gantt: + button: + close_all: Zatvori sve + tool_panel: Alati diff --git a/easy_gantt/config/locales/hu.yml b/easy_gantt/config/locales/hu.yml new file mode 100644 index 0000000..0b8ade7 --- /dev/null +++ b/easy_gantt/config/locales/hu.yml @@ -0,0 +1,159 @@ +--- +hu: + button_print: Nyomtatás + button_project_menu_easy_gantt: Easy Gantt + button_top_menu_easy_gantt: Projekt Gantt + button_use_actual_delay: Használja a jelenlegi késést + easy_gantt: + button: + close_all: Mind bezárása + close_all_parent_issues: Minden feladat bezására + create_baseline: Baselines + critical_path: Kritikus végrehajtási útvonal + day_zoom: Napok + delayed_project_filter: Elhalasztott projektek szűrése + jump_today: Ugrás a mai napra + load_sample_data: Minta adat betöltése + month_zoom: Hónapok + print_fit: Oldalhoz igazítás + problem_finder: Problémák + reload: Újratöltés (mentés) + remove_delay: Késés megszüntetése + resource_management: Erőforrások kezelése + tool_panel: Eszközök + week_zoom: Hetek + critical_path: + disabled: Letiltva + last: Utolsó + last_text: Nem halasztható feladatok + longest: Leghosszabb + longest_text: Leghosszabb feladatsorozat megjelenítése + errors: + duplicate_link: Kettős kapcsolat létrehozása nem lehetséges + fresh_milestone: A nem mentett mérföldkövekhez nem adható feladat. Először mentse + el a mérföldkövet. + link_target_new: A nem mentett feladatokhoz nem hozható létre link. Először + mentse el a változásokat. + link_target_readonly: Csak olvasható célfeladat + loop_link: Visszahivatkozó kapcsolat létrehozása nem lehetséges + no_rest_api: Az Easy Gantt-nek szüksége van a REST API szolgáltatásra. Engedélyezze + az adminisztráció => Beállítások => REST webes szolgáltatás bekapcsolásával + overdue: Hamarosan be kell fejeződnie, vagy már be kellett volna fejezni. + overmile: "%{effective_date} dátummal kell befejeződnie hogy tartsa a mérföldkő + határidejét" + short_delay: "%{diff} nappal hosszabbnak kell lennie" + too_short: Nincs elegendő nap a hátralévő %{rest} becsült óra számára + unsaved_parent: Nem adhat feladatot nem mentett szülőfeladathoz. Először mentse + a változásokat. + unsupported_link_type: Nem támogatott linktípus + gateway: + entity_save_failed: "%{entityType} %{entityName} hiba miatt nem sikerült menteni" + send_failed: Kérés nem sikerült + label_pro_upgrade: Frissítsen PRO verzióra + link_dir: + link_end: megelőzőként + link_start: következőként + popup: + add_task: + heading: Feladat jellemző hozzáadása + text: Új feladat/ mérföldkő tulajdonság csak az Easy Gantt PRO csomagban érhető + el Frissítés + keresése! + baseline: + heading: Baselines jellemzői + text: Alapbeállítás jellemzői csak az Easy Gantt PRO + csomagban érhető el. + critical: + heading: Kritikus végrehajtási út funkció + text: Kritikus végrehajtási út funkció csak az Easy Gantt PRO csomagban elérhető + el. Kövesse + a frissítéseket! + resource: + heading: Erőforrástervező funkció + text: Az Erőforrástervező funkció csak az Easy Redmine csomagban érhető el. Easy Redmine + reload_modal: + label_errors: Hibák + text_reload_appeal: Szeretné figyelmen kívül hagyni a nem mentett elemeket és + újratölteni az adatokat egy másik szerverről? + title: Nem sikerült a Gannt megfelelő mentése + sample_global_free: + text: A minta adatokat nem lehet bezárni.A Gantt átfogó projektjei csak a PRO + verzióban érhetők el + video: + text: Itt látható Gantt átfogó projektjeinek tulajdonságai + title: Gantt átfogó projektjeinek minta videója + video_id: EiiqBrrY4m4 + sample: + close_label: Zárja be ezt az ablakot és töltsön fel valós projekt adatokat + header: Minta adat betöltődött + text: Előkészítettünk néhány mintaadatot Önnek, hogy minden Easy Gantt funkciót + kipróbálhasson probléma nélkül. Emellett javasoljuk, tekintse meg a videós + bemutatót is. Az ablak bezárása után betöltődnek a valós projektadatok. + video_label: Nézze meg az oktatóvideót + video: + text: Itt látható a Gantt modul legtöbb tulajdonsága + title: Bemutató videó + video_id: UHgqfsrD59Q + text_blocker_milestone: A mérföldkő megakadályozza, hogy a feladatot át lehessen + vinni az adott dátumon túl + text_blocker_move_pre: A feladatnak olyan kapcsolatai vannak amelyek nem engedik + meg hogy az adott dátumon túl átvihető legyen. + title: + button_test: Teszt gomb (csak teszteléskor) + day_zoom: Váltás napi nézetre + jump_today: Mai napi idővonal megjelenítése + month_zoom: Váltás havi nézetre + print_fit: Gantt egész oldalas nagyítása + week_zoom: Váltás heti nézetre + easy_printable_templates_categories: + easy_gantt: Easy Gantt + easy_query: + name: + easy_gantt_easy_issue_query: Easy Gantt + easy_gantt_easy_project_query: Global Easy Gantt + error_easy_gantt_view_permission: Nincs felhatalazma az Easy Gantt eléréséhez + error_epm_easy_gantt_already_active: Easy Gantt már aktív az oldalon + field_easy_gantt_default_zoom: Alapértelmezett nézet + field_easy_gantt_relation_delay_in_workdays: Kapcsolódó késések munkanapokban + field_easy_gantt_show_holidays: Szabadnapok megjelenítése + field_easy_gantt_show_project_progress: Projekt folyamatának mutatása + field_easy_gantt_show_task_soonest_start: Legkorábbi kezdés megjelenítése + field_keep_link_delay_in_drag: Átmozgatás során a késés és kapcsolat megőrzése + field_relation: Kapcsolat + heading_delay_popup: Adja meg a késést napokban + heading_demo_feature_popup: Hamarosan + heading_easy_gantts_issues: Ingyenes Easy Gantt + label_easy_gantt: Easy Gantt + label_easy_gantt_critical_path: Kritikus végrehajtási útvonal + label_easy_gantt_settings: Gantt beállítások + label_filter_group_easy_gantt_easy_issue_query: Feladat mezők + label_finish_to_finish: Befejezéstől a befejezésig + label_parent_issue_plural: Szülőfeladatok + label_start_to_finish: Kezdéstől a befejezésig + label_start_to_start: Kezdéstől kezdésig + link_easy_gantt_plugin: https://www.easyredmine.com/easy-gantt-upgrade-to-pro + permission_edit_easy_gantt: Easy Gantt & Erőforrástervezés szerkesztése a projekten + permission_edit_global_easy_gantt: Globális Easy Gantt és Erőforrástervezés szerkesztése + permission_edit_personal_easy_gantt: Személyes Easy Gantt és Erőforrástervező szerkesztése + permission_view_easy_gantt: Easy Gantt & Erőforrástervezés megtekintése a projekten + permission_view_global_easy_gantt: Globális Easy Gantt és Erőforrástervezés megtekintése + permission_view_personal_easy_gantt: Személyes Easy Gantt és Erőforrástervező megtekintése + project_default_page: + easy_gantt: Easy Gantt + project_module_easy_gantt: Easy Gantt + text_demo_feature_popup: Ez a tulajdonság hamarosan elérhető lesz + text_easy_gantt_footer: Az Easy működteti a Redmine Gantt + text_easy_gantt_keep_link_delay_in_drag: Tartsa meg a késés mértékét a feladat korábbra + helyezésekor + text_easy_gantt_print_easy_gantt_current: Mutasd az aktív Easy Gantt-et (csak arról + az oldalról működik, ahonnan az Easy Gantt-et betöltötték ) + text_easy_gantt_relation_delay_in_workdays: A kapcsolódó késésben a munkaszüneti + napokat ne számolja + text_easy_gantt_show_holidays: Mutassa a jelenlegi Gantt felhasználó szabadságát + (csak Easy Redmine-ban működik) + text_easy_gantt_show_project_progress: Jelenítse meg a százalékos készültségét a + projekt lapon (lassan töltődhet be) + text_easy_gantt_show_task_soonest_start: Jelenítse meg a legkorábbi érvényes dátumokat + a kapcsolatok vagy szülői függőség által meghatározott feladatoknál + title_easy_gantt_settings: Easy Gantt diff --git a/easy_gantt/config/locales/it.yml b/easy_gantt/config/locales/it.yml new file mode 100644 index 0000000..5996f4d --- /dev/null +++ b/easy_gantt/config/locales/it.yml @@ -0,0 +1,164 @@ +--- +it: + button_print: Stampa + button_project_menu_easy_gantt: Easy Gantt + button_top_menu_easy_gantt: Progetti Gantt + button_use_actual_delay: Utilizza il ritardo effettivo + easy_gantt: + button: + close_all: Chiudi tutto + close_all_parent_issues: Chiudi tutti i task padre + create_baseline: Baseline + critical_path: Percorso critico + day_zoom: Giorni + delayed_project_filter: Filtra Progetti Rinviati + jump_today: Passa a oggi + load_sample_data: Carica dati di prova + month_zoom: Mesi + print_fit: Adatta alla pagina + problem_finder: Problemi + reload: Ricarica (salva) + remove_delay: Rimuovi ritardo + resource_management: Gestione risorse + tool_panel: Strumenti + week_zoom: Settimane + critical_path: + disabled: Disabilitato + last: Finale + last_text: Task che non dovrebbero essere posticipati + longest: Più lungo + longest_text: Mostra la più lunga sequenza di task + errors: + duplicate_link: Creazione copia collegamento non riuscita + fresh_milestone: Impossibile aggiungere task a una milestone non salvata. Salvare + prima la milestone. + link_target_new: Impossibile creare collegamento a un'attività non salvata. + Salvare prima le modifiche. + link_target_readonly: Task obiettivo di sola lettura + loop_link: Creazione collegamento ciclico non riuscita + no_rest_api: Easy Gantt necessita che le REST API siano attive. Per farlo andare + in Amministrazione -> Impostazione -> API -> Attiva servizi web REST + overdue: dovrebbe terminare in futuro o dovrebbe essere già chiusa + overmile: Dovrebbe terminare il %{effective_date} per rispettare la milestone + short_delay: dovrebbe essere più lunga di %{diff} giorni + too_short: non include giorni a sufficienza per le %{rest} ore stimate + unsaved_parent: Impossibile aggiungere task a un padre non salvato. Salvare + prima le modifiche. + unsupported_link_type: Tipo di collegamento non supportato + gateway: + entity_save_failed: Salvataggio di %{entityType} %{entityName} non riuscito + a causa di un errore + send_failed: Richiesta non riuscita + label_pro_upgrade: Effettua l'upgrade alla versione PRO + link_dir: + link_end: come precedente + link_start: come seguente + popup: + add_task: + heading: Aggiungi funzionalità al task + text: La funzionalità nuova task/milestone è disponibile solo in Easy Gantt + PRO Controlla + aggiornamenti! + baseline: + heading: Funzionalità delle baseline + text: La funzionalità Baseline è disponibile solo in Easy Gantt PRO Controlla + aggiornamenti! + critical: + heading: Funzionalità Percorso critico + text: La funzionalità Percorso critico è disponibile solo in Easy Gantt PRO + Check for + update! + resource: + heading: Funzionalità Gestione risorse + text: La funzionalità Gestione risorse è disponibile solo in + Easy Redmine + reload_modal: + label_errors: Errori + text_reload_appeal: Desideri ignorare le voci non salvate e ricaricare i dati + dal server? + title: Gantt non salvato correttamente + sample_global_free: + text: I dati di prova non possono essere chiusi. Il Gantt per tutti i progetti + è disponile solo nella versione PRO + video: + text: Qui puoi vedere la maggior parte delle funzionalità del Gantt per tutti + i progetti + title: Video di esempio di Gantt su tutti i progetti + video_id: EiiqBrrY4m4 + sample: + close_label: Chiudi questa finestra e carica i dati del progetto reale + header: I dati di esempio sono stati caricati! + text: Abbiamo alcuni dati di esempio per farti provare tutte le funzionalità + di Easy Gantt senza problemi. Ti consigliamo anche di guardare la + videoguida che ti mostrerà degli utili accorgimenti. Chiudendo questa finestra + si caricheranno i dati reali del progetto. + video_label: Guarda il video tutorial + video: + text: Qui puoi vedere la maggior parte delle funzionalità di questo modulo + Gantt + title: Video di esempio + video_id: UHgqfsrD59Q + text_blocker_milestone: Una milestone impedisce lo spostamento del task oltre + questa data + text_blocker_move_pre: Il task ha una relazione che impedisce il suo spostamento + oltre questa data + title: + button_test: Pulsante Test (solo per testare) + day_zoom: Ingrandisci a mostrare la scaletta giornaliera + jump_today: Mostra la timeline aggiornata ad oggi + month_zoom: Ingrandisci a mostrare la scaletta mensile + print_fit: Ridimensiona Gantt a una singola pagina + week_zoom: Ingrandisci a mostrare la scaletta settimanale + easy_printable_templates_categories: + easy_gantt: Easy Gantt + easy_query: + name: + easy_gantt_easy_issue_query: Easy Gantt + easy_gantt_easy_project_query: Global Easy Gantt + error_easy_gantt_view_permission: Non sei autorizzato a vedere Easy Gantt + error_epm_easy_gantt_already_active: Easy Gantt risulta già attivo sulla pagina + field_easy_gantt_default_zoom: Ingrandimento predefinito + field_easy_gantt_relation_delay_in_workdays: Differimento di relazione nei giorni + lavorativi + field_easy_gantt_show_holidays: Mostra giorni festivi + field_easy_gantt_show_project_progress: Mostra l'avanzamento del progetto + field_easy_gantt_show_task_soonest_start: Mostra l'inizio più prossimo + field_keep_link_delay_in_drag: Ritardo di collegamento costante durante il trascinamento + field_relation: Relazione + heading_delay_popup: Definisci il ritardo in giorni + heading_demo_feature_popup: Disponibile a breve + heading_easy_gantts_issues: Easy Gantt Free + label_easy_gantt: Easy Gantt + label_easy_gantt_critical_path: Percorso critico + label_easy_gantt_settings: Impostazioni Gantt + label_filter_group_easy_gantt_easy_issue_query: Settori del task + label_finish_to_finish: Termina dalla fine + label_parent_issue_plural: Task padre + label_start_to_finish: Parti dalla fine + label_start_to_start: Parti dall'inizio + link_easy_gantt_plugin: https://www.easyredmine.com/easy-gantt-upgrade-to-pro + permission_edit_easy_gantt: Modifica Easy Gantt e Gestione risorse in un progetto + permission_edit_global_easy_gantt: Modifica Easy Gantt e Gestione risorse + permission_edit_personal_easy_gantt: Modifica Easy Gantt e Gestione risorse personali + permission_view_easy_gantt: Visualizza Easy Gantt e Gestione risorse applicati a + un progetto + permission_view_global_easy_gantt: Visualizza Easy Gantt e Gestione risorse + permission_view_personal_easy_gantt: Visualizza Easy Gantt e Gestione risorse personali + project_default_page: + easy_gantt: Easy Gantt + project_module_easy_gantt: Easy Gantt + text_demo_feature_popup: Questa funzionalità sarà presto disponibile. + text_easy_gantt_footer: Redmine Gantt è offerto da Easy + text_easy_gantt_keep_link_delay_in_drag: Mantieni costanti i rapporti di ritardo + mentre il task viene spostato indietro + text_easy_gantt_print_easy_gantt_current: Mostra Easy Gantt attuale (funziona solo + da pagine in cui è caricato Easy Gantt) + text_easy_gantt_relation_delay_in_workdays: Non tenere conto dei giorni non lavorativi + nel differimento di relazione + text_easy_gantt_show_holidays: Mostra sul Gantt i giorni di ferie dell'utente (funziona + solo in Easy Redmine) + text_easy_gantt_show_project_progress: Mostra la percentuale di completamento sulla + barra del progetto (potrebbe risultare lenta da caricare) + text_easy_gantt_show_task_soonest_start: Mostra le date disponibili più vicine per + i task definiti da relazioni o dipendenza + title_easy_gantt_settings: Easy Gantt diff --git a/easy_gantt/config/locales/ja.yml b/easy_gantt/config/locales/ja.yml new file mode 100644 index 0000000..a7eaf2a --- /dev/null +++ b/easy_gantt/config/locales/ja.yml @@ -0,0 +1,146 @@ +--- +ja: + button_print: 印刷 + button_project_menu_easy_gantt: Easy Gantt + button_top_menu_easy_gantt: Projects Gantt + button_use_actual_delay: 実際の遅延日数を使用 + easy_gantt: + button: + close_all: すべて閉じる + close_all_parent_issues: すべての親タスクを閉じる + create_baseline: ベースライン + critical_path: クリティカルパス + day_zoom: 日数 + delayed_project_filter: 遅延しているプロジェクトをフィルタする + jump_today: 今日に移動 + load_sample_data: サンプルデータを読み込む + month_zoom: 月数 + print_fit: ページに合わせる + problem_finder: 問題 + reload: 再読み込み(保存) + remove_delay: 遅延を削除 + resource_management: リソース・マネージメント + tool_panel: ツール + week_zoom: 週数 + critical_path: + disabled: 無効 + last: 最終 + last_text: 遅延できないタスク + longest: 最長 + longest_text: タスクの最長シーケンスを表示 + errors: + duplicate_link: リンクのコピーを作成できません + fresh_milestone: 保存されていないマイルストーンにはタスクを追加できません。まず、マイルストーンを保存してください。 + link_target_new: 保存されていないタスクにはリンクを作成できません。まず、変更を保存してください。 + link_target_readonly: ターゲット・タスクは読み取り専用です + loop_link: 無限ループを作ってはなりません。 + no_rest_api: このEasy Ganttを使うためには、REST APIを有効にする必要があります。管理⇒設定⇒APIと進み、REST Webサービスを有効にしてください。 + overdue: あとで完了するか、すでにタスクはクローズされています + overmile: マイルストーンを保つには、%{effective date}に完了する必要があります。 + short_delay: "%{diff} 日以上、必要です" + too_short: 予定作業時間%{rest}に対して、日数が不足しています + unsaved_parent: 保存されていない親タスクにタスクの追加はできません。まず、変更を保存してください + unsupported_link_type: サポートされていないリンクタイプです + gateway: + entity_save_failed: "%{entityType} %{entityName} はエラーのため、保存できませんでした" + send_failed: 要求は失敗しました + label_pro_upgrade: PROにアップグレード + link_dir: + link_end: 前の通り + link_start: 以下の通り + popup: + add_task: + heading: タスクの機能追加 + text: 今回新たに追加されたタスク/マイルストーン機能は、Easy Gantt PROでのみ使用できます。\r\nPROのアップデートされた機能については、こちらからご確認ください!" + baseline: + heading: ベースライン機能 + text: "ベースライン機能は、Easy Gantt PROでのみ使用できます。\r\nPROのアップデートされた機能については、こちらからご確認ください!" + critical: + heading: クリティカルパス機能 + text: "クリティカルパス機能は、Easy Gantt PROでのみ使用できます。\r\nPROのアップデートされた機能については、こちらからご確認ください!" + resource: + heading: リソース・マネージャー機能 + text: リソースマネージメント機能はEasy Redmineでのみ使用できます。 + reload_modal: + label_errors: エラー + text_reload_appeal: 保存されていない変更を破棄して、サーバのデータを再読み込みしますか? + title: Ganttが正しく保存されませんでした。 + sample_global_free: + text: 全プロジェクト横断管理ガントチャートはPROバージョンのみで使用可能です。 + video: + text: 全プロジェクト横断管理ガントチャートの大半の機能はこちらから見ることができます。 + title: 全プロジェクト横断管理ガントチャートのデモビデオ + video_id: zd3M0KYxcXM + sample: + close_label: このウィンドウを閉じて、実際のプロジェクトデータを読み込む + header: サンプルデータが読み込まれました! + text: "Easy Ganttの機能をストレスなく試していただくため、サンプルデータを用意しました。また、便利な使い方を説明したビデオガイドもご用意しています。このウィンドウを閉じると、実際のプロジェクトデータが読み込まれます。" + video_label: ビデオチュートリアルを見る + video: + text: このGanttモジュールの機能はこちらから見ることができます + title: サンプルビデオ + video_id: UHgqfsrD59Q + text_blocker_milestone: マイルストーンによってタスクの延期がブロックされています。 + text_blocker_move_end: このタスクをこの日付以降に延期すると、以下のタスクのうち一つ以上のタスクが延期されます。 + text_blocker_move_pre: このタスクはリレーションにより拘束されているため、このタスクをこの日付以降にずらすことはできません。 + title: + button_test: テスト ボタン(テスト専用) + day_zoom: 日単位で表示 + jump_today: 今日のチャートを表示する + month_zoom: 月単位で表示 + print_fit: Ganttのチャートを1ページの大きさにする + week_zoom: 週単位で表示 + easy_printable_templates_categories: + easy_gantt: Easy Gantt + easy_query: + name: + easy_gantt_easy_issue_query: Easy Gantt + easy_gantt_easy_project_query: Global Easy Gantt + error_easy_gantt_view_permission: Easy Ganttを見る権限がありません。 + error_epm_easy_gantt_already_active: 既に有効になっています このページ上のEasy Ganttはすでに有効になっています。 + error_small_screen: ガントチャートを表示するには端末の画面が小さすぎます + field_easy_gantt_default_zoom: デフォルト・タイムライン + field_easy_gantt_fixed_delay: タスク間の固定ラグタイム + field_easy_gantt_relation_delay_in_workdays: 作業日数におけるリレーション全体のデレイ + field_easy_gantt_show_holidays: 休日の表示 + field_easy_gantt_show_project_progress: プロジェクト進捗の表示 + field_easy_gantt_show_task_soonest_start: 最も早い開始日を表示 + field_keep_link_delay_in_drag: ドラッグ作業中、タスク間ラグタイムは変わりません + field_relation: リレーション + heading_delay_popup: 遅延を日数で確認 + heading_demo_feature_popup: Coming soon + heading_easy_gantts_issues: Easy Gantt無料版 + label_easy_gantt: Easy Gantt + label_easy_gantt_critical_path: クリティカルパス + label_easy_gantt_critical_path: Critical path + label_easy_gantt_global: グローバル + label_easy_gantt_issue_loaded: 全タスク読み込み完了 + label_easy_gantt_load: 読み込む + label_easy_gantt_recalculate_fixed_delay: タスク間の固定ラグタイムの再計算 + label_easy_gantt_settings: Gantt設定 + label_filter_group_easy_gantt_easy_issue_query: タスクフィールド + label_finish_to_finish: 終了‐終了 + label_parent_issue_plural: 親タスク + label_start_to_finish: 開始‐終了 + label_start_to_start: 開始‐開始 + link_easy_gantt_plugin: https://www.easyredmine.com/easy-gantt-upgrade-to-pro + notice_easy_gantt_fixed_delay_recalculated: 関連付けられた全タスク間の固定ラグタイムが再計算完了 + permission_edit_easy_gantt: 任意のプロジェクト上でEasy Gantt とResource Managementを編集する + permission_edit_global_easy_gantt: Easy Gantt(全体)とリソース・マネージメントを編集 + permission_edit_personal_easy_gantt: Easy Gantt(個別)とリソース・マネージメントを編集 + permission_view_easy_gantt: プロジェクトのEasy Ganttとリソース・マネージメントを閲覧 + permission_view_global_easy_gantt: Easy Ganttチャート(全体)とリソース・マネージメントを閲覧 + permission_view_personal_easy_gantt: Easy Ganttチャート(個別)とリソース・マネージメントを閲覧 + project_default_page: + easy_gantt: Easy Gantt + project_module_easy_gantt: Easy Gantt + text_demo_feature_popup: 本機能はもうじき利用可能になります + text_easy_gantt_fixed_delay: ドラッグバック作業中、タスク間ラグタイムは変わりません + text_easy_gantt_footer: Easyにより運営されるRedmine Gantt + text_easy_gantt_keep_link_delay_in_drag: タスクがドラッグされてもタスク間の日程は維持 + text_easy_gantt_print_easy_gantt_current: 現在のEasy Gantt を表示する(Easy Gantt が読み込まれたページからのみ可能です) + text_easy_gantt_relation_delay_in_workdays: リレーション遅延による非稼働日をカウントしない + text_easy_gantt_show_holidays: ユーザーの休日をGanttに表示する(Easy Redmineでのみ有効) + text_easy_gantt_show_project_progress: プロジェクトバー上に進捗率を表示する(読み込み速度が遅い可能性があり) + text_easy_gantt_show_task_soonest_start: 親タスク、またはリレーションから規定されるもっとも妥当性の低い日程を表示 + title_easy_gantt_settings: Easy Gantt diff --git a/easy_gantt/config/locales/ko.yml b/easy_gantt/config/locales/ko.yml new file mode 100644 index 0000000..be786aa --- /dev/null +++ b/easy_gantt/config/locales/ko.yml @@ -0,0 +1,143 @@ +--- +ko: + button_print: 프린트 + button_project_menu_easy_gantt: 쉬운 간트 + button_top_menu_easy_gantt: 프로젝트 간트 + button_use_actual_delay: 실제 지연일 사용 + easy_gantt: + button: + close_all: 모두 닫기 + close_all_parent_issues: 모든 상위작업 닫기 + create_baseline: 베이스라인 + critical_path: 중요한 경로 + day_zoom: 일 + delayed_project_filter: 지연된 프로젝트 필터 + jump_today: 오늘로 이동하기 + load_sample_data: 샘플 데이터 로딩 + month_zoom: 월 + print_fit: 페이지에 맞추기 + problem_finder: 문제 + reload: 다시 불러오기 (저장) + remove_delay: 지연 삭제 + resource_management: 리소스 관리 + tool_panel: 도구 + week_zoom: 주 + critical_path: + disabled: 비활성화 + last: 지난 + last_text: 지연되서는 안되는 작업들 + longest: 가장 긴 + longest_text: 가장 긴 작업 보이기 + errors: + duplicate_link: 복제된 링크를 생성할 수 없습니다 + fresh_milestone: 저장되지 않은 마일스톤에 작업을 추가 할 수 없습니다. 먼저 마일스톤을 저장하십시오. + link_target_new: 저장되지 않은 작업에 대한 링크를 만들 수 없습니다. 변경 사항을 먼저 저장하십시오. + link_target_readonly: 읽기전용 타겟 작업 + loop_link: 루프된 링크를 생성할 수 없음 + no_rest_api: Easy Gantt는 REST API를 활성화해야합니다. 관리 -> 설정 -> API -> REST 웹 서비스 순서로 + 활성화 하세요. + overdue: 이후에 종료하고 이미 종료했어야 합니다 + overmile: 마일스톤을 유지하려면 %{effective_date}에 작업을 끝내야합니다. + short_delay: "%{diff}보다 길어야 합니다" + too_short: 예상 시간의 %{rest} 시간 동안 충분한 일 수가 없습니다. + unsaved_parent: 저장되지 ㅇ낳은 상위 작업을 작업에 추가할 수 없습니다. 변경사항을 먼저 저장하세요 + unsupported_link_type: 지원하지 않는 링크 종류 + gateway: + entity_save_failed: "%{entityType} %{entityName}는 에러로 인해 저장하지 못했습니다" + send_failed: 요청 반려됨 + label_pro_upgrade: PRO버전으로 업그레이드하기 + link_dir: + link_end: 진행하는 동안 + link_start: 다음과 같이 + popup: + add_task: + heading: 작업 특성 추가 + text: 새로운 작업 / 마일스톤 기능은 Easy Gantt PRO에서만 사용할 수 있습니다. + 업데이트를 확인하세요! + baseline: + heading: 베이스라인 특징 + text: 베이스 라인 기능은 Easy Gantt PRO에서만 사용할 수 있습니다. + 업데이트를 확인하세요! + critical: + heading: 중요한 경로 특징 + text: 중요 경로 기능은 Easy Gantt PRO에서만 사용할 수 있습니다. + 업데이트를 확인하세요! + resource: + heading: 리소스 관리 특징 + text: 리소스 관리 기능은 Easy Redmine 에서 + 사용하실 수 있습니다 + reload_modal: + label_errors: 에러 + text_reload_appeal: 저장하지 않은 항목을 무시하고 서버에서 데이터를 다시 불러올까요? + title: 간트는 올바르게 저장할 수 없습니다 + sample_global_free: + text: 샘플 데이터를 닫을 수 없습니다. 모든 프로젝트의 Gantt는 PRO 버전에서만 사용이 가능합니다. + video: + text: 여기서 모든 프로젝트에 대해 Gantt의 대부분 기능을 볼 수 있습니다. + title: 모든 프로젝트에 관한 간트 비디오 샘플 + video_id: EiiqBrrY4m4 + sample: + close_label: 이 창을 닫고 실제 프로젝트 데이터 불러오기 + header: 샘플데이터가 로딩되었습니다! + text: " 스트레스 받지 않고 모든 Easy Gantt 기능을 사용해보세요 를 위한 샘플 데이터가 준비되어 + 있습니다. 또한 유용한 조정을 보여주는 비디오 가이드를 보는 것을 추천하여 드립니다. 이 창을 닫으면 실제 프로젝트 데이터가 로딩됩니다." + video_label: 비디오 튜토리얼 보기 + video: + text: 이 간트 모듈의 가장 최근 기능들을 확인하실 수 있습니다 + title: 샘플 비디오 + video_id: UHgqfsrD59Q + text_blocker_milestone: 마일스톤이 날짜 이후에 작업을 하지 못하도록 차단하게 되어 있습니다 + text_blocker_move_pre: 작업이날짜 이후에 작업을 이동하지 못하도록 차단하는 상태로 있습니다. + title: + button_test: 테스트 버튼 (테스트용) + day_zoom: 요일별 확대 + jump_today: 금일 타임라인 보기 + month_zoom: 월별로 확대 + print_fit: 간트 차트 한 페이지로 보이기 + week_zoom: 주별로 확대 + easy_printable_templates_categories: + easy_gantt: 쉬운 간트 + easy_query: + name: + easy_gantt_easy_issue_query: 쉬운 간트 + easy_gantt_easy_project_query: 글로벌 Easy Gantt + error_easy_gantt_view_permission: 이지간트 보기 권한을 가지고 있지 않습니다. + error_epm_easy_gantt_already_active: 페이지의 Easy Gantt는 이미 활성화 되어 있습니다 + field_easy_gantt_default_zoom: 영구설정 확대 + field_easy_gantt_relation_delay_in_workdays: 작업 날짜에서 관련 지연일 + field_easy_gantt_show_holidays: 휴일 보이기 + field_easy_gantt_show_project_progress: 프로젝트 과정 보이기 + field_easy_gantt_show_task_soonest_start: 가장 빨리 시작하는 것 보이기 + field_keep_link_delay_in_drag: 드래그 동안 지속적인 링크 지연 + field_relation: 관련 + heading_delay_popup: 지연 날짜 설정 + heading_demo_feature_popup: 곧 출시됩니다 + heading_easy_gantts_issues: 무료 쉬운 간트 + label_easy_gantt: 쉬운 간트 + label_easy_gantt_critical_path: 중요한 경로 + label_easy_gantt_settings: 간트 설정 + label_filter_group_easy_gantt_easy_issue_query: 작업 필드 + label_finish_to_finish: 종료하기 종료 + label_parent_issue_plural: 상위 작업 + label_start_to_finish: 종료하기 시작 + label_start_to_start: 시작하기 시작 + link_easy_gantt_plugin: https://www.easyredmine.com/easy-gantt-upgrade-to-pro + permission_edit_easy_gantt: 쉬운 간트 수정 & 프로젝트 리소스 관리 + permission_edit_global_easy_gantt: 글로벌 쉬운 간트 수정 & 리소스 관리 + permission_edit_personal_easy_gantt: 개별 쉬운 간트 수정 & 리소스 관리 + permission_view_easy_gantt: 쉬운 간트 확인 & 프로젝트 리소스 관리 + permission_view_global_easy_gantt: 글로벌 쉬운 간트 보기 & 리소스 관리 + permission_view_personal_easy_gantt: 개별 쉬운 간트 확인 & 리소스 관리 + project_default_page: + easy_gantt: 쉬운 간트 + project_module_easy_gantt: 쉬운 간트 + text_demo_feature_popup: 이 기능은 곧 사용가능합니다. + text_easy_gantt_footer: Easy가 지원하는 Redmine Gantt + text_easy_gantt_keep_link_delay_in_drag: 작업이 지연되는 동안 연장시간을 일관적으로 유지하십시오 + text_easy_gantt_print_easy_gantt_current: 현재 쉬운 간트 표 보기 (쉬운 간트가 표시된 페이지에만 작업하실 수 + 있습니다) + text_easy_gantt_relation_delay_in_workdays: 관련 지연에서 일하지 않은 날은 세지 않기 + text_easy_gantt_show_holidays: 간트에 있는 현재 사용자 휴일 보기 (Easy Redmine에서만 확인할 수 있습니다) + text_easy_gantt_show_project_progress: 프로젝트 바에서 완료율 보기 (로딩하는데 시간이 걸릴 수 있습니다) + text_easy_gantt_show_task_soonest_start: 관련 혹은 상위그룹으로 정렬된 작업의 가장 적은 유효날짜 표시 + title_easy_gantt_settings: 쉬운 간트 diff --git a/easy_gantt/config/locales/mk.yml b/easy_gantt/config/locales/mk.yml new file mode 100644 index 0000000..0ae1092 --- /dev/null +++ b/easy_gantt/config/locales/mk.yml @@ -0,0 +1,2 @@ +--- +mk: diff --git a/easy_gantt/config/locales/nl.yml b/easy_gantt/config/locales/nl.yml new file mode 100644 index 0000000..dbb7b65 --- /dev/null +++ b/easy_gantt/config/locales/nl.yml @@ -0,0 +1,159 @@ +--- +nl: + button_print: Afdrukken + button_project_menu_easy_gantt: Easy Gantt + button_top_menu_easy_gantt: Projecten Gantt + button_use_actual_delay: Gebruik actuele vertraging + easy_gantt: + button: + close_all: Sluit alle + close_all_parent_issues: Sluit alle overkoepelende taken + create_baseline: Baselines + critical_path: Kritiek pad + day_zoom: Dagen + delayed_project_filter: Filter Vertraagde Projecten + jump_today: Ga naar vandaag + load_sample_data: Laad sample gegevens + month_zoom: Maanden + print_fit: Aangepast aan pagina + problem_finder: Problemen + reload: Herladen (opslaan) + remove_delay: Verwijder vertraging + resource_management: Resource management + tool_panel: Tools + week_zoom: Weken + critical_path: + disabled: Uitgeschakeld + last: Laatste + last_text: Taken die niet vertraagd mogen worden + longest: Langste + longest_text: Toon langste sequentie taken + errors: + duplicate_link: Kan link niet dupliceren + fresh_milestone: Kan geen taak toevoegen aan niet-opgeslagen mijlpaal. Sla eerst + de mijlpaal op. + link_target_new: Kan geen link maken naar niet-opgeslagen taak. Sla eerst de + wijzigingen op. + link_target_readonly: Alleen lezen doeltaak + loop_link: Kan geloopte link niet aanmaken + no_rest_api: Voor Easy Gantt moet REST API ingeschakeld zijn. Zet het aan bij Administratie + -> Instellingen -> API -> REST web service inschakelen + overdue: zou in de toekomst moeten eindigen of al gesloten moeten zijn + overmile: moet eindigen op %{effective_date} om mijlpaal te houden + short_delay: Moet %{diff} dagen langer zijn + too_short: bevat niet genoeg dagen voor %{rest} uren geschatte tijd + unsaved_parent: Kan geen taak toevoegen aan niet opgeslagen overkoepeling. Sla + de wijzigingen eerst op. + unsupported_link_type: Niet ondersteund type link + gateway: + entity_save_failed: "%{entityType} %{entityName} niet opgeslagen door fout" + send_failed: Verzoek mislukt + label_pro_upgrade: Upgraden naar PRO versie + link_dir: + link_end: als voorgaande + link_start: als volgt + popup: + add_task: + heading: Voeg eigenschap taak toe + text: "Nieuwe taak/mijlpaal eigenschap is alleen beschikbaar in Easy Gantt + PRO \r\nControleer + voor een update!" + baseline: + heading: Baselines functie + text: "De Baselines functie is alleen beschikbaar in Easy Gantt PRO \r\nControleer + voor een update!" + critical: + heading: Kritiek pad functie + text: "Kritiek pad functie is alleen beschikbaar in Easy Gantt PRO \r\nKijk hier + voor een update!" + resource: + heading: Resource manager functie + text: Resource management functie is alleen beschikbaar in + Easy Redmine + reload_modal: + label_errors: Fouten + text_reload_appeal: Wilt u niet opgeslagen items negeren en data opnieuw laden + van de server? + title: Gantt niet juist opgeslagen + sample_global_free: + text: Sample data kunnen niet gesloten worden. Gantt over alle projecten is + alleen beschikbaar in de PRO versie + video: + text: Hier zie je de meeste functies van Gantt in alle projecten + title: Sample video van Gantt over alle projecten + video_id: EiiqBrrY4m4 + sample: + close_label: Sluit dit venster en laad echte projectgegevens + header: Sample data zijn geladen! + text: We hebben wat sample gegevens voorbereid om alle Easy Gantt functies + stress-vrij te proberen. We adviseren ook om een video met wat nuttige + tips te bekijken. Het sluiten van dit venster laadt echte project gegevens. + video_label: Bekijk video tutorial + video: + text: Hier ziet u de meeste functies van de Gantt module + title: Sample video + video_id: UHgqfsrD59Q + text_blocker_milestone: Mijlpaal blokkeert taak om verplaatst te worden na deze + datum + text_blocker_move_pre: Taak heeft een relatie die het verschuiven van deze taak + na deze datum blokkeert + title: + button_test: Test button (alleen voor testen) + day_zoom: Zoom naar dagweergave + jump_today: Geef tijdlijn voor vandaag weer + month_zoom: Zoom naar maandelijkse weergave + print_fit: Schaal gantt tot een pagina + week_zoom: Zoom naar wekelijkse weergave + easy_printable_templates_categories: + easy_gantt: Easy Gantt + easy_query: + name: + easy_gantt_easy_issue_query: Easy Gantt + easy_gantt_easy_project_query: Global Easy Gantt + error_easy_gantt_view_permission: U heeft geen toestemming om Easy Gantt te bekijken + error_epm_easy_gantt_already_active: Easy gantt is al actief op de pagina + field_easy_gantt_default_zoom: Standaard zoom + field_easy_gantt_relation_delay_in_workdays: Relatie vertragingen in werkdagen + field_easy_gantt_show_holidays: Toon vakanties + field_easy_gantt_show_project_progress: Toon projectvoortgang + field_easy_gantt_show_task_soonest_start: Toon eerste start + field_keep_link_delay_in_drag: Constante link vertraging tijdens slepen + field_relation: Relatie + heading_delay_popup: Definieer vertraging in dagen + heading_demo_feature_popup: Binnenkort + heading_easy_gantts_issues: Easy Gantt Gratis + label_easy_gantt: Easy Gantt + label_easy_gantt_critical_path: Kritiek pad + label_easy_gantt_settings: Gantt instellingen + label_filter_group_easy_gantt_easy_issue_query: Taken velden + label_finish_to_finish: Finish om af te ronden + label_parent_issue_plural: Ouder kwesties + label_start_to_finish: Start tot finish + label_start_to_start: Start om te beginnen + link_easy_gantt_plugin: https://www.easyredmine.com/easy-gantt-upgrade-naar-pro + permission_edit_easy_gantt: Bewerk Easy Gantt & Resource management op een project + permission_edit_global_easy_gantt: Bewerk globaal Easy Gantt & Resource management + permission_edit_personal_easy_gantt: Bewerk persoonlijk Easy Gantt & Resource management + permission_view_easy_gantt: Bekijk Easy Gantt & Resource management op een project + permission_view_global_easy_gantt: Bekijk globaal Easy Gantt & Resource management + permission_view_personal_easy_gantt: Bekijk persoonlijk Easy Gantt & Resource management + project_default_page: + easy_gantt: Easy Gantt + project_module_easy_gantt: Easy Gantt + text_demo_feature_popup: Deze functie is binnenkort beschikbaar + text_easy_gantt_footer: Redmine Gantt mogelijk gemaakt door Easy + text_easy_gantt_keep_link_delay_in_drag: Hou relatievertraging constant terwijl + taak teruggedraaid wordt + text_easy_gantt_print_easy_gantt_current: Toon huidige Easy Gantt (werkt alleen + op pagina's waar easy gantt is geladen) + text_easy_gantt_relation_delay_in_workdays: Tel niet-werkdagen niet mee in relatie + vertraging + text_easy_gantt_show_holidays: Toon vakantie van huidige gebruiker op gantt (werkt + alleen in Easy Redmine) + text_easy_gantt_show_project_progress: Bekijk voortgangspercentage op de balk van + het project (laadt wellicht langzaam) + text_easy_gantt_show_task_soonest_start: Toon laagst geldige data voor taken gedefinieerd + door relaties of ouder + title_easy_gantt_settings: Easy Gantt diff --git a/easy_gantt/config/locales/no.yml b/easy_gantt/config/locales/no.yml new file mode 100644 index 0000000..38901c6 --- /dev/null +++ b/easy_gantt/config/locales/no.yml @@ -0,0 +1,2 @@ +--- +'no': diff --git a/easy_gantt/config/locales/pl.yml b/easy_gantt/config/locales/pl.yml new file mode 100644 index 0000000..b53e1ce --- /dev/null +++ b/easy_gantt/config/locales/pl.yml @@ -0,0 +1,160 @@ +--- +pl: + button_print: Drukuj + button_project_menu_easy_gantt: Easy Gantt + button_top_menu_easy_gantt: Projekty Gantt + button_use_actual_delay: Użyj aktualnego opóźnienia + easy_gantt: + button: + close_all: Zamknij wszystko + close_all_parent_issues: Zamknij wszystkie zadania nadrzędne + create_baseline: Bazy + critical_path: Ścieżka krytyczna + day_zoom: Dni + delayed_project_filter: Filtruj opóźnione projekty + jump_today: Skok do dzisiaj + load_sample_data: Załaduj proste dane + month_zoom: Miesiące + print_fit: Dopasuj do strony + problem_finder: Problemy + reload: Przeładuj (zapisz) + remove_delay: Usuń opóźnienie + resource_management: Zarządzanie zasobami + tool_panel: Narzędzia + week_zoom: Tygodnie + critical_path: + disabled: Wyłączony + last: Ostatni + last_text: Zadania, które nie powinny być opóźnione + longest: Najdłuższy + longest_text: Pokaż najdłuższą sekwencję zadań + errors: + duplicate_link: Nie można utworzyć zduplikowanego linku + fresh_milestone: Nie można dodać zadania do niezapisanych kroków milowych. Najpierw + zapisz krok milowy. + link_target_new: Nie można utworzyć linku do niezapisanego zadania. Najpierw + zapisz zmiany. + link_target_readonly: Docelowe zadanie jedynie do odczytu + loop_link: Nie można utworzyć zapętlonego linku + no_rest_api: Easy Gantt potrzebuje włączonego API REST. Włącz go w Administracja + -> Ustawienia -> API -> Włącz usługę sieciową REST + overdue: powinno się zakończyć w przyszłości lub być już zamknięte + overmile: powinno zakończyć się %{effective_date}, aby zachować krok milowy + short_delay: powinno być dłuższe niż %{diff} dni + too_short: nie zawiera wystarczającej liczby dni dla %{rest} godzin oszacowanego + czasu + unsaved_parent: Nie można dodać zadania do niezapisanego zadania nadrzędnego. + Najpierw zapisz zmiany + unsupported_link_type: Nieobsługiwany typ linku + gateway: + entity_save_failed: Nie powiodło się zapisanie %{entityType} %{entityName} z + powodu błędu + send_failed: Wymagane pole + label_pro_upgrade: Zaktualizuj do wersji PRO + link_dir: + link_end: jak poprzedni + link_start: jak następujący + popup: + add_task: + heading: Dodaj funkcję zadania + text: "Funkcja nowego zadania/kroku milowego jest dostępna jedynie w Easy + Gantt PRO \r\nSprawdź + aktualizację!" + baseline: + heading: Funkcja baz + text: "Funkcja baz jest dostępna jedynie w Easy Gantt PRO \r\nSprawdź + aktualizację!" + critical: + heading: Funkcja ścieżki krytycznej + text: "Funkcja ścieżki krytycznej jest dostępna jedynie w Easy Gantt PRO \r\nSprawdź aktualizację!" + resource: + heading: Funkcja menedżera zasobów + text: Funkcja ścieżki krytycznej jest dostępna jedynie w + Easy Redmine + reload_modal: + label_errors: Błędy + text_reload_appeal: Czy chcesz zignorować niezapisane pozycje i ponownie załadować + dane z serwera? + title: Poprawne zapisanie Gantta nie powiodło się + sample_global_free: + text: Przykładowe dane nie mogą zostać zamknięte. Gantt w stosunku do innych + projektów jest dostępny jedynie w wersji PRO + video: + text: Tutaj możesz zobaczyć większość funkcji Gantta w stosunku do wszystkich + projektów + title: Przykładowe wideo Gantta w stosunku do wszystkich projektów + video_id: EiiqBrrY4m4 + sample: + close_label: Zamknij to okno i załaduj prawdziwe dane projektowe + header: Przykładowe dane są załadowane! + text: Przygotowaliśmy dla ciebie pewne przykładowe dane w celu wypróbowania + wszystkich funkcji Easy Gantt bez stresu. + video_label: Zobacz samouczek filmowy + video: + text: Tutaj możesz zobaczyć większość funkcji tego modułu Gantta + title: Przykładowy film + video_id: UHgqfsrD59Q + text_blocker_milestone: Krok milowy blokuje przeniesienie zadania poza tę datę + text_blocker_move_pre: Zadanie posiada relację, która blokuje przesunięcie go + poza tę datę + title: + button_test: Przycisk testowy (tylko dla testowania) + day_zoom: Powiększ do skali dni + jump_today: Wyświetl linię czasu dla dzisiaj + month_zoom: Powiększ do skali miesiąca + print_fit: Skaluj Gantta na jedną stronę + week_zoom: Powiększ do skali tygodnia + easy_printable_templates_categories: + easy_gantt: Easy Gantt + easy_query: + name: + easy_gantt_easy_issue_query: Easy Gantt + easy_gantt_easy_project_query: Globalny Easy Gantt + error_easy_gantt_view_permission: Nie masz uprawnień do zobaczenia Easy Gantt + error_epm_easy_gantt_already_active: Easy Gantt jest już aktywny na stronie + field_easy_gantt_default_zoom: Domyślne powiększenie + field_easy_gantt_relation_delay_in_workdays: Opóźnienie relacji w dniach roboczych + field_easy_gantt_show_holidays: Pokaż urlopy + field_easy_gantt_show_project_progress: Pokaż postęp projektu + field_easy_gantt_show_task_soonest_start: Pokaż najwcześniejsze rozpoczęcie + field_keep_link_delay_in_drag: Stałe opóźnienie linku podczas przeciągania + field_relation: Relacja + heading_delay_popup: Określ opóźnienie w dniach + heading_demo_feature_popup: Wkrótce + heading_easy_gantts_issues: Bezpłatny Easy Gantt + label_easy_gantt: Easy Gantt + label_easy_gantt_critical_path: Ścieżka krytyczna + label_easy_gantt_settings: Ustawienia Gantta + label_filter_group_easy_gantt_easy_issue_query: Pole zadania + label_finish_to_finish: Zakończ aby zakończyć + label_parent_issue_plural: Zadania nadrzędne + label_start_to_finish: Rozpocznij aby zakończyć + label_start_to_start: Zacznij aby zacząć + link_easy_gantt_plugin: https://www.easyredmine.com/easy-gantt-upgrade-to-pro + permission_edit_easy_gantt: Edytuj Easy Gantt i Zarządzanie zasobami w projekcie + permission_edit_global_easy_gantt: Edytuj globalne Easy Gantt i Zarządzanie zasobami + permission_edit_personal_easy_gantt: Edytuj personalne Easy Gantt i Zarządzanie + zasobami + permission_view_easy_gantt: Zobacz Easy Gantt i Zarządzanie zasobami w projekcie + permission_view_global_easy_gantt: Zobacz globalne Easy Gantt i Zarządzanie zasobami + permission_view_personal_easy_gantt: Zobacz personalne Easy Gantt i Zarządzanie + zasobami + project_default_page: + easy_gantt: Easy Gantt + project_module_easy_gantt: Easy Gantt + text_demo_feature_popup: Ta funkcja będzie dostępna wkrótce. + text_easy_gantt_footer: Redmine Gantt powered by Easy + text_easy_gantt_keep_link_delay_in_drag: Utrzymaj stałe opóźnienie relacji gdy zadanie + jest przeciągane z powrotem + text_easy_gantt_print_easy_gantt_current: Pokaż obecny Easy Gantt (działa tylko + na stronie, na której jest załadowany Easy Gantt) + text_easy_gantt_relation_delay_in_workdays: Nie bierz pod uwagę nieroboczych dni + w opóźnieniu relacji + text_easy_gantt_show_holidays: Pokaż urlop obecnego użytkownika na Gantt (działa + jedynie w Easy Redmine) + text_easy_gantt_show_project_progress: Pokaż zakończony procent na pasku projektu + (pobranie może być wolne) + text_easy_gantt_show_task_soonest_start: Pokaż najbliższe ważne daty dla zadań określonych + przez relacje lub zadanie nadrzędne + title_easy_gantt_settings: Easy Gantt diff --git a/easy_gantt/config/locales/pt-BR.yml b/easy_gantt/config/locales/pt-BR.yml new file mode 100644 index 0000000..79d7105 --- /dev/null +++ b/easy_gantt/config/locales/pt-BR.yml @@ -0,0 +1,161 @@ +--- +pt-BR: + button_print: Imprimir + button_project_menu_easy_gantt: Diagrama de Easy Gantt + button_top_menu_easy_gantt: Projetos diagrama de Gantt + button_use_actual_delay: Use atraso real + easy_gantt: + button: + close_all: Fechar todos + close_all_parent_issues: Fechar todas tarefas principais + create_baseline: Linhas de base + critical_path: Trajeto crítico + day_zoom: Dias + delayed_project_filter: Filtrar Projetos Adiados + jump_today: Saltar para hoje + load_sample_data: Carregar dados de amostra + month_zoom: Meses + print_fit: Ajustar à página + problem_finder: Problemas + reload: Recarregar (salvar) + remove_delay: Retirar atraso + resource_management: Gestão de recursos + tool_panel: Ferramentas + week_zoom: Semanas + critical_path: + disabled: Desativado + last: Último + last_text: Tarefas que não devem ser adiada + longest: Mais longo + longest_text: Mostrar sequência mais longa + errors: + duplicate_link: Não é possível duplicar link + fresh_milestone: Não é possível adicionar tarefa a marcos não-salvos. Primeiro + salve o marco. + link_target_new: Não é possível criar tarefas não salvas. Salve as mudanças + primeiro. + link_target_readonly: Tarefa alvo somente para leitura + loop_link: Não é possível criar link em repetição + no_rest_api: Diagrama de Easy Gantt precisa de REST API ativado. Ligar em Administração + ->Configurações -> API -> Habilitar serviço web REST + overdue: deve terminar no futuro ou já deve estar fechado + overmile: Deve terminar em %{effective_date} a fim de manter o marco + short_delay: deve ser mais longo que %{diff} dias + too_short: Não contém suficiente dias para %{rest} horas de tempo estimado + unsaved_parent: Não é possível adicionar tarefa ao principal não-salvos. Primeiro + salvar as alterações. + unsupported_link_type: Tipo de link não assistido + gateway: + entity_save_failed: "%{entityType} %{entityName} falha ao salvar devido a erro" + send_failed: Falha na solicitação + label_pro_upgrade: Atualizar para a versão PRO + link_dir: + link_end: como precedendo + link_start: como a seguir + popup: + add_task: + heading: Adicionar recurso tarefa + text: Novo recurso tarefa/marco está disponível apenas em Easy Gantt PRO Check for update! + baseline: + heading: Caracteristica de linhas de base + text: Caracteristica de linhas de base está disponível em Easy Gantt PRO Verifique se + há atualização! + critical: + heading: Caracteristica de trajeto crítico + text: Recurso de trajeto crítico somente está disponível em Easy Gantt PRO + Verificar + se há atualização! + resource: + heading: Recurso gerente de recursos + text: Recurso gerente de recursos só está disponível em + Easy Redmine + reload_modal: + label_errors: Erros + text_reload_appeal: Você quer ignorar itens que não foram salvos e recarregar + os dados do servidor? + title: Gantt não salvou corretamente + sample_global_free: + text: Dados de amostra não podem ser fechados. Projetos gerais Gantt só estão + disponíveis na versão PRO + video: + text: Aqui você pode ver a maioria das características de projetos gerais + Gantt + title: Video de amostra de projetos gerais Gantt + video_id: EiiqBrrY4m4 + sample: + close_label: Fechar a janela e carregar os dados reais de projeto + header: Os dados da amostra estão carregados! + text: Nós preparamos alguns exemplos de dados para você experimentar + todos os recursos Easy Gantt sem estresse . Nós também queremos encorajá-lo + a assistir a um vídeo gia mostrando ajustes úteis. Ao fechar esta janela irá + carregar os dados de projeto real. + video_label: Assistir video tutorial + video: + text: Aqui você pode ver a maioria das funcionalidades deste módulo Gantt + title: Video exemplo + video_id: UHgqfsrD59Q + text_blocker_milestone: Marco está bloqueando tarefa a ser deslocado para além + desta data + text_blocker_move_pre: Tarefa tem relação que bloqueia mover tarefa para além + desta data + title: + button_test: Botão de teste (somente para teste) + day_zoom: Aumentar a escala de dia + jump_today: Exibir linha do tempo em hoje + month_zoom: Fazer zoom a escala mês + print_fit: Escale o Gantt para uma página + week_zoom: Fazer zoom a escala semana + easy_printable_templates_categories: + easy_gantt: Easy Gantt + easy_query: + name: + easy_gantt_easy_issue_query: Easy Gantt + easy_gantt_easy_project_query: Easy Gantt Global + error_easy_gantt_view_permission: Você não tem permissão para visualizar o Easy + Gantt + error_epm_easy_gantt_already_active: Easy Gantt na página já está ativado. + field_easy_gantt_default_zoom: Padrão de zoom + field_easy_gantt_relation_delay_in_workdays: Relação atrasos nos dias úteis + field_easy_gantt_show_holidays: Mostrar férias + field_easy_gantt_show_project_progress: Mostrar progresso do projeto + field_easy_gantt_show_task_soonest_start: Mostrar início mais breve + field_keep_link_delay_in_drag: Constante atraso de conexão durante o arrasto + field_relation: Relação + heading_delay_popup: Definir atraso em dias + heading_demo_feature_popup: Em breve + heading_easy_gantts_issues: Easy Gantt grátis + label_easy_gantt: Easy Gantt + label_easy_gantt_critical_path: Trajeto crítico + label_easy_gantt_settings: Configurações Gantt + label_filter_group_easy_gantt_easy_issue_query: Campos de tarefas + label_finish_to_finish: Final ao final + label_parent_issue_plural: Problemas principais + label_start_to_finish: Início ao final + label_start_to_start: Início ao início + link_easy_gantt_plugin: https://www.easyredmine.com/easy-gantt-upgrade-to-pro + permission_edit_easy_gantt: Editar Easy Gantt & Gestão de recursos em um projeto + permission_edit_global_easy_gantt: Editar Easy Gantt & Gestão de recursos + permission_edit_personal_easy_gantt: Editar Easy Gant pessoal & Gestão de recursos + permission_view_easy_gantt: Visualizar Easy Gantt & Gestão de recursos em um projeto + permission_view_global_easy_gantt: Visualizar Easy Gantt global & Gestão de recursos + permission_view_personal_easy_gantt: Visualizar Easy Gant geral & Gestão de recursos + project_default_page: + easy_gantt: Easy Gantt + project_module_easy_gantt: Easy Gantt + text_demo_feature_popup: Esta funcionalidade estará disponível em breve. + text_easy_gantt_footer: Redmine Gantt distribuído por Easy + text_easy_gantt_keep_link_delay_in_drag: Manter atraso de relação constante enquanto + a tarefa é arrastado de volta + text_easy_gantt_print_easy_gantt_current: Mostrar Easy Gantt atual (somente funciona + a partir da página onde o Easy Gantt for carregado) + text_easy_gantt_relation_delay_in_workdays: Não contar os dias não úteis em relação + ao atraso + text_easy_gantt_show_holidays: Mostrar férias atuais de usuário em Gantt (funciona + em Easy Redmine) + text_easy_gantt_show_project_progress: Mostrar porcentagem completa na barra do + projeto (pode ser lento para carregar) + text_easy_gantt_show_task_soonest_start: Mostrar datas válidas mais baixas para + as tarefas definidas por equações ou principal + title_easy_gantt_settings: Easy Gantt diff --git a/easy_gantt/config/locales/pt.yml b/easy_gantt/config/locales/pt.yml new file mode 100644 index 0000000..e40c485 --- /dev/null +++ b/easy_gantt/config/locales/pt.yml @@ -0,0 +1,2 @@ +--- +pt: diff --git a/easy_gantt/config/locales/ro.yml b/easy_gantt/config/locales/ro.yml new file mode 100644 index 0000000..583d9e1 --- /dev/null +++ b/easy_gantt/config/locales/ro.yml @@ -0,0 +1,19 @@ +--- +ro: + button_print: Imprimare + button_project_menu_easy_gantt: Gantt Simplu + button_top_menu_easy_gantt: Proiecte Gantt + easy_gantt: + button: + close_all_parent_issues: Închide toate sarcinile sursă + day_zoom: Zile + jump_today: Sari la azi + month_zoom: Luni + reload: Reincarca (salveaza) + tool_panel: Instrumente + week_zoom: Săptămani + field_easy_gantt_default_zoom: Zoom implicit + field_easy_gantt_show_holidays: Arată vacanţe + field_easy_gantt_show_project_progress: Arată progresul proiectului + field_relation: Relaţie + label_easy_gantt: Gantt Simplu diff --git a/easy_gantt/config/locales/ru.yml b/easy_gantt/config/locales/ru.yml new file mode 100644 index 0000000..35ced1e --- /dev/null +++ b/easy_gantt/config/locales/ru.yml @@ -0,0 +1,185 @@ +--- +ru: + button_print: Печать + button_project_menu_easy_gantt: Диаграмма Ганта Easy + button_top_menu_easy_gantt: Диаграмма Ганта + button_use_actual_delay: Использовать фактическую задержку + easy_gantt_toolbar: + day: Дни + month: Месяцы + week: Недели + easy_gantt: + buton_create_baseline: Базовый план + button_critical_path: Критический путь + button_resource_management: Управление персоналом + button: + close_all: Закрыть все + close_all_parent_issues: Закрыть все родительские задачи + create_baseline: Базовый план + critical_path: Критический путь + day_zoom: Дни + delayed_project_filter: Фильтр Просроченные Проекты + jump_today: Сегодня + load_sample_data: Загрузить демо-данные + month_zoom: Месяцы + print_fit: Подогнать + problem_finder: Ошибки + reload: Перезагрузить (сохранить) + remove_delay: Удалить задержку + resource_management: Управление персоналом + tool_panel: Инструменты + week_zoom: Недели + critical_path: + disabled: Отключен + last: Короткий + last_text: Первоочередные задачи + longest: Длинный + longest_text: Самая длинная последовательность задач + error_overmile: Дата выполнения задачи должна находиться до даты Вехи (Версии) + error_too_short: Залача слишком короткая для рассчета запланированного времени + errors: + duplicate_link: Нельзя создать двойную ссылку + fresh_milestone: Нельзя добавить задачу к несохраненной вехе. Сначала сохраните + веху. + link_target_new: Нельзя добавить связь к несохраненной задаче. Сохраните изменения. + link_target_readonly: Задача доступна только для чтения + loop_link: Невозможно создать цикличную связь + no_rest_api: Для работы Easy Gantt требуется REST API. Перейдите в Администрирование-> + Настройки -> API -> Включить веб-сервис REST + overdue: должна заканчиваться в будущем или должна быть уже закрыта + overmile: Дата выполнения задачи должна находиться до даты Вехи (Версии) + short_delay: должна быть длиннее на %{diff} дней + too_short: недостаточно дней для %{rest} часов запланированного времени + unsaved_parent: Невозможно добавить задачу к несохраненной родительской задаче. + Пожалуйста, сохраните сначала родительскую задачу. + unsupported_link_type: Данный тип связи не поддерживатеся + gateway: + entity_save_failed: "%{entityType} %{entityName} не удалось сохранить из-за + ошибки" + send_failed: Ошибка запроса + label_pro_upgrade: Обновление до PRO-версии + link_dir: + link_end: предшествует + link_start: следует за + popup: + add_task: + heading: Функция "Добавить задачу" + text: "Новая задача/веха доступна только в Easy Gantt PRO \r\nCheck + for update!" + baseline: + heading: Функция "Базовый план" + text: "Базовый план доступен только в Easy Gantt PRO \r\nCheck + for update!" + critical: + heading: Фунция - Критический путь + text: "Критический путь доступен только в Easy Гант PRO \r\nПроверьте + обновления!" + resource: + heading: Функция управления персоналом + text: Resource management доступен только в + Easy Redmine + reload_modal: + label_errors: Ошибки + text_reload_appeal: Вы действительно хотите не сохранить элементы и перезагрузить + данные с сервера? + title: Не удалось правильно сохранить Гант + sample_global_free: + text: Демо-данные не могут быть удалены. Гант для всех проектов доступен только + в PRO версии. + video: + text: Здесть вы можете увидеть большинство возможностей Ганта для всех проектов + title: Видео-демонстрация возможностей Ганта для всех проектов. + video_id: EiiqBrrY4m4 + sample: + close_label: Закройте данное окно и загрузите реальные данные по проекту. + header: Демо-данные загружены! + text: Мы подготовили специально демо-данные - попробуйте все возможности + Easy Гант без ущерба для Вас!. Мы также предлагаем вам просмотреть + видео-инструкцию, показывающее особенности работы. При закрытии данного окна + будут загружены реальные данные по проекту. + video_label: Смотреть видео-инструкцию + video: + text: Здесь вы можете ознакомиться с большинством функций модуля Ганта. + title: Демо-видео + video_id: UHgqfsrD59Q + soon: + add_task: + heading: Функция Дабавить задачу + text: Эта функция скоро появится в продукции. Проверить + доступность! + baseline: + heading: Функция Базовый План + text: Функция "Базовый План" скоро появится в продукции. Проверить + доступность!" + resource: + heading: Функция Управление Персоналом + text: Функция Управление Персоналом доступна только в + Easy Redmine + text_blocker_milestone: Веха блокирует перемещение задачи вне данной даты + text_blocker_move_pre: У данной задачи есть связь, которая не позволяет передвинуть + ее вне этой даты + title: + button_test: Тест (только для тестирования) + day_zoom: Отображение временной шкалы по дням + jump_today: Показать на графике сегодня + month_zoom: Отображение временной шкалы по месяцам + print_fit: Вписать диаграмму в страницу + week_zoom: Отображение временной шкалы по неделям + easy_printable_templates_categories: + easy_gantt: Диаграмма Ганта Easy + easy_query: + name: + easy_gantt_easy_issue_query: Easy Гант + easy_gantt_easy_project_query: Глобальный Easy Гант + error_easy_gantt_view_permission: У вас нет доступа для просмотра Easy Gantt + error_epm_easy_gantt_already_active: Easy Гант уже присутствует на этой странице + field_easy_gantt_default_zoom: Масштаб по умолчанию + field_easy_gantt_relation_delay_in_workdays: Задержка в рабочих днях + field_easy_gantt_show_holidays: Показать нерабочие дни + field_easy_gantt_show_project_progress: Показать прогресс проекта + field_easy_gantt_show_task_soonest_start: Показать скорейшее начало + field_keep_link_delay_in_drag: Сохранять задержку при перемещении + field_relation: Связь + heading_delay_popup: Задать задержку (в днях) + heading_demo_feature_popup: Скоро в продукции + heading_easy_gantts_issues: Гант по задачам + label_easy_gantt: Easy Гант + label_easy_gantt_critical_path: Критический путь + label_easy_gantt_settings: Настройки Ганта + label_filter_group_easy_gantt_easy_issue_query: Поля задачи + label_finish_to_finish: Финиш --> Финиш + label_parent_issue_plural: Родительские задачи + label_start_to_finish: Начало-Окончание + label_start_to_start: Старт --> Старт + link_easy_gantt_footer: https://www.easyredmine.com/redmine-gantt-plugin + link_easy_gantt_plugin: https://www.easyredmine.com/easy-gantt-upgrade-to-pro + permission_edit_easy_gantt: Редактировать Easy Гант & Resource management на проекте + permission_edit_global_easy_gantt: Редактировать глобальный Easy Гант & Resource + management + permission_edit_personal_easy_gantt: Реадактировать персональный Easy Gantt & Resource + management + permission_view_easy_gantt: Отображать Easy Гант & Resource management на проекте + permission_view_global_easy_gantt: Отображать глобальный Easy Гант & Resource management + permission_view_personal_easy_gantt: Отображать персональный Easy Гант & Resource + management + project_default_page: + easy_gantt: Easy Гант + project_module_easy_gantt: Easy Гант + text_demo_feature_popup: Эта функия будет скоро доступна + text_easy_gantt_footer: Redmine Гант от Easy + text_easy_gantt_keep_link_delay_in_drag: Сохранять постоянную задержку при перетаскивании + задачи назад + text_easy_gantt_print_easy_gantt_current: Показать текущую диаграмму (только для + страниц с модулем Easy Gantt) + text_easy_gantt_relation_delay_in_workdays: Не учитывать нерабочие дни при запаздывании + text_easy_gantt_show_holidays: Показать выходные на Ганте (работают только в Easy + Redmine) + text_easy_gantt_show_project_progress: Показать процент выполнения на диаграмме + (может потребовать дополнительного времени) + text_easy_gantt_show_task_soonest_start: Показать минимально допустимые даты для + связанных задач или подзадач + title_easy_gantt_settings: Easy Гант diff --git a/easy_gantt/config/locales/sk.yml b/easy_gantt/config/locales/sk.yml new file mode 100644 index 0000000..354e579 --- /dev/null +++ b/easy_gantt/config/locales/sk.yml @@ -0,0 +1,2 @@ +--- +sk: diff --git a/easy_gantt/config/locales/sl.yml b/easy_gantt/config/locales/sl.yml new file mode 100644 index 0000000..31bb26c --- /dev/null +++ b/easy_gantt/config/locales/sl.yml @@ -0,0 +1,2 @@ +--- +sl: diff --git a/easy_gantt/config/locales/sq.yml b/easy_gantt/config/locales/sq.yml new file mode 100644 index 0000000..9c092f0 --- /dev/null +++ b/easy_gantt/config/locales/sq.yml @@ -0,0 +1,2 @@ +--- +sq: diff --git a/easy_gantt/config/locales/sr-YU.yml b/easy_gantt/config/locales/sr-YU.yml new file mode 100644 index 0000000..24e1796 --- /dev/null +++ b/easy_gantt/config/locales/sr-YU.yml @@ -0,0 +1,2 @@ +--- +sr-YU: diff --git a/easy_gantt/config/locales/sr.yml b/easy_gantt/config/locales/sr.yml new file mode 100644 index 0000000..43a1014 --- /dev/null +++ b/easy_gantt/config/locales/sr.yml @@ -0,0 +1,2 @@ +--- +sr: diff --git a/easy_gantt/config/locales/sv.yml b/easy_gantt/config/locales/sv.yml new file mode 100644 index 0000000..ed425ea --- /dev/null +++ b/easy_gantt/config/locales/sv.yml @@ -0,0 +1,2 @@ +--- +sv: diff --git a/easy_gantt/config/locales/th.yml b/easy_gantt/config/locales/th.yml new file mode 100644 index 0000000..3596914 --- /dev/null +++ b/easy_gantt/config/locales/th.yml @@ -0,0 +1,2 @@ +--- +th: diff --git a/easy_gantt/config/locales/tr.yml b/easy_gantt/config/locales/tr.yml new file mode 100644 index 0000000..3be79e7 --- /dev/null +++ b/easy_gantt/config/locales/tr.yml @@ -0,0 +1,2 @@ +--- +tr: diff --git a/easy_gantt/config/locales/zh-TW.yml b/easy_gantt/config/locales/zh-TW.yml new file mode 100644 index 0000000..44cf0f8 --- /dev/null +++ b/easy_gantt/config/locales/zh-TW.yml @@ -0,0 +1,2 @@ +--- +zh-TW: diff --git a/easy_gantt/config/locales/zh.yml b/easy_gantt/config/locales/zh.yml new file mode 100644 index 0000000..68de3f8 --- /dev/null +++ b/easy_gantt/config/locales/zh.yml @@ -0,0 +1,138 @@ +--- +zh: + button_print: 打印 + button_project_menu_easy_gantt: Easy Gantt + button_top_menu_easy_gantt: 项目甘特图 + button_use_actual_delay: 采用实际延迟时间 + easy_gantt: + button: + close_all: 关闭全部 + close_all_parent_issues: 关闭所有上一级任务 + create_baseline: 原始时间线 + critical_path: 关键路径 + day_zoom: 天 + delayed_project_filter: 筛选延误的项目 + jump_today: 转到今天 + load_sample_data: 载入示例数据 + month_zoom: 月 + print_fit: 适应页面大小 + problem_finder: 问题 + reload: 保存 + remove_delay: 删除延误 + resource_management: 资源管理 + tool_panel: 工具栏 + week_zoom: 周 + critical_path: + disabled: 禁用 + last: 最近的 + last_text: 任务不能推迟 + longest: 最长 + longest_text: 显示最长的任务队列 + errors: + duplicate_link: 不能创建重复的连接 + fresh_milestone: 不能给未保存的里程碑添加任务, 请先保存里程碑 + link_target_new: 不能为未保存任务创建连接, 请先保存任务 + link_target_readonly: 只读的目标任务 + loop_link: 不能创建循环的连接 + no_rest_api: Easy Gantt需要将REST API开启. 请在管理(Administration)->设置 -> API-> 开启REST网络服务中进行设置. + overdue: 结束日该是将来的日期, 或者应该是已经完成了. + overmile: 必须在 %{effective_date}结束, 以达到里程碑进度要求 + short_delay: 应当增加 %{diff} 天 + too_short: 预计要%{rest} 小时, 所给天数不够. + unsaved_parent: 上一级任务未保存, 不能在其下添加任务. 请先保存 + unsupported_link_type: 不支持的连接类型 + gateway: + entity_save_failed: "%{entityType} %{entityName}保存失败" + send_failed: 请求失败 + label_pro_upgrade: 升级到专业版 + link_dir: + link_end: 同上一次 + link_start: 同下一个 + popup: + add_task: + heading: 添加任务属性 + text: 新任务/里程碑的特性只有有 Easy Gantt PRO(专业版)内可用. 访问 进行更新! + baseline: + heading: Baseline的属性 + text: 原始时间线(Baseline)功能仅在Easy Gantt 专业版中可用 检查更新! + critical: + heading: 关键路径(Path)的属性 + text: 关键路径的属性只有在Easy Gantt 专业版内可用. 访问进行更新! +  + resource: + heading: 资源管理器的属性 + text: 资源管理器的属性只在 Easy Redmine + 中可用 + reload_modal: + label_errors: 错误 + text_reload_appeal: 忽略未保存数据, 重新从服务器下载数据? + title: Gantt图没有保存成功 + sample_global_free: + text: 不能关闭示例数据. 所有项目的Gantt只有在专业版中可用. + video: + text: 在这里可以查看所有项目的甘特图Gantt的属性 + title: 所有项目的Gantt示例视频 + video_id: EiiqBrrY4m4 + sample: + close_label: 关闭窗口, 并下载真正的项目数据 + header: 示例数据下载完成 + text: 查看一些示例数据来轻松了解Easy Gantt的特性. 也可以观看演示视频来进一步提高, 关闭窗口将下载真正的项目数据. + video_label: 观看视频教程 + video: + text: 在这里可以了解Gantt模块的大部份特性 + title: 示例视频 + video_id: UHgqfsrD59Q + text_blocker_milestone: 里程碑不允许该任务移到此日期之后 + text_blocker_move_pre: 由于一些关系的限制, 该任务不能移到此日期之后 + title: + button_test: 测试按钮(仅作测试用) + day_zoom: 放大到按天显示 + jump_today: 显示今天的时间安排 + month_zoom: 按月度显示 + print_fit: 缩放甘特图到一页大小 + week_zoom: 按周显示 + easy_printable_templates_categories: + easy_gantt: Easy 甘特图 + easy_query: + name: + easy_gantt_easy_issue_query: Easy Gantt + easy_gantt_easy_project_query: '' + error_easy_gantt_view_permission: 你没有查看Easy 甘特图的权限 + error_epm_easy_gantt_already_active: 本页内的Easy gantt 已经生效! + field_easy_gantt_default_zoom: 默认时间单位 + field_easy_gantt_relation_delay_in_workdays: 工作日内延期关系 + field_easy_gantt_show_holidays: 显示假日 + field_easy_gantt_show_project_progress: 显示项目进度 + field_easy_gantt_show_task_soonest_start: 显示最近的开始时间 + field_keep_link_delay_in_drag: 拖放时使用固定时间延迟 + field_relation: 关系 + heading_delay_popup: 设置延迟天数 + heading_demo_feature_popup: 马上就绪 + heading_easy_gantts_issues: Easy Gantt Free + label_easy_gantt: Easy Gantt + label_easy_gantt_critical_path: 关键路径 + label_easy_gantt_settings: Easy Gantt 设置 + label_filter_group_easy_gantt_easy_issue_query: 任务 + label_finish_to_finish: 以最后结束 + label_parent_issue_plural: 上一级的问题 + label_start_to_finish: 从结尾开始 + label_start_to_start: 从头开始 + link_easy_gantt_plugin: https://www.easyredmine.com/easy-gantt-upgrade-to-pro + permission_edit_easy_gantt: 编辑项目中的Easy Gantt和资源管理 + permission_edit_global_easy_gantt: 编辑通用Easy Gantt 和资源管理 + permission_edit_personal_easy_gantt: 编辑个人定制 Easy Gantt & Resource management + permission_view_easy_gantt: 查看项目中的Easy Gantt和资源管理 + permission_view_global_easy_gantt: 查看通用的 Easy Gantt 和资源管理 + permission_view_personal_easy_gantt: 查看个人定制Easy Gantt & Resource management + project_default_page: + easy_gantt: Easy Gantt + project_module_easy_gantt: Easy Gantt + text_demo_feature_popup: 该特性将很快生效 + text_easy_gantt_footer: 由Easy开发的 Redmine Gantt + text_easy_gantt_keep_link_delay_in_drag: 将任务拖放回来时, 保持原有的关系和延误情况 + text_easy_gantt_print_easy_gantt_current: 显示当前的Easy 甘特图(只有在已载入Easy 甘特图的页面有效) + text_easy_gantt_relation_delay_in_workdays: 在相关延误天数计算时, 不要计入非工作日 + text_easy_gantt_show_holidays: 在Gantt上显示当前用户的假日安排(仅在Easy Redmine上可用) + text_easy_gantt_show_project_progress: 在项目进度条上显示完成比例(可能导致加载较慢) + text_easy_gantt_show_task_soonest_start: 显示由关系或其前提决定的任务的最近有效日期 + title_easy_gantt_settings: Easy Gantt diff --git a/easy_gantt/config/routes.rb b/easy_gantt/config/routes.rb new file mode 100644 index 0000000..9f1d9f1 --- /dev/null +++ b/easy_gantt/config/routes.rb @@ -0,0 +1,13 @@ +# Because of plugin deactivations +if Redmine::Plugin.installed?(:easy_gantt) + get '(projects/:project_id)/easy_gantt' => 'easy_gantt#index', as: 'easy_gantt' + + scope format: true, defaults: { format: 'json' }, constraints: { format: 'json' } do + scope 'projects/:project_id' do + get 'easy_gantt/issues' => 'easy_gantt#issues', as: 'issues_easy_gantt' + put 'easy_gantt/relation/:id' => 'easy_gantt#change_issue_relation_delay', as: 'relation_easy_gantt' + get 'easy_gantt/project_issues' => 'easy_gantt#project_issues', as: 'project_issues_easy_gantt' + end + get 'easy_gantt/projects' => 'easy_gantt#projects', as: 'projects_easy_gantt' + end +end diff --git a/easy_gantt/db/migrate/20170213152215_add_default_printable_template.rb b/easy_gantt/db/migrate/20170213152215_add_default_printable_template.rb new file mode 100644 index 0000000..b753c9f --- /dev/null +++ b/easy_gantt/db/migrate/20170213152215_add_default_printable_template.rb @@ -0,0 +1,10 @@ +class AddDefaultPrintableTemplate < ActiveRecord::Migration[6.1] + + def up + # deprecated + end + + def down + end + +end diff --git a/easy_gantt/db/migrate/20170224134615_update_rest_api_settings.rb b/easy_gantt/db/migrate/20170224134615_update_rest_api_settings.rb new file mode 100644 index 0000000..37c6621 --- /dev/null +++ b/easy_gantt/db/migrate/20170224134615_update_rest_api_settings.rb @@ -0,0 +1,10 @@ +class UpdateRestApiSettings < ActiveRecord::Migration[6.1] + + def up + Setting.where(name: 'rest_api_enabled').update_all(value: '1') + end + + def down + end + +end diff --git a/easy_gantt/init.rb b/easy_gantt/init.rb new file mode 100644 index 0000000..cf464e1 --- /dev/null +++ b/easy_gantt/init.rb @@ -0,0 +1,21 @@ +Redmine::Plugin.register :easy_gantt do + name 'Easy Gantt plugin' + author 'Easy Software Ltd' + url 'https://www.easysoftware.com' + author_url 'https://www.easysoftware.com' + description 'Cool gantt for redmine' + version '3.0' + + requires_redmine version_or_higher: '6.0.0' + + settings partial: 'settings/easy_gantt', default: { + 'critical_path' => 'last', + 'default_zoom' => 'day', + 'show_project_progress' => '1', + 'show_lowest_progress_tasks' => '0', + 'show_task_soonest_start' => '0', + 'relation_delay_in_workdays' => '0' + } +end + +require_relative 'after_init' \ No newline at end of file diff --git a/easy_gantt/lib/easy_gantt.rb b/easy_gantt/lib/easy_gantt.rb new file mode 100644 index 0000000..7d84e51 --- /dev/null +++ b/easy_gantt/lib/easy_gantt.rb @@ -0,0 +1,34 @@ +module EasyGantt + + def self.non_working_week_days(user=nil) + if user.is_a?(Integer) + user = Principal.find_by(id: user) + elsif user.nil? + user = User.current + end + + working_days = user.try(:current_working_time_calendar).try(:working_week_days) + working_days = Array(working_days).map(&:to_i) + + if working_days.any? + (1..7).to_a - working_days + else + Array(Setting.non_working_week_days).map(&:to_i) + end + end + + # Experimental function + def self.load_fixed_delay? + false + end + + def self.easy_gantt_pro? + Redmine::Plugin.installed?(:easy_gantt_pro) + end + + def self.easy_baseline? + Redmine::Plugin.installed?(:easy_baseline) + end + +end + \ No newline at end of file diff --git a/easy_gantt/lib/easy_gantt/application_helper_patch.rb b/easy_gantt/lib/easy_gantt/application_helper_patch.rb new file mode 100644 index 0000000..e864a62 --- /dev/null +++ b/easy_gantt/lib/easy_gantt/application_helper_patch.rb @@ -0,0 +1,17 @@ +module EasyGantt + module ApplicationHelperPatch + + def self.prepended(base) + base.class_eval do + + def link_to_project_with_easy_gantt(project, options = {}) + { controller: 'easy_gantt', action: 'index', project_id: project } + end + + end + end + + end +end + +ApplicationHelper.prepend EasyGantt::ApplicationHelperPatch diff --git a/easy_gantt/lib/easy_gantt/hooks.rb b/easy_gantt/lib/easy_gantt/hooks.rb new file mode 100644 index 0000000..3d695de --- /dev/null +++ b/easy_gantt/lib/easy_gantt/hooks.rb @@ -0,0 +1,7 @@ +module EasyGantt + class Hooks < Redmine::Hook::ViewListener + def helper_options_for_default_project_page(context={}) + context[:default_pages] << 'easy_gantt' if context[:enabled_modules].include?('easy_gantt') + end + end +end diff --git a/easy_gantt/lib/easy_gantt/issue_patch.rb b/easy_gantt/lib/easy_gantt/issue_patch.rb new file mode 100644 index 0000000..ad9e3e3 --- /dev/null +++ b/easy_gantt/lib/easy_gantt/issue_patch.rb @@ -0,0 +1,50 @@ +module EasyGantt + module IssuePatch + + def self.prepended(base) + base.include InstanceMethods + + base.class_eval do + + scope :gantt_opened, lambda { + joins(:status).where(IssueStatus.table_name => { is_closed: false }) + } + + end + end + + module InstanceMethods + + def gantt_editable?(user=nil) + user ||= User.current + + (user.allowed_to?(:edit_easy_gantt, project) || + user.allowed_to_globally?(:edit_global_easy_gantt) || + (assigned_to_id == user.id && + user.allowed_to_globally?(:edit_personal_easy_gantt))) && + user.allowed_to?(:manage_issue_relations, project) && + user.allowed_to?(:edit_issues, project) + end + + def gantt_latest_due + if @gantt_latest_due.nil? + dates = relations_from.map{|relation| relation.gantt_previous_latest_start } + + p = @parent_issue || parent + if p && Setting.parent_issue_dates == 'derived' + dates << p.gantt_latest_due + end + + @gantt_latest_due = dates.compact.max + end + + @gantt_latest_due + end + + end + + end +end + +Issue.prepend EasyGantt::IssuePatch + diff --git a/easy_gantt/lib/easy_gantt/issue_relation_patch.rb b/easy_gantt/lib/easy_gantt/issue_relation_patch.rb new file mode 100644 index 0000000..e3078b6 --- /dev/null +++ b/easy_gantt/lib/easy_gantt/issue_relation_patch.rb @@ -0,0 +1,70 @@ +# +# THIS FILE MUST BE LOADED BEFORE IssueQuery !!! +# +module EasyGantt + module IssueRelationPatch + + def self.prepended(base) + base.include InstanceMethods + + start_to_start = 'start_to_start' + finish_to_finish = 'finish_to_finish' + start_to_finish = 'start_to_finish' + + new_types = base::TYPES.merge( + + start_to_start => { + name: :label_start_to_start, + sym_name: :label_start_to_start, + order: 20, + sym: start_to_start + }, + + finish_to_finish => { + name: :label_finish_to_finish, + sym_name: :label_finish_to_finish, + order: 21, + sym: finish_to_finish + }, + + start_to_finish => { + name: :label_start_to_finish, + sym_name: :label_start_to_finish, + order: 22, + sym: start_to_finish + } + + ) + + base.class_eval do + const_set :TYPE_START_TO_START, start_to_start + const_set :TYPE_FINISH_TO_FINISH, finish_to_finish + const_set :TYPE_START_TO_FINISH, start_to_finish + + remove_const :TYPES + const_set :TYPES, new_types.freeze + + inclusion_validator = _validators[:relation_type].find{|v| v.kind == :inclusion} + inclusion_validator.instance_variable_set(:@delimiter, new_types.keys) + end + end + + module InstanceMethods + + # +---------+ (5) +---------+ + # | Issue 1 |--------->| Issue 2 | + # +---------+ +---------+ + # (issue_from) (issue_to) + # + def gantt_previous_latest_start + if (IssueRelation::TYPE_PRECEDES == relation_type) && delay && issue_to && (issue_to.start_date || issue_to.due_date) + (issue_to.start_date || issue_to.due_date) - 1 - delay + end + end + + end + + end +end + +IssueRelation.prepend EasyGantt::IssueRelationPatch diff --git a/easy_gantt/lib/easy_gantt/project_patch.rb b/easy_gantt/lib/easy_gantt/project_patch.rb new file mode 100644 index 0000000..4da7280 --- /dev/null +++ b/easy_gantt/lib/easy_gantt/project_patch.rb @@ -0,0 +1,157 @@ +module EasyGantt + module ProjectPatch + + def self.prepended(base) + base.extend ClassMethods + base.include InstanceMethods + end + + module InstanceMethods + + def gantt_editable?(user=nil) + user ||= User.current + + (user.allowed_to?(:edit_easy_gantt, self) || + user.allowed_to_globally?(:edit_global_easy_gantt)) && + user.allowed_to?(:edit_project, self) + end + + def gantt_reschedule(days) + transaction do + all_issues = Issue.joins(:project).where(gantt_subprojects_conditions) + all_issues.update_all("start_date = start_date + INTERVAL '#{days}' DAY," + + "due_date = due_date + INTERVAL '#{days}' DAY") + + all_versions = Version.joins(:project).where(gantt_subprojects_conditions) + all_versions.update_all("effective_date = effective_date + INTERVAL '#{days}' DAY") + + Redmine::Hook.call_hook(:model_project_gantt_reschedule, project: project, days: days, all_issues: all_issues) + end + end + + # Weighted completed percent including subprojects + def gantt_completed_percent + return @gantt_completed_percent if @gantt_completed_percent || @gantt_completed_percent_added + + i_table = Issue.table_name + + scope = Issue.where("#{i_table}.estimated_hours IS NOT NULL"). + where("#{i_table}.estimated_hours > 0"). + joins(:project). + where(gantt_subprojects_conditions) + + if scope.exists? + sum = scope.select('(SUM(done_ratio / 100.0 * estimated_hours) / SUM(estimated_hours) * 100) AS sum_alias').reorder(nil).first + @gantt_completed_percent = sum ? sum.sum_alias.to_f : 0.0 + else + @gantt_completed_percent = 100.0 + end + + @gantt_completed_percent + end + + def gantt_start_date + return @gantt_start_date if @gantt_start_date || @gantt_start_date_added + + @gantt_start_date = [ + Issue.joins(:project).where(gantt_subprojects_conditions).minimum('start_date'), + Version.joins(:project).where(gantt_subprojects_conditions).minimum('effective_date') + ].compact.min + end + + def gantt_due_date + return @gantt_due_date if @gantt_due_date || @gantt_due_date_added + + @gantt_due_date = [ + Issue.joins(:project).where(gantt_subprojects_conditions).maximum('due_date'), + Version.joins(:project).where(gantt_subprojects_conditions).maximum('effective_date') + ].compact.max + end + + def gantt_subprojects_conditions + p_table = Project.table_name + "#{p_table}.status <> #{Project::STATUS_ARCHIVED} AND #{p_table}.lft >= #{lft} AND #{p_table}.rgt <= #{rgt}" + end + + end + + module ClassMethods + + def load_gantt_dates(projects) + p_table = Project.table_name + i_table = Issue.table_name + v_table = Version.table_name + + project_ids = projects.map(&:id) + + data = [] + data.concat Project.where(id: project_ids). + joins("JOIN #{p_table} p2 ON p2.lft >= #{p_table}.lft AND p2.rgt <= #{p_table}.rgt"). + joins("JOIN #{i_table} i ON i.project_id = p2.id"). + where('p2.status <> ?', Project::STATUS_ARCHIVED). + group("#{p_table}.id"). + pluck(Arel.sql("#{p_table}.id, MIN(i.start_date), MAX(i.due_date)")) + + data.concat Project.where(id: project_ids). + joins("JOIN #{p_table} p2 ON p2.lft >= #{p_table}.lft AND p2.rgt <= #{p_table}.rgt"). + joins("JOIN #{v_table} v ON v.project_id = p2.id"). + where('p2.status <> ?', Project::STATUS_ARCHIVED). + group("#{p_table}.id"). + pluck(Arel.sql("#{p_table}.id, MIN(v.effective_date), MAX(v.effective_date)")) + + result = {} + data.each do |id, min, max| + if result.has_key?(id) + result[id][0] = [result[id][0], min].compact.min + result[id][1] = [result[id][1], max].compact.max + else + result[id] = [min, max] + end + end + + projects.each do |project| + project_data = result[project.id] + + if project_data + project.instance_variable_set :@gantt_start_date, project_data[0] + project.instance_variable_set :@gantt_due_date, project_data[1] + end + + project.instance_variable_set :@gantt_start_date_added, true + project.instance_variable_set :@gantt_due_date_added, true + end + end + + def load_gantt_completed_percent(projects) + p_table = Project.table_name + i_table = Issue.table_name + + project_ids = projects.map(&:id) + result = Project.where(id: project_ids). + joins("JOIN #{p_table} p2 ON p2.lft >= #{p_table}.lft AND p2.rgt <= #{p_table}.rgt"). + joins("JOIN #{i_table} i ON i.project_id = p2.id"). + where("i.estimated_hours IS NOT NULL AND i.estimated_hours > 0"). + where("p2.status <> ?", Project::STATUS_ARCHIVED). + group("#{p_table}.id"). + pluck(Arel.sql("#{p_table}.id, (SUM(i.done_ratio / 100.0 * i.estimated_hours) / SUM(i.estimated_hours) * 100)")). + to_h + + projects.each do |project| + done_ratio = result[project.id] + + if done_ratio + project.instance_variable_set :@gantt_completed_percent, done_ratio + else + project.instance_variable_set :@gantt_completed_percent, 100.0 + end + + project.instance_variable_set :@gantt_completed_percent_added, true + end + end + + end + + end +end + +Project.prepend EasyGantt::ProjectPatch diff --git a/easy_gantt/lib/easy_gantt/queries_controller_patch.rb b/easy_gantt/lib/easy_gantt/queries_controller_patch.rb new file mode 100644 index 0000000..7c04405 --- /dev/null +++ b/easy_gantt/lib/easy_gantt/queries_controller_patch.rb @@ -0,0 +1,27 @@ +module EasyGantt + module QueriesControllerPatch + + def self.prepended(base) + base.prepend(InstanceMethods) + end + + module InstanceMethods + + # Redmine return only direct sublasses but + # Gantt query inherit from IssueQuery + def query_class + case params[:type] + when 'EasyGantt::EasyGanttIssueQuery' + EasyGantt::EasyGanttIssueQuery + else + super + end + end + + end + + end +end + +QueriesController.prepend EasyGantt::QueriesControllerPatch + diff --git a/easy_gantt/lib/easy_gantt/version_patch.rb b/easy_gantt/lib/easy_gantt/version_patch.rb new file mode 100644 index 0000000..8180f6c --- /dev/null +++ b/easy_gantt/lib/easy_gantt/version_patch.rb @@ -0,0 +1,23 @@ +module EasyGantt + module VersionPatch + + def self.prepended(base) + base.include(InstanceMethods) + end + + module InstanceMethods + + def gantt_editable?(user=nil) + user ||= User.current + + (user.allowed_to?(:edit_easy_gantt, project) || + user.allowed_to_globally?(:edit_global_easy_gantt)) && + user.allowed_to?(:manage_versions, project) + end + + end + + end +end + +Version.prepend EasyGantt::VersionPatch diff --git a/easy_gantt/spec/controllers/easy_gantt_controller_spec.rb b/easy_gantt/spec/controllers/easy_gantt_controller_spec.rb new file mode 100644 index 0000000..9e30591 --- /dev/null +++ b/easy_gantt/spec/controllers/easy_gantt_controller_spec.rb @@ -0,0 +1,28 @@ +require File.expand_path('../../../../easyproject/easy_plugins/easy_extensions/test/spec/spec_helper', __FILE__) + +RSpec.describe EasyGanttController, logged: :admin do + + around(:each) do |example| + with_settings(rest_api_enabled: 1) { example.run } + end + + context 'rest api' do + let(:project) { FactoryGirl.create(:project, add_modules: ['easy_gantt']) } + + it 'disabled' do + with_settings(rest_api_enabled: 0) do + get :index, project_id: project + expect(response).to be_error + + get :index + expect(response).to be_error + end + end + + it 'enabled' do + get :index + expect(response).to be_success + end + end + +end diff --git a/easy_gantt/spec/features/add_task_spec.rb b/easy_gantt/spec/features/add_task_spec.rb new file mode 100644 index 0000000..a9d7afd --- /dev/null +++ b/easy_gantt/spec/features/add_task_spec.rb @@ -0,0 +1,24 @@ +require File.expand_path('../../../../easyproject/easy_plugins/easy_extensions/test/spec/spec_helper', __FILE__) + +RSpec.feature 'Add task', logged: :admin, js: true do + let!(:project) { FactoryGirl.create(:project, add_modules: ['easy_gantt']) } + + around(:each) do |example| + with_settings(rest_api_enabled: 1) { example.run } + end + + describe 'toolbar' do + + unless Redmine::Plugin.installed?(:easy_gantt_pro) + scenario 'should open help' do + visit easy_gantt_path(project) + wait_for_ajax + page.find('.easy-gantt__menu-tools').hover + expect(page).to have_selector('#button_add_task_help', text: I18n.t(:label_new)) + page.find('#button_add_task_help').click + expect(page).to have_selector('#add_task_help_modal_popup') + end + end + + end +end diff --git a/easy_gantt/spec/features/correct_tree_spec.rb b/easy_gantt/spec/features/correct_tree_spec.rb new file mode 100644 index 0000000..9709303 --- /dev/null +++ b/easy_gantt/spec/features/correct_tree_spec.rb @@ -0,0 +1,73 @@ +require File.expand_path('../../../../easyproject/easy_plugins/easy_extensions/test/spec/spec_helper', __FILE__) + +RSpec.feature 'Correct tree', logged: :admin, js: true do + let(:project) { + FactoryGirl.create(:project, add_modules: ['easy_gantt'], number_of_issues: 0) + } + let(:sub_project) { + FactoryGirl.create(:project, parent_id: project.id, number_of_issues: 1) + } + let(:project_issues) { + FactoryGirl.create_list(:issue, 3, :project_id => project.id) + } + let(:milestone) { + FactoryGirl.create(:version, project_id: project.id, due_date: Date.today + 7 ) + } + let(:milestone_issues) { + FactoryGirl.create_list(:issue, 3, :fixed_version_id => milestone.id, :project_id => project.id) + } + let(:sub_issues) { + FactoryGirl.create_list(:issue, 3, :parent_issue_id => milestone_issues[0].id, :project_id => project.id) + } + let(:sub_sub_issues) { + FactoryGirl.create_list(:issue, 3, :parent_issue_id => sub_issues[0].id, :project_id => project.id) + } + + around(:each) do |example| + with_settings(rest_api_enabled: 1) { example.run } + end + it 'should show project items in correct order' do + sub_sub_issues + project_issues + sub_project + visit easy_gantt_path(project) + wait_for_ajax + bars_area = page.find('.gantt_grid_data') + # bars_area = page.find('.gantt_bars_area') + order_list = [ + project, + project_issues[0], + project_issues[1], + project_issues[2], + milestone, + milestone_issues[0], + sub_issues[0], + sub_sub_issues[0], + sub_sub_issues[1], + sub_sub_issues[2], + sub_issues[1], + sub_issues[2], + milestone_issues[1], + milestone_issues[2], + sub_project + ] + prev_id = nil + order_list.each do |issue| + if issue.is_a? Project + id = 'p' + issue.id.to_s + name = issue.name + elsif issue.is_a? Version + id = 'm' + issue.id.to_s + name = issue.name + else + id = issue.id + name = issue.subject + end + expect(bars_area).to have_text(name) + unless prev_id.nil? + expect(bars_area.find("div[task_id='#{prev_id}']+div[task_id='#{id}']")).not_to be_nil + end + prev_id = id + end + end +end \ No newline at end of file diff --git a/easy_gantt/spec/features/easy_gantt_spec.rb b/easy_gantt/spec/features/easy_gantt_spec.rb new file mode 100644 index 0000000..1c4c4ec --- /dev/null +++ b/easy_gantt/spec/features/easy_gantt_spec.rb @@ -0,0 +1,25 @@ +require File.expand_path('../../../../easyproject/easy_plugins/easy_extensions/test/spec/spec_helper', __FILE__) + +RSpec.feature 'Easy gantt', js: true, logged: :admin do + let!(:project) { FactoryGirl.create(:project, add_modules: ['easy_gantt']) } + + around(:each) do |example| + with_settings(rest_api_enabled: 1) { example.run } + end + + describe 'show gantt' do + + scenario 'show easy gantt on project' do + visit easy_gantt_path(project) + wait_for_ajax + # unless Redmine::Plugin.installed?(:easy_gantt_pro) + # page.find('#sample_close_button').click + # wait_for_ajax + # end + expect(page).to have_css('#easy_gantt') + expect(page.find('.gantt_grid_data')).to have_content(project.name) + expect(page.find('#header')).to have_content(project.name) + end + + end +end diff --git a/easy_gantt/spec/features/gantt_save_spec.rb b/easy_gantt/spec/features/gantt_save_spec.rb new file mode 100644 index 0000000..8cfdca1 --- /dev/null +++ b/easy_gantt/spec/features/gantt_save_spec.rb @@ -0,0 +1,52 @@ +require File.expand_path('../../../../easyproject/easy_plugins/easy_extensions/test/spec/spec_helper', __FILE__) + +RSpec.feature 'Gantt save', logged: :admin, js: true, slow: true do + let(:subproject) { + FactoryGirl.create(:project, :parent_id => superproject.id, add_modules: ['easy_gantt'], number_of_issues: 3) + } + let(:superproject) { + FactoryGirl.create(:project, add_modules: ['easy_gantt'], number_of_issues: 3) + } + let(:superproject_milestone_issues) { + FactoryGirl.create_list(:issue, 3, :fixed_version_id => superproject_milestone.id, :project_id => superproject.id) + } + let(:subproject_milestone_issues) { + FactoryGirl.create_list(:issue, 3, :fixed_version_id => subproject_milestone.id, :project_id => subproject.id) + } + let(:subproject_milestone) { + FactoryGirl.create(:version, project_id: subproject.id) + } + let(:superproject_milestone) { + FactoryGirl.create(:version, project_id: superproject.id) + } + let(:subissues) { + FactoryGirl.create_list(:issue, 3, :parent_issue_id => superproject.issues[0].id, :project_id => superproject.id) + } + + around(:each) do |example| + with_settings(rest_api_enabled: 1) { example.run } + end + + + it 'should save issue' do + issue = superproject.issues[0] + old_start_date = issue.start_date + visit easy_gantt_path(superproject) + wait_for_ajax + expect(page).to have_text(issue.subject) + move_script= <<-EOF + (function(){var issue = ysy.data.issues.getByID(#{issue.id}); + issue.set({start_date:moment('#{Date.today + 2.days}'),end_date:moment('#{Date.today + 4.days}')}); + return "success";})() + EOF + save_button = page.find('#button_save') + expect(page).to have_css('#button_save.disabled') + expect(page.evaluate_script(move_script)).to eq('success') + expect(page).not_to have_css('#button_save.disabled') + save_button.click + wait_for_ajax + expect(page).to have_css('#button_save.disabled') + issue.reload + expect(issue.start_date).to eq(old_start_date + 2.days) + end +end \ No newline at end of file diff --git a/easy_gantt/spec/features/global_gantt_spec.rb b/easy_gantt/spec/features/global_gantt_spec.rb new file mode 100644 index 0000000..63e9820 --- /dev/null +++ b/easy_gantt/spec/features/global_gantt_spec.rb @@ -0,0 +1,30 @@ +require File.expand_path('../../../../easyproject/easy_plugins/easy_extensions/test/spec/spec_helper', __FILE__) + +RSpec.feature 'Global gantt', logged: :admin, js: true do + let!(:superproject) { + FactoryGirl.create(:project, add_modules: ['easy_gantt'], number_of_issues: 3) + } + around(:each) do |example| + with_settings(rest_api_enabled: 1) { example.run } + end + unless Redmine::Plugin.installed?(:easy_gantt_pro) then + it 'should load sample Data' do + visit easy_gantt_path + wait_for_ajax + expect(page).to have_css('#sample_cont') + expect(page).to have_text('1. Administrative Projects') + expect(page).to have_text('2. HR Projects') + expect(page).to have_text('3. IT Projects') + expect(page).to have_text('4. Product Development') + end + it 'should open sample project' do + visit easy_gantt_path + wait_for_ajax + expect(page).to have_text('3. IT Projects') + page.find('[task_id="p3"] .gantt_open').click + expect(page).to have_text('Client Project') + expect(page).to have_text('Implementation of IS') + expect(page).to have_text('Managing projects') + end + end +end \ No newline at end of file diff --git a/easy_gantt/spec/requests/permissions_spec.rb b/easy_gantt/spec/requests/permissions_spec.rb new file mode 100644 index 0000000..a27b49e --- /dev/null +++ b/easy_gantt/spec/requests/permissions_spec.rb @@ -0,0 +1,71 @@ +require File.expand_path('../../../../easyproject/easy_plugins/easy_extensions/test/spec/spec_helper', __FILE__) + +RSpec.describe 'Permission', type: :request do + + let!(:user) { FactoryGirl.create(:user) } + + let!(:project1) { FactoryGirl.create(:project, number_of_issues: 3, add_modules: ['easy_gantt']) } + let!(:project2) { FactoryGirl.create(:project, number_of_issues: 3, add_modules: ['easy_gantt']) } + let!(:project3) { FactoryGirl.create(:project, number_of_issues: 3, add_modules: ['easy_gantt']) } + + let!(:role_nothing) { FactoryGirl.create(:role, permissions: []) } + let!(:role_project_view) { FactoryGirl.create(:role, permissions: [:view_issues, :view_easy_gantt]) } + let!(:role_project_edit) { FactoryGirl.create(:role, permissions: [:view_issues, :view_easy_gantt, :edit_easy_gantt, :manage_issue_relations, :edit_issues]) } + + let(:query) do + _query = EasyIssueGanttQuery.new(name: '_') + _query.filters = {} + _query.add_filter('status_id', 'o', nil) + _query + end + + around(:each) do |example| + with_settings(rest_api_enabled: 1) { example.run } + end + + before(:each) do + FactoryGirl.create(:member, project: project1, user: user, roles: [role_nothing]) + FactoryGirl.create(:member, project: project2, user: user, roles: [role_project_view]) + FactoryGirl.create(:member, project: project3, user: user, roles: [role_project_edit]) + + logged_user(user) + end + + # context 'Project level' do + # end + + context 'Global level' do + + def get_issues(project) + params = query.to_params.merge(opened_project_id: project.id, key: User.current.api_key, format: 'json') + get projects_easy_gantt_path, params + + expect(response).to be_ok + + json = JSON.parse(body) + json['easy_gantt_data']['issues'] + end + + # TODO: Global gantt now works with projects + + # it 'should see nothing' do + # binding.pry unless $__binding + # issues = get_issues(project1) + # expect(issues).to be_empty + # end + + # it 'only view' do + # issues = get_issues(project2) + # expect(issues).to be_any + # expect(issues.map{|i| i['permissions']['editable']}).to all(be_nil) + # end + + # it 'editable' do + # issues = get_issues(project3) + # expect(issues).to be_any + # expect(issues.map{|i| i['permissions']['editable']}).to all(be_truthy) + # end + + end + +end diff --git a/easy_gantt_pro/.run-linter.sh b/easy_gantt_pro/.run-linter.sh new file mode 100644 index 0000000..8de2200 --- /dev/null +++ b/easy_gantt_pro/.run-linter.sh @@ -0,0 +1,18 @@ +#!/bin/bash -l + +any_error=false + +if [[ -d "plugins" ]]; then + directory="plugins" +else + directory="." +fi + +for file in $(find $directory -name "*.rb" -type f -print); do + ruby -wc $file 1>/dev/null + [[ $? -eq 1 ]] && any_error=true +done + +if [[ $any_error = "true" ]]; then + exit 1 +fi diff --git a/easy_gantt_pro/.run-tests.sh b/easy_gantt_pro/.run-tests.sh new file mode 100644 index 0000000..86de26d --- /dev/null +++ b/easy_gantt_pro/.run-tests.sh @@ -0,0 +1,169 @@ +#!/bin/bash -l + +# -v print lines as they are read +# -x print lines as they are executed +# -e abort script at first error +set -e + +if [[ $# -eq 0 ]]; then + echo "You must set database adapter" + exit 1 +fi + +export ADAPTER=$1 +shift + +case $ADAPTER in + mysql2) + export DB_USERNAME=$MYSQL_USERNAME + export DB_PASSWORD=$MYSQL_PASSWORD + ;; + postgresql) + export DB_USERNAME=$PG_USERNAME + export DB_PASSWORD=$PG_PASSWROD + ;; + *) echo "You must set adapter mysql2 or postgresql" + exit 1 +esac + +while [[ $# -ge 1 ]]; do + arg=$1 + + case $arg in + # Show variables for debugging + --show-variables) SHOW_VARIABLES="true" + ;; + # Add plugins which are not part of EASY_BASE_REPO + --add-plugins) shift + ADDITIONAL_PLUGINS=($(echo ${1//,/ })) + ;; + # Remove plugins + --remove-plugins) shift + UNDESIRED_PLUGINS=($(echo ${1//,/ })) + ;; + *) # Nothing to do + ;; + esac + shift +done + +_plugin=($(echo $CI_REPOSITORY_URL | tr '/' ' ')) +CURRENT_PLUGIN=${_plugin[-1]/.git/} +BASE_ROOT=$CI_PROJECT_DIR/.redmine +PLUGINS_ROOT=$BASE_ROOT/plugins +CURRENT_PLUGIN_ROOT=$PLUGINS_ROOT/$CURRENT_PLUGIN +COMMON_BRANCHES=(bleeding-edge devel release-candidate bug-fixing master) + +# Try to find common branch as fallback for additional plugins +if [[ ${#ADDITIONAL_PLUGINS[@]} -ne 0 ]]; then + # Get all ancestors branches + # logs=$(git log --branches --source --oneline | awk '{print $2}' | uniq) + logs=$(git log --oneline --merges | grep into | sed 's/.* into //g' | uniq | head -n 10 | tr -d "'") + + # Iterater through all ancestor branches until get first common branch + for branch in $logs; do + if [[ " ${COMMON_BRANCHES[@]} " = *" $branch "* ]]; then + CLOSEST_COMMON_BRANCH=$branch + break + fi + done +fi + +if [[ $SHOW_VARIABLES = "true" ]]; then + echo "EASY_BASE_REPO:" $EASY_BASE_REPO + echo "BASE_ROOT:" $BASE_ROOT + echo "CURRENT_PLUGIN:" $CURRENT_PLUGIN + echo "ADDITIONAL_PLUGINS:" ${ADDITIONAL_PLUGINS[*]} + echo "CLOSEST_COMMON_BRANCH:" $CLOSEST_COMMON_BRANCH + echo "UNDESIRED_PLUGINS:" ${UNDESIRED_PLUGINS[*]} +fi + +# Ensure deleteing database even if test failed +function before_exit { + return_value=$? + bundle exec rake db:drop + exit $return_value +} + +trap before_exit SIGHUP SIGINT SIGTERM EXIT + +# Setup base easy project +[[ -d $BASE_ROOT ]] && rm -rf $BASE_ROOT +git clone --depth 1 ssh://git@git.easy.cz/$EASY_BASE_REPO.git $BASE_ROOT +cd $BASE_ROOT + +# Init database +ruby -ryaml -rsecurerandom -e " + database = 'redmine_'+SecureRandom.hex(8).to_s + config = { + 'adapter' => ENV['ADAPTER'], + 'database' => database, + 'host' => '127.0.0.1', + 'username' => ENV['DB_USERNAME'], + 'password' => ENV['DB_PASSWORD'], + 'encoding' => 'utf8' + } + config = { + 'test' => config.merge({'database' => 'test_'+database}), + 'development' => config, + 'production' => config + }.to_yaml + File.write('config/database.yml', config) +" + +# Init current plugin +[[ -d $CURRENT_PLUGIN_ROOT ]] && rm -rf $CURRENT_PLUGIN_ROOT +ln -s $CI_PROJECT_DIR $CURRENT_PLUGIN_ROOT + +# Init other plugins +pushd $PLUGINS_ROOT + for plugin in ${ADDITIONAL_PLUGINS[*]}; do + echo "--> Init plugin: $plugin" + + [[ -d $plugin ]] && rm -rf $plugin + git clone ssh://git@git.easy.cz/devel/$plugin.git $plugin + + pushd $plugin + # Checkout to the same branch if exist + if [[ $(git branch --remotes --list origin/$CI_COMMIT_REF_NAME) ]]; then + echo "---> Checking out $CI_COMMIT_REF_NAME" + git checkout $CI_COMMIT_REF_NAME + git pull + + # If not try to use closest common branch + elif [[ -n $CLOSEST_COMMON_BRANCH && -n $(git branch --remotes --list origin/$CLOSEST_COMMON_BRANCH) ]]; then + echo "---> Checking out $CLOSEST_COMMON_BRANCH" + git checkout $CLOSEST_COMMON_BRANCH + git pull + + else + echo "---> No common branch. Using default." + fi + popd + done +popd + +# Removal of undesired plugins +pushd $PLUGINS_ROOT + for plugin in ${UNDESIRED_PLUGINS[*]}; do + echo "--> Remove plugin: $plugin" + + if [[ -d $plugin ]]; then + echo "---> Remove from plugins" + rm -rf $plugin + elif [[ -d easyproject/easy_plugins/$plugin ]]; then + echo "---> Remove from easyproject/easy_plugins" + rm -rf easyproject/easy_plugins/$plugin + else + echo "---> Plugin doesn't exist" + fi + done +popd + +to_test="{$(echo ${ADDITIONAL_PLUGINS[*]} $CURRENT_PLUGIN | tr ' ' ',')}" + +bundle update +bundle exec rake db:drop db:create db:migrate +bundle exec rake easyproject:install +bundle exec rake test:prepare RAILS_ENV=test +bundle exec rake easyproject:tests:spec NAME=$to_test RAILS_ENV=test diff --git a/easy_gantt_pro/LICENSE b/easy_gantt_pro/LICENSE new file mode 100644 index 0000000..1255e47 --- /dev/null +++ b/easy_gantt_pro/LICENSE @@ -0,0 +1,60 @@ +LICENCE + +All Easy Redmine Extensions are distributed under GNU/GPL 2 license (see below). +If not otherwise stated, all images, cascading style sheets, and included JavaScript are NOT GPL, and are released under the Easy Redmine Commercial Use License (see below). + +GNU GENERAL PUBLIC LICENSE +Version 2, June 1991 +Copyright (C) 1989, 1991 Free Software Foundation, Inc. +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + +Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. + +Preamble +The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Library General Public License instead.) You can apply it to your programs, too. +When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. +To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. +For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. +We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. +Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. +Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. +The precise terms and conditions for copying, distribution and modification follow. + +TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION +0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". +Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. +1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. +You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. +2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: +a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. +b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. +c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) +These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. +Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. +In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. +3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: +a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, +b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, +c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) +The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. +If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. +4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. +5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. +6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. +7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. +If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. +It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. +This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. +8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. +9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. +Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. +10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. +NO WARRANTY +11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. +12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. +END OF TERMS AND CONDITIONS + +EASY REDMINE COMMERCIAL USE LICENSE +The Easy Redmine Commercial Use License is a GPL compatible license that pertains only to the images, cascading style sheets and JavaScript elements of Easy Redmine Themes and Styles produced by Easy Software Ltd. As stated by the GPL version 2.0 license, these elements of product that are not compiled together but are sent independently of GPL code, and combined in a client's browser, do not have to be GPL themselves. These images, cascading style sheets and JavaScript elements are copyright Easy Software Ltd. and can be used and manipulated for your own or if you have signed Easy Redmine Partner Agreement for your clients purposes. You cannot redistribute these files as your own, or include them in any package or extension of your own without prior consent of Easy Software Ltd. +Unauthorised distribution or making it accessible to a third party without prior Easy Software Ltd. consent, authorizes Easy Software Ltd. to invoice contractual penalty in the amount of 10 000 EUR for any breach of the this License. + diff --git a/easy_gantt_pro/README.md b/easy_gantt_pro/README.md new file mode 100644 index 0000000..a214974 --- /dev/null +++ b/easy_gantt_pro/README.md @@ -0,0 +1,3 @@ +# Easy Gantt PRO + +For documentation and requirements, go to https://www.easyredmine.com/redmine-gantt-plugin diff --git a/easy_gantt_pro/after_init.rb b/easy_gantt_pro/after_init.rb new file mode 100644 index 0000000..79ab697 --- /dev/null +++ b/easy_gantt_pro/after_init.rb @@ -0,0 +1,72 @@ +lib_dir = File.join(File.dirname(__FILE__), 'lib', 'easy_gantt_pro') + +# Redmine patches +patch_path = File.join(lib_dir, '*_patch.rb') +Dir.glob(patch_path).each do |file| + require file +end + +require lib_dir +require File.join(lib_dir, 'hooks') + +Redmine::MenuManager.map :easy_gantt_tools do |menu| + menu.delete(:add_task) + menu.delete(:critical) + menu.delete(:baseline) + + menu.push(:baseline, 'javascript:void(0)', + param: :project_id, + caption: :'easy_gantt.button.create_baseline', + icon: 'projects', + html: { icon: 'icon-projects' }, + if: proc { |project| + project.present? && + Redmine::Plugin.installed?(:easy_baseline) && + project.module_enabled?('easy_baselines') && + User.current.allowed_to?(:view_baselines, project) + }, + after: :tool_panel) + + menu.push(:critical, 'javascript:void(0)', + param: :project_id, + caption: :'easy_gantt.button.critical_path', + icon: 'summary', + html: { icon: 'icon-summary' }, + if: proc { |p| p.present? && Setting.plugin_easy_gantt['critical_path'] != 'disabled' }, + after: :tool_panel) + + menu.push(:add_task, 'javascript:void(0)', + param: :project_id, + caption: :label_new, + icon: 'add', + html: { icon: 'icon-add' }, + if: proc { |project| + project.present? && + User.current.allowed_to?(:edit_easy_gantt, project) && + (User.current.allowed_to?(:add_issues, project) || + User.current.allowed_to?(:manage_versions, project)) + }, + after: :tool_panel) + + menu.push(:delayed_project_filter, 'javascript:void(0)', + caption: :'easy_gantt.button.delayed_project_filter', + icon: 'list', + html: { icon: 'icon-list' }, + if: proc { + Setting.plugin_easy_gantt['show_project_progress'] == '1' + }) + + menu.push(:delayed_issue_filter, 'javascript:void(0)', + caption: :'easy_gantt.button.delayed_issue_filter', + icon: 'list', + html: { icon: 'icon-list' }) + + menu.push(:show_lowest_progress_tasks, 'javascript:void(0)', + caption: :'easy_gantt.button.show_lowest_progress_tasks', + icon: 'warning', + html: { icon: 'icon-warning' }, + if: proc { |project| + project.nil? && Setting.plugin_easy_gantt['show_lowest_progress_tasks'] == '1' + }) + +end diff --git a/easy_gantt_pro/app/controllers/easy_gantt_pro_controller.rb b/easy_gantt_pro/app/controllers/easy_gantt_pro_controller.rb new file mode 100644 index 0000000..ec0b582 --- /dev/null +++ b/easy_gantt_pro/app/controllers/easy_gantt_pro_controller.rb @@ -0,0 +1,64 @@ +class EasyGanttProController < EasyGanttController + accept_api_auth :lowest_progress_tasks + + before_action :require_admin, only: [:recalculate_fixed_delay] + + # TODO: Calculate progress date on DB + def lowest_progress_tasks + project_ids = Array(params[:project_ids]) + + @data = Hash.new { |hash, key| hash[key] = { date: Date.new(9999), ids: [] } } + + issues = Issue.open.joins(:status). + where(project_id: project_ids). + where.not(start_date: nil, due_date: nil). + pluck(:project_id, :id, :start_date, :due_date, :done_ratio) + + issues.each do |p_id, i_id, start_date, due_date, done_ratio| + diff = due_date - start_date + add_days = (diff * done_ratio.to_i) / 100 + progress_date = start_date + add_days.days + + project_data = @data[p_id] + if project_data[:date] == progress_date + project_data[:ids] << i_id + elsif project_data[:date] > progress_date + project_data[:date] = progress_date + project_data[:ids] = [i_id] + end + end + + ids = @data.flat_map{|_, data| data[:ids]} + @issues = Issue.select(:project_id, :id, :subject).where(id: ids) + end + + def recalculate_fixed_delay + statuses = [Project::STATUS_ACTIVE] + + issues = Issue.joins(:project).where(projects: { status: statuses }) + relations = IssueRelation.preload(:issue_from, :issue_to). + where(relation_type: IssueRelation::TYPE_PRECEDES). + where(issue_from_id: issues, issue_to_id: issues). + where.not(delay: nil) + + relations.each do |relation| + next if relation.issue_from.nil? || relation.issue_to.nil? + + from = relation.issue_from.due_date || relation.issue_from.start_date + to = relation.issue_to.start_date || relation.issue_to.due_date + + next if from.nil? || to.nil? + + saved_delay = relation.delay + correct_delay = (to-from-1).to_i + + if saved_delay != correct_delay + relation.update_column(:delay, correct_delay) + end + end + + flash[:notice] = l(:notice_easy_gantt_fixed_delay_recalculated) + redirect_to :back + end + +end diff --git a/easy_gantt_pro/app/helpers/.gitkeep b/easy_gantt_pro/app/helpers/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/easy_gantt_pro/app/helpers/easy_gantt_suppress_notification.rb b/easy_gantt_pro/app/helpers/easy_gantt_suppress_notification.rb new file mode 100644 index 0000000..c808fb2 --- /dev/null +++ b/easy_gantt_pro/app/helpers/easy_gantt_suppress_notification.rb @@ -0,0 +1,3 @@ +class EasyGanttSuppressNotification < ActiveSupport::CurrentAttributes + attribute :value +end \ No newline at end of file diff --git a/easy_gantt_pro/app/views/easy_gantt_pro/_gantt_tools.html.erb b/easy_gantt_pro/app/views/easy_gantt_pro/_gantt_tools.html.erb new file mode 100644 index 0000000..e69de29 diff --git a/easy_gantt_pro/app/views/easy_gantt_pro/_includes.html.erb b/easy_gantt_pro/app/views/easy_gantt_pro/_includes.html.erb new file mode 100644 index 0000000..32bff36 --- /dev/null +++ b/easy_gantt_pro/app/views/easy_gantt_pro/_includes.html.erb @@ -0,0 +1,16 @@ +<% include_calendar_headers_tags %> +<%= easy_gantt_include_css('easy_gantt_pro', media: 'all', from_plugin: 'easy_gantt_pro') %> + + <%= easy_gantt_include_js( + 'common', 'sorting', 'email_silencer', + (['baseline', 'add_task'] if @project), + ('critical' if @project && Setting.plugin_easy_gantt['critical_path'] != 'disabled'), + ('project_move' if @project.nil?), + ('lowest_progress' if @project.nil? && Setting.plugin_easy_gantt['show_lowest_progress_tasks'] == '1'), + ('delayed_projects' if @project.nil? && Setting.plugin_easy_gantt['show_project_progress'] == '1'), + ('gg_resource' if @project.nil? && false), + ('fixed_delay' if EasyGantt.load_fixed_delay?), + ('delayed_issues' if params[:controller] == 'easy_gantt'), + from_plugin: 'easy_gantt_pro') + %> + diff --git a/easy_gantt_pro/app/views/easy_gantt_pro/_js_prepare.html.erb b/easy_gantt_pro/app/views/easy_gantt_pro/_js_prepare.html.erb new file mode 100644 index 0000000..6ae375c --- /dev/null +++ b/easy_gantt_pro/app/views/easy_gantt_pro/_js_prepare.html.erb @@ -0,0 +1,148 @@ +<% + subproject_gantt_params = query.to_params.merge(key: User.current.api_key, subproject_loading: '1', opened_project_id: ':projectID', format: 'json') + + subproject_gantt_path = if @project + issues_easy_gantt_path(@project.id, subproject_gantt_params) + else + projects_easy_gantt_path(subproject_gantt_params) + end + + conditional_paths = {} + + if @project && EasyGantt.easy_baseline? + conditional_paths[:baselineRoot] = project_easy_baselines_path(@project, key: User.current.api_key, format: 'json') + conditional_paths[:baselineGET] = easy_baseline_gantt_path(':baselineID', key: User.current.api_key, format: 'json') + conditional_paths[:baselineDELETE] = project_easy_baseline_path(@project, ':baselineID', key: User.current.api_key, format: 'json') + end +%> + + \ No newline at end of file diff --git a/easy_gantt_pro/app/views/easy_gantt_pro/lowest_progress_tasks.api.rsb b/easy_gantt_pro/app/views/easy_gantt_pro/lowest_progress_tasks.api.rsb new file mode 100644 index 0000000..3f47311 --- /dev/null +++ b/easy_gantt_pro/app/views/easy_gantt_pro/lowest_progress_tasks.api.rsb @@ -0,0 +1,15 @@ +api.easy_gantt_data do + + api.array :issues do + @issues.each do |issue| + api.issue do + api.id issue.id + api.project_id issue.project_id + api.name issue.subject + api.progress_date @data[issue.project_id][:date] + api.url issue_path(issue) + end + end + end + +end diff --git a/easy_gantt_pro/app/views/easy_page_modules/projects/_easy_global_gantt_edit.html.erb b/easy_gantt_pro/app/views/easy_page_modules/projects/_easy_global_gantt_edit.html.erb new file mode 100644 index 0000000..e69de29 diff --git a/easy_gantt_pro/app/views/easy_page_modules/projects/_easy_global_gantt_show.html.erb b/easy_gantt_pro/app/views/easy_page_modules/projects/_easy_global_gantt_show.html.erb new file mode 100644 index 0000000..1374227 --- /dev/null +++ b/easy_gantt_pro/app/views/easy_page_modules/projects/_easy_global_gantt_show.html.erb @@ -0,0 +1,8 @@ +<% if easy_page_modules_data[:query].nil? %> +

+ <%= l(:label_easy_page_module_settings_missing) %> +

+<% else %> + <%= render template: 'easy_gantt/index', locals: { show_query: false, + query: easy_page_modules_data[:query] } %> +<% end %> diff --git a/easy_gantt_pro/app/views/easy_page_modules/projects/_easy_project_gantt_edit.html.erb b/easy_gantt_pro/app/views/easy_page_modules/projects/_easy_project_gantt_edit.html.erb new file mode 100644 index 0000000..e69de29 diff --git a/easy_gantt_pro/app/views/easy_page_modules/projects/_easy_project_gantt_show.html.erb b/easy_gantt_pro/app/views/easy_page_modules/projects/_easy_project_gantt_show.html.erb new file mode 100644 index 0000000..5f502c0 --- /dev/null +++ b/easy_gantt_pro/app/views/easy_page_modules/projects/_easy_project_gantt_show.html.erb @@ -0,0 +1,12 @@ +<% if easy_page_modules_data[:query].nil? %> +

+ <%= l(:label_easy_page_module_settings_missing) %> +

+<% elsif easy_page_modules_data[:can_view] %> + <%= render template: 'easy_gantt/index', locals: { show_query: false, + query: easy_page_modules_data[:query] } %> +<% else %> +

+ <%= l(:error_easy_gantt_view_permission) %> +

+<% end %> diff --git a/easy_gantt_pro/app/views/easy_settings/_easy_gantt_pro.html.erb b/easy_gantt_pro/app/views/easy_settings/_easy_gantt_pro.html.erb new file mode 100644 index 0000000..893a8f8 --- /dev/null +++ b/easy_gantt_pro/app/views/easy_settings/_easy_gantt_pro.html.erb @@ -0,0 +1,38 @@ +<% # Its a hook only !!! %> + +

+ <%= content_tag :label, l(:field_easy_gantt_fixed_delay) %> + + <%= link_to l(:label_easy_gantt_recalculate_fixed_delay), easy_gantt_recalculate_fixed_delay_path, data: { confirm: l(:warning_easy_gantt_recalculate_fixed_delay) } %> + +

+ +

+ <%= content_tag :label, l(:label_easy_gantt_critical_path) %> + + + + + + +

+

+ +

+ <%= label_tag 'settings_show_lowest_progress_tasks', l(:label_show_lowest_progress_tasks) %> + <%= check_box_tag 'settings[show_lowest_progress_tasks]', '1', @settings['show_lowest_progress_tasks'] == '1' %> + + <%= l(:text_show_lowest_progress_tasks) %> + +

diff --git a/easy_gantt_pro/app/views/hooks/easy_gantt_pro/_view_easy_gantt_index_bottom.html.erb b/easy_gantt_pro/app/views/hooks/easy_gantt_pro/_view_easy_gantt_index_bottom.html.erb new file mode 100644 index 0000000..537003c --- /dev/null +++ b/easy_gantt_pro/app/views/hooks/easy_gantt_pro/_view_easy_gantt_index_bottom.html.erb @@ -0,0 +1,4 @@ +<%= content_for :header_tags do %> + <%= render 'easy_gantt_pro/includes', project: @project, query: query %> + <%= render 'easy_gantt_pro/js_prepare', project: @project, query: query %> +<% end %> diff --git a/easy_gantt_pro/app/views/hooks/easy_gantt_pro/_view_easy_gantts_issues_toolbars.html.erb b/easy_gantt_pro/app/views/hooks/easy_gantt_pro/_view_easy_gantts_issues_toolbars.html.erb new file mode 100644 index 0000000..b88ff33 --- /dev/null +++ b/easy_gantt_pro/app/views/hooks/easy_gantt_pro/_view_easy_gantts_issues_toolbars.html.erb @@ -0,0 +1,42 @@ +<% if @project && Redmine::Plugin.installed?(:easy_baseline) && @project.module_enabled?('easy_baselines') %> + <% baselines = Project.where(easy_baseline_for: @project) %> + + +<% end %> + + + + diff --git a/easy_gantt_pro/assets/javascripts/easy_gantt_pro/add_task.js b/easy_gantt_pro/assets/javascripts/easy_gantt_pro/add_task.js new file mode 100644 index 0000000..7972acd --- /dev/null +++ b/easy_gantt_pro/assets/javascripts/easy_gantt_pro/add_task.js @@ -0,0 +1,500 @@ +/** + * Created by Ringael on 5. 8. 2015. + */ +window.ysy = window.ysy || {}; +ysy.pro = ysy.pro || {}; +ysy.pro.addTask = { + _name: "AddTask", + initToolbar: function (ctx) { + var addTaskPanel = new ysy.view.AddTaskPanel(); + addTaskPanel.init(ysy.settings.addTask); + ctx.children.push(addTaskPanel); + }, + patch: function () { + ysy.proManager.register("initToolbar", this.initToolbar); + ysy.proManager.register("close", this.close); + var proManager = ysy.proManager; + var addTaskClass = ysy.pro.addTask; + ysy.view.AllButtons.prototype.extendees.add_task = { + bind: function () { + this.model = ysy.settings.addTask; + this._register(ysy.settings.resource); + }, + func: function () { + proManager.closeAll(addTaskClass); + var addTask = ysy.settings.addTask; + addTask.setSilent("open", !addTask.open); + //if(!addTask.open){ysy.view.addTask.clear();} + addTask._fireChanges(this, "toggle"); + }, + isOn: function () { + return ysy.settings.addTask.open + }, + isHidden: function () { + return ysy.settings.resource.open; + // return !ysy.settings.permissions.allowed("add_issues") + } + }; + dhtmlx.dragScroll = function () { + var $background = $(".gantt_task_bg"); + if (!$background.hasClass("inited")) { + $background.addClass("inited"); + var dnd = new dhtmlxDnD($background[0], {marker: true}); + var temp; + /*var lastScroll = null; + var addTask = false; + var startDate=null; + var endDate=null;*/ + dnd.attachEvent("onDragStart", function () { + temp = {}; + temp.addTask = ysy.settings.addTask.open; + if (temp.addTask) { + temp.addType = ysy.settings.addTask.type; + temp.startDate = gantt.dateFromPos(dnd.getRelativePos().x); + temp.lastScroll = gantt.getCachedScroll(); + } else { + dnd.destroyMarker(); + temp.lastScroll = gantt.getCachedScroll(); + } + }); + dnd.attachEvent("onDragMove", function () { + var diff = dnd.getDiff(); + if (temp.addTask) { + //ysy.log.debug("POS="+(dnd.getRelativePos().x),"taskModal"); + temp.endDate = gantt.dateFromPos(dnd.getRelativePos().x); + if (temp.addType === "milestone") { + temp.line = addTaskClass.modifyMileMarker( + dnd.config.marker, + {start_date: moment(temp.endDate)}, + dnd.config.offset, + temp.lastScroll + ); + } else { + temp.line = addTaskClass.modifyIssueMarker( + dnd.config.marker, + {start_date: moment(temp.startDate), end_date: moment(temp.endDate), type: temp.addType}, + dnd.config.offset, + temp.lastScroll + ); + } + } else { + gantt.scrollTo(Math.max(temp.lastScroll.x - diff.x, 0), undefined); + } + }); + dnd.attachEvent("onDragEnd", function () { + if (temp.addTask) { + if (dnd.config.started) { + ysy.log.debug("start: " + temp.startDate.toString() + " end: " + temp.endDate.toString(), "taskModal"); + var task = {start_date: moment(temp.startDate), end_date: moment(temp.endDate)}; + addTaskClass.roundDates(task, temp.addType !== "milestone"); + var preFill = { + start_date: task.start_date.format("YYYY-MM-DD"), + due_date: task.end_date.format("YYYY-MM-DD"), + project_id: ysy.settings.projectID + }; + ysy.log.debug("line=" + temp.line, "taskModal"); + //preFill.parent=ysy.pro.addTask.getMilestoneByLine(temp.line); + preFill.parent = addTaskClass.getIdByLine(temp.line, temp.addType); + addTaskClass.openModal(temp.addType, preFill); + } + } + }); + } + }; + }, + close: function () { + var addTask = ysy.settings.addTask; + if (addTask.setSilent("open", false)) { + addTask._fireChanges(this, "close"); + } + } +}; +//############################################################################################# +ysy.view.AddTaskPanel = function () { + ysy.view.Widget.call(this); +}; +ysy.main.extender(ysy.view.Widget, ysy.view.AddTaskPanel, { + name: "AddTaskPanelWidget", + templateName: "AddTaskPanel", + buttons: ["issue", "milestone"], + _repaintCore: function () { + var sett = ysy.settings.addTask; + var target = this.$target; + if (sett.open) { + target.show(); + } else { + target.hide(); + return; + } + target.find(".add_task_type").removeClass("active"); + target.find("#add_task_" + sett.type).addClass("active"); + this.tideFunctionality(); + }, + tideFunctionality: function () { + var types = this.buttons; + var $target = this.$target; + var model = this.model; + var self = this; + var bind = function (type) { + $target.find("#add_task_" + type).off("click").on("click", function () { + //ysy.log.debug("AddTask issue button pressed","taskModal"); + if (model.type === type) { + ysy.pro.addTask.openModal(type); + } else { + model.setSilent("type", type); + model._fireChanges(self, "toggle"); + } + }); + + }; + for (var i = 0; i < types.length; i++) { + bind(types[i]); + } + this.$target.find("#button_add_task_help").off("click").on("click", ysy.proManager.showHelp); + } +}); +//############################################################# +$.extend(ysy.pro.addTask, { + openModal: function (addType, preFill) { + if (preFill === undefined) { + preFill = {project_id: ysy.settings.projectID}; + } + if (addType === "milestone") { + preFill.due_date = moment(preFill.due_date).add(1, "days").format("YYYY-MM-DD"); + } + if (preFill.parent) { + var parent = preFill.parent; + if (parent.id > 1000000000000) { + dhtmlx.message(ysy.settings.labels.errors2.unsaved_parent, "error"); + return; + } + preFill.project_id = parent.project_id; + preFill.fixed_version_id = parent.type === 'milestone' ? parent.id : parent.fixed_version_id; + if (parent.type === 'task') { + preFill.parent_issue_id = parent.id; + } + delete preFill["parent"]; + } + var $target = ysy.main.getModal("form-modal", "90%"); + var submitFunc = function (e) { + var addTaskClass = ysy.pro.addTask; + if (window.fillFormTextAreaFromCKEditor) { + window.fillFormTextAreaFromCKEditor("issue_description"); + window.fillFormTextAreaFromCKEditor("version_description"); + } + if ($(this).is("form")) { + var data = $(this).serializeArray(); + } else { + data = $target.find("form").serializeArray(); + } + var errors = addTaskClass.collectErrors($target, data); + if (errors.length) { + dhtmlx.message(errors.join("
"), "error"); + $("#content").find(".flash").appendTo('body'); + return false; + } + var transformed = addTaskClass.transformData(data, addType); + if (addType === "milestone") { + addTaskClass.createMilestone(transformed); + } else { + addTaskClass.createIssue(transformed); + } + $target.dialog("close"); + return false; + }; + if (addType === "milestone") { + ysy.gateway.polymorficGet(ysy.settings.paths.newMilestonePath.replace(":projectID", preFill.project_id), { + version: preFill + }, function (data) { + var $content = $(data).find("#content"); + if ($content.length) { + $target.html($content.html()); + } else { + $target.html(data); + } + var project_id = $target.find("#version_project_id").val(); + var title = $target.find("h2"); + title.replaceWith($("

").html(title.html())); + showModal("form-modal"); + $target.find("input[type=submit], .form-actions").hide(); + $target.find("#new_version").submit(submitFunc); + $target.dialog({ + buttons: [ + { + id: "add_milestone_modal_submit", + class: "button-1 button-positive", + text: ysy.settings.labels.buttons.create, + click: submitFunc + } + ] + }); + $target.find("#version_name").focus(); + }); + } else { + ysy.gateway.polymorficGet(ysy.settings.paths.newIssuePath, { + issue: preFill + }, function (data) { + $target.html(data); + var title = $target.find("h2"); + title.replaceWith($("

").html(title.html())); + showModal("form-modal"); + $target.find("input[type=submit], .form-actions").hide(); + $target.dialog({ + buttons: [ + { + id: "add_issue_modal_submit", + class: "button-1 button-positive", + text: ysy.settings.labels.buttons.create, + click: submitFunc + } + ] + }); + $target.find("#issue-form").submit(submitFunc); + if ($target.find("#project_id").length === 0) { + // because there may be no project field in EasyRedmine New issue form + $target.find("#issue-form").append($('')); + } + window.initEasyAutocomplete(); + $target.find("#issue_subject").focus(); + }); + } + + }, + collectErrors: function ($target, data) { + var errors = []; + var required = {}; + $target.find("label > span.required").each(function () { + // finder for classic Redmine and Redmine-like inputs + var $label = $(this).parent("label"); + var $input = $label.parent().find("#" + $label.attr("for")); + var label = $label.text(); + var name = $input.attr("name"); + if (name) { + required[name] = label.substring(0, label.length - 2); + } + }); + for (var key in required) { + if (!required.hasOwnProperty(key)) continue; + var valid = false; + for (var i = 0; i < data.length; i++) { + if (data[i].name === key) { + if (data[i].value !== "") { + valid = true; + } + break; + } + } + if (!valid) { + errors.push(required[key] + " " + ysy.view.getLabel("addTask", "error_blank")); + } + } + return errors; + }, + transformData: function (data, addType) { + var structured = ysy.main.formToJson(data); + if (addType == "issue") { + var entityStructured = structured.issue; + } else if (addType == "milestone") { + entityStructured = structured.version; + } else { + entityStructured = {}; + } + var transformed = { + project_id: ysy.settings.project ? ysy.settings.project.id : null + }; + var parseInteger = function (number) { + if (number === "") return null; + return parseInt(number); + }; + var parseDecimal = function (number) { + if (number === "") return null; + return parseFloat(number); + }; + var functionMap = { + // name: nothing, + is_private: parseInteger, + tracker_id: parseInteger, + status_id: parseInteger, + // status: nothing, + // sharing: nothing, + // subject: nothing, + // description: nothing, + priority_id: parseInteger, + project_id: parseInteger, + assigned_to_id: parseInteger, + fixed_version_id: parseInteger, + easy_version_category_id: parseInteger, + old_fixed_version_id: parseInteger, + parent_issue_id: parseInteger, + // start_date: nothing, + // due_date: nothing, + effective_date: function (value) { + transformed.start_date = value; + return null; + }, + estimated_hours: parseDecimal, + done_ratio: parseInteger, + // custom_field_values: nothing, + // easy_distributed_tasks: nothing, + // easy_repeat_settings: nothing, + // easy_repeat_simple_repeat_end_at: nothing, + // watcher_user_ids: nothing, + // easy_ldap_entity_mapping: nothing, + // skip_estimated_hours_validation: nothing, + activity_id: parseInteger + }; + var entityKeys = Object.getOwnPropertyNames(entityStructured); + for (var i = 0; i < entityKeys.length; i++) { + var key = entityKeys[i]; + if (functionMap.hasOwnProperty(key)) { + var parsed = functionMap[key](entityStructured[key], key); + } else { + parsed = entityStructured[key]; + } + transformed[key] = parsed; + } + return transformed; + }, + roundDates: function (task, sort) { + if (task.end_date) { + if (sort && task.end_date < task.start_date) { + task.end_date.add(12, "hours"); + var end = task.end_date; + task.end_date = task.start_date; + task.start_date = end; + } else { + task.end_date.add(-12, "hours"); + } + task.end_date._isEndDate = true; + gantt.date.date_part(task.end_date); + } + gantt.date.date_part(task.start_date); + }, + createIssue: function (jissue) { + ysy.log.debug("creating issue " + JSON.stringify(jissue), "taskModal"); + $.extend(jissue, { + id: dhtmlx.uid(), + name: jissue.subject, + //progress:0, + columns: { + subject: jissue.subject + }, + permissions: { + editable: true + }, + css: "fresh" + }); + var issue = new ysy.data.Issue(); + issue.init(jissue); + ysy.data.issues.push(issue); + gantt._selected_task = issue.getID(); + }, + createMilestone: function (jmile) { + ysy.log.debug("creating milestone " + JSON.stringify(jmile), "taskModal"); + $.extend(jmile, { + id: dhtmlx.uid(), + permissions: { + editable: true + }, + css: "fresh" + }); + var mile = new ysy.data.Milestone(); + mile.init(jmile); + ysy.data.milestones.push(mile); + gantt._selected_task = mile.getID(); + }, + modifyIssueMarker: function ($marker, task, offset, lastScroll) { + this.roundDates(task, true); + //var pos = gantt._get_task_pos(task); + var posx = gantt.posFromDate(task.start_date); + var cfg = gantt.config; + var height = gantt._get_task_height(); + var row_height = cfg.row_height; + var padd = Math.floor((row_height - height) / 2); + + var top = parseFloat($marker.style.top) - offset.top; + top = Math.max(top, lastScroll.y); + var line = Math.floor(top / row_height); + var clampedLine = Math.min(Math.max(line, 0), gantt._order.length - 1); + + //ysy.log.debug("offset: "+offset.top+" scroll: "+lastScroll.y+" line: "+line,"add_task_marker"); + + var roundedTop = clampedLine * row_height + padd; + var width = gantt.posFromDate(task.end_date) - posx; + //var div = document.createElement("div"); + //var width = gantt._get_task_width(task); + $marker.className = "gantt_task_line planned gantt_" + task.type + "-type"; + $marker.innerHTML = '
'; + var styles = [ + "left:" + (posx + offset.left) + "px", + "top:" + (roundedTop + offset.top) + "px", + "height:" + height + 'px', + //"line-height:" + height + 'px', + "width:" + width + 'px' + ]; + + $marker.style.cssText = styles.join(";"); + return line; + }, + modifyMileMarker: function ($marker, task, offset, lastScroll) { + gantt._working_time_helper.round_date(task.start_date); + //var pos = gantt._get_task_pos(task); + var posx = gantt.posFromDate(task.start_date); + var cfg = gantt.config; + var row_height = cfg.row_height; + var height = gantt._get_task_height(); + + var padd = Math.floor((row_height - height) / 2); + var top = parseFloat($marker.style.top) - offset.top; + top = Math.max(top, lastScroll.y); + var line = Math.floor(top / row_height); + var clampedLine = Math.min(Math.max(line, 0), gantt._order.length - 1); + var roundedTop = clampedLine * row_height + padd; + //ysy.log.debug("top="+top+", rTop="+roundedTop); + $marker.className = "gantt_task_line planned gantt_milestone-type"; + $marker.innerHTML = '
'; + var styles = [ + "left:" + (posx + offset.left - Math.floor(height / 2) - 1) + "px", + "top:" + (roundedTop + offset.top) + "px", + "height:" + height + 'px', + "line-height:" + height + 'px', + "width:" + height + 'px' + ]; + + $marker.style.cssText = styles.join(";"); + return line; + }, + allowedParent: { + issue: ["task", "milestone", "project"], + milestone: ["project"] + }, + getIdByLine: function (line, type) { + var allowed = this.allowedParent[type]; + var order = gantt._order; + if (line == order.length - 1) { + // empty line + return null; + } + if (line < 0 || line >= order.length) { + ysy.log.debug("wrong line number", "taskModal"); + return null; + } + var targetID = order[line]; + var task = gantt.getTask(targetID); + if (!task) { + ysy.log.debug("task not found", "taskModal"); + return null; + } + for (var i = 0; i < allowed.length; i++) { + if (gantt._get_safe_type(task.type) === allowed[i]) { + return { + id: task.real_id, + type: task.type, + project_id: task.widget.model.project_id, + fixed_version_id: task.widget.model.fixed_version_id + }; + } + } + return this.getIdByLine(line - 1, type); + } +}); diff --git a/easy_gantt_pro/assets/javascripts/easy_gantt_pro/baseline.js b/easy_gantt_pro/assets/javascripts/easy_gantt_pro/baseline.js new file mode 100644 index 0000000..37f478b --- /dev/null +++ b/easy_gantt_pro/assets/javascripts/easy_gantt_pro/baseline.js @@ -0,0 +1,427 @@ +/** + * Created by Ringael on 5. 8. 2015. + */ +window.ysy = window.ysy || {}; +ysy.pro = ysy.pro || {}; +ysy.pro.baseline = { + name: "Baseline PRO", + initToolbar: function (ctx) { + var baselinePanel = new ysy.view.BaselinePanel(); + baselinePanel.init(ysy.data.baselines, ysy.settings.baseline); + ctx.children.push(baselinePanel); + }, + patch: function () { + var baselineClass = ysy.pro.baseline; + ysy.proManager.register("initToolbar", this.initToolbar); + ysy.proManager.register("close", this.close); + ysy.proManager.register("ganttConfig", this.ganttConfig); + + + ysy.view.AllButtons.prototype.extendees.baseline = { + bind: function () { + this.model = ysy.settings.baseline; + this._register(ysy.settings.resource); + }, + func: function () { + ysy.proManager.closeAll(baselineClass); + var sett = ysy.settings.baseline; + sett.setSilent("open", !sett.open); + sett._fireChanges(this, "toggle"); + }, + isOn: function () { + return ysy.settings.baseline.open; + }, + isHidden: function () { + return ysy.settings.resource.open; + }, + //model:ysy.settings.baseline, + icon: "projects" + }; + ysy.view.columnBuilders = ysy.view.columnBuilders || {}; + ysy.view.columnBuilders.baseline_start_date = function (task) { + var base = ysy.pro.baseline._getBaselineForTask(task); + if (!base || !base.start_date) return; + if (base.start_date.isSame(task.start_date)) + return '' + ysy.settings.labels.baselines.label_same + ''; + return '' + base.start_date.format(gantt.config.date_format) + ''; + }; + ysy.view.columnBuilders.baseline_end_date = function (task) { + var base = ysy.pro.baseline._getBaselineForTask(task); + if (!base || !base.end_date) return; + if (base.end_date.isSame(task.end_date)) + return '' + ysy.settings.labels.baselines.label_same + ''; + return '' + base.end_date.format(gantt.config.date_format) + ''; + }; + ysy.view.Gantt.prototype.addBaselineLayer = function (ctx) { + baselineClass.addLayer(); + }; + ysy.settings.baseline.register(this.settingWatcher, this); + }, + settingWatcher: function () { + var settings = ysy.settings.baseline; + //ysy.log.debug("settings.baseline onChange ["+settings.open+","+settings.active+"]","baseline"); + // GET ACTIVE BASELINE + if (settings.open && settings.active) { + var active = settings.active; + var baseline = ysy.data.baselines.getByID(active); + if (!baseline.loaded) { + ysy.log.debug("loadBaseline " + active, "baseline"); + ysy.data.baselines.getByID(active).fetch(); + return; + } + } + // GET BASELINE HEADER + //ysy.log.debug("settings.baseline onChange ["+settings.open+","+settings.active+"]","baseline"); + if (settings.open && !settings.headerLoaded) { + ysy.log.debug("loadBaselineHeader", "baseline"); + ysy.gateway.loadBaselineHeader(function (data) { + var baselines = ysy.data.baselines; + //baselines.clearSilent(); + var base_array = baselines.getArray(); + for (var i = 0; i < base_array.length; i++) { + base_array[i]._deleted = true; + } + baselines.clearCache(); + var array = data.easy_baselines; + for (i = 0; i < array.length; i++) { + var base_data = array[i]; + var baseline = baselines.getByID(base_data.id); + if (!baseline) baseline = new ysy.data.Baseline(); + baseline.init(base_data, ysy.data.baselines); + baseline._deleted = false; + baselines.pushSilent(baseline); + } + //ysy.log.debug("header loaded","baseline"); + baselines._fireChanges(ysy.settings.baseline, "header loaded"); + if (!settings.active && baselines.getArray().length) { + var arr = baselines.getArray(); + var last = arr[arr.length - 1]; + settings.setSilent("active", last.id); + ysy.log.debug("last baseline ID=" + last.id + " active", "baseline"); + last.fetch(); + //settings._fireChanges(this,"first baseline active"); + } + }); + settings.headerLoaded = true; + return; + } + ysy.log.debug("Baseline: gantt.render()", "baseline"); + ysy.view.initGantt(); + if (ysy.settings.baseline.open) { + $("#gantt_cont").addClass("gantt-baselines"); + } else { + $("#gantt_cont").removeClass("gantt-baselines"); + } + //ysy.pro.baseline.addLayer(); + gantt.render(); + }, + close: function () { + var sett = ysy.settings.baseline; + if (sett.setSilent("open", false)) { + sett._fireChanges(this, "close"); + } + }, + resetHeader: function () { + ysy.settings.baseline.setSilent("headerLoaded", false); + ysy.settings.baseline.setSilent("active", false); + ysy.settings.baseline._fireChanges(this, "reload after saveBaseline"); + }, + _getBaselineForTask: function (task) { + var baseline = ysy.data.baselines.getByID(ysy.settings.baseline.active); + if (!baseline || !baseline.loaded) return; + //ysy.log.debug("baseline drawn for task "+task.id,"baseline"); + ysy.log.debug("baseline drawn", "baseline_render"); + if (task.type === gantt.config.types.milestone) { + var base = baseline.versions[task.real_id]; + } else if (gantt._get_safe_type(task.type) === gantt.config.types.task) { + base = baseline.issues[task.real_id]; + } + return base; + }, + addLayer: function () { + if (!ysy.settings.baseline.open) return; + var layer = gantt.addTaskLayer(function draw_planned(task) { + if (!ysy.settings.baseline.open) return; + if (!ysy.settings.baseline.active) return; + var base = ysy.pro.baseline._getBaselineForTask(task); + if (!base) return; + var base_start = base.start_date; + var base_end = base.end_date; + ysy.log.debug("start=" + task.start_date.format("DD.MM.YYYY") + " bstart=" + base.start_date.format("DD.MM.YYYY"), "baseline_render"); + /*if (!task.base_start){ + task.base_start=moment(task.start_date); + } + if(!task.base_end) { + task.base_end=gantt.date.Date(task.end_date); + }*/ + var sizes = gantt.getTaskPosition(task, base_start, base_end); + var className = 'gantt-baseline'; + if (task.type === "milestone") { + var side = sizes.height; + sizes.width = side; + sizes.left -= side / 2 + 1; + className += " gantt_milestone-type"; + } else { + sizes.width -= 2; + delete sizes.height; + } + var el = document.createElement('div'); + el.className = className; + el.style.left = sizes.left + 'px'; + if (sizes.height) { + el.style.height = sizes.height + 'px'; + } + el.style.width = sizes.width + 'px'; + el.style.top = sizes.top + gantt.config.task_height + 13 + 'px'; + return el; + }); + ysy.log.debug("TaskLayer ID=" + layer + " created", "baseline"); + }, + ganttConfig: function (config) { + if (!ysy.settings.baseline.open) return; + var labels = ysy.settings.labels.baselines; + var baselineColumns = [ + { + name: "baseline_start_date", + title: labels.baseline + ' ' + labels.startDate + }, + { + name: "baseline_end_date", + title: labels.baseline + ' ' + labels.dueDate + } + ]; + var columns = gantt.config.columns; + columns = columns.concat(ysy.view.leftGrid.constructColumns(baselineColumns)); + $.extend(config, { + columns: columns, + task_height: 16, + row_height: 40 + }); + } +}; +//############################################################################################# +ysy.view.BaselinePanel = function () { + ysy.view.Widget.call(this); +}; +ysy.main.extender(ysy.view.Widget, ysy.view.BaselinePanel, { + name: "BaselinePanelWidget", + templateName: "BaselineOption", + _repaintCore: function () { + var sett = ysy.settings.baseline; + var target = this.$target; + if (sett.open) { + target.show(); + } else { + target.hide(); + return; + } + if (!this.template) { + var templ = ysy.view.getTemplate(this.templateName); + if (templ) { + this.template = templ; + } else { + return true; + } + } + var baseOut = [ + //{name:"None selected",id:"none"} + ]; + var model = this.model.getArray(); + if (model.length === 0) { + target.find("#baseline_select").hide(); + target.find("#baseline_delete").hide(); + } else { + for (var i = 0; i < model.length; i++) { + baseOut.push({name: model[i].name, id: model[i].id, selected: model[i].id === sett.active ? " selected" : ""}); + } + var rendered = Mustache.render(this.template, {baselines: baseOut}); + target.find("#baseline_select").show().html(rendered); + target.find("#baseline_delete").show(); + } + + this.tideFunctionality(); + }, + tideFunctionality: function () { + var baselineClass = ysy.pro.baseline; + this.$target.find("#baseline_select").off("change").on("change", function (event) { + var baseID = parseInt($(this).val()); + ysy.settings.baseline.setSilent("active", baseID); + ysy.settings.baseline._fireChanges(ysy.view.BaselinePanel, "select") + }); + this.$target.find("#baseline_create:not(.disabled)").off("click").on("click", function () { + ysy.log.debug("Create baseline button pressed", "baseline"); + if (!ysy.history.isEmpty()) { + dhtmlx.message(ysy.settings.labels.baselines.error_not_saved, "error"); + return; + } + baselineClass.openCreateModal(); + }); + this.$target.find("#baseline_delete:not(.disabled)").off("click").on("click", function () { + ysy.log.debug("Delete baseline button pressed", "baseline"); + var baseline = ysy.data.baselines.getByID(ysy.settings.baseline.active); + if (!baseline) return; + var confirmLabel = $(this).data("confirmation"); + var confirmation = window.confirm(confirmLabel); + if (!confirmation) return; + ysy.gateway.deleteBaseline(baseline.id, function () { + ysy.log.debug("deleteBaseline callback", "baseline"); + baselineClass.resetHeader(); + }, + function (response) { + // FAIL + var responseJSON = response.responseJSON; + if (responseJSON && responseJSON.errors) { + var errors = responseJSON.errors.join(", "); + } + dhtmlx.message(ysy.view.getLabel("baseline", "delete_failed") + ": " + errors, "error"); + }); + }); + this.$target.find("#button_baseline_help").off("click").on("click", ysy.proManager.showHelp); + } +}); +//################################################################################################## +ysy.pro.baseline.openCreateModal = function () { + + var generic = moment().format(gantt.config.date_format + " HH:mm") + " " + ysy.settings.project.name; + var $target = ysy.main.getModal("form-modal", "50%"); + var submitFce = function () { + var name = $target.find("#baseline_modal_name").val(); + ysy.gateway.saveBaseline(name, function (data) { + ysy.log.debug("saveBaseline callback", "baseline"); + ysy.pro.baseline.resetHeader(); + //var baselines = ysy.data.baselines; + //var baseline = new ysy.data.Baseline(); + //baseline.init({ + // name: data.easy_baseline.name || name || generic, + // id: data.easy_baseline.id, + // mapping: data + //}, baselines); + //baselines.pushSilent(baseline); + //baselines.clearCache(); + //ysy.settings.baseline.setSilent("active", baseline.id); + //ysy.settings.baseline._fireChanges(ysy.pro.baseline, "baseline saved"); + }, + function (response) { + // FAIL + var responseText = response.responseText; + try { + var json = JSON.parse(responseText); + if (json.errors.length) { + responseText = json.errors.join(", "); + } + } catch (e) { + } + dhtmlx.message(ysy.view.getLabel("baselineCreateModal").request_failed + ": " + responseText, "error"); + }); + $target.dialog("close"); + }; + + var template = ysy.view.getTemplate("baselineCreateModal"); + //var labels= + var obj = $.extend({}, ysy.view.getLabel("baselineCreateModal"), {generic: generic}); + var rendered = Mustache.render(template, obj); + $target.html(rendered); + showModal("form-modal"); + $target.dialog({ + buttons: [ + { + id: "baseline_modal_submit", + text: ysy.settings.labels.buttons.button_submit, + class: "button-1 button-positive", + click: submitFce + }, + { + id: "baseline_modal_cancel", + text: ysy.settings.labels.buttons.button_cancel, + class: "button-2", + click: function () { + $target.dialog("close"); + } + } + ] + }); +}; +//################################################################################################## +ysy.data.Baseline = function () { + ysy.data.Data.call(this); +}; +ysy.main.extender(ysy.data.Data, ysy.data.Baseline, { + _name: "Baseline", + fetch: function () { + var thiss = this; + ysy.log.debug("baseline " + this.id + " fetch", "baseline"); + if (this.loaded) { + ysy.settings.baseline._fireChanges(this, "baseline active and loaded"); + return; + } else if (!this.isLoading) { + ysy.gateway.loadBaselineData(this.id, function (data) { + thiss.construct(data.easy_baseline_gantt); + thiss.loaded = true; + thiss.isLoading = false; + //ysy.log.debug("SourceData of baseline " + thiss.id + " loaded", "baseline"); + thiss.fetch(); + }); + } + this.isLoading = true; + }, + construct: function (data) { + this.issues = {}; + for (var i = 0; i < data.issues.length; i++) { + var source = data.issues[i]; + var target = { + start_date: source.start_date ? moment(source.start_date, "YYYY-MM-DD") : moment().startOf("day"), + end_date: source.due_date ? moment(source.due_date, "YYYY-MM-DD") : moment().startOf("day"), + done_ratio: source.done_ratio, + id: source.connected_to_issue_id + }; + if (target.start_date.isAfter(target.end_date)) { + if (source.start_date) { + target.end_date = moment(target.start_date); + } else { + target.start_date = moment(target.end_date); + } + } + target.end_date._isEndDate = true; + this.issues[target.id] = target; + } + this.versions = {}; + for (i = 0; i < data.versions.length; i++) { + source = data.versions[i]; + target = { + start_date: moment(source.start_date, "YYYY-MM_DD"), + id: source.connected_to_version_id + }; + target.start_date._isEndDate = true; + this.versions[target.id] = target; + } + } +}); +if (ysy.gateway === undefined) { + ysy.gateway = {}; +} +$.extend(ysy.gateway, { + loadBaselineHeader: function (callback) { + var urlTemplate = ysy.settings.paths.baselineRoot; + this.polymorficGetJSON(urlTemplate, null, callback); + }, + loadBaselineData: function (baselineID, callback) { + var urlTemplate = ysy.settings.paths.baselineGET.replace(":baselineID", baselineID); + this.polymorficGetJSON(urlTemplate, null, callback); + }, + saveBaseline: function (name, callback, fail) { + ysy.log.debug("Create baseline request", "baseline"); + //return callback(); + var data = null; + if (name) { + data = {easy_baseline: {name: name}}; + } + var urlTemplate = ysy.settings.paths.baselineRoot; + this.polymorficPost(urlTemplate, null, data, callback, fail); + }, + deleteBaseline: function (baselineID, callback) { + ysy.log.debug("Delete baseline ID=" + baselineID + " request", "baseline"); + //return callback(); + var urlTemplate = ysy.settings.paths.baselineDELETE.replace(":baselineID", baselineID); + this.polymorficDelete(urlTemplate, null, callback); + } +}); diff --git a/easy_gantt_pro/assets/javascripts/easy_gantt_pro/common.js b/easy_gantt_pro/assets/javascripts/easy_gantt_pro/common.js new file mode 100644 index 0000000..5e44b8d --- /dev/null +++ b/easy_gantt_pro/assets/javascripts/easy_gantt_pro/common.js @@ -0,0 +1,80 @@ +/** + * Created by Ringael on 2. 11. 2015. + */ +window.ysy = window.ysy || {}; +ysy.pro = ysy.pro || {}; +ysy.pro.common = ysy.pro.common || {}; +$.extend(ysy.pro.common, { + patch: function () { + ysy.proManager.register("ganttConfig", this.ganttConfig); + ysy.view.columnBuilders = ysy.view.columnBuilders || {}; + ysy.view.columnBuilders.name = function (obj) { + var id = parseInt(obj.real_id); + if (isNaN(id) || id > 1000000000000) return obj.text; + if (obj.type === "project") { + var nonLoadedIssues = obj.widget && obj.widget.model.issues_count; + var path = ysy.settings.paths.rootPath + "projects/"; + var text = ysy.main.escapeText(obj.text); + return "" + text + "" + + (nonLoadedIssues ? "" + nonLoadedIssues + "" : ""); + } + }; + $.extend(ysy.data.loader, { + loadProject: function (projectID) { + var self = this; + ysy.gateway.polymorficGetJSON( + ysy.settings.paths.subprojectGantt + .replace(new RegExp('(:|\%3A)projectID', 'g'), projectID), + null, + function (data) { + self._handleProjectData(data, projectID); + }, + function () { + ysy.log.error("Error: Unable to load data"); + } + ); + return true; + }, + _handleProjectData: function (data, projectID) { + var json = data.easy_gantt_data; + // -- PROJECTS -- + this._loadProjects(json.projects); + // -- ISSUES -- + this._loadIssues(json.issues, projectID); + // -- RELATIONS -- + this._loadRelations(json.relations); + // -- MILESTONES -- + this._loadMilestones(json.versions); + ysy.log.debug("minor data loaded", "load"); + this._fireChanges(); + }, + loadProjectIssues: function (projectID) { + ysy.gateway.polymorficGetJSON( + ysy.settings.paths.projectOpenedIssues + .replace(new RegExp('(:|\%3A)projectID', 'g'), projectID), + null, + $.proxy(this._handleProjectData, this), + function () { + ysy.log.error("Error: Unable to load data"); + } + ); + return true; + }, + openIssuesOfProject: function (projectID) { + var project = ysy.data.projects.getByID(projectID); + if (!project) return; + ysy.data.limits.openings["s" + projectID] = true; + if (!project.issues_count) return; + delete project.issues_count; + gantt.open(project.getID()); + this.loadProjectIssues(projectID); + } + }); + }, + ganttConfig: function (config) { + $.extend(config, { + allowedParent_task: ["project", "milestone", "task", "empty"], + allowedParent_task_global: ["project", "milestone", "task"] + }); + } +}); diff --git a/easy_gantt_pro/assets/javascripts/easy_gantt_pro/critical.js b/easy_gantt_pro/assets/javascripts/easy_gantt_pro/critical.js new file mode 100644 index 0000000..83d57a7 --- /dev/null +++ b/easy_gantt_pro/assets/javascripts/easy_gantt_pro/critical.js @@ -0,0 +1,337 @@ +window.ysy = window.ysy || {}; +ysy.pro = ysy.pro || {}; +ysy.pro.critical = { + name: "CriticalPath", + patch: function () { + if(ysy.settings.criticalType === 'disabled') { + ysy.pro.critical = null; + return; + } + ysy.proManager.register("extendGanttTask", this.extendGanttTask); + ysy.proManager.register("close", this.close); + ysy.proManager.register("initToolbar", this.initToolbar); + $.extend(ysy.view.AllButtons.prototype.extendees, { + critical: { + bind: function () { + this.model = ysy.settings.critical; + this._register(ysy.settings.resource); + }, + func: function () { + ysy.proManager.closeAll(ysy.pro.critical); + var critical = ysy.settings.critical; + critical.setSilent("open", !critical.open); + ysy.pro.critical.toggle(critical.open); + //critical._fireChanges(this, "toggle"); + }, + isOn: function () { + return ysy.settings.critical.open + }, + isHidden: function () { + return ysy.settings.resource.open; + } + }, + show_only_critical: { + bind: function () { + this.model = ysy.settings.critical; + }, + func: function () { + this.model.show_only_critical = !this.model.show_only_critical; + ysy.pro.critical.updateRegistration(this.model.show_only_critical); + this.model._fireChanges(this, "click"); + }, + isOn: function () { + return this.model.show_only_critical; + } + } + }); + }, + byLongestPath: { + critie_arr: null, + crities: null, + _construct: function () { + var issues = ysy.data.issues.getArray(); + var relations = ysy.data.relations.getArray(); + var crities = {}; + var critie_arr = []; + for (var i = 0; i < issues.length; i++) { + var issue = issues[i]; + var critie = { + issue: issue, + duration: issue.getDuration("days"), + source: [], + target: [], + longest: null + }; + crities[issue.id] = critie; + critie_arr.push(critie); + } + for (i = 0; i < relations.length; i++) { + var relation = relations[i]; + if (relation.isSimple) continue; + var source_critie = crities[relation.source_id]; + var target_critie = crities[relation.target_id]; + if (source_critie && target_critie) { + source_critie.source.push(relation); + target_critie.target.push(relation); + } else { + if (!source_critie) { + ysy.log.debug("source_critie " + relation.source_id + " missing", "critical"); + } + if (!target_critie) { + ysy.log.debug("target_critie " + relation.target_id + " missing", "critical"); + } + } + } + this.crities = crities; + this.critie_arr = critie_arr; + for (i = 0; i < critie_arr.length; i++) { + critie = critie_arr[i]; + this._resolve(critie); + } + return this.crities; + }, + _resolve: function (critie) { + if (critie.longest !== null) return; + var bestLongest = 0; + for (var i = 0; i < critie.target.length; i++) { // iterate over relations which targets critie + var relation = critie.target[i]; + var prevCritie = this.crities[relation.source_id]; + if (!prevCritie) { + ysy.log.warning("nextCritie missing"); + continue; + } + this._resolve(prevCritie); + var longest = prevCritie.longest + + relation.delay + - (relation.type === "start_to_finish" || relation.type === "finish_to_finish" ? critie.duration : 0) + - (relation.type === "start_to_start" || relation.type === "start_to_finish" ? prevCritie.duration : 0); + + if (longest > bestLongest) { + critie.prevMain = prevCritie; + bestLongest = longest; + } + } + critie.longest = bestLongest + critie.duration; + }, + findPath: function () { + if (!this.critie_arr) { + this._construct(); + } + var looser = null; + var longest = 0; + for (var i = 0; i < this.critie_arr.length; i++) { + var critie = this.critie_arr[i]; + if (longest < critie.longest) { + longest = critie.longest; + looser = critie; + } + } + var path = {}; + var last = null; + if (looser) { + while (looser.prevMain) { + last = looser.issue.id; + path[last] = true; + looser = looser.prevMain; + } + last = looser.issue.id; + path[last] = true; + path["last"] = last; + } + this.reset(); + ysy.log.debug("findPath() path=" + JSON.stringify(path), "critical"); + return path; + //this.path=path; + //gantt.selectTask(path[path.length-1]); + }, + reset: function () { + this.critie_arr = null; + this.crities = null; + this.path = null; + } + }, + byLastIssue: { + findPath: function () { + var issues = ysy.data.issues.getArray(); + + var lastIssues = []; + var lastDate = null; + for (var i = 0; i < issues.length; i++) { + var issue = issues[i]; + if (!issue._end_date.isValid()) continue; + if (lastDate !== null) { + if (lastDate.isAfter(issue._end_date)) continue; + if (lastDate.isBefore(issue._end_date)) { + //lastDate = issue.end_date; + lastIssues = []; + } + } + lastDate = issue._end_date; + lastIssues.push(issue); + } + if (lastIssues.length === 0) { + return {}; + } + var path = {}; + var soonestIssue = lastIssues[0]; + for (i = 0; i < lastIssues.length; i++) { + issue = lastIssues[i]; + path[issue.id] = issue; + if (soonestIssue._start_date.isAfter(issue._start_date)) { + soonestIssue = issue; + } + var predecessor = this._resolve(issue, path); + if (predecessor !== null && predecessor._start_date.isBefore(soonestIssue._start_date)) { + soonestIssue = predecessor; + } + } + path["last"] = soonestIssue.id; + return path; + }, + _resolve: function (issue, path) { + var relations = ysy.data.relations.getArray(); + var soonestIssue = null; + for (var i = 0; i < relations.length; i++) { + var relation = relations[i]; + if (relation.isSimple) continue; + if (relation.getTarget() !== issue) continue; + //if (relation.type !== "precedes") continue; + + var diff = relation.getActDelay() - relation.delay; + var source = relation.getSource(); + if (diff > 0) continue; + // var gapStart = relation.getSourceDate(source); + // var gapEnd = moment(gapStart).add(diff, "days"); + // gapEnd._isEndDate = gapStart._isEndDate; + // if (gantt._working_time_helper.is_work_units_between(gapStart, gapEnd, "day")) continue; + path[source.id] = source; + if (soonestIssue === null || source._start_date.isBefore(soonestIssue._start_date)) { + soonestIssue = source; + } + var predecessor = this._resolve(source, path); + if (predecessor !== null + && (soonestIssue === null + || predecessor._start_date.isBefore(soonestIssue._start_date) + )) { + soonestIssue = predecessor; + } + } + return soonestIssue; + } + + }, + findPath: function () { + if (ysy.settings.criticalType === "last") return this.byLastIssue.findPath(); + if (ysy.settings.criticalType === "longest") return this.byLongestPath.findPath(); + return {}; + }, + getPath: function () { + var critical = ysy.settings.critical; + if (critical.open === false) return null; + if (critical.active === false) return null; + var path; + if (critical._cache) { + return critical._cache; + } + path = ysy.pro.critical.findPath(); + critical._cache = path; + if (critical._prevCache) { + for (var key in critical._prevCache) { + if (!critical._prevCache.hasOwnProperty(key)) continue; + if (!path[key]) { + gantt.refreshTask(key); + } + } + for (key in path) { + if (!path.hasOwnProperty(key)) continue; + if (!critical._prevCache[key]) { + gantt.refreshTask(key); + } + } + } + critical._prevCache = path; + window.setTimeout(function () { + critical._cache = null; + }, 0); + return path; + }, + updateRegistration: function (isOn) { + if (isOn) { + ysy.proManager.register("filterTask", this.filterTask); + } else { + ysy.proManager.unregister("filterTask", this.filterTask); + } + } + , + filterTask: function (id, task) { + if (task.type === "task") { + var path = ysy.pro.critical.getPath(); + return !!(path && path[task.real_id]); + } + return true; + }, + toggle: function (state) { + var critical = ysy.settings.critical; + critical.setSilent("active", state === undefined ? !critical.active : state); + if (critical.active) { + var last = ysy.pro.critical.getPath()["last"]; + if (gantt.isTaskExists(last)) { + gantt.selectTask(last); + } else { + gantt._selected_task = last; + } + } + critical._fireChanges(this, "toggle"); + }, + close: function () { + var sett = ysy.settings.critical; + if (sett.setSilent("open", false)) { + ysy.pro.critical.updateRegistration(false); + sett._fireChanges(this, "close"); + } + }, + initToolbar: function (ctx) { + var criticalPanel = new ysy.view.CriticalPanel(); + criticalPanel.init(ysy.settings.critical); + ctx.children.push(criticalPanel); + }, + extendGanttTask: function (issue, gantt_issue) { + var path = ysy.pro.critical.getPath(); + if (path && path[issue.id]) { + gantt_issue.css += " critical"; + } + } + + +}; +//############################################################################################# +ysy.view = ysy.view || {}; +ysy.view.CriticalPanel = function () { + ysy.view.Widget.call(this); +}; +ysy.main.extender(ysy.view.Widget, ysy.view.CriticalPanel, { + name: "CriticalPanelWidget", + templateName: "CriticalPanel", + _repaintCore: function () { + var sett = ysy.settings.critical; + var target = this.$target; + if (sett.open) { + target.show(); + } else { + target.hide(); + } + if (sett.active) { + target.find("#critical_show").addClass("active"); + } else { + target.find("#critical_show").removeClass("active"); + } + this.tideFunctionality(); + }, + tideFunctionality: function () { + this.$target.find("#critical_show").off("click").on("click", function (event) { + ysy.log.debug("Show Critical path button pressed", "critical"); + ysy.pro.critical.toggle(); + }); + this.$target.find("#button_critical_help").off("click").on("click", ysy.proManager.showHelp); + } +}); diff --git a/easy_gantt_pro/assets/javascripts/easy_gantt_pro/delayed_issues.js b/easy_gantt_pro/assets/javascripts/easy_gantt_pro/delayed_issues.js new file mode 100644 index 0000000..475f2a6 --- /dev/null +++ b/easy_gantt_pro/assets/javascripts/easy_gantt_pro/delayed_issues.js @@ -0,0 +1,66 @@ +window.ysy = window.ysy || {}; +ysy.pro = ysy.pro || {}; +ysy.pro.delayed_issues = { + delayStates: null, + patch: function () { + + ysy.pro.toolPanel.registerButton( + { + id: "delayed_issue_filter", + bind: function () { + this.model = ysy.data.limits; + }, + func: function () { + this.model.filter_delayed_issues = !this.model.filter_delayed_issues; + ysy.pro.delayed_issues.updateRegistration(this.model.filter_delayed_issues); + if (this.model.filter_delayed_issues) { + ysy.pro.delayed_issues.decideDelayState(); + } + this.model._fireChanges(this, "click"); + }, + isOn: function () { + return this.model.filter_delayed_issues; + }, + isHidden: function () { + return ysy.settings.resource.open; + } + }); + }, + updateRegistration: function (isOn) { + if (isOn) { + ysy.proManager.register("filterTask", this.filterTask); + } else { + ysy.proManager.unregister("filterTask", this.filterTask); + } + }, + decideDelayState: function () { + this.delayStates = {}; // true if delayed + for (var id in gantt._pull) { + if (!gantt._pull.hasOwnProperty(id)) continue; + this.decideIfTaskDelayed(id); + } + }, + decideIfTaskDelayed: function (taskId) { + if (this.delayStates[taskId] !== undefined) return this.delayStates[taskId]; + var task = gantt._pull[taskId]; + if (task.type === "task") { + if (gantt._branches[task.id]) { + var branch = gantt._branches[task.id]; + for (var i = 0; i < branch.length; i++) { + var childId = branch[i]; + if (this.decideIfTaskDelayed(childId)) { + return this.delayStates[task.id] = true; + } + } + return this.delayStates[task.id] = false; + } else { + var diff = (moment() - task.start_date) / (86400000 + task.end_date - task.start_date); + return this.delayStates[task.id] = task.progress !== 1 && task.progress < diff; + } + } + return this.delayStates[task.id] = true; + }, + filterTask: function (id, task) { + return ysy.pro.delayed_issues.delayStates[id]; + } +}; \ No newline at end of file diff --git a/easy_gantt_pro/assets/javascripts/easy_gantt_pro/delayed_projects.js b/easy_gantt_pro/assets/javascripts/easy_gantt_pro/delayed_projects.js new file mode 100644 index 0000000..aa20474 --- /dev/null +++ b/easy_gantt_pro/assets/javascripts/easy_gantt_pro/delayed_projects.js @@ -0,0 +1,53 @@ +window.ysy = window.ysy || {}; +ysy.pro = ysy.pro || {}; +ysy.pro.delayed_projects = { + patch: function () { + if (!ysy.settings.global) return; + if (!ysy.settings.showProjectProgress) return; + ysy.pro.toolPanel.registerButton( + { + id: "delayed_project_filter", + bind: function () { + this.model = ysy.data.limits; + }, + func: function () { + this.model.filter_delayed_projects = !this.model.filter_delayed_projects; + ysy.pro.delayed_projects.updateRegistration(this.model.filter_delayed_projects); + this.model._fireChanges(this, "click"); + }, + isOn: function () { + return this.model.filter_delayed_projects; + }, + isRemoved: function () { + return !ysy.settings.global; + } + }); + var today = moment(); + ysy.data.Project.prototype.problems = $.extend(ysy.data.Project.prototype.problems, { + overDue: function () { + if (this.done_ratio === 100) return; + if (!this.start_date) return; + if (!this.end_date) return; + // if (this.end_date.isBefore(today)) return ysy.settings.labels.problems.progressDateOverdue; + var progressDate = this.start_date + this.done_ratio / 100.0 * (this.end_date - this.start_date); + if (progressDate < today) { + return ysy.settings.labels.problems.progressDateOverdue.replace("%{days}",today.diff(progressDate,"days")); + } + } + }); + }, + updateRegistration: function (isOn) { + if (isOn) { + ysy.proManager.register("filterTask", this.filterTask); + } else { + ysy.proManager.unregister("filterTask", this.filterTask); + } + }, + filterTask: function (id, task) { + if (task.type === "project") { + if (task.progress === 1) return false; + if (task.progress >= (moment() - task.start_date) / (task.end_date - task.start_date)) return false; + } + return true; + } +}; \ No newline at end of file diff --git a/easy_gantt_pro/assets/javascripts/easy_gantt_pro/easy_gantt_pro.js b/easy_gantt_pro/assets/javascripts/easy_gantt_pro/easy_gantt_pro.js new file mode 100644 index 0000000..559b47a --- /dev/null +++ b/easy_gantt_pro/assets/javascripts/easy_gantt_pro/easy_gantt_pro.js @@ -0,0 +1,12 @@ +/* + * COMMON + * = require easy_gantt_pro/common + * = require easy_gantt_pro/sorting + * = require easy_gantt_pro/email_silencer + * = require easy_gantt_pro/delayed_issues + * + * PROJECT SPECIFIC + * = require easy_gantt_pro/baseline + * = require easy_gantt_pro/add_task + * = require easy_gantt_pro/critical +*/ diff --git a/easy_gantt_pro/assets/javascripts/easy_gantt_pro/easy_gantt_pro_global.js b/easy_gantt_pro/assets/javascripts/easy_gantt_pro/easy_gantt_pro_global.js new file mode 100644 index 0000000..87c7f12 --- /dev/null +++ b/easy_gantt_pro/assets/javascripts/easy_gantt_pro/easy_gantt_pro_global.js @@ -0,0 +1,13 @@ +/* + * COMMON + * = require easy_gantt_pro/common + * = require easy_gantt_pro/sorting + * = require easy_gantt_pro/email_silencer + * = require easy_gantt_pro/delayed_issues + * + * GLOBAL SPECIFIC + * = require easy_gantt_pro/project_move + * = require easy_gantt_pro/lowest_progress + * = require easy_gantt_pro/delayed_projects + * = require easy_gantt_pro/gg_resource + */ diff --git a/easy_gantt_pro/assets/javascripts/easy_gantt_pro/email_silencer.js b/easy_gantt_pro/assets/javascripts/easy_gantt_pro/email_silencer.js new file mode 100644 index 0000000..cc39803 --- /dev/null +++ b/easy_gantt_pro/assets/javascripts/easy_gantt_pro/email_silencer.js @@ -0,0 +1,32 @@ +window.ysy = window.ysy || {}; +ysy.pro = ysy.pro || {}; +ysy.pro.silencer = ysy.pro.silencer || {}; +$.extend(ysy.pro.silencer, { + setting: null, + name: "Silencer", + patch: function () { + ysy.proManager.register("beforeSaveIssue", this.updateSaveData); + this.setting = new ysy.data.Data(); + this.setting.init({name: this.name, turnedOn: false}); + var $checkBox = $(' ' + + ''); + $("#button_year_zoom").after($checkBox); + ysy.view.AllButtons.prototype.extendees.silencer = { + widget: ysy.view.CheckBox, + bind: function () { + this.model = ysy.pro.silencer.setting; + }, + func: function () { + this.model.turnedOn = this.$target.is(":checked"); + this.model._fireChanges(this, "click"); + }, + isOn: function () { + return this.model.turnedOn; + } + }; + }, + updateSaveData: function (data) { + if (ysy.pro.silencer.setting.turnedOn) + data.easy_gantt_suppress_notification = true; + } +}); \ No newline at end of file diff --git a/easy_gantt_pro/assets/javascripts/easy_gantt_pro/gg_resource.js b/easy_gantt_pro/assets/javascripts/easy_gantt_pro/gg_resource.js new file mode 100644 index 0000000..1db881b --- /dev/null +++ b/easy_gantt_pro/assets/javascripts/easy_gantt_pro/gg_resource.js @@ -0,0 +1,158 @@ +window.ysy = window.ysy || {}; +ysy.pro = ysy.pro || {}; +ysy.pro.ggrm = { + _name: "GGRM", + patch: function () { + if (!ysy.pro.resource || !ysy.pro.resource.project_canvas_renderer) { + ysy.pro.ggrm = null; + return; + } + + ysy.pro.ggrm = { + patch: function () { + ysy.proManager.register("close", this.close); + var ggrmClass = ysy.pro.ggrm; + ysy.view.AllButtons.prototype.extendees.ggrm = { + bind: function () { + this.model = ysy.settings.resource; + }, + func: function () { + var resource = ysy.settings.resource; + if (resource.ggrm) { + ggrmClass.close(); + } else { + ggrmClass.open(); + } + }, + isOn: function () { + return ysy.settings.resource.ggrm; + } + }; + ysy.pro.resource.compileStyles(); + ysy.pro.sumRow.summers.ggrm = { + day: function (date, project) { + if (project.start_date.isAfter(date)) return 0; + if (project.end_date.isBefore(date)) return 0; + if (!project._ganttResources) return 0; + var allocation = project._ganttResources[date.format("YYYY-MM-DD")]; + if (allocation > 0) return allocation; + return 0; + }, + week: function (first_date, last_date, project) { + if (project.start_date.isAfter(last_date)) return 0; + if (project.end_date.isBefore(first_date)) return 0; + if (!project._ganttResources) return 0; + var sum = 0; + var mover = moment(first_date); + while (mover.isBefore(last_date)) { + var allocation = project._ganttResources[mover.format("YYYY-MM-DD")]; + if (allocation > 0) sum += allocation; + mover.add(1, "day"); + } + return sum; + }, + entities: ["projects"], + title: "GGRM" + }; + }, + open: function () { + var resource = ysy.settings.resource; + if (resource.setSilent("ggrm", true)) { + this.loadResources(); + ysy.settings.sumRow.setSummer("ggrm"); + ysy.view.bars.registerRenderer("project", this.outerRenderer); + ysy.proManager.closeAll(this); + resource._fireChanges(this, "toggle"); + } + }, + close: function (except) { + var resource = ysy.settings.resource; + if (resource.setSilent("ggrm", false)) { + ysy.settings.sumRow.removeSummer("ggrm"); + ysy.view.bars.removeRenderer("project", ysy.pro.ggrm.outerRenderer); + resource._fireChanges(this, "toggle"); + } + }, + loadResources: function (projectId) { + var ids = []; + var start_date; + var end_date; + var project; + if (projectId) { + ids.push(projectId); + project = ysy.data.projects.getByID(projectId); + start_date = project.start_date; + end_date = project.end_date; + } else { + var projects = ysy.data.projects.getArray(); + for (var i = 0; i < projects.length; i++) { + project = projects[i]; + ids.push(project.id); + if (!start_date || project.start_date.isBefore(start_date)) { + start_date = project.start_date; + } + if (!end_date || project.end_date.isAfter(end_date)) { + end_date = project.end_date; + } + } + } + ysy.gateway.polymorficPostJSON( + ysy.settings.paths.globalGanttResources + .replace(":start", start_date.format("YYYY-MM-DD")) + .replace(":end", end_date.format("YYYY-MM-DD")), + { + project_ids: ids + }, + $.proxy(this._handleResourcesData, this), + function () { + ysy.log.error("Error: Unable to load data"); + //ysy.pro.resource.loader.loading = false; + } + ); + }, + _handleResourcesData: function (data) { + var json = data.easy_resource_data; + if (!json) return; + this._resetProjects(); + this._loadProjects(json.projects); + }, + _resetProjects: function () { + var projects = ysy.data.projects.getArray(); + for (var i = 0; i < projects.length; i++) { + delete projects[i]._ganttResources; + } + }, + _loadProjects: function (json) { + var projects = ysy.data.projects; + for (var i = 0; i < json.length; i++) { + var project = projects.getByID(json[i].id); + if (!project) continue; + project._ganttResources = json[i].resources_sums; + project._fireChanges(this, "load resources"); + } + }, + outerRenderer: function (task, next) { + var div = next().call(this, task, next); + var allodiv = ysy.pro.ggrm._projectRenderer.call(gantt, task); + div.appendChild(allodiv); + return div; + }, + _projectRenderer: function (task) { + var resourceClass = ysy.pro.resource; + var project = task.widget && task.widget.model; + var allocPack = {allocations: project._ganttResources || {}, types: {}}; + var canvasList = ysy.view.bars.canvasListBuilder(); + canvasList.build(task, this); + if (ysy.settings.zoom.zoom !== "day") { + $.proxy(resourceClass.issue_week_renderer, this)(task, allocPack, canvasList); + } else { + $.proxy(resourceClass.issue_day_renderer, this)(task, allocPack, canvasList); + } + var element = canvasList.getElement(); + element.className += " project"; + return element; + } + }; + ysy.pro.ggrm.patch(); + } +}; \ No newline at end of file diff --git a/easy_gantt_pro/assets/javascripts/easy_gantt_pro/lowest_progress.js b/easy_gantt_pro/assets/javascripts/easy_gantt_pro/lowest_progress.js new file mode 100644 index 0000000..9ee2706 --- /dev/null +++ b/easy_gantt_pro/assets/javascripts/easy_gantt_pro/lowest_progress.js @@ -0,0 +1,150 @@ +window.ysy = window.ysy || {}; +ysy.pro = ysy.pro || {}; +ysy.pro.lowestProgress = { + patch: function () { + if (!ysy.settings.showLowestProgress) { + ysy.pro.lowestProgress = null; + return; + } + + ysy.pro.lowestProgress = { + name: "LowestProgressIssue", + data: {}, + laggies: {}, + loaded: false, + setting: null, + className: " gantt-issue-lagging", + patch: function () { + var lowestClass = ysy.pro.lowestProgress; + this.setting = new ysy.data.Data(); + this.setting.init({name: this.name, turnedOn: false}); + ysy.pro.toolPanel.registerButton({ + id: "show_lowest_progress_tasks", + bind: function () { + this.model = lowestClass.setting; + }, + func: function () { + this.model.turnedOn = !this.model.turnedOn; + this.model._fireChanges(this, "click"); + if (this.model.turnedOn) { + var send = lowestClass.load(); + if (!send) { + lowestClass.colorizeLaggies(); + } + } else { + lowestClass.resetLaggies(); + lowestClass.decolorizeLaggies(); + } + }, + isOn: function () { + return this.model.turnedOn; + } + }); + gantt.templates.task_text = function (start, end, task) { + if (!lowestClass.setting.turnedOn) return ""; + if (task.type === "project") { + if (task.widget && task.widget.model) { + var id = task.widget.model.id; + var issuePack = lowestClass.data[id]; + if (issuePack) { + return Mustache.render( + ysy.view.templates.lowestProgressText, issuePack + ).replace(/,\s+#\$@&/, ""); + } + } + } + return ""; + }; + ysy.data.loader.register(function () { + if (!this.setting.turnedOn) return; + if (!ysy.data.loader.loaded) { + this.resetLaggies(); + } + var send = this.load(); + if (!send) { + this.colorizeLaggies(); + } + }, this); + }, + load: function () { + var ids = []; + var projects = ysy.data.projects.getArray(); + for (var i = 0; i < projects.length; i++) { + var id = projects[i].id; + if (this.data[id] === undefined) { + ids.push(id); + } + } + if (!ids.length) return false; + ysy.gateway.polymorficPostJSON( + ysy.settings.paths.lowestProgressTasks, + {project_ids: ids}, + $.proxy(ysy.pro.lowestProgress._loadLaggies, this), + ysy.pro.lowestProgress._handleError + ); + return true; + }, + _handleError: function (e) { + console.error(e); + }, + _loadLaggies: function (data) { + if (!data || !data.easy_gantt_data) return; + var issues = data.easy_gantt_data.issues; + for (var i = 0; i < issues.length; i++) { + var issue = issues[i]; + this.laggies[issue.id] = issue; + var projectId = issue.project_id; + if (!this.data[projectId]) { + this.data[projectId] = {progress_date: issue.progress_date, issues: []}; + } + this.data[projectId].issues.push(issue); + } + var projects = ysy.data.projects.getArray(); + for (i = 0; i < projects.length; i++) { + projectId = projects[i].id; + if (this.data[projectId] === undefined) { + this.data[projectId] = false; + } + } + this._redrawAllProjects("loaded"); + this.colorizeLaggies(); + this.loaded = true; + }, + resetLaggies: function () { + this.data = {}; + this.laggies = {}; + this.loaded = false; + this._redrawAllProjects("reset"); + }, + _redrawAllProjects: function (reason) { + var projects = ysy.data.projects.getArray(); + for (var i = 0; i < projects.length; i++) { + projects[i]._fireChanges(this, reason); + } + }, + colorizeLaggies: function () { + var issues = ysy.data.issues; + for (var id in this.laggies) { + if (!this.laggies.hasOwnProperty(id)) continue; + var laggie = this.laggies[id]; + var issue = issues.getByID(laggie.id); + if (!issue) continue; + if (issue.css && issue.css.indexOf(this.className) > -1) continue; + if (issue.css === undefined) issue.css = ""; + issue.css += this.className; + issue._fireChanges(this, "colorizeLaggies"); + } + }, + decolorizeLaggies: function () { + var issues = ysy.data.issues.getArray(); + for (var i = 0; i < issues.length; i++) { + var issue = issues[i]; + if (!issue.css || issue.css.indexOf(this.className) === -1) continue; + issue.css = issue.css.replace(this.className, ""); + issue._fireChanges(this, "decolorizeLaggies"); + } + } + }; + ysy.pro.lowestProgress.patch(); + } +}; \ No newline at end of file diff --git a/easy_gantt_pro/assets/javascripts/easy_gantt_pro/project_move.js b/easy_gantt_pro/assets/javascripts/easy_gantt_pro/project_move.js new file mode 100644 index 0000000..3662c8b --- /dev/null +++ b/easy_gantt_pro/assets/javascripts/easy_gantt_pro/project_move.js @@ -0,0 +1,51 @@ +window.ysy = window.ysy || {}; +ysy.pro = ysy.pro || {}; +ysy.pro.projectMove = { + patch: function () { + ysy.proManager.register("extendGanttTask", this.extendGanttTask); + //var proManager = ysy.proManager; + //var projectMoveClass = ysy.pro.projectMove; + ysy.data.Project.prototype._shift = 0; + gantt.attachEvent("onBeforeTaskOpened", function (id) { + var task = gantt._pull[id]; + if (!task || !task.widget) return true; + if (task.type !== "project") return true; + var project = task.widget.model; + if (project._shift) { + dhtmlx.message(ysy.settings.labels.projectMove.error_opening_unsaved, "error"); + return false; + } + return true; + }); + gantt.attachEvent("onTaskOpened", function (id) { + var task = gantt._pull[id]; + if (!task || !task.widget) return true; + if (task.type === "project") { + task.editable = false; + } + }); + ysy.data.saver.sendProjects = function () { + var j, data; + if (!ysy.data.projects) return; + var projects = ysy.data.projects.array; + for (j = 0; j < projects.length; j++) { + var project = projects[j]; + if (!project._changed) continue; + //if (project._deleted && project._created) continue; + data = { + days: project._shift + //project: { + //} + }; + ysy.gateway.sendProject("PUT", project, data, ysy.data.saver.callbackBuilder(project)); + } + }; + }, + extendGanttTask: function (project, gantt_issue) { + if (gantt_issue.type !== "project") return; + if (project.needLoad || project.issues_count && !project.has_subprojects){ + gantt_issue.editable = true; + gantt_issue.shift = project._shift * (60 * 60 * 24); + } + } +}; diff --git a/easy_gantt_pro/assets/javascripts/easy_gantt_pro/sorting.js b/easy_gantt_pro/assets/javascripts/easy_gantt_pro/sorting.js new file mode 100644 index 0000000..dd6a8e0 --- /dev/null +++ b/easy_gantt_pro/assets/javascripts/easy_gantt_pro/sorting.js @@ -0,0 +1,80 @@ +window.ysy = window.ysy || {}; +ysy.pro = ysy.pro || {}; +ysy.pro.sorting = $.extend(ysy.pro.sorting, { + name:"Sorter", + patch: function () { + var setting = new ysy.data.Data(); + setting.init({_name: "Sorting", sortBy: null}); + ysy.settings.sorting = setting; + + gantt.attachEvent("onGanttReady", $.proxy(this.bindClick, gantt)); + + }, + columnCompares: { + start_date: function (a, b) { + return (a._start_date || a.start_date) - (b._start_date || b.start_date); + }, + end_date: function (a, b) { + return (a._end_date || a.end_date) - (b._end_date || b.end_date); + }, + subject: function (a, b) { + return a.text.localeCompare(b.text); + } + }, + getCompareFunction: function (column, sort) { + var sortCoefficient = sort ? -1 : 1; + if (this.columnCompares[column]) { + var func = this.columnCompares[column]; + return function (a, b) { + var result = func(a, b); + if (result === 0) result = a.order - b.order; + return sortCoefficient * result; + } + } + return function (a, b) { + var result = ysy.pro.sorting.columnsValidation(a, b); + if (result === undefined) { + result = a.columns[column].toString().localeCompare(b.columns[column].toString()); + if (result === 0) result = a.order - b.order; + } + return sortCoefficient * result; + } + }, + columnsValidation: function (a, b) { + if (!a.columns) { + if (!b.columns) return a.order - b.order; + return -1 + } + if (!b.columns) return 1; + return undefined; + }, + bindClick: function () { + this._click.gantt_grid_head_cell = dhtmlx.bind(function (e, id, trg) { + var column = trg.getAttribute("column_id"); + + if (!this.callEvent("onGridHeaderClick", [column, e])) + return; + + if (this._sort && this._sort.direction && this._sort.name == column) { + var sort = this._sort.direction; + if (sort === "desc") { + // remove sorting by column (on third click) + this._sort = null; + this.sort(); + return; + } + // invert sort direction + sort = (sort == "desc") ? "asc" : "desc"; + } else { + sort = "asc"; + } + var sortFunction = ysy.pro.sorting.getCompareFunction(column, sort == "desc"); + this._sort = { + name: column, + criteria: sortFunction, + direction: sort + }; + this.sort(sortFunction); + }, this); + } +}); \ No newline at end of file diff --git a/easy_gantt_pro/assets/stylesheets/easy_gantt_pro/easy_gantt_pro.css b/easy_gantt_pro/assets/stylesheets/easy_gantt_pro/easy_gantt_pro.css new file mode 100644 index 0000000..3a70d4f --- /dev/null +++ b/easy_gantt_pro/assets/stylesheets/easy_gantt_pro/easy_gantt_pro.css @@ -0,0 +1,93 @@ +.gantt_grid_head_cell .gantt_sort.gantt_none { + background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAcAAAANCAYAAABlyXS1AAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH4AcbExA7CgFb8AAAAHFJREFUGNNjYEAC9f1T/zNgA/X9U/9vv3jrP7ICRpiEhbMrXOGJvbsZGguzGRnr+6f+t3ByxTDpxL7dDIz////Hbg/tACNOvzEwQBy04/JtDAkPXVWIP9EVeOiqMjAyMjLCBf7///9/+6Vb/3F6DV0CADA7O+B8p/IiAAAAAElFTkSuQmCC) +} + + +/* BASELINE CSS (start)*/ +.gantt-baseline { + position: absolute; + border-radius: 2px; + opacity: 0.6; + margin-top: -7px; + height: 12px; + background: #ffd180; + border: 1px solid rgb(255, 153, 0); +} + +.gantt-baselines .gantt_task_line{ + margin-top: -9px; +} +.gantt-baselines .gantt_line_wrapper { + margin-top: -9px; +} +.gantt-baselines .gantt_task_link .gantt_link_arrow { + margin-top: -10px +} +/* BASELINE CSS (end)*/ +/*.gantt_task_line.planned.gantt_milestone-type, */ +.gantt-baseline.gantt_milestone-type { + -webkit-transform: scale(0.75) rotate(45deg); + -moz-transform: scale(0.75) rotate(45deg); + -ms-transform: scale(0.75) rotate(45deg); + -o-transform: scale(0.75) rotate(45deg); + transform: scale(0.75) rotate(45deg); + visibility: visible; +} + +.gantt_task_line.gantt-scheme-project-status-15 .gantt_task_content { + /* planned project */ + background: rgba(78, 191, 103, 0.1); + border-color: rgba(58, 160, 81, 1) !important; +} + +.gantt-scheme-project-status-15 { + background: rgba(78, 191, 103, 0.1); + border-color: rgba(58, 160, 81, 0.1); + color: #4ebf67; +} + +.gantt-scheme-project-status-15 a { + color: #4ebf67; +} + +.gantt_task_line .gantt_task_content { + overflow: hidden; +} +.gantt-grid-row-project{ + font-weight: bold; +} +.task-type .gantt_grid_superitem{ + font-weight: normal; +} +.gantt-grid-project-issues-expand{ + /*display: inline-block;*/ + border: 1px solid #eeeeaa; + background-color: #ffffcc; + min-width: 15px; + margin: 5px; + font-size: 10px; + padding: 3px 6px; + vertical-align: bottom; + border-radius: 3px; + color:#888888; +} + +.gantt-issue-lagging, .gantt-issue-lagging .gantt_task_content { + background-color: #ffaa44; +} +input[type="checkbox"].gantt-grid-checkbox{ + margin: 5px; +} +.gantt-grid-checkbox-cont{ + display: none; + cursor: pointer; + position: absolute; + left: 0; + top: 0; +} +.bulk-edit-mode .gantt-grid-checkbox-cont{ + display: block; +} +.bulk-edit-mode .gantt_grid_body_subject{ + padding-left: 5px; +} diff --git a/easy_gantt_pro/config/locales/ar.yml b/easy_gantt_pro/config/locales/ar.yml new file mode 100644 index 0000000..b93c21a --- /dev/null +++ b/easy_gantt_pro/config/locales/ar.yml @@ -0,0 +1,2 @@ +--- +ar: diff --git a/easy_gantt_pro/config/locales/cs.yml b/easy_gantt_pro/config/locales/cs.yml new file mode 100644 index 0000000..8b97b98 --- /dev/null +++ b/easy_gantt_pro/config/locales/cs.yml @@ -0,0 +1,76 @@ +--- +cs: + easy_gantt_pro: + add_task_modal: + no_parent: '' + title: Přidat úkol + add_task: + body: Nový úkol verze můžete vytvořit kliknutím na tlačítko Úkol (nebo tlačítko + Verze) nebo přetažením kamkoliv do grafu + title: Nápověda k přidání úkolu + baselines: + confirm_delete: Opravdu chcete smazat tuto baseline + create_failed: Nová baseline nemohla být vytvořena + delete_failed: Baseline nemohla být odstraněna + error_not_saved: Musíte uložit všechny změny v projektu před vytvořením baseline + label_baseline: Baseline + label_same: "-stejný-" + off_desc: Zapnout Baseline modul k zobrazení a vytváření baselines. + off_link: Nastavení modulu + off_title: Baseline modul je vypnutý + cashflow: + label_cashflow: Peněžní tok + critical: + hide_button: Skrýt Kritickou cestu + legend_issues: Úkol na Kritické cestě + show_button: Zobrazit Kritickou cestu + lowest_progress: + label_progress_date: Datum průběhu + project_move: + error_opening_unsaved: Musíte nejprve uložit změny před otevřením projektu + resources: + label_resources: Zdroje + easy_gantt: + button: + bulk_edit: Hromadná úprava + delayed_issue_filter: Filtrovat opožděné úkoly + show_lowest_progress_tasks: Zobrazit úkoly s nejnižším datem průběhu + show_only_critical: Filtrovat kritické + popup: + add_task: + heading: Pomoc s přidáním úkolu + text: Nový úkol nebo milník může být vytvořen kliknutím na tlačítko úkolu + (respektive tlačítko milníku) nebo přetažením kdekoli v grafu + baseline: + heading: Popis baseline + heading_new_baseline: Nová baseline + text: Vytvoří kopii projektu pro další porovnání + critical: + heading: Popis Kritické cesty + text: Nejdelší sled činností v plánu projektu, který musí být dokončen včas, + aby byl daný projekt dokončen v řádném termínu. Aktivitu na kritické cestě + nelze zahájit, dokud aktivita jejího předchůdce není dokončena; pokud je + odložena o jeden den, bude celý projekt odložen o jeden den, ledaže by aktivita + navazující na opožděnou aktivitu byla dokončena o den dříve. Pro správnou + funkci kritické cesty by projekt měl mít jen jeden počáteční a jeden koncový + úkol a tyto úkoly by měly být spojeny vazbami. + scheme_by: + label_by_priority: Priority + label_by_status: Stavu + label_by_tracker: Trackeru + label_color_by: Barva dle + title: + delayed_issue_filter: Zobrazit pouze úkoly, jejichž datum průběhu je v minulosti + show_only_critical: Zobrazit pouze úkoly na kritické cestě + easy_pages: + modules: + easy_global_gantt: Easy gantt + easy_proposer: + easy_gantt: + index: + caption: Easy Gantt PRO + description: Přejít na Easy Gantt PRO + heading_easy_gantts_issues: Easy Gantt PRO + label_show_lowest_progress_tasks: Úkoly s nejpomalejším průběhem + text_show_lowest_progress_tasks: Zobrazit úkoly s nejhorším datem průběhu (= datum + odpovídající poměru % Hotovo v ganttu) diff --git a/easy_gantt_pro/config/locales/da.yml b/easy_gantt_pro/config/locales/da.yml new file mode 100644 index 0000000..d3dcd59 --- /dev/null +++ b/easy_gantt_pro/config/locales/da.yml @@ -0,0 +1,2 @@ +--- +da: diff --git a/easy_gantt_pro/config/locales/de.yml b/easy_gantt_pro/config/locales/de.yml new file mode 100644 index 0000000..639dee9 --- /dev/null +++ b/easy_gantt_pro/config/locales/de.yml @@ -0,0 +1,78 @@ +--- +de: + easy_gantt_pro: + add_task_modal: + no_parent: '' + title: Aufgabe hinzufügen + baselines: + confirm_delete: Möchten Sie wirklich dieses Basisplan löschen + create_failed: Neuer Basisplan konnte nicht erstellt werden + delete_failed: Der Basisplan konnte nicht gelöscht werden + error_not_saved: Sie müssen erst mal alle Veränderungen im Projekt abspeichern + bevor Sie einen Basisplan erstellen können + label_baseline: Basisplan + label_same: "-gleich-" + cashflow: + label_cashflow: Cashflow + critical: + hide_button: Versteckter kritischer Weg + legend_issues: Aufgabe auf dem kritischen Weg + show_button: Den kritischen Weg anzeigen + lowest_progress: + label_progress_date: Das Fortschrittsdatum + project_move: + error_opening_unsaved: Sie müssen erst mal alle Veränderungen abspeichern bevor + das Projekt eröffnen + resources: + label_resources: Die Ressourcen + easy_gantt: + button: + bulk_edit: Menge bearbeiten + delayed_issue_filter: Verzögerte Probleme filtern + show_lowest_progress_tasks: Die Aufgaben mit der niedrigsten Verhältnis Fortschritt/Datum anzeigen + show_only_critical: Kritisch Filtern + popup: + add_task: + heading: Hilfe für die Aufgabe hinzufügen + text: Neue Aufgabe oder Meilenstein können erstellt werden in dem Sie auf + die Aufgabe Taste (entsprechen Meilenstein Taste) klicken oder in dem Sie + es überall in dem Graph ziehen. + baseline: + heading: Basisplan Beschreibung + heading_new_baseline: Neuer Basisplan + text: Kopie des Projekts für den weiteren Vergleich erstellen + critical: + heading: Kritischer Weg Beschreibung + text: Längste Sequenz der Aktivitäten in der Projekt Planung, soll fristgemäß + beendet werden um den Projekt am Stichtag fertigzustellen. Eine Aktivität + auf dem kritischen Weg kann solang nicht erstell werden solang die vorherigen + Aktivitäten nicht abgeschlossen sind; falls es sich doch verspätet um einen + Tag, der ganzer Projekt wird sich um einen Tag verspäten, es sei denn die + Aktivitäten die verspäteten Aktivitäten folgen ein Tag früher fertig gestellt + werden. Um eine ordnungsgemäße Funktionalität des kritischen Weges zu sichern, + soll der Projekt 1 Startaufgabe und 1 Endaufgabe beinhalten und die Aufgaben + sollen mit Relationen verbunden sein + scheme_by: + label_by_priority: nach Priorität + label_by_status: nach Status + label_by_tracker: nach Verflogung + label_color_by: Färben nach + title: + delayed_issue_filter: Nur Probleme anzeigen dessen Fortschrittsdatum liegt hinter + heute + show_only_critical: Nur die Aufgaben anzeigen die sich derzeit auf dem kritischen + Weg befinden + easy_pages: + modules: + easy_global_gantt: Easy Gantt + easy_project_gantt: Easy Gantt + easy_proposer: + easy_gantt: + index: + caption: Easy Gantt PRO + description: Gehen Sie zu Easy Gantt PRO + heading_easy_gantts_issues: Easy Gantt PRO + label_show_lowest_progress_tasks: Aufgaben mit dem kleinstem Fortschritt + text_show_lowest_progress_tasks: Die Aufgabe mit der schlechtesten Fortschritt/Datum + Relation anzeigen (=das Datum , das korrespondiert mit der Fertigstellung der + Aufgabe in dem Verhältnis-Balken in Gantt) diff --git a/easy_gantt_pro/config/locales/en-AU.yml b/easy_gantt_pro/config/locales/en-AU.yml new file mode 100644 index 0000000..56b80b3 --- /dev/null +++ b/easy_gantt_pro/config/locales/en-AU.yml @@ -0,0 +1,2 @@ +--- +en-AU: diff --git a/easy_gantt_pro/config/locales/en-GB.yml b/easy_gantt_pro/config/locales/en-GB.yml new file mode 100644 index 0000000..a32c228 --- /dev/null +++ b/easy_gantt_pro/config/locales/en-GB.yml @@ -0,0 +1,2 @@ +--- +en-GB: diff --git a/easy_gantt_pro/config/locales/en-US.yml b/easy_gantt_pro/config/locales/en-US.yml new file mode 100644 index 0000000..54b3be8 --- /dev/null +++ b/easy_gantt_pro/config/locales/en-US.yml @@ -0,0 +1,2 @@ +--- +en-US: diff --git a/easy_gantt_pro/config/locales/en.yml b/easy_gantt_pro/config/locales/en.yml new file mode 100644 index 0000000..5b44d86 --- /dev/null +++ b/easy_gantt_pro/config/locales/en.yml @@ -0,0 +1,72 @@ +--- +en: + easy_gantt_pro: + add_task_modal: + no_parent: '' + title: Add task + baselines: + confirm_delete: Do you really want to delete this baseline + create_failed: New baseline could not be created + delete_failed: Baseline could not be deleted + error_not_saved: You have to save all changes in project before creating its + baseline + label_baseline: Baseline + label_same: "-same-" + cashflow: + label_cashflow: Cash Flow + critical: + hide_button: Hide Critical path + legend_issues: Task on Critical path + show_button: Show Critical path + lowest_progress: + label_progress_date: Progress date + project_move: + error_opening_unsaved: You have to save changes first before opening the project + resources: + label_resources: Resources + easy_gantt: + button: + bulk_edit: Bulk edit + delayed_issue_filter: Filter Delayed Issues + show_lowest_progress_tasks: Show tasks with lowest progress date + show_only_critical: Filter Critical + popup: + add_task: + heading: Add task help + text: New task or milestone can be created by a click on Task button (respective + Milestone button) or by dragging anywhere in graph + baseline: + heading: Baseline description + heading_new_baseline: New baseline + text: Creates copy of the project for further comparisons + critical: + heading: Critical path description + text: Longest sequence of activities in a project plan which must be completed + on time for the project to complete on due date. An activity on the critical + path cannot be started until its predecessor activity is complete; if it + is delayed for a day, the entire project will be delayed for a day unless + the activity following the delayed activity is completed a day earlier. + For proper function of a critical path, the project should have just 1 starting + and 1 ending task and the tasks should be connected with relations. + scheme_by: + label_by_priority: by Priority + label_by_status: by Status + label_by_tracker: by Tracker + label_color_by: Color by + title: + delayed_issue_filter: Show only issues which progress date is behind today + show_only_critical: Show only tasks on Critical path + easy_pages: + modules: + easy_global_gantt: Easy gantt + easy_project_gantt: Easy gantt + easy_proposer: + easy_gantt: + index: + caption: Easy Gantt PRO + description: Go to Easy Gantt PRO + heading_easy_gantts_issues: Easy Gantt PRO + label_show_lowest_progress_tasks: Lowest progress tasks + text_show_lowest_progress_tasks: Display tasks with the worst progress date (= date + corresponding to task's done ratio bar in gantt) + warning_easy_gantt_recalculate_fixed_delay: This count affect relations delay on every task in the system. Are you sure to continue? diff --git a/easy_gantt_pro/config/locales/es.yml b/easy_gantt_pro/config/locales/es.yml new file mode 100644 index 0000000..e42e485 --- /dev/null +++ b/easy_gantt_pro/config/locales/es.yml @@ -0,0 +1,75 @@ +--- +es: + easy_gantt_pro: + add_task_modal: + no_parent: '' + title: Añadir tarea + baselines: + confirm_delete: Seguro que deseas eliminar esta referencia + create_failed: No se ha podido crear la nueva referencia + delete_failed: No se ha podido eliminar la referencia + error_not_saved: Para crear la referencia del proyecto, debes guardar todos + los cambios + label_baseline: Referencia + label_same: "-igual-" + cashflow: + label_cashflow: Flujo de liquidez + critical: + hide_button: Ocultar senda Crítica + legend_issues: Tarea en senda Crítica + show_button: Mostrar senda Crítica + lowest_progress: + label_progress_date: Fecha de progreso + project_move: + error_opening_unsaved: Debes guardar los cambios antes de abrir el proyecto + resources: + label_resources: Recursos + easy_gantt: + button: + bulk_edit: Edición de volumen + delayed_issue_filter: Filtrar Asuntos Pendientes + show_lowest_progress_tasks: Mostrar tareas con la fecha de progreso más corta + show_only_critical: Filtrar Críticos + popup: + add_task: + heading: Añadir ayuda de tarea + text: Se puede crear una nueva tarea u objetivo haciendo clic en el botón + de Tarea (o en el botón respectivo de Objetivo) o arrastrándolo a cualquier + lugar del gráfico + baseline: + heading: Descripción de referencia + heading_new_baseline: Nueva referencia + text: Crea una copia del proyecto para futuras comparaciones + critical: + heading: Descripción de la senda crítica + text: Es la secuencia más extensa de actividades en un plan de proyecto que + debe realizarse a tiempo para que el proyecto se finalice en fecha. No se + puede iniciar una actividad en la senda crítica hasta que la actividad anterior + se haya realizado; si se retrasa durante un día, el proyecto completo se + retrasará durante un día, a menos que la actividad correspondiente a la + actividad con retraso se realice un día antes. Para que la senda crítica + funcione correctamente, el proyecto debería tener solamente 1 tarea de inicio + y 1 tarea de finalización, y las tareas deben estar conectadas mediante + relaciones. + scheme_by: + label_by_priority: por Prioridad + label_by_status: por Estado + label_by_tracker: por Rastreador + label_color_by: Color por + title: + delayed_issue_filter: Mostrar sólo asuntos cuya fecha de progreso es posterior + a hoy + show_only_critical: Mostrar sólo tareas en senda Crítica + easy_pages: + modules: + easy_global_gantt: Easy gantt + easy_project_gantt: Easy Gantt + easy_proposer: + easy_gantt: + index: + caption: Easy Gantt PRO + description: Ir a Easy Gant PRO + heading_easy_gantts_issues: Easy Gantt PRO + label_show_lowest_progress_tasks: Tareas con menor progreso + text_show_lowest_progress_tasks: Mostrar tareas con la peor fecha de progreso (= + fecha correspondiente al gráfico de relación de tareas realizadas en gantt) diff --git a/easy_gantt_pro/config/locales/fi.yml b/easy_gantt_pro/config/locales/fi.yml new file mode 100644 index 0000000..e173d18 --- /dev/null +++ b/easy_gantt_pro/config/locales/fi.yml @@ -0,0 +1,2 @@ +--- +fi: diff --git a/easy_gantt_pro/config/locales/fr.yml b/easy_gantt_pro/config/locales/fr.yml new file mode 100644 index 0000000..4992cad --- /dev/null +++ b/easy_gantt_pro/config/locales/fr.yml @@ -0,0 +1,77 @@ +--- +fr: + easy_gantt_pro: + add_task_modal: + no_parent: '' + title: Ajouter tâche + baselines: + confirm_delete: Souhaitez-vous réellement effacer cette ligne de base + create_failed: La nouvelle ligne de base n'a pas pu être créée + delete_failed: La ligne de base n'a pas pu être supprimée + error_not_saved: Vous devez enregistrer toutes les modifications dans le projet + avant de créer sa ligne de base + label_baseline: Ligne de base + label_same: "-idem-" + cashflow: + label_cashflow: Flux de Trésorerie + critical: + hide_button: Masquer chemin critique + legend_issues: Tâche sur chemin critique + show_button: Afficher chemin critique + lowest_progress: + label_progress_date: Date progrès + project_move: + error_opening_unsaved: Vous devez d'abord sauvegarder les modifications avant + d'ouvrir le projet + resources: + label_resources: Ressources + easy_gantt: + button: + bulk_edit: Modifier en bloc + delayed_issue_filter: Filtrer les Publications Retardées + show_lowest_progress_tasks: Afficher les tâches avec la date de progrès la plus + basse + show_only_critical: Filtre critique + popup: + add_task: + heading: Ajouter tâche aide + text: Une nouvelle tâche ou un jalon peuvent être créés par un clic sur le + bouton Tâche (bouton Jalon correspondant) ou en faisant glisser partout + dans le graphique + baseline: + heading: Description ligne de base + heading_new_baseline: Nouvelle ligne de base + text: Crée une copie du projet pour d'autres comparaisons + critical: + heading: Description du chemin critique + text: La plus longue séquence d'activités dans un plan de projet qui doivent + être achevées à temps pour que le projet se termine à la date d'échéance. + Une activité sur le chemin critique ne peut pas être démarrée jusqu'à ce + que l'activité qui l'a précède soit terminée ; si elle est retardée d'une + journée, l'ensemble du projet sera retardé d'une journée, à moins que l'activité + faisant suite à l'activité retardée se termine un jour plus tôt. Pour qu'un + chemin critique fonctionne correctement, le projet doit avoir seulement + 1 départ et 1 tâche de fin, et les tâches doivent être connectées avec des + relations . + scheme_by: + label_by_priority: par Priorité + label_by_status: par statut + label_by_tracker: par tracker + label_color_by: Couleur par + title: + delayed_issue_filter: Affiche uniquement les publications dont la date d'avancement + est antérieure à aujourd'hui + show_only_critical: Afficher uniquement les tâches sur Chemin critique + easy_pages: + modules: + easy_global_gantt: Easy gantt + easy_project_gantt: Easy gantt + easy_proposer: + easy_gantt: + index: + caption: Easy Gantt PRO + description: Rendez-vous sur Easy Gantt PRO + heading_easy_gantts_issues: Easy Gantt PRO + label_show_lowest_progress_tasks: Tâches les moins avancées + text_show_lowest_progress_tasks: Affiche les tâches avec la pire date d'avancement + (= date correspondant à la barre de ratio de la tâche effectuée dans gantt) diff --git a/easy_gantt_pro/config/locales/he.yml b/easy_gantt_pro/config/locales/he.yml new file mode 100644 index 0000000..de35edf --- /dev/null +++ b/easy_gantt_pro/config/locales/he.yml @@ -0,0 +1,2 @@ +--- +he: diff --git a/easy_gantt_pro/config/locales/hr.yml b/easy_gantt_pro/config/locales/hr.yml new file mode 100644 index 0000000..662a199 --- /dev/null +++ b/easy_gantt_pro/config/locales/hr.yml @@ -0,0 +1,2 @@ +--- +hr: diff --git a/easy_gantt_pro/config/locales/hu.yml b/easy_gantt_pro/config/locales/hu.yml new file mode 100644 index 0000000..ba0f4ca --- /dev/null +++ b/easy_gantt_pro/config/locales/hu.yml @@ -0,0 +1,73 @@ +--- +hu: + easy_gantt_pro: + add_task_modal: + no_parent: Kritikus végrehajtási útvonal elrejtése + title: Feladat hozzáadása + baselines: + confirm_delete: Tényleg törölni szeretné az alapvonalat (baseline)? + create_failed: Nem sikerült új alapvonalat (baseline) létrehozni + delete_failed: Alapvonal (baseline) nem törölhető + error_not_saved: A projekt minden változását menteni kell az alapvonal (baseline) + létrehozása előtt + label_baseline: Baseline + label_same: "-ugyanaz-" + cashflow: + label_cashflow: Cash Flow + critical: + hide_button: Kritikus végrehajtási útvonal elrejtése + legend_issues: Feladata a kritikus elérési útvonalon + show_button: Kritikus elérési útvonal megjelenítése + lowest_progress: + label_progress_date: Haladási dátum + project_move: + error_opening_unsaved: Mielőtt megnyitja a projektet a változásokat kell elmenteni + resources: + label_resources: Források + easy_gantt: + button: + bulk_edit: Többszörös szerkesztés + delayed_issue_filter: Szűrés késleltetett feladatokra + show_lowest_progress_tasks: A leglassabban haladó feladatok mutatása + show_only_critical: Kritikusak szűrése + popup: + add_task: + heading: Feladathoz tartozó segítség hozzáadása + text: Új feladat vagy új mérföldkő létrehozása a Feladat gommbal (illetve + a Mérföldkő gombal) vagy ezen feladatok grafikonra történő ráhelyezésével + lehetséges. + baseline: + heading: Alapvonal (baseline) leírása + heading_new_baseline: Új alapvonal (baseline) + text: Másolatot készít a projektről további összehasonlítás céljából + critical: + heading: Kritikus végrehajtási útvonal leírása + text: "Olyan tevékenységek leghosszabb sorozata a projekt tervben, amelyeket + időben kell teljesíteni, a projekt határidőre történő befejezése érdekében.\r\nEgy + tevékenység a kritikus útvonalon nem kezdhető el addig, amíg a megelőző + tevékenység nem fejeződött be; ha késik egy nappal, az egész projekt késni + fog egy napot, kivéve ha a következő tevékenység egy nappal korábban fejeződik + be. \r\nA kritikus útvonal funkció megfelelő használatához a projektnek + csak 1 kezdő és 1 befejező feladat kell hogy legyen és kapcsolatokkal kell + összekötni azokat." + scheme_by: + label_by_priority: Prioritás alapján + label_by_status: Állapot alapján + label_by_tracker: Tracker (Feladattípus) alapján + label_color_by: Színkódolás + title: + delayed_issue_filter: Azoknak a feladatoknak a megjelenítése, amelyek haladási + dátuma a mai napnál későbbre esik + show_only_critical: Csak a kritikus útvonal feladatainak megjelenítése + easy_pages: + modules: + easy_global_gantt: Easy gantt + easy_project_gantt: Easy gantt + easy_proposer: + easy_gantt: + index: + caption: Easy Gantt PRO + description: Easy Gantt PRO-hoz + heading_easy_gantts_issues: Easy Gantt PRO + label_show_lowest_progress_tasks: Leglassabban haladó feladatok + text_show_lowest_progress_tasks: A leglassabban haladó feladatok kilistázása diff --git a/easy_gantt_pro/config/locales/it.yml b/easy_gantt_pro/config/locales/it.yml new file mode 100644 index 0000000..0d2530c --- /dev/null +++ b/easy_gantt_pro/config/locales/it.yml @@ -0,0 +1,73 @@ +--- +it: + easy_gantt_pro: + add_task_modal: + no_parent: '' + title: Aggiungi task + baselines: + confirm_delete: Sei sicuro di voler cancellare questa baseline? + create_failed: Non è possbile creare una nuova baseline + delete_failed: La baseline non può essere cancellata + error_not_saved: Devi salvare tutte le modifiche al progetto prima di crearne + la baseline + label_baseline: Baseline + label_same: "-uguale-" + cashflow: + label_cashflow: Flussi di Cassa + critical: + hide_button: Nascondi Percorso critico + legend_issues: Task nel Percorso critico + show_button: Mostra Percorso critico + lowest_progress: + label_progress_date: Data avanzamento + project_move: + error_opening_unsaved: Devi salvare le modifiche prima di aprire il progetto + resources: + label_resources: Risorse + easy_gantt: + button: + bulk_edit: Modifica massiccia + delayed_issue_filter: Filtra Questioni Posticipate + show_lowest_progress_tasks: Mostra i task con tempo di avanzamento minore + show_only_critical: Filtra Criticità + popup: + add_task: + heading: Aggiungi indicazione al task + text: Puoi creare nuove task o milestone cliccando rispettivamente i pulsanti + Task o Milestone, oppure tramite trascinamento in ogni punto del grafico. + baseline: + heading: Descrizione baseline + heading_new_baseline: Nuova baseline + text: Crea una copia del progetto per ulteriori confronti + critical: + heading: Descrizione percorso critico + text: La più lunga sequenza di attività che deve essere portata a termine + in tempo affinché il progetto venga completato alla data stabilita. Un'attività + nel percorso critico non può avere inizio finché il suo predecessore non + sia stato completato; se viene rinviata di un giorno, l'intero progetto + subirà questo ritardo, a meno che l'attività seguente non venga ultimata + con un giorno di anticipo. Per un corretto funzionamento del percorso critico, + il progetto dovrebbe avere un solo task iniziale e uno finale e tutti dovrebbero + essere legati da relazioni. + scheme_by: + label_by_priority: per Priorità + label_by_status: per Stato + label_by_tracker: per Tracker + label_color_by: Colora di + title: + delayed_issue_filter: Mostra solo questioni con data di completamento precedente + a quella attuale + show_only_critical: Mostra solo i task sul Percorso critico + easy_pages: + modules: + easy_global_gantt: Easy Gantt + easy_project_gantt: Easy Gantt + easy_proposer: + easy_gantt: + index: + caption: Easy Gantt PRO + description: Vai a Easy Gantt PRO + heading_easy_gantts_issues: Easy Gantt PRO + label_show_lowest_progress_tasks: Task con avanzamento minore + text_show_lowest_progress_tasks: Mostra task con lo stato di avanzamento minore + (corrispondente alla barra di completamento del Gantt) diff --git a/easy_gantt_pro/config/locales/ja.yml b/easy_gantt_pro/config/locales/ja.yml new file mode 100644 index 0000000..f7016f8 --- /dev/null +++ b/easy_gantt_pro/config/locales/ja.yml @@ -0,0 +1,62 @@ +--- +ja: + easy_gantt_pro: + add_task_modal: + no_parent: '' + title: チケットの追加 + baselines: + confirm_delete: 本当にこのベースラインを削除してもよろしいですか? + create_failed: 新しいベースラインを作成できませんでした + delete_failed: ベースラインを削除できませんでした + error_not_saved: ベースラインを作成する前に変更を保存してください。 + label_baseline: ベースライン + label_same: "-同じ-" + cashflow: + label_cashflow: キャッシュフロー + critical: + hide_button: クリティカルパスを隠す + legend_issues: クリティカルパス上のチケット + show_button: クリティカルパスを表示 + lowest_progress: + label_progress_date: 進捗日 + project_move: + error_opening_unsaved: プロジェクトを開く前に変更内容を保存してください。 + resources: + label_resources: リソース + easy_gantt: + button: + bulk_edit: 一括編集 + delayed_issue_filter: 遅延したタスクをフィルタする + show_lowest_progress_tasks: 最も遅れているチケットを表示 + show_only_critical: クリティカルのみ表示 + popup: + add_task: + heading: ヘルプ - チケットの追加 + text: 新しいチケットを作成するには、'新しいチケット'ボタンをクリックするか、ガントチャート内の余白をドラッグしてください。 + baseline: + heading: ベースラインの説明 + heading_new_baseline: 新しいベースライン + text: 比較のためにプロジェクトのコピーを作成します + critical: + heading: クリティカルパスの説明 + text: プロジェクトを期日までに完了させるにあたって、所要時間が最長となる経路を指します。
先行するチケットが完了しないと、後続のチケットを開始できません。
つまり、先行するチケットが1日遅れた場合、後続のチケットを一日前倒しで完了させないと、プロジェクト全体が1日遅延します。
クリティカルパスを正しく機能させるためには、プロジェクト内のすべてのチケットが、1つのチケットを開始、終了するよう、関連付ける必要があります。 + scheme_by: + label_by_priority: 優先度別 + label_by_status: ステータス別 + label_by_tracker: トラッカー別 + label_color_by: 色別 + title: + delayed_issue_filter: 本日時点で遅延しているタスクのみを表示 + show_only_critical: クリティカルパス上のタスクのみ表示 + easy_pages: + modules: + easy_global_gantt: Easyガントチャート + easy_project_gantt: 簡単なガントチャート + easy_proposer: + easy_gantt: + index: + caption: EasyガントチャートPRO + description: EasyガントチャートPROに移動 + heading_easy_gantts_issues: EasyガントチャートPRO + label_show_lowest_progress_tasks: 進捗遅れのチケット + text_show_lowest_progress_tasks: 最も進捗率の低いチケットを表示する(チケットの進捗に対応する日付) diff --git a/easy_gantt_pro/config/locales/ko.yml b/easy_gantt_pro/config/locales/ko.yml new file mode 100644 index 0000000..32f5815 --- /dev/null +++ b/easy_gantt_pro/config/locales/ko.yml @@ -0,0 +1,65 @@ +--- +ko: + easy_gantt_pro: + add_task_modal: + no_parent: '' + title: 작업 추가 + baselines: + confirm_delete: 이 기초항목을 삭제하시겠습니까 + create_failed: 새로운 기준치가 생성될 수 없습니다 + delete_failed: 기준치를 삭제할 수 없습니다 + error_not_saved: 기준치를 생성하기 전에 프로젝트의 모든 변경사항을 저장하셔야 합니다 + label_baseline: 기초 + label_same: "-동일한-" + cashflow: + label_cashflow: 현금 흐름 + critical: + hide_button: 중요한 경로 숨기기 + legend_issues: 중요한 경로의 작업 + show_button: 중요한 경로 보이기 + lowest_progress: + label_progress_date: 진행일 + project_move: + error_opening_unsaved: 이 프로젝트를 실행하기 전에 먼저 변경사항을 저장해야합니다 + resources: + label_resources: 리소스 + easy_gantt: + button: + bulk_edit: 단체 수정 + delayed_issue_filter: 지연된 문제 필터 + show_lowest_progress_tasks: 가장 느린 진행 날짜를 가진 작업 보기 + show_only_critical: 중요 필터 + popup: + add_task: + heading: 작업 도우미 추가 + text: 작업 버튼 (각 중요 시점 버튼)을 클릭하거나 그래프의 아무 곳이나 드래그하여 새로운 작업 혹은 마일스톤을 만들 수 있습니다 + baseline: + heading: 기준치 설명 + heading_new_baseline: 새로운 기준치 + text: 더 많은 비교를 위해 프로젝트 사본 만들기 + critical: + heading: 중요한 경로 설명 + text: 마감일에 프로젝트가 완성 될 때까지 완료해야하는 프로젝트 계획에서 가장 길게 지속되는 활동입니다. 중요 경로의 활동은 이전 + 활동이 완료 될 때까지 시작할 수 없습니다. 하루 동안 지연 될 경우, 지연된 작업의 다음 작업이 완료되지 않는 이상, 전체 프로젝트는 + 하루 지연되게 됩니다. 중요한 경로의 적절한 기능을 위해서, 프로젝트가 단지 1 개의 시작 작업과 1 개의 종료 작업을 가지고 있어야 + 합니다. 또한, 작업의 관계는 연결되어야합니다. + scheme_by: + label_by_priority: 우선순위 별 + label_by_status: 상태 별 + label_by_tracker: 추적기 별로 + label_color_by: 색으로 칠함 + title: + delayed_issue_filter: 진행날짜가 오늘 이후에 있는 문제만 표시 + show_only_critical: 중요 필터의 작업만 보기 + easy_pages: + modules: + easy_global_gantt: 쉬운 간트 + easy_project_gantt: 쉬운 간트 + easy_proposer: + easy_gantt: + index: + caption: 쉬운 간트 PRO + description: 쉬운 간트 PRO로 가기 + heading_easy_gantts_issues: 쉬운 간트 PRO + label_show_lowest_progress_tasks: 가장 적게 진행된 작업 + text_show_lowest_progress_tasks: 가장 느린 진행날짜로 진행되는 작업 보기 (=작업의 완료비율이 간트 막대에 표시됨) diff --git a/easy_gantt_pro/config/locales/mk.yml b/easy_gantt_pro/config/locales/mk.yml new file mode 100644 index 0000000..0ae1092 --- /dev/null +++ b/easy_gantt_pro/config/locales/mk.yml @@ -0,0 +1,2 @@ +--- +mk: diff --git a/easy_gantt_pro/config/locales/nl.yml b/easy_gantt_pro/config/locales/nl.yml new file mode 100644 index 0000000..fb97186 --- /dev/null +++ b/easy_gantt_pro/config/locales/nl.yml @@ -0,0 +1,74 @@ +--- +nl: + easy_gantt_pro: + add_task_modal: + no_parent: '' + title: Voeg taak toe + baselines: + confirm_delete: Wilt u deze baseline echt verwijderen + create_failed: Er kon geen nieuwe baseline worden aangemaakt + delete_failed: Baseline kon niet worden verwijderd + error_not_saved: U moet alle wijzigingen in het project opslaan voor het aanmaken + van zijn baseline + label_baseline: Baseline + label_same: "-hetzelfde-" + cashflow: + label_cashflow: Cash Flow + critical: + hide_button: Verberg Kritiek pad + legend_issues: Taak op Kritiek pad + show_button: Toon Kritiek pad + lowest_progress: + label_progress_date: Voortgangsdatum + project_move: + error_opening_unsaved: U moet eerst de wijzigingen opslaan voor het openen van + het project + resources: + label_resources: Resources + easy_gantt: + button: + bulk_edit: Bulk bewerken + delayed_issue_filter: Filter Vertraagde Issues + show_lowest_progress_tasks: Toon taken met de laagste voortgangsdatum + show_only_critical: Filter Kritiek + popup: + add_task: + heading: Voeg taak hulp toe + text: Nieuwe taak of mijlpaal kan aangemaakt worden door te klikken op de + Taak button (respectievelijke Mijlpaal button) of door deze naar een willekeurige + plek in de grafiek te slepen. + baseline: + heading: Baseline beschrijving + heading_new_baseline: Nieuwe baseline + text: Maakt kopie van het project voor verdere vergelijkingen + critical: + heading: Kritiek pad beschrijving + text: Langste opeenvolging van activiteiten in een projectplan dat op tijd + moet worden afgerond om het project te voltooien op de vervaldag. Een activiteit + op het kritieke pad kan niet worden gestart tot de voorgaande activiteit + is voltooid; als deze een dag is uitgesteld, zal het gehele project een + dag worden uitgesteld, tenzij de activiteit na de vertraagde activiteit + een dag eerder is voltooid. Voor een goede werking van het kritieke pad, + moet het project slechts 1 begin- en 1 eindtaak hebben en de taken moeten + zijn verbonden met relaties. + scheme_by: + label_by_priority: op Prioriteit + label_by_status: op Status + label_by_tracker: op Tracker + label_color_by: Kleur op + title: + delayed_issue_filter: Toon alleen issues met een voortgangsdatum na vandaag + show_only_critical: Toon alleen taken op Kritiek pad + easy_pages: + modules: + easy_global_gantt: Easy gantt + easy_project_gantt: Easy gantt + easy_proposer: + easy_gantt: + index: + caption: Easy Gantt PRO + description: Ga naar Easy Gantt PRO + heading_easy_gantts_issues: Easy Gantt PRO + label_show_lowest_progress_tasks: Taken met laagste voortgang + text_show_lowest_progress_tasks: Toon taken met de slechtste voortgangsdatum (=datum + die overeenkomt met de voortgangsbalk van de taak in gantt) diff --git a/easy_gantt_pro/config/locales/no.yml b/easy_gantt_pro/config/locales/no.yml new file mode 100644 index 0000000..38901c6 --- /dev/null +++ b/easy_gantt_pro/config/locales/no.yml @@ -0,0 +1,2 @@ +--- +'no': diff --git a/easy_gantt_pro/config/locales/pl.yml b/easy_gantt_pro/config/locales/pl.yml new file mode 100644 index 0000000..46baa4c --- /dev/null +++ b/easy_gantt_pro/config/locales/pl.yml @@ -0,0 +1,74 @@ +--- +pl: + easy_gantt_pro: + add_task_modal: + no_parent: '' + title: Dodaj zadanie + baselines: + confirm_delete: Czy na pewno chcesz usunąć tę bazę? + create_failed: Nowa baza nie mogła zostać utworzona + delete_failed: Baza nie mogła zostać usunięta + error_not_saved: Musisz zapisać wszystkie zmiany w projekcie przez utworzeniem + jego bazy + label_baseline: Baza + label_same: "-tak samo-" + cashflow: + label_cashflow: Przepływy środków pieniężnych + critical: + hide_button: Ukryj ścieżkę krytyczną + legend_issues: Zadanie na ścieżce krytycznej + show_button: Pokaż ścieżkę krytyczną + lowest_progress: + label_progress_date: Data postępu + project_move: + error_opening_unsaved: Musisz zapisać zmiany przed otworzeniem projektu + resources: + label_resources: Zasoby + easy_gantt: + button: + bulk_edit: Edycja seryjna + delayed_issue_filter: Filtruj opóźnione problemy + show_lowest_progress_tasks: Pokaż zadania z najbliższą datą postępu + show_only_critical: Filtruj krytyczne + popup: + add_task: + heading: Dodaj zadanie pomocy + text: Nowe zadanie lub krok milowy mogą zostać utworzone poprzez kliknięcie + przycisku Zadanie (indywidualnego przycisku Krok milowy) lub poprzez przesunięcie + ich w dowolne miejsce na grafie + baseline: + heading: Opis bazy + heading_new_baseline: Nowa baza + text: Twórzy kopię projektu dla późniejszych porównań + critical: + heading: Opis ścieżki krytycznej + text: Najdłuższa sekwencja działań w planie projektu, które muszą być ukończone + na czas, aby zakończyć projekt w terminie. Działanie na ścieżce krytycznej + nie może zostać uruchomione, dopóki poprzedzające ją działanie nie zostało + zakończone. Jeśli jest ono opóźnione o jeden dzień, cały projekt będzie + opóźniony o jeden dzień, chyba że działanie następujące po opóźnionym działaniu + zostanie zakończone dzień wcześniej. Dla prawidłowego funkcjonowania ścieżki + krytycznej, projekt powinien mieć tylko 1 zadanie początkowe i 1 zadanie + końcowe, które to zadania powinny być połączone relacjami. + scheme_by: + label_by_priority: wg Ważności + label_by_status: wg Statusu + label_by_tracker: wg Narzędzia śledzenia + label_color_by: Kolor wg + title: + delayed_issue_filter: Pokaż wyłącznie problemy, których data postępu jest poza + dzisiejszą + show_only_critical: Pokaż wyłącznie zadania na ścieżce krytycznej + easy_pages: + modules: + easy_global_gantt: Easy gantt + easy_project_gantt: Easy gantt + easy_proposer: + easy_gantt: + index: + caption: Easy Gantt PRO + description: Idź do Easy Gantt PRO + heading_easy_gantts_issues: Easy Gantt PRO + label_show_lowest_progress_tasks: Najbliższe zadania postępu + text_show_lowest_progress_tasks: Wyświetl zadania z najgorszą datą postępu (= datą + odpowiadającą wskaźnikowi wykonania zadania w Gantt) diff --git a/easy_gantt_pro/config/locales/pt-BR.yml b/easy_gantt_pro/config/locales/pt-BR.yml new file mode 100644 index 0000000..096612b --- /dev/null +++ b/easy_gantt_pro/config/locales/pt-BR.yml @@ -0,0 +1,73 @@ +--- +pt-BR: + easy_gantt_pro: + add_task_modal: + no_parent: '' + title: Adicionar tarefa + baselines: + confirm_delete: Você realmente quer apagar essa linha de base? + create_failed: Linha de base não pode ser criada + delete_failed: Linha de base não pode ser apagada + error_not_saved: Você deve salvar todas as alterações no projeto antes de criar + sua linha de base + label_baseline: Linha de base + label_same: "-mesmo-" + cashflow: + label_cashflow: Fluxo de caixa + critical: + hide_button: Esconder trajeto crítico + legend_issues: Tarefas em trajeto crítico + show_button: Mostrar trajeto crítico + lowest_progress: + label_progress_date: Data progresso + project_move: + error_opening_unsaved: Você deve salvar as alterações antes de abrir o projeto + resources: + label_resources: Funcionalidade + easy_gantt: + button: + bulk_edit: Edição em massa + delayed_issue_filter: Filtrar Problemas de Atraso + show_lowest_progress_tasks: Mostrar tarefas com menor data de progresso + show_only_critical: Filtro crucial + popup: + add_task: + heading: Adicionar tarefa ajuda + text: Nova tarefa ou marco pode ser criado ao clicar o botão de tarefas (ou + no caso o botão Marco) ou arrastando em qualquer lugar no gráfico + baseline: + heading: Descrição da linha de base + heading_new_baseline: Linha de base nova + text: Cria cópia do projeto para futuras comparações + critical: + heading: Descrição de trajeto crítico + text: A sequência de atividades mais longa em um plano de projeto que deve + ser concluída a tempo para o projeto ser concluído na data devida. Uma atividade + no trajeto crítico não pode ser iniciada até que a sua atividade antecessora + for completada; se for adiada por um dia, todo o projeto será adiado por + um dia a menos que a atividade após a atividade retardada seja concluída + um dia antes. Para o bom funcionamento de um trajeto crítico, o projeto + deve ter apenas 1 tarefa ínicial e uma tarefa final e as tarefas devem ser + conectadas com relações. + scheme_by: + label_by_priority: por Prioridade + label_by_status: por Situação + label_by_tracker: por Rastreador + label_color_by: Coloridos por + title: + delayed_issue_filter: Mostrar apenas as problemas os qual a data de progresso + estiver atrasada hoje + show_only_critical: Mostrar somente tarefas no Trajeto crítico + easy_pages: + modules: + easy_global_gantt: Easy Gantt + easy_project_gantt: Easy Gantt + easy_proposer: + easy_gantt: + index: + caption: Easy Gantt PRO + description: Vá para Easy Gantt PRO + heading_easy_gantts_issues: Easy Gantt PRO + label_show_lowest_progress_tasks: Tarefas de menor progresso + text_show_lowest_progress_tasks: Tarefas de exibição com o pior progresso em relação + data (= relação entre data correspondente às tarefas feitas em gantt) diff --git a/easy_gantt_pro/config/locales/pt.yml b/easy_gantt_pro/config/locales/pt.yml new file mode 100644 index 0000000..e40c485 --- /dev/null +++ b/easy_gantt_pro/config/locales/pt.yml @@ -0,0 +1,2 @@ +--- +pt: diff --git a/easy_gantt_pro/config/locales/ro.yml b/easy_gantt_pro/config/locales/ro.yml new file mode 100644 index 0000000..0df6ab3 --- /dev/null +++ b/easy_gantt_pro/config/locales/ro.yml @@ -0,0 +1,2 @@ +--- +ro: diff --git a/easy_gantt_pro/config/locales/ru.yml b/easy_gantt_pro/config/locales/ru.yml new file mode 100644 index 0000000..168082b --- /dev/null +++ b/easy_gantt_pro/config/locales/ru.yml @@ -0,0 +1,72 @@ +--- +ru: + easy_gantt_pro: + add_task_modal: + no_parent: '' + title: Добавить задачу + baselines: + confirm_delete: Вы действительно хотите удалить этот базовый план + create_failed: Новый базовый план не может быть создан + delete_failed: Базовый план не может быть удален + error_not_saved: Вы должны сохранить все изменения в проекте перед созданием + базового плана. + label_baseline: Базовый план + label_same: "-то же-" + cashflow: + label_cashflow: Cash Flow + critical: + hide_button: Скрыть критический путь + legend_issues: Задача на критическом пути + show_button: Показать критический путь + lowest_progress: + label_progress_date: сроки по прогрессу + project_move: + error_opening_unsaved: Сохраните, пожалуйста, все изменения в в проекте перед + его открытием. + resources: + label_resources: Ресурсы + easy_gantt: + button: + bulk_edit: Групповое редактирование + delayed_issue_filter: Фильтр по просроченным задчам + show_lowest_progress_tasks: Показать задачи с самой ранней датой по прогрессу + show_only_critical: Фильтр критических задач + popup: + add_task: + heading: Add Issue help + text: New Issue of milestone can be created by a click on Issue button (respective + Milestone button) or by dragging anywhere in graph + baseline: + heading: Базовый план - Описание + heading_new_baseline: Новый базовый план + text: Создание копии изначального проекта для дальнейшиго сравнения + critical: + heading: Критический Путь - описание + text: Длинные последовательности задач, которые должны выполняться своевременно, + для соблюдения Дедлайна проекта. Новые задачи на критическом пути не могут + уходить в работу, пока предыдущая задача не будет завершена.Если хотябы + на одной из задач возникнет задержка, хотябы на день, то и дедлайн проекта + будет передвинут на один день вперед, до тех пор, пока одна из задач не + будет выполненна на день раньше. + scheme_by: + label_by_priority: по приоритету + label_by_status: по статусу + label_by_tracker: по задачам + label_color_by: Цвет + title: + delayed_issue_filter: Показать только задачи, дата по прогрессу которых ранее + сегодня + show_only_critical: Показать только задачи на Критическом пути + easy_pages: + modules: + easy_global_gantt: Easy gantt + easy_project_gantt: Easy gantt + easy_proposer: + easy_gantt: + index: + caption: Easy Gantt PRO + description: Перейти в Easy Gantt PRO + heading_easy_gantts_issues: Easy Gantt PRO + label_show_lowest_progress_tasks: Задачи с наименьшим прогрессом + text_show_lowest_progress_tasks: Показывать задачи с самой большой задержкой (отношение + длительности на диаграмме Ганта к проценту выполения) diff --git a/easy_gantt_pro/config/locales/sk.yml b/easy_gantt_pro/config/locales/sk.yml new file mode 100644 index 0000000..354e579 --- /dev/null +++ b/easy_gantt_pro/config/locales/sk.yml @@ -0,0 +1,2 @@ +--- +sk: diff --git a/easy_gantt_pro/config/locales/sl.yml b/easy_gantt_pro/config/locales/sl.yml new file mode 100644 index 0000000..31bb26c --- /dev/null +++ b/easy_gantt_pro/config/locales/sl.yml @@ -0,0 +1,2 @@ +--- +sl: diff --git a/easy_gantt_pro/config/locales/sq.yml b/easy_gantt_pro/config/locales/sq.yml new file mode 100644 index 0000000..9c092f0 --- /dev/null +++ b/easy_gantt_pro/config/locales/sq.yml @@ -0,0 +1,2 @@ +--- +sq: diff --git a/easy_gantt_pro/config/locales/sr-YU.yml b/easy_gantt_pro/config/locales/sr-YU.yml new file mode 100644 index 0000000..24e1796 --- /dev/null +++ b/easy_gantt_pro/config/locales/sr-YU.yml @@ -0,0 +1,2 @@ +--- +sr-YU: diff --git a/easy_gantt_pro/config/locales/sr.yml b/easy_gantt_pro/config/locales/sr.yml new file mode 100644 index 0000000..43a1014 --- /dev/null +++ b/easy_gantt_pro/config/locales/sr.yml @@ -0,0 +1,2 @@ +--- +sr: diff --git a/easy_gantt_pro/config/locales/sv.yml b/easy_gantt_pro/config/locales/sv.yml new file mode 100644 index 0000000..ed425ea --- /dev/null +++ b/easy_gantt_pro/config/locales/sv.yml @@ -0,0 +1,2 @@ +--- +sv: diff --git a/easy_gantt_pro/config/locales/th.yml b/easy_gantt_pro/config/locales/th.yml new file mode 100644 index 0000000..3596914 --- /dev/null +++ b/easy_gantt_pro/config/locales/th.yml @@ -0,0 +1,2 @@ +--- +th: diff --git a/easy_gantt_pro/config/locales/tr.yml b/easy_gantt_pro/config/locales/tr.yml new file mode 100644 index 0000000..3be79e7 --- /dev/null +++ b/easy_gantt_pro/config/locales/tr.yml @@ -0,0 +1,2 @@ +--- +tr: diff --git a/easy_gantt_pro/config/locales/zh-TW.yml b/easy_gantt_pro/config/locales/zh-TW.yml new file mode 100644 index 0000000..44cf0f8 --- /dev/null +++ b/easy_gantt_pro/config/locales/zh-TW.yml @@ -0,0 +1,2 @@ +--- +zh-TW: diff --git a/easy_gantt_pro/config/locales/zh.yml b/easy_gantt_pro/config/locales/zh.yml new file mode 100644 index 0000000..acfb082 --- /dev/null +++ b/easy_gantt_pro/config/locales/zh.yml @@ -0,0 +1,63 @@ +--- +zh: + easy_gantt_pro: + add_task_modal: + no_parent: '' + title: 添加任务 + baselines: + confirm_delete: 确定要删除这个原始时间线? + create_failed: 不能创建新的 原时间线(Baseline) + delete_failed: 该Baseline不能被删除 + error_not_saved: 在创建Baseline前必须先保存项目中所有变更 + label_baseline: 原时间线 + label_same: "- 相同 -" + cashflow: + label_cashflow: 显示资金 + critical: + hide_button: 不显示关键路径 + legend_issues: 关键路径上的任务 + show_button: 显示关键路径 + lowest_progress: + label_progress_date: 进度日期 + project_move: + error_opening_unsaved: 在打开此项目前请先保存变更 + resources: + label_resources: 资源 + easy_gantt: + button: + bulk_edit: 批量编辑 + delayed_issue_filter: 延误原因筛选 + show_lowest_progress_tasks: 显示各任务最近的进度更新日期 + show_only_critical: 筛选出关键的 + popup: + add_task: + heading: 添加任务帮助 + text: 通过点击任务(里程碑)按钮, 或在图形中拖放创建新的任务(里程碑) + baseline: + heading: 原时间线的描述 + heading_new_baseline: 新建Baseline + text: 创建该项目的复本来进行比较 + critical: + heading: 关键路径的描述 + text: 项目中最长用时的路线上的工作必须在预定时间内完成. 关键路径上的工作只有在其前一个工作完成后才能启动; 如果其中一个工作延误了一天, 则整个项目会延误一天(除非后续有工作提前[完成). + 一个关键路径只能有一个开始和一个结束任务, 且这些任务之间用关系(Relation)连接 + scheme_by: + label_by_priority: 按优先级 + label_by_status: 按状态 + label_by_tracker: 按状态 + label_color_by: 按颜色 + title: + delayed_issue_filter: 显示原因是进度日期在今天之后的 + show_only_critical: 仅显示关键路径上的任务 + easy_pages: + modules: + easy_global_gantt: Easy gantt(甘特图) + easy_project_gantt: Easy 甘特图 + easy_proposer: + easy_gantt: + index: + caption: Easy 甘特图专业版 + description: 转到Easy 甘特图专业版 + heading_easy_gantts_issues: Easy Gantt PRO + label_show_lowest_progress_tasks: 进度最慢的任务 + text_show_lowest_progress_tasks: 显示任务最差的更新日期( 对应任务完成比例的日期, 显示在任务进度条上) diff --git a/easy_gantt_pro/config/routes.rb b/easy_gantt_pro/config/routes.rb new file mode 100644 index 0000000..df48a2b --- /dev/null +++ b/easy_gantt_pro/config/routes.rb @@ -0,0 +1,11 @@ +# Because of plugin deactivations +if Redmine::Plugin.installed?(:easy_gantt_pro) + scope format: true, defaults: { format: 'json' }, constraints: { format: 'json' } do + put 'easy_gantt/reschedule_project/:id', to: 'easy_gantt#reschedule_project', as: 'easy_gantt_reschedule_project' + post 'easy_gantt/lowest_progress_tasks', to: 'easy_gantt_pro#lowest_progress_tasks', as: 'easy_gantt_lowest_progress_tasks' + end + + scope 'easy_gantt', controller: 'easy_gantt_pro', as: 'easy_gantt' do + get 'recalculate_fixed_delay' + end +end diff --git a/easy_gantt_pro/init.rb b/easy_gantt_pro/init.rb new file mode 100644 index 0000000..e559950 --- /dev/null +++ b/easy_gantt_pro/init.rb @@ -0,0 +1,13 @@ +Redmine::Plugin.register :easy_gantt_pro do + name 'PRO Easy Gantt' + author 'Easy Software Ltd' + url 'https://www.easysoftware.com' + author_url 'https://www.easysoftware.com' + description 'PRO version' + version '3.0' + + requires_redmine_plugin :easy_gantt, version_or_higher: '3.0' +end + +require_relative 'after_init' + diff --git a/easy_gantt_pro/lib/easy_gantt_pro.rb b/easy_gantt_pro/lib/easy_gantt_pro.rb new file mode 100644 index 0000000..dab20c1 --- /dev/null +++ b/easy_gantt_pro/lib/easy_gantt_pro.rb @@ -0,0 +1,2 @@ +module EasyGanttPro +end \ No newline at end of file diff --git a/easy_gantt_pro/lib/easy_gantt_pro/hooks.rb b/easy_gantt_pro/lib/easy_gantt_pro/hooks.rb new file mode 100644 index 0000000..05e3d62 --- /dev/null +++ b/easy_gantt_pro/lib/easy_gantt_pro/hooks.rb @@ -0,0 +1,7 @@ +module EasyGanttPro + class Hooks < Redmine::Hook::ViewListener + render_on :view_easy_gantt_index_bottom, partial: 'hooks/easy_gantt_pro/view_easy_gantt_index_bottom' + render_on :view_easy_gantts_issues_toolbars, partial: 'hooks/easy_gantt_pro/view_easy_gantts_issues_toolbars' + render_on :view_easy_gantt_settings, partial: 'easy_settings/easy_gantt_pro' + end +end diff --git a/easy_gantt_pro/lib/easy_gantt_pro/issues_controller_patch.rb b/easy_gantt_pro/lib/easy_gantt_pro/issues_controller_patch.rb new file mode 100644 index 0000000..b0aa129 --- /dev/null +++ b/easy_gantt_pro/lib/easy_gantt_pro/issues_controller_patch.rb @@ -0,0 +1,24 @@ +module EasyGanttPro + module IssuesControllerPatch + + def self.prepended(base) + base.include InstanceMethods + + base.class_eval do + before_action :easy_gantt_suppress_notification + end + end + + module InstanceMethods + + private + + def easy_gantt_suppress_notification + EasyGanttSuppressNotification.value = (params[:issue] && params[:issue][:easy_gantt_suppress_notification] == 'true') + end + + end + end +end +IssuesController.prepend EasyGanttPro::IssuesControllerPatch + diff --git a/easy_gantt_pro/lib/easy_gantt_pro/suppress_notification.rb b/easy_gantt_pro/lib/easy_gantt_pro/suppress_notification.rb new file mode 100644 index 0000000..e315321 --- /dev/null +++ b/easy_gantt_pro/lib/easy_gantt_pro/suppress_notification.rb @@ -0,0 +1,25 @@ +module EasyGanttPro + module SuppressNotification + + def self.prepended(base) + base.prepend(InstanceMethods) + end + + module InstanceMethods + + def notify? + if EasyGanttSuppressNotification.value == true + false + else + super + end + end + + end + + end +end + +Issue.prepend EasyGanttPro::SuppressNotification +Journal.prepend EasyGanttPro::SuppressNotification + diff --git a/easy_gantt_pro/spec/features/add_task_spec.rb b/easy_gantt_pro/spec/features/add_task_spec.rb new file mode 100644 index 0000000..59c15f3 --- /dev/null +++ b/easy_gantt_pro/spec/features/add_task_spec.rb @@ -0,0 +1,91 @@ +require File.expand_path('../../../../easyproject/easy_plugins/easy_extensions/test/spec/spec_helper', __FILE__) + +RSpec.feature 'Add task', logged: :admin, js: true, js_wait: :long do + let!(:project) { FactoryGirl.create(:project, add_modules: ['easy_gantt']) } + + around(:each) do |example| + with_settings(rest_api_enabled: 1, text_formatting: 'textile') { example.run } + end + + def open_add_toolbar + find('.easy-gantt__menu-tools').hover + click_link(I18n.t(:label_new)) + find('#button_jump_today').hover + end + + describe 'toolbar' do + + it 'should prevent submitting invalid task' do + # TODO: Remove this conditions + skip if EasyGantt.platform == 'easyproject' + + visit easy_gantt_path(project) + wait_for_ajax + within('#content') do + open_add_toolbar + click_link(I18n.t(:label_issue_new)) + end + wait_for_ajax + find('#add_issue_modal_submit').click + expect(page).to have_selector('.flash.error') + expect(find('.flash.error')).to have_text(I18n.t(:field_subject)) + end + + it 'should create valid task' do + # TODO: Remove this conditions + skip if EasyGantt.platform == 'easyproject' + + visit easy_gantt_path(project) + wait_for_ajax + within('#content') do + open_add_toolbar + click_link(I18n.t(:label_issue_new)) + end + wait_for_ajax + within('#form-modal') do + fill_in(I18n.t(:field_subject), with: 'Issue256') + end + find('#add_issue_modal_submit').click + expect(page).to have_selector('.gantt_row.fresh.task-type', text: 'Issue256') + accept_confirm { visit('about:blank') } + end + + it 'should prevent submitting invalid milestone' do + # TODO: Remove this conditions + skip if EasyGantt.platform == 'easyproject' + + visit easy_gantt_path(project) + wait_for_ajax + within('#content') do + open_add_toolbar + click_link(I18n.t(:label_version_new)) + click_link(I18n.t(:label_version_new)) + end + wait_for_ajax + find('#add_milestone_modal_submit').click + expect(page).to have_selector('.flash.error') + expect(find('.flash.error')).to have_text(I18n.t(:field_name)) + end + + it 'should create valid milestone' do + # TODO: Remove this conditions + skip if EasyGantt.platform == 'easyproject' + + visit easy_gantt_path(project) + wait_for_ajax + within('#content') do + open_add_toolbar + click_link(I18n.t(:label_version_new)) + click_link(I18n.t(:label_version_new)) + end + wait_for_ajax + within('#form-modal') do + fill_in(I18n.t(:field_name), with: 'Milestone256') + end + find('#add_milestone_modal_submit').click + expect(page).to have_selector('.gantt_row.fresh.milestone-type', text: 'Milestone256') + accept_confirm { visit('about:blank') } + end + + end +end diff --git a/easy_gantt_pro/spec/features/baseline_spec.rb b/easy_gantt_pro/spec/features/baseline_spec.rb new file mode 100644 index 0000000..dee1be3 --- /dev/null +++ b/easy_gantt_pro/spec/features/baseline_spec.rb @@ -0,0 +1,68 @@ +require File.expand_path('../../../../easyproject/easy_plugins/easy_extensions/test/spec/spec_helper', __FILE__) + +RSpec.feature 'Baseline', :logged => :admin, :js => true do + let!(:project_no_baseline) { FactoryGirl.create(:project, add_modules: %w(easy_gantt)) } + let!(:project) { FactoryGirl.create(:project, add_modules: %w(easy_gantt easy_baselines)) } + + around(:each) do |example| + with_settings(rest_api_enabled: 1) do + example.run + end + end + + def open_baseline_toolbar + find('.easy-gantt__menu-tools').hover + expect(page).to have_selector('#button_baseline') + find('#button_baseline').click + find('#button_jump_today').hover + end + + it 'should not be displayed if module is turned off' do + visit easy_gantt_path(project_no_baseline) + wait_for_ajax + page.find('.easy-gantt__menu-tools').hover + expect(page).to have_no_selector('#button_baseline') + end + + it 'should load empty header' do + # TODO: Remove this conditions + skip if EasyGantt.platform == 'easyproject' + + visit easy_gantt_path(project) + wait_for_ajax + within('#content') do + find('.easy-gantt__menu-tools').hover + open_baseline_toolbar + expect(page).to have_selector('#baseline_create') + expect(page).to have_no_selector('#baseline_select') + end + end + + it 'create, load and delete new baseline' do + # TODO: Remove this conditions + skip if EasyGantt.platform == 'easyproject' + + visit easy_gantt_path(project) + wait_for_ajax + within('#content') do + open_baseline_toolbar + expect(page).to have_no_selector('.gantt-baseline') + find('#baseline_create').click + end + within('#form-modal') do + fill_in(I18n.t(:field_name), :with => 'Baseline of '+project.name) + end + find('#baseline_modal_submit').click + wait_for_ajax 20 + expect(page).to have_selector('#baseline_select') #.to have_content('Baseline of '+project.name) + expect(page).to have_selector('.gantt-baselines') + expect(page).to have_selector('.gantt-baseline') + message = accept_confirm do + find('#baseline_delete').click + end + expect(message).to eq(I18n.t(:text_are_you_sure)) + expect(page).to have_no_selector('.gantt-baseline') + expect(page).to have_no_selector('#baseline_select') + end + +end diff --git a/easy_gantt_pro/spec/features/correct_tree_spec.rb b/easy_gantt_pro/spec/features/correct_tree_spec.rb new file mode 100644 index 0000000..56966e2 --- /dev/null +++ b/easy_gantt_pro/spec/features/correct_tree_spec.rb @@ -0,0 +1,57 @@ +require File.expand_path('../../../../easyproject/easy_plugins/easy_extensions/test/spec/spec_helper', __FILE__) + +RSpec.feature 'Correct tree', logged: :admin, js: true do + + let(:subproject) { + FactoryGirl.create(:project, parent_id: superproject.id, add_modules: ['easy_gantt'], number_of_issues: 0) + } + let(:superproject) { + FactoryGirl.create(:project, add_modules: ['easy_gantt'], number_of_issues: 0) + } + let(:superproject_issues) { + FactoryGirl.create_list(:issue, 3, fixed_version_id: milestone_superproject.id, project_id: superproject.id) + } + let(:subproject_issues) { + FactoryGirl.create_list(:issue, 3, fixed_version_id: milestone_subproject.id, project_id: subproject.id) + } + let(:milestone_subproject) { + FactoryGirl.create(:version, project_id: subproject.id) + } + let(:milestone_superproject) { + FactoryGirl.create(:version, project_id: superproject.id) + } + let(:subissues) { + FactoryGirl.create_list(:issue, 3, parent_issue_id: superproject_issues[0].id, project_id: superproject.id) + } + + around(:each) do |example| + with_settings(rest_api_enabled: 1) { example.run } + end + + it 'should show superproject items and hide subproject items and show them after open' do + superproject_issues + subproject_issues + milestone_subproject + visit easy_gantt_path(superproject) + wait_for_ajax + # superproject items are shown + expect(page).to have_text(superproject.name) + expect(page).to have_text(milestone_superproject.name) + superproject_issues.each do |issue| + expect(page).to have_text(issue.subject) + end + # subproject items are hidden + expect(page).to have_text(subproject.name) + expect(page).not_to have_text(milestone_subproject.name) + subproject_issues.each do |issue| + expect(page).not_to have_text(issue.subject) + end + # open subproject to show its items + page.find("div[task_id='p#{subproject.id}'] .gantt_open").click + wait_for_ajax + expect(page).to have_text(milestone_subproject.name) + subproject_issues.each do |issue| + expect(page).to have_text(issue.subject) + end + end +end diff --git a/easy_gantt_pro/spec/features/critical_spec.rb b/easy_gantt_pro/spec/features/critical_spec.rb new file mode 100644 index 0000000..c933ec3 --- /dev/null +++ b/easy_gantt_pro/spec/features/critical_spec.rb @@ -0,0 +1,53 @@ +require File.expand_path('../../../../easyproject/easy_plugins/easy_extensions/test/spec/spec_helper', __FILE__) + +RSpec.feature 'Critical', js: true, logged: :admin do + + let!(:project) { FactoryGirl.create(:project, add_modules: ['easy_gantt']) } + #let!(:issue1) { FactoryGirl.create(:issue) } + #let!(:issue2) { FactoryGirl.create(:issue) } + #let!(:relation) { FactoryGirl.create(:issue_relation, source_id:issue1.id, target_id:issue2.id) } + + around(:each) do |example| + with_settings(rest_api_enabled: 1) do + with_easy_settings(easy_gantt_critical_path: 'last') do + example.run + end + end + end + + def open_critical_toolbar + find('.easy-gantt__menu-tools').hover + find('#button_critical').click + find('#button_jump_today').hover + end + + # TODO: '.gantt_task_line.gantt_task-type.critical' is empty + # + # it 'should test draw critical path' do + # # TODO: Remove this conditions + # skip if EasyGantt.platform == 'easyproject' + # + # visit easy_gantt_path(project) + # wait_for_ajax + # within('#content') do + # open_critical_toolbar + # #click_link(l(:critical_path,:scope=>[:easy_gantt,:buttons])) + # expect(page).to have_css('#critical_show.active') + # expect(find('.gantt_task_line.gantt_task-type.critical')).to have_content(project.issues.first.subject) + # end + # end + + it 'should open help' do + # TODO: Remove this conditions + skip if EasyGantt.platform == 'easyproject' + + visit easy_gantt_path(project) + wait_for_ajax + within('#content') do + open_critical_toolbar + find('#button_critical_help').click + end + expect(page).to have_css('#critical_help_modal_popup') + end + +end diff --git a/easy_gantt_pro/spec/features/easy_gantt_spec.rb b/easy_gantt_pro/spec/features/easy_gantt_spec.rb new file mode 100644 index 0000000..f90cddd --- /dev/null +++ b/easy_gantt_pro/spec/features/easy_gantt_spec.rb @@ -0,0 +1,21 @@ +require File.expand_path('../../../../easyproject/easy_plugins/easy_extensions/test/spec/spec_helper', __FILE__) + +feature 'easy gantt', :js => true, :logged => :admin do + let!(:project) { FactoryGirl.create(:project, add_modules: ['easy_gantt']) } + describe 'show gantt' do + around(:each) do |example| + with_settings(rest_api_enabled: 1) do + example.run + end + end + + scenario 'show global easy gantt' do + visit easy_gantt_path + wait_for_ajax + expect(page).to have_css('#easy_gantt') + expect(page.find('.gantt_grid_data')).to have_content(project.name) + end + + end + +end diff --git a/easy_gantt_pro/spec/features/global_gantt_spec.rb b/easy_gantt_pro/spec/features/global_gantt_spec.rb new file mode 100644 index 0000000..130fb85 --- /dev/null +++ b/easy_gantt_pro/spec/features/global_gantt_spec.rb @@ -0,0 +1,65 @@ +require File.expand_path('../../../../easyproject/easy_plugins/easy_extensions/test/spec/spec_helper', __FILE__) + +RSpec.feature 'Global gantt', logged: :admin, js: true do + let(:subproject) { + FactoryGirl.create(:project, :parent_id => superproject.id, add_modules: ['easy_gantt'], number_of_issues: 3) + } + let(:superproject) { + FactoryGirl.create(:project, add_modules: ['easy_gantt'], number_of_issues: 3) + } + let(:project2) { + FactoryGirl.create(:project, add_modules: ['easy_gantt'], number_of_issues: 3) + } + let(:milestone_issues) { + FactoryGirl.create_list(:issue, 3, :fixed_version_id => milestone_superproject.id, :project_id => superproject.id) + } + let(:milestone_superproject) { + FactoryGirl.create(:version, project_id: superproject.id) + } + around(:each) do |example| + with_settings(rest_api_enabled: 1) { example.run } + end + it 'should load projects' do + superproject + subproject + project2 + visit easy_gantt_path + wait_for_ajax + expect(page).not_to have_text(subproject.name) + expect(page).to have_text(superproject.name) + expect(page).to have_text(project2.name) + end + + it 'should load only subproject' do + superproject + subproject + visit easy_gantt_path + wait_for_ajax + expect(page).to have_text(superproject.name) + page.find("div[task_id='p#{superproject.id}'] .gantt_open").click + wait_for_ajax + superproject.issues.each do |issue| + expect(page).not_to have_text(issue.subject) + end + expect(page).to have_text(subproject.name) + end + + it 'should load superproject\'s issues' do + superproject + subproject + visit easy_gantt_path + wait_for_ajax + expect(page).to have_text(superproject.name) + superproject.issues.each do |issue| + expect(page).not_to have_text(issue.subject) + end + page.find("div[task_id='p#{superproject.id}'] .gantt-grid-project-issues-expand").click + wait_for_ajax + expect(superproject.issues.length).to eq(3) + superproject.issues.each do |issue| + expect(page).to have_text(issue.subject) + end + expect(page).to have_text(subproject.name) + end + +end \ No newline at end of file diff --git a/easy_gantt_pro/test/test_helper.rb b/easy_gantt_pro/test/test_helper.rb new file mode 100644 index 0000000..54685d3 --- /dev/null +++ b/easy_gantt_pro/test/test_helper.rb @@ -0,0 +1,2 @@ +# Load the Redmine helper +require File.expand_path(File.dirname(__FILE__) + '/../../../test/test_helper') diff --git a/easy_gantt_pro/version b/easy_gantt_pro/version new file mode 100644 index 0000000..c97a1f2 --- /dev/null +++ b/easy_gantt_pro/version @@ -0,0 +1 @@ +2016.07.RC