From 9936a38843ac9bbd1b3a9db4a2dca87282433148 Mon Sep 17 00:00:00 2001 From: Benjamin ter Kuile Date: Thu, 21 Dec 2017 11:51:55 +0100 Subject: [PATCH] initial github commit --- .editorconfig | 20 + .ember-cli | 9 + .eslintrc.js | 19 + .gitignore | 23 ++ .npmignore | 16 + .travis.yml | 41 ++ .watchmanconfig | 3 + LICENSE.md | 9 + README.md | 26 ++ addon/.gitkeep | 0 .../action-list/organization-filter.coffee | 23 ++ .../action-list/organization-filter.js | 30 ++ .../action-list/project-filter.coffee | 42 ++ .../components/action-list/project-filter.js | 60 +++ addon/components/panda-image.coffee | 13 + addon/components/panda-image.js | 15 + .../edit-active-project-scenario.coffee | 18 + .../project/edit-active-project-scenario.js | 30 ++ .../instance-action-value-display.coffee | 53 +++ .../project/instance-action-value-display.js | 54 +++ addon/components/state-badge.coffee | 27 ++ addon/components/state-badge.js | 50 +++ addon/components/ui-multi-select.coffee | 13 + addon/components/ui-multi-select.js | 20 + addon/components/value-type-display.coffee | 34 ++ addon/components/value-type-display.js | 32 ++ addon/controllers/action-list.coffee | 48 +++ addon/controllers/action-list.js | 82 ++++ .../active-workflow-action-definitions.coffee | 44 ++ .../active-workflow-action-definitions.js | 60 +++ .../project/active-workflow-groups.coffee | 46 +++ .../mixins/project/active-workflow-groups.js | 58 +++ addon/models/panda/action-event.coffee | 12 + addon/models/panda/action-event.js | 14 + .../project-instance-action-connection.coffee | 7 + .../project-instance-action-connection.js | 11 + .../panda/workflow-action-connection.coffee | 8 + .../panda/workflow-action-connection.js | 15 + .../panda/workflow-action-definition.coffee | 50 +++ .../panda/workflow-action-definition.js | 91 ++++ addon/routes/action-list.coffee | 67 +++ addon/routes/action-list.js | 54 +++ addon/styles/addon.scss | 45 ++ addon/styles/components/_buttons.sass | 50 +++ addon/styles/components/_format-json.sass | 8 + addon/styles/components/_forms.sass | 42 ++ addon/styles/components/_multiselect.sass | 2 + ...project-instance-action-value-display.sass | 3 + addon/styles/components/_resources.sass | 13 + addon/styles/components/_select-duration.sass | 6 + addon/styles/components/_sortable.sass | 2 + addon/styles/components/_state-badge.sass | 3 + addon/styles/components/_tables.sass | 9 + addon/styles/components/_title-box.sass | 71 ++++ .../_action-execution-offset-diagram.sass | 19 + .../_workflow-action-offsets-graph.sass | 3 + .../_workflow-group-offset-diagram.sass | 31 ++ addon/styles/resources/_businessdays.sass | 6 + addon/styles/resources/_login-form.sass | 5 + addon/styles/resources/_manual.sass | 7 + addon/styles/resources/_project-instance.sass | 50 +++ addon/styles/resources/_projects.sass | 19 + addon/styles/resources/_tracker-subject.sass | 34 ++ .../_workflow-action-definition.sass | 50 +++ .../_workflow-group-constraints.sass | 4 + addon/styles/resources/_workflow-groups.sass | 71 ++++ addon/styles/semantic-ui-additions.sass | 29 ++ .../edit-active-project-scenario.emblem | 1 + addon/utils/business-day-computations.coffee | 14 + addon/utils/business-day-computations.js | 28 ++ addon/utils/moment-business-days.coffee | 148 +++++++ addon/utils/moment-business-days.js | 204 +++++++++ addon/utils/panda-route-setup.coffee | 51 +++ addon/utils/panda-route-setup.js | 78 ++++ .../active-workflow-action-definitions.coffee | 45 ++ .../active-workflow-action-definitions.js | 73 ++++ .../project/active-workflow-groups.coffee | 45 ++ addon/utils/project/active-workflow-groups.js | 73 ++++ addon/utils/project/contract.coffee | 4 + addon/utils/project/contract.js | 8 + ...flow-action-definition-offset-nodes.coffee | 295 +++++++++++++ ...workflow-action-definition-offset-nodes.js | 389 ++++++++++++++++++ addon/validations/organization.coffee | 10 + addon/validations/organization.js | 21 + addon/validations/project-instance.coffee | 10 + addon/validations/project-instance.js | 21 + addon/validations/project.coffee | 10 + addon/validations/project.js | 21 + addon/validations/user.coffee | 14 + addon/validations/user.js | 30 ++ .../workflow-action-definition.coffee | 10 + .../validations/workflow-action-definition.js | 21 + app/.gitkeep | 0 .../action-list/organization-filter.js | 1 + app/components/action-list/project-filter.js | 1 + app/components/format-json.coffee | 14 + app/components/labeled-content-choice.js | 4 + app/components/modals/base-disabled.coffee | 85 ++++ ...display-workflow-action-definitions.coffee | 12 + .../modals/edit-tracker-group.coffee | 4 + .../modals/edit-tracker-subject.coffee | 18 + app/components/modals/sort-records.coffee | 10 + app/components/number-input-with-panda.coffee | 23 ++ .../modals/edit-organization.coffee | 13 + app/components/panda-image.js | 1 + .../project-workflow-groups-graph.coffee | 29 ++ .../action-execution-offset-diagram.coffee | 303 ++++++++++++++ .../project/edit-active-project-scenario.js | 1 + .../project/instance-action-value-display.js | 1 + app/components/project/modals/contract.coffee | 88 ++++ .../modals/edit-instance-action.coffee | 112 +++++ .../modals/edit-project-scenario.coffee | 67 +++ .../project/modals/edit-project.coffee | 25 ++ .../modals/edit-projects-context.coffee | 7 + .../project/modals/edit-workflow-group.coffee | 71 ++++ .../modals/load-project-instances.coffee | 116 ++++++ .../modals/new-project-from-contract.coffee | 53 +++ ...rkflow-action-definition-in-project.coffee | 42 ++ .../modals/project-scenarios-show-grid.coffee | 12 + .../project/scenario-edit-item.coffee | 9 + .../show/modals/dunlop-project-info.coffee | 12 + .../modals/new-project-instance.coffee | 120 ++++++ .../workflow-action-constraints-graph.coffee | 54 +++ .../workflow-action-offsets-graph.coffee | 227 ++++++++++ .../workflow-group-offset-diagram.coffee | 371 +++++++++++++++++ app/components/select-duration.coffee | 20 + app/components/select-week-days.coffee | 5 + .../set-workflow-group-tracker-group.coffee | 20 + app/components/state-badge.js | 1 + app/components/ui-multi-select.js | 1 + app/components/user/modals/edit-user.coffee | 17 + app/components/user/modals/new-user.coffee | 15 + app/components/value-type-display.js | 1 + .../workflow-action-definition-item.coffee | 69 ++++ .../modal-header.coffee | 4 + .../edit-workflow-action-definition.coffee | 333 +++++++++++++++ .../workflow-group-edit-constraint.coffee | 14 + app/components/workflow-group-header.coffee | 65 +++ app/controllers/action-list.js | 1 + app/controllers/organization.coffee | 16 + app/controllers/project.coffee | 3 + app/controllers/project/index.coffee | 15 + .../project/show/dunlop-generator.coffee | 12 + app/controllers/project/show/dunlop.coffee | 94 +++++ app/controllers/project/show/index.coffee | 3 + .../show/project-instance-action/index.coffee | 89 ++++ .../show/project-instance/index.coffee | 47 +++ .../project/show/project-instance/show.coffee | 11 + .../project/show/tracker-subject.coffee | 53 +++ .../project/show/tracker-subjects.coffee | 3 + app/controllers/tracker-subject/index.coffee | 3 + .../tracker-subject/show/index.coffee | 22 + .../tracker-subject/show/project.coffee | 54 +++ .../tracker-subject/show/tracker-group.coffee | 14 + .../show/tracking-connections.coffee | 64 +++ app/controllers/user/show.coffee | 24 ++ app/helpers/can.coffee | 7 + app/helpers/date.coffee | 15 + app/helpers/dynamic-path-t.coffee | 7 + app/helpers/l.coffee | 14 + app/models/panda/action-event.js | 1 + app/models/panda/adapter.coffee | 6 + app/models/panda/organization.coffee | 40 ++ .../project-instance-action-connection.js | 1 + .../panda/project-instance-action.coffee | 49 +++ .../panda/project-instance-group.coffee | 10 + app/models/panda/project-instance.coffee | 14 + app/models/panda/project-scenario.coffee | 11 + app/models/panda/project.coffee | 66 +++ app/models/panda/tracker-group.coffee | 12 + app/models/panda/tracker-item.coffee | 5 + app/models/panda/tracker-subject.coffee | 11 + .../panda/workflow-action-connection.js | 1 + .../panda/workflow-action-constraint.coffee | 9 + .../panda/workflow-action-definition.coffee | 1 + .../panda/workflow-action-offset.coffee | 10 + .../panda/workflow-group-constraint.coffee | 7 + app/models/panda/workflow-group.coffee | 24 ++ .../panda/workflow-instance-type.coffee | 6 + app/routes/action-list.js | 1 + app/routes/organization.coffee | 33 ++ app/routes/organization/index.coffee | 3 + app/routes/organization/show.coffee | 3 + app/routes/project.coffee | 25 ++ app/routes/project/index.coffee | 6 + app/routes/project/show.coffee | 9 + app/routes/project/show/add-progress.coffee | 3 + app/routes/project/show/dunlop.coffee | 16 + .../show/project-instance-action/index.coffee | 33 ++ .../show/project-instance-action/show.coffee | 3 + .../show/project-instance-group/index.coffee | 8 + .../show/project-instance-group/show.coffee | 3 + .../show/project-instance/index.coffee | 39 ++ .../project/show/project-instance/show.coffee | 9 + .../project/show/tracker-subject.coffee | 6 + app/routes/tracker-subject/index.coffee | 3 + .../tracker-subject/show/project.coffee | 6 + app/routes/tracker_subject.coffee | 12 + app/routes/user.coffee | 11 + app/routes/user/index.coffee | 3 + app/routes/user/profile.coffee | 3 + app/routes/user/show.coffee | 34 ++ app/templates/action-list.emblem | 124 ++++++ .../action-list/organization-filter.emblem | 12 + .../action-list/project-filter.emblem | 26 ++ .../components/edit-attribute-item.emblem | 22 + app/templates/components/flash-message.emblem | 1 + app/templates/components/format-json.emblem | 1 + .../components/labeled-content-choice.emblem | 1 + .../components/number-input-with-value.emblem | 2 + .../project-workflow-groups-graph.emblem | 2 + .../action-execution-offset-diagram.emblem | 26 ++ .../edit-active-project-scenario.emblem | 15 + .../instance-action-value-display.emblem | 42 ++ .../project/scenario-edit-item.emblem | 3 + .../workflow-action-constraints-graph.emblem | 5 + .../workflow-action-offsets-graph.emblem | 34 ++ .../workflow-group-offset-diagram.emblem | 27 ++ .../components/select-duration.emblem | 27 ++ .../components/select-week-days.emblem | 1 + .../set-workflow-group-tracker-group.emblem | 9 + app/templates/components/state-badge.emblem | 2 + .../components/ui-multi-select.emblem | 6 + .../components/value-type-display.emblem | 1 + .../workflow-action-definition-item.emblem | 31 ++ .../modal-header.emblem | 11 + .../workflow-group-edit-constraint.emblem | 17 + .../components/workflow-group-header.emblem | 13 + app/templates/event.emblem | 74 ++++ app/templates/manual.emblem | 16 + app/templates/manual/index.emblem | 41 ++ app/templates/manual/project/dunlop.emblem | 38 ++ app/templates/manual/tracker/index.emblem | 11 + ...display-workflow-action-definitions.emblem | 15 + .../modals/-edit-tracker-group.emblem | 7 + .../modals/-edit-tracker-subject.emblem | 19 + .../organization/-edit-organization.emblem | 26 ++ app/templates/organization/index.emblem | 22 + app/templates/organization/show/index.emblem | 26 ++ .../organization/show/project-actions.emblem | 9 + app/templates/project/-contract.emblem | 16 + .../project/-edit-instance-action.emblem | 147 +++++++ .../project/-edit-project-scenario.emblem | 20 + app/templates/project/-edit-project.emblem | 99 +++++ .../project/-edit-projects-context.emblem | 12 + .../project/-edit-workflow-group.emblem | 121 ++++++ .../project/-load-project-instances.emblem | 76 ++++ .../project/-new-project-from-contract.emblem | 17 + ...rkflow-action-definition-in-project.emblem | 26 ++ .../-project-scenarios-show-grid.emblem | 28 ++ app/templates/project/index.emblem | 24 ++ .../project/show/-dunlop-project-info.emblem | 40 ++ .../project/show/add-progress.emblem | 79 ++++ app/templates/project/show/analysis.emblem | 19 + .../project/show/dunlop-generator.emblem | 42 ++ app/templates/project/show/dunlop.emblem | 93 +++++ app/templates/project/show/index.emblem | 50 +++ .../show/project-instance-action/index.emblem | 87 ++++ .../show/project-instance-group/index.emblem | 35 ++ .../show/project-instance-group/show.emblem | 29 ++ .../-new-project-instance.emblem | 45 ++ .../show/project-instance/index.emblem | 78 ++++ .../project/show/project-instance/show.emblem | 37 ++ .../show/project-instance/show/actions.emblem | 19 + .../show/project-instance/show/graphs.emblem | 0 .../show/project-instance/show/groups.emblem | 12 + .../show/project-instance/show/index.emblem | 15 + .../show/project-instance/show/list.emblem | 12 + .../project/show/tracker-subject.emblem | 30 ++ .../project/show/tracker-subjects.emblem | 19 + app/templates/tracker-subject/index.emblem | 20 + .../tracker-subject/show/index.emblem | 36 ++ .../tracker-subject/show/project.emblem | 29 ++ .../tracker-subject/show/tracker-group.emblem | 23 ++ .../show/tracking-connections.emblem | 36 ++ app/templates/user/-edit-user.emblem | 11 + app/templates/user/-new-user.emblem | 14 + app/templates/user/index.emblem | 21 + app/templates/user/profile.emblem | 17 + app/templates/user/show.emblem | 84 ++++ .../-authorize-users.emblem | 1 + .../-edit-workflow-action-definition.emblem | 244 +++++++++++ bower.json | 7 + config/ember-try.js | 83 ++++ config/environment.js | 6 + ember-cli-build.js | 19 + esa-coffee-workaround.sh | 6 + index.js | 6 + package.json | 81 ++++ .../assets/icons/svg/logical-conjunction.svg | 71 ++++ .../icons/svg/logical-conjunction_backup.svg | 87 ++++ .../assets/icons/svg/logical-disjunction.svg | 66 +++ .../icons/svg/logical-disjunction_backup.svg | 87 ++++ .../icons/svg/logical-drawing-board.svg | 83 ++++ public/assets/under-construction.svg | 83 ++++ testem.js | 19 + tests/.eslintrc.js | 5 + tests/dummy/app/adapters/application.coffee | 9 + tests/dummy/app/app.js | 17 + tests/dummy/app/components/.gitkeep | 0 tests/dummy/app/controllers/.gitkeep | 0 tests/dummy/app/helpers/.gitkeep | 0 tests/dummy/app/i18n/translations.coffee | 95 +++++ .../app/i18n/translations/action-list.coffee | 5 + .../app/i18n/translations/attributes.coffee | 74 ++++ .../dummy/app/i18n/translations/manual.coffee | 24 ++ .../i18n/translations/manual/project.coffee | 13 + .../dummy/app/i18n/translations/models.coffee | 39 ++ .../app/i18n/translations/organization.coffee | 8 + .../project-instance-action.coffee | 5 + .../i18n/translations/project-instance.coffee | 6 + .../app/i18n/translations/project.coffee | 36 ++ tests/dummy/app/i18n/translations/user.coffee | 8 + .../workflow-action-definition.coffee | 37 ++ .../i18n/translations/workflow-group.coffee | 21 + tests/dummy/app/index.html | 25 ++ tests/dummy/app/initializers/globals.coffee | 50 +++ tests/dummy/app/models/.gitkeep | 0 tests/dummy/app/models/user.coffee | 8 + tests/dummy/app/resolver.js | 3 + tests/dummy/app/router.coffee | 12 + tests/dummy/app/routes/.gitkeep | 0 tests/dummy/app/routes/application.coffee | 56 +++ tests/dummy/app/styles/app.scss | 0 tests/dummy/app/templates/application.emblem | 28 ++ tests/dummy/app/templates/components/.gitkeep | 0 .../app/templates/global/-top-menu.emblem | 95 +++++ tests/dummy/app/templates/index.emblem | 14 + tests/dummy/app/templates/loading.emblem | 6 + tests/dummy/app/templates/login.emblem | 6 + tests/dummy/config/environment.js | 60 +++ tests/dummy/config/targets.js | 9 + .../favicons/android-chrome-192x192.png | Bin 0 -> 8641 bytes .../favicons/android-chrome-512x512.png | Bin 0 -> 27402 bytes .../assets/favicons/apple-touch-icon.png | Bin 0 -> 8075 bytes .../public/assets/favicons/browserconfig.xml | 9 + .../public/assets/favicons/favicon-16x16.png | Bin 0 -> 772 bytes .../public/assets/favicons/favicon-32x32.png | Bin 0 -> 1406 bytes .../dummy/public/assets/favicons/favicon.ico | Bin 0 -> 15086 bytes .../public/assets/favicons/manifest.json | 18 + .../public/assets/favicons/mstile-150x150.png | Bin 0 -> 5963 bytes .../assets/favicons/safari-pinned-tab.svg | 52 +++ tests/dummy/public/assets/favicons/source.svg | 137 ++++++ .../assets/icons/svg/logical-conjunction.svg | 71 ++++ .../icons/svg/logical-conjunction_backup.svg | 87 ++++ .../assets/icons/svg/logical-disjunction.svg | 66 +++ .../icons/svg/logical-disjunction_backup.svg | 87 ++++ .../icons/svg/logical-drawing-board.svg | 83 ++++ .../manual/action-offsets/non-rooted.png | Bin 0 -> 4543 bytes .../action-offsets/rooted-critical-path.png | Bin 0 -> 4360 bytes .../rooted-non-critical-path.png | Bin 0 -> 4345 bytes .../public/assets/manual/manual-index.png | Bin 0 -> 32768 bytes ...orkflow-action-definition-confirmation.png | Bin 0 -> 48457 bytes .../assets/panda/android-icon-144x144.png | Bin 0 -> 6367 bytes .../assets/panda/android-icon-192x192.png | Bin 0 -> 8164 bytes .../assets/panda/android-icon-36x36.png | Bin 0 -> 2039 bytes .../assets/panda/android-icon-48x48.png | Bin 0 -> 2438 bytes .../assets/panda/android-icon-72x72.png | Bin 0 -> 3306 bytes .../assets/panda/android-icon-96x96.png | Bin 0 -> 4095 bytes .../assets/panda/apple-icon-114x114.png | Bin 0 -> 4749 bytes .../assets/panda/apple-icon-120x120.png | Bin 0 -> 5046 bytes .../assets/panda/apple-icon-144x144.png | Bin 0 -> 6367 bytes .../assets/panda/apple-icon-152x152.png | Bin 0 -> 6839 bytes .../assets/panda/apple-icon-180x180.png | Bin 0 -> 8722 bytes .../public/assets/panda/apple-icon-57x57.png | Bin 0 -> 2803 bytes .../public/assets/panda/apple-icon-60x60.png | Bin 0 -> 2865 bytes .../public/assets/panda/apple-icon-72x72.png | Bin 0 -> 3306 bytes .../public/assets/panda/apple-icon-76x76.png | Bin 0 -> 3449 bytes .../assets/panda/apple-icon-precomposed.png | Bin 0 -> 8648 bytes .../dummy/public/assets/panda/apple-icon.png | Bin 0 -> 8648 bytes .../public/assets/panda/browserconfig.xml | 2 + .../public/assets/panda/favicon-16x16.png | Bin 0 -> 1252 bytes .../public/assets/panda/favicon-32x32.png | Bin 0 -> 1875 bytes .../public/assets/panda/favicon-96x96.png | Bin 0 -> 4095 bytes tests/dummy/public/assets/panda/favicon.ico | Bin 0 -> 1150 bytes tests/dummy/public/assets/panda/manifest.json | 41 ++ .../public/assets/panda/ms-icon-144x144.png | Bin 0 -> 6367 bytes .../public/assets/panda/ms-icon-150x150.png | Bin 0 -> 6737 bytes .../public/assets/panda/ms-icon-310x310.png | Bin 0 -> 20425 bytes .../public/assets/panda/ms-icon-70x70.png | Bin 0 -> 3213 bytes .../dummy/public/assets/panda/panda-base.png | Bin 0 -> 78176 bytes .../dummy/public/assets/panda/panda-base.svg | 93 +++++ .../public/assets/panda/panda-base_backup.svg | 93 +++++ .../public/assets/team-panda/Panda svg.svg | 118 ++++++ .../public/assets/team-panda/team-panda.png | Bin 0 -> 130421 bytes tests/dummy/public/crossdomain.xml | 15 + tests/dummy/public/robots.txt | 3 + tests/helpers/destroy-app.js | 5 + tests/helpers/module-for-acceptance.js | 23 ++ tests/helpers/resolver.js | 11 + tests/helpers/start-app.js | 15 + tests/index.html | 33 ++ tests/integration/.gitkeep | 0 .../organization-filter-test.coffee | 25 ++ .../action-list/project-filter-test.coffee | 25 ++ .../components/panda-image-test.coffee | 25 ++ .../edit-active-project-scenario-test.coffee | 25 ++ .../instance-action-value-display-test.coffee | 25 ++ .../components/state-badge-test.coffee | 25 ++ .../components/ui-multi-select-test.coffee | 25 ++ .../components/value-type-display-test.coffee | 25 ++ tests/test-helper.js | 8 + tests/unit/.gitkeep | 0 .../unit/controllers/action-list-test.coffee | 11 + .../models/panda/action-event-test.coffee | 11 + tests/unit/models/panda/adapter-test.coffee | 11 + .../models/panda/organization-test.coffee | 11 + .../models/panda/tracker-subject-test.coffee | 11 + .../workflow-action-connection-test.coffee | 11 + .../panda/workflow-instance-type-test.coffee | 11 + tests/unit/routes/action-list-test.coffee | 10 + .../unit/utils/panda-route-setup-test.coffee | 9 + vendor/.gitkeep | 0 413 files changed, 12623 insertions(+) create mode 100644 .editorconfig create mode 100644 .ember-cli create mode 100644 .eslintrc.js create mode 100644 .gitignore create mode 100644 .npmignore create mode 100644 .travis.yml create mode 100644 .watchmanconfig create mode 100644 LICENSE.md create mode 100644 README.md create mode 100644 addon/.gitkeep create mode 100644 addon/components/action-list/organization-filter.coffee create mode 100644 addon/components/action-list/organization-filter.js create mode 100644 addon/components/action-list/project-filter.coffee create mode 100644 addon/components/action-list/project-filter.js create mode 100644 addon/components/panda-image.coffee create mode 100644 addon/components/panda-image.js create mode 100644 addon/components/project/edit-active-project-scenario.coffee create mode 100644 addon/components/project/edit-active-project-scenario.js create mode 100644 addon/components/project/instance-action-value-display.coffee create mode 100644 addon/components/project/instance-action-value-display.js create mode 100644 addon/components/state-badge.coffee create mode 100644 addon/components/state-badge.js create mode 100644 addon/components/ui-multi-select.coffee create mode 100644 addon/components/ui-multi-select.js create mode 100644 addon/components/value-type-display.coffee create mode 100644 addon/components/value-type-display.js create mode 100644 addon/controllers/action-list.coffee create mode 100644 addon/controllers/action-list.js create mode 100644 addon/mixins/project/active-workflow-action-definitions.coffee create mode 100644 addon/mixins/project/active-workflow-action-definitions.js create mode 100644 addon/mixins/project/active-workflow-groups.coffee create mode 100644 addon/mixins/project/active-workflow-groups.js create mode 100644 addon/models/panda/action-event.coffee create mode 100644 addon/models/panda/action-event.js create mode 100644 addon/models/panda/project-instance-action-connection.coffee create mode 100644 addon/models/panda/project-instance-action-connection.js create mode 100644 addon/models/panda/workflow-action-connection.coffee create mode 100644 addon/models/panda/workflow-action-connection.js create mode 100644 addon/models/panda/workflow-action-definition.coffee create mode 100644 addon/models/panda/workflow-action-definition.js create mode 100644 addon/routes/action-list.coffee create mode 100644 addon/routes/action-list.js create mode 100644 addon/styles/addon.scss create mode 100644 addon/styles/components/_buttons.sass create mode 100644 addon/styles/components/_format-json.sass create mode 100644 addon/styles/components/_forms.sass create mode 100644 addon/styles/components/_multiselect.sass create mode 100644 addon/styles/components/_project-instance-action-value-display.sass create mode 100644 addon/styles/components/_resources.sass create mode 100644 addon/styles/components/_select-duration.sass create mode 100644 addon/styles/components/_sortable.sass create mode 100644 addon/styles/components/_state-badge.sass create mode 100644 addon/styles/components/_tables.sass create mode 100644 addon/styles/components/_title-box.sass create mode 100644 addon/styles/diagrams/project/_action-execution-offset-diagram.sass create mode 100644 addon/styles/diagrams/project/_workflow-action-offsets-graph.sass create mode 100644 addon/styles/diagrams/project/_workflow-group-offset-diagram.sass create mode 100644 addon/styles/resources/_businessdays.sass create mode 100644 addon/styles/resources/_login-form.sass create mode 100644 addon/styles/resources/_manual.sass create mode 100644 addon/styles/resources/_project-instance.sass create mode 100644 addon/styles/resources/_projects.sass create mode 100644 addon/styles/resources/_tracker-subject.sass create mode 100644 addon/styles/resources/_workflow-action-definition.sass create mode 100644 addon/styles/resources/_workflow-group-constraints.sass create mode 100644 addon/styles/resources/_workflow-groups.sass create mode 100644 addon/styles/semantic-ui-additions.sass create mode 100644 addon/templates/components/project/edit-active-project-scenario.emblem create mode 100644 addon/utils/business-day-computations.coffee create mode 100644 addon/utils/business-day-computations.js create mode 100644 addon/utils/moment-business-days.coffee create mode 100644 addon/utils/moment-business-days.js create mode 100644 addon/utils/panda-route-setup.coffee create mode 100644 addon/utils/panda-route-setup.js create mode 100644 addon/utils/project/active-workflow-action-definitions.coffee create mode 100644 addon/utils/project/active-workflow-action-definitions.js create mode 100644 addon/utils/project/active-workflow-groups.coffee create mode 100644 addon/utils/project/active-workflow-groups.js create mode 100644 addon/utils/project/contract.coffee create mode 100644 addon/utils/project/contract.js create mode 100644 addon/utils/project/workflow-action-definition-offset-nodes.coffee create mode 100644 addon/utils/project/workflow-action-definition-offset-nodes.js create mode 100644 addon/validations/organization.coffee create mode 100644 addon/validations/organization.js create mode 100644 addon/validations/project-instance.coffee create mode 100644 addon/validations/project-instance.js create mode 100644 addon/validations/project.coffee create mode 100644 addon/validations/project.js create mode 100644 addon/validations/user.coffee create mode 100644 addon/validations/user.js create mode 100644 addon/validations/workflow-action-definition.coffee create mode 100644 addon/validations/workflow-action-definition.js create mode 100644 app/.gitkeep create mode 100644 app/components/action-list/organization-filter.js create mode 100644 app/components/action-list/project-filter.js create mode 100644 app/components/format-json.coffee create mode 100644 app/components/labeled-content-choice.js create mode 100644 app/components/modals/base-disabled.coffee create mode 100644 app/components/modals/display-workflow-action-definitions.coffee create mode 100644 app/components/modals/edit-tracker-group.coffee create mode 100644 app/components/modals/edit-tracker-subject.coffee create mode 100644 app/components/modals/sort-records.coffee create mode 100644 app/components/number-input-with-panda.coffee create mode 100644 app/components/organization/modals/edit-organization.coffee create mode 100644 app/components/panda-image.js create mode 100644 app/components/project-workflow-groups-graph.coffee create mode 100644 app/components/project/action-execution-offset-diagram.coffee create mode 100644 app/components/project/edit-active-project-scenario.js create mode 100644 app/components/project/instance-action-value-display.js create mode 100644 app/components/project/modals/contract.coffee create mode 100644 app/components/project/modals/edit-instance-action.coffee create mode 100644 app/components/project/modals/edit-project-scenario.coffee create mode 100644 app/components/project/modals/edit-project.coffee create mode 100644 app/components/project/modals/edit-projects-context.coffee create mode 100644 app/components/project/modals/edit-workflow-group.coffee create mode 100644 app/components/project/modals/load-project-instances.coffee create mode 100644 app/components/project/modals/new-project-from-contract.coffee create mode 100644 app/components/project/modals/position-workflow-action-definition-in-project.coffee create mode 100644 app/components/project/modals/project-scenarios-show-grid.coffee create mode 100644 app/components/project/scenario-edit-item.coffee create mode 100644 app/components/project/show/modals/dunlop-project-info.coffee create mode 100644 app/components/project/show/project-instance/modals/new-project-instance.coffee create mode 100644 app/components/project/workflow-action-constraints-graph.coffee create mode 100644 app/components/project/workflow-action-offsets-graph.coffee create mode 100644 app/components/project/workflow-group-offset-diagram.coffee create mode 100644 app/components/select-duration.coffee create mode 100644 app/components/select-week-days.coffee create mode 100644 app/components/set-workflow-group-tracker-group.coffee create mode 100644 app/components/state-badge.js create mode 100644 app/components/ui-multi-select.js create mode 100644 app/components/user/modals/edit-user.coffee create mode 100644 app/components/user/modals/new-user.coffee create mode 100644 app/components/value-type-display.js create mode 100644 app/components/workflow-action-definition-item.coffee create mode 100644 app/components/workflow-action-definition/modal-header.coffee create mode 100644 app/components/workflow-action-definition/modals/edit-workflow-action-definition.coffee create mode 100644 app/components/workflow-group-edit-constraint.coffee create mode 100644 app/components/workflow-group-header.coffee create mode 100644 app/controllers/action-list.js create mode 100644 app/controllers/organization.coffee create mode 100644 app/controllers/project.coffee create mode 100644 app/controllers/project/index.coffee create mode 100644 app/controllers/project/show/dunlop-generator.coffee create mode 100644 app/controllers/project/show/dunlop.coffee create mode 100644 app/controllers/project/show/index.coffee create mode 100644 app/controllers/project/show/project-instance-action/index.coffee create mode 100644 app/controllers/project/show/project-instance/index.coffee create mode 100644 app/controllers/project/show/project-instance/show.coffee create mode 100644 app/controllers/project/show/tracker-subject.coffee create mode 100644 app/controllers/project/show/tracker-subjects.coffee create mode 100644 app/controllers/tracker-subject/index.coffee create mode 100644 app/controllers/tracker-subject/show/index.coffee create mode 100644 app/controllers/tracker-subject/show/project.coffee create mode 100644 app/controllers/tracker-subject/show/tracker-group.coffee create mode 100644 app/controllers/tracker-subject/show/tracking-connections.coffee create mode 100644 app/controllers/user/show.coffee create mode 100644 app/helpers/can.coffee create mode 100644 app/helpers/date.coffee create mode 100644 app/helpers/dynamic-path-t.coffee create mode 100644 app/helpers/l.coffee create mode 100644 app/models/panda/action-event.js create mode 100644 app/models/panda/adapter.coffee create mode 100644 app/models/panda/organization.coffee create mode 100644 app/models/panda/project-instance-action-connection.js create mode 100644 app/models/panda/project-instance-action.coffee create mode 100644 app/models/panda/project-instance-group.coffee create mode 100644 app/models/panda/project-instance.coffee create mode 100644 app/models/panda/project-scenario.coffee create mode 100644 app/models/panda/project.coffee create mode 100644 app/models/panda/tracker-group.coffee create mode 100644 app/models/panda/tracker-item.coffee create mode 100644 app/models/panda/tracker-subject.coffee create mode 100644 app/models/panda/workflow-action-connection.js create mode 100644 app/models/panda/workflow-action-constraint.coffee create mode 100644 app/models/panda/workflow-action-definition.coffee create mode 100644 app/models/panda/workflow-action-offset.coffee create mode 100644 app/models/panda/workflow-group-constraint.coffee create mode 100644 app/models/panda/workflow-group.coffee create mode 100644 app/models/panda/workflow-instance-type.coffee create mode 100644 app/routes/action-list.js create mode 100644 app/routes/organization.coffee create mode 100644 app/routes/organization/index.coffee create mode 100644 app/routes/organization/show.coffee create mode 100644 app/routes/project.coffee create mode 100644 app/routes/project/index.coffee create mode 100644 app/routes/project/show.coffee create mode 100644 app/routes/project/show/add-progress.coffee create mode 100644 app/routes/project/show/dunlop.coffee create mode 100644 app/routes/project/show/project-instance-action/index.coffee create mode 100644 app/routes/project/show/project-instance-action/show.coffee create mode 100644 app/routes/project/show/project-instance-group/index.coffee create mode 100644 app/routes/project/show/project-instance-group/show.coffee create mode 100644 app/routes/project/show/project-instance/index.coffee create mode 100644 app/routes/project/show/project-instance/show.coffee create mode 100644 app/routes/project/show/tracker-subject.coffee create mode 100644 app/routes/tracker-subject/index.coffee create mode 100644 app/routes/tracker-subject/show/project.coffee create mode 100644 app/routes/tracker_subject.coffee create mode 100644 app/routes/user.coffee create mode 100644 app/routes/user/index.coffee create mode 100644 app/routes/user/profile.coffee create mode 100644 app/routes/user/show.coffee create mode 100644 app/templates/action-list.emblem create mode 100644 app/templates/components/action-list/organization-filter.emblem create mode 100644 app/templates/components/action-list/project-filter.emblem create mode 100644 app/templates/components/edit-attribute-item.emblem create mode 100644 app/templates/components/flash-message.emblem create mode 100644 app/templates/components/format-json.emblem create mode 100644 app/templates/components/labeled-content-choice.emblem create mode 100644 app/templates/components/number-input-with-value.emblem create mode 100644 app/templates/components/project-workflow-groups-graph.emblem create mode 100644 app/templates/components/project/action-execution-offset-diagram.emblem create mode 100644 app/templates/components/project/edit-active-project-scenario.emblem create mode 100644 app/templates/components/project/instance-action-value-display.emblem create mode 100644 app/templates/components/project/scenario-edit-item.emblem create mode 100644 app/templates/components/project/workflow-action-constraints-graph.emblem create mode 100644 app/templates/components/project/workflow-action-offsets-graph.emblem create mode 100644 app/templates/components/project/workflow-group-offset-diagram.emblem create mode 100644 app/templates/components/select-duration.emblem create mode 100644 app/templates/components/select-week-days.emblem create mode 100644 app/templates/components/set-workflow-group-tracker-group.emblem create mode 100644 app/templates/components/state-badge.emblem create mode 100644 app/templates/components/ui-multi-select.emblem create mode 100644 app/templates/components/value-type-display.emblem create mode 100644 app/templates/components/workflow-action-definition-item.emblem create mode 100644 app/templates/components/workflow-action-definition/modal-header.emblem create mode 100644 app/templates/components/workflow-group-edit-constraint.emblem create mode 100644 app/templates/components/workflow-group-header.emblem create mode 100644 app/templates/event.emblem create mode 100644 app/templates/manual.emblem create mode 100644 app/templates/manual/index.emblem create mode 100644 app/templates/manual/project/dunlop.emblem create mode 100644 app/templates/manual/tracker/index.emblem create mode 100644 app/templates/modals/-display-workflow-action-definitions.emblem create mode 100644 app/templates/modals/-edit-tracker-group.emblem create mode 100644 app/templates/modals/-edit-tracker-subject.emblem create mode 100644 app/templates/organization/-edit-organization.emblem create mode 100644 app/templates/organization/index.emblem create mode 100644 app/templates/organization/show/index.emblem create mode 100644 app/templates/organization/show/project-actions.emblem create mode 100644 app/templates/project/-contract.emblem create mode 100644 app/templates/project/-edit-instance-action.emblem create mode 100644 app/templates/project/-edit-project-scenario.emblem create mode 100644 app/templates/project/-edit-project.emblem create mode 100644 app/templates/project/-edit-projects-context.emblem create mode 100644 app/templates/project/-edit-workflow-group.emblem create mode 100644 app/templates/project/-load-project-instances.emblem create mode 100644 app/templates/project/-new-project-from-contract.emblem create mode 100644 app/templates/project/-position-workflow-action-definition-in-project.emblem create mode 100644 app/templates/project/-project-scenarios-show-grid.emblem create mode 100644 app/templates/project/index.emblem create mode 100644 app/templates/project/show/-dunlop-project-info.emblem create mode 100644 app/templates/project/show/add-progress.emblem create mode 100644 app/templates/project/show/analysis.emblem create mode 100644 app/templates/project/show/dunlop-generator.emblem create mode 100644 app/templates/project/show/dunlop.emblem create mode 100644 app/templates/project/show/index.emblem create mode 100644 app/templates/project/show/project-instance-action/index.emblem create mode 100644 app/templates/project/show/project-instance-group/index.emblem create mode 100644 app/templates/project/show/project-instance-group/show.emblem create mode 100644 app/templates/project/show/project-instance/-new-project-instance.emblem create mode 100644 app/templates/project/show/project-instance/index.emblem create mode 100644 app/templates/project/show/project-instance/show.emblem create mode 100644 app/templates/project/show/project-instance/show/actions.emblem create mode 100644 app/templates/project/show/project-instance/show/graphs.emblem create mode 100644 app/templates/project/show/project-instance/show/groups.emblem create mode 100644 app/templates/project/show/project-instance/show/index.emblem create mode 100644 app/templates/project/show/project-instance/show/list.emblem create mode 100644 app/templates/project/show/tracker-subject.emblem create mode 100644 app/templates/project/show/tracker-subjects.emblem create mode 100644 app/templates/tracker-subject/index.emblem create mode 100644 app/templates/tracker-subject/show/index.emblem create mode 100644 app/templates/tracker-subject/show/project.emblem create mode 100644 app/templates/tracker-subject/show/tracker-group.emblem create mode 100644 app/templates/tracker-subject/show/tracking-connections.emblem create mode 100644 app/templates/user/-edit-user.emblem create mode 100644 app/templates/user/-new-user.emblem create mode 100644 app/templates/user/index.emblem create mode 100644 app/templates/user/profile.emblem create mode 100644 app/templates/user/show.emblem create mode 100644 app/templates/workflow-action-definition/-authorize-users.emblem create mode 100644 app/templates/workflow-action-definition/-edit-workflow-action-definition.emblem create mode 100644 bower.json create mode 100644 config/ember-try.js create mode 100644 config/environment.js create mode 100644 ember-cli-build.js create mode 100644 esa-coffee-workaround.sh create mode 100644 index.js create mode 100644 package.json create mode 100644 public/assets/icons/svg/logical-conjunction.svg create mode 100644 public/assets/icons/svg/logical-conjunction_backup.svg create mode 100644 public/assets/icons/svg/logical-disjunction.svg create mode 100644 public/assets/icons/svg/logical-disjunction_backup.svg create mode 100644 public/assets/icons/svg/logical-drawing-board.svg create mode 100644 public/assets/under-construction.svg create mode 100644 testem.js create mode 100644 tests/.eslintrc.js create mode 100644 tests/dummy/app/adapters/application.coffee create mode 100644 tests/dummy/app/app.js create mode 100644 tests/dummy/app/components/.gitkeep create mode 100644 tests/dummy/app/controllers/.gitkeep create mode 100644 tests/dummy/app/helpers/.gitkeep create mode 100644 tests/dummy/app/i18n/translations.coffee create mode 100644 tests/dummy/app/i18n/translations/action-list.coffee create mode 100644 tests/dummy/app/i18n/translations/attributes.coffee create mode 100644 tests/dummy/app/i18n/translations/manual.coffee create mode 100644 tests/dummy/app/i18n/translations/manual/project.coffee create mode 100644 tests/dummy/app/i18n/translations/models.coffee create mode 100644 tests/dummy/app/i18n/translations/organization.coffee create mode 100644 tests/dummy/app/i18n/translations/project-instance-action.coffee create mode 100644 tests/dummy/app/i18n/translations/project-instance.coffee create mode 100644 tests/dummy/app/i18n/translations/project.coffee create mode 100644 tests/dummy/app/i18n/translations/user.coffee create mode 100644 tests/dummy/app/i18n/translations/workflow-action-definition.coffee create mode 100644 tests/dummy/app/i18n/translations/workflow-group.coffee create mode 100644 tests/dummy/app/index.html create mode 100644 tests/dummy/app/initializers/globals.coffee create mode 100644 tests/dummy/app/models/.gitkeep create mode 100644 tests/dummy/app/models/user.coffee create mode 100644 tests/dummy/app/resolver.js create mode 100644 tests/dummy/app/router.coffee create mode 100644 tests/dummy/app/routes/.gitkeep create mode 100644 tests/dummy/app/routes/application.coffee create mode 100644 tests/dummy/app/styles/app.scss create mode 100644 tests/dummy/app/templates/application.emblem create mode 100644 tests/dummy/app/templates/components/.gitkeep create mode 100644 tests/dummy/app/templates/global/-top-menu.emblem create mode 100644 tests/dummy/app/templates/index.emblem create mode 100644 tests/dummy/app/templates/loading.emblem create mode 100644 tests/dummy/app/templates/login.emblem create mode 100644 tests/dummy/config/environment.js create mode 100644 tests/dummy/config/targets.js create mode 100644 tests/dummy/public/assets/favicons/android-chrome-192x192.png create mode 100644 tests/dummy/public/assets/favicons/android-chrome-512x512.png create mode 100644 tests/dummy/public/assets/favicons/apple-touch-icon.png create mode 100644 tests/dummy/public/assets/favicons/browserconfig.xml create mode 100644 tests/dummy/public/assets/favicons/favicon-16x16.png create mode 100644 tests/dummy/public/assets/favicons/favicon-32x32.png create mode 100644 tests/dummy/public/assets/favicons/favicon.ico create mode 100644 tests/dummy/public/assets/favicons/manifest.json create mode 100644 tests/dummy/public/assets/favicons/mstile-150x150.png create mode 100644 tests/dummy/public/assets/favicons/safari-pinned-tab.svg create mode 100644 tests/dummy/public/assets/favicons/source.svg create mode 100644 tests/dummy/public/assets/icons/svg/logical-conjunction.svg create mode 100644 tests/dummy/public/assets/icons/svg/logical-conjunction_backup.svg create mode 100644 tests/dummy/public/assets/icons/svg/logical-disjunction.svg create mode 100644 tests/dummy/public/assets/icons/svg/logical-disjunction_backup.svg create mode 100644 tests/dummy/public/assets/icons/svg/logical-drawing-board.svg create mode 100644 tests/dummy/public/assets/manual/action-offsets/non-rooted.png create mode 100644 tests/dummy/public/assets/manual/action-offsets/rooted-critical-path.png create mode 100644 tests/dummy/public/assets/manual/action-offsets/rooted-non-critical-path.png create mode 100644 tests/dummy/public/assets/manual/manual-index.png create mode 100644 tests/dummy/public/assets/manual/workflow-action-definition-confirmation.png create mode 100644 tests/dummy/public/assets/panda/android-icon-144x144.png create mode 100644 tests/dummy/public/assets/panda/android-icon-192x192.png create mode 100644 tests/dummy/public/assets/panda/android-icon-36x36.png create mode 100644 tests/dummy/public/assets/panda/android-icon-48x48.png create mode 100644 tests/dummy/public/assets/panda/android-icon-72x72.png create mode 100644 tests/dummy/public/assets/panda/android-icon-96x96.png create mode 100644 tests/dummy/public/assets/panda/apple-icon-114x114.png create mode 100644 tests/dummy/public/assets/panda/apple-icon-120x120.png create mode 100644 tests/dummy/public/assets/panda/apple-icon-144x144.png create mode 100644 tests/dummy/public/assets/panda/apple-icon-152x152.png create mode 100644 tests/dummy/public/assets/panda/apple-icon-180x180.png create mode 100644 tests/dummy/public/assets/panda/apple-icon-57x57.png create mode 100644 tests/dummy/public/assets/panda/apple-icon-60x60.png create mode 100644 tests/dummy/public/assets/panda/apple-icon-72x72.png create mode 100644 tests/dummy/public/assets/panda/apple-icon-76x76.png create mode 100644 tests/dummy/public/assets/panda/apple-icon-precomposed.png create mode 100644 tests/dummy/public/assets/panda/apple-icon.png create mode 100644 tests/dummy/public/assets/panda/browserconfig.xml create mode 100644 tests/dummy/public/assets/panda/favicon-16x16.png create mode 100644 tests/dummy/public/assets/panda/favicon-32x32.png create mode 100644 tests/dummy/public/assets/panda/favicon-96x96.png create mode 100644 tests/dummy/public/assets/panda/favicon.ico create mode 100644 tests/dummy/public/assets/panda/manifest.json create mode 100644 tests/dummy/public/assets/panda/ms-icon-144x144.png create mode 100644 tests/dummy/public/assets/panda/ms-icon-150x150.png create mode 100644 tests/dummy/public/assets/panda/ms-icon-310x310.png create mode 100644 tests/dummy/public/assets/panda/ms-icon-70x70.png create mode 100644 tests/dummy/public/assets/panda/panda-base.png create mode 100644 tests/dummy/public/assets/panda/panda-base.svg create mode 100644 tests/dummy/public/assets/panda/panda-base_backup.svg create mode 100644 tests/dummy/public/assets/team-panda/Panda svg.svg create mode 100644 tests/dummy/public/assets/team-panda/team-panda.png create mode 100644 tests/dummy/public/crossdomain.xml create mode 100644 tests/dummy/public/robots.txt create mode 100644 tests/helpers/destroy-app.js create mode 100644 tests/helpers/module-for-acceptance.js create mode 100644 tests/helpers/resolver.js create mode 100644 tests/helpers/start-app.js create mode 100644 tests/index.html create mode 100644 tests/integration/.gitkeep create mode 100644 tests/integration/components/action-list/organization-filter-test.coffee create mode 100644 tests/integration/components/action-list/project-filter-test.coffee create mode 100644 tests/integration/components/panda-image-test.coffee create mode 100644 tests/integration/components/project/edit-active-project-scenario-test.coffee create mode 100644 tests/integration/components/project/instance-action-value-display-test.coffee create mode 100644 tests/integration/components/state-badge-test.coffee create mode 100644 tests/integration/components/ui-multi-select-test.coffee create mode 100644 tests/integration/components/value-type-display-test.coffee create mode 100644 tests/test-helper.js create mode 100644 tests/unit/.gitkeep create mode 100644 tests/unit/controllers/action-list-test.coffee create mode 100644 tests/unit/models/panda/action-event-test.coffee create mode 100644 tests/unit/models/panda/adapter-test.coffee create mode 100644 tests/unit/models/panda/organization-test.coffee create mode 100644 tests/unit/models/panda/tracker-subject-test.coffee create mode 100644 tests/unit/models/panda/workflow-action-connection-test.coffee create mode 100644 tests/unit/models/panda/workflow-instance-type-test.coffee create mode 100644 tests/unit/routes/action-list-test.coffee create mode 100644 tests/unit/utils/panda-route-setup-test.coffee create mode 100644 vendor/.gitkeep diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..219985c --- /dev/null +++ b/.editorconfig @@ -0,0 +1,20 @@ +# EditorConfig helps developers define and maintain consistent +# coding styles between different editors and IDEs +# editorconfig.org + +root = true + + +[*] +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true +indent_style = space +indent_size = 2 + +[*.hbs] +insert_final_newline = false + +[*.{diff,md}] +trim_trailing_whitespace = false diff --git a/.ember-cli b/.ember-cli new file mode 100644 index 0000000..ee64cfe --- /dev/null +++ b/.ember-cli @@ -0,0 +1,9 @@ +{ + /** + Ember CLI sends analytics information by default. The data is completely + anonymous, but there are times when you might want to disable this behavior. + + Setting `disableAnalytics` to true will prevent any data from being sent. + */ + "disableAnalytics": false +} diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 0000000..b3e41aa --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,19 @@ +module.exports = { + root: true, + parserOptions: { + ecmaVersion: 2017, + sourceType: 'module' + }, + extends: 'eslint:recommended', + env: { + browser: true + }, + globals: { + moment: true, + t: true, + is_translation_path: true + }, + rules: { + 'no-cond-assign': 0 + } +}; diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8fa39a6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,23 @@ +# See https://help.github.com/ignore-files/ for more about ignoring files. + +# compiled output +/dist +/tmp + +# dependencies +/node_modules +/bower_components + +# misc +/.sass-cache +/connect.lock +/coverage/* +/libpeerconnection.log +npm-debug.log* +yarn-error.log +testem.log + +# ember-try +.node_modules.ember-try/ +bower.json.ember-try +package.json.ember-try diff --git a/.npmignore b/.npmignore new file mode 100644 index 0000000..889b2bf --- /dev/null +++ b/.npmignore @@ -0,0 +1,16 @@ +/bower_components +/config/ember-try.js +/dist +/tests +/tmp +**/.gitkeep +.bowerrc +.editorconfig +.ember-cli +.gitignore +.eslintrc.js +.watchmanconfig +.travis.yml +bower.json +ember-cli-build.js +testem.js diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..d04ff8a --- /dev/null +++ b/.travis.yml @@ -0,0 +1,41 @@ +--- +language: node_js +node_js: + # we recommend testing addons with the same minimum supported node version as Ember CLI + # so that your addon works for all apps + - "4" + +sudo: false +dist: trusty + +addons: + chrome: stable + +cache: + directories: + - $HOME/.npm + +env: + # we recommend new addons test the current and previous LTS + # as well as latest stable release (bonus points to beta/canary) + - EMBER_TRY_SCENARIO=ember-lts-2.8 + - EMBER_TRY_SCENARIO=ember-lts-2.12 + - EMBER_TRY_SCENARIO=ember-release + - EMBER_TRY_SCENARIO=ember-beta + - EMBER_TRY_SCENARIO=ember-canary + - EMBER_TRY_SCENARIO=ember-default + +matrix: + fast_finish: true + allow_failures: + - env: EMBER_TRY_SCENARIO=ember-canary + +before_install: + - npm config set spin false + - npm install -g npm@4 + - npm --version + +script: + # Usually, it's ok to finish the test scenario without reverting + # to the addon's original dependency state, skipping "cleanup". + - node_modules/.bin/ember try:one $EMBER_TRY_SCENARIO --skip-cleanup diff --git a/.watchmanconfig b/.watchmanconfig new file mode 100644 index 0000000..e7834e3 --- /dev/null +++ b/.watchmanconfig @@ -0,0 +1,3 @@ +{ + "ignore_dirs": ["tmp", "dist"] +} diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..0d5e3ef --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,9 @@ +The MIT License (MIT) + +Copyright (c) 2017 + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..1120c48 --- /dev/null +++ b/README.md @@ -0,0 +1,26 @@ +# ember-panda + +This README outlines the details of collaborating on this Ember addon. + +## Installation + +* `git clone ` this repository +* `cd ember-panda` +* `npm install` + +## Running + +* `ember serve` +* Visit your app at [http://localhost:4200](http://localhost:4200). + +## Running Tests + +* `npm test` (Runs `ember try:each` to test your addon against multiple Ember versions) +* `ember test` +* `ember test --server` + +## Building + +* `ember build` + +For more information on using ember-cli, visit [https://ember-cli.com/](https://ember-cli.com/). diff --git a/addon/.gitkeep b/addon/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/addon/components/action-list/organization-filter.coffee b/addon/components/action-list/organization-filter.coffee new file mode 100644 index 0000000..a042aaf --- /dev/null +++ b/addon/components/action-list/organization-filter.coffee @@ -0,0 +1,23 @@ +import Ember from 'ember' + +export default Ember.Component.extend + tagName: 'span' + organizations: [] + actions: + apply_filters: -> + @get('apply_filters')?() + set_scope_organization: (organization) -> + if organization? + #organization.reload().then => + organization_id = organization?.get('id') || '' + @set 'scope_organization', organization + @set 'filters.workflow_action_definition_organization_id', organization_id + @$('.select-organization .dropdown.button').addClass('orange') + @send 'apply_filters' + else + @setProperties + scope_organization: null + scope_scenario: null + 'filters.workflow_action_definition_organization_id': '' + @$('.select-organization .dropdown.button').removeClass('orange') + @send 'apply_filters' diff --git a/addon/components/action-list/organization-filter.js b/addon/components/action-list/organization-filter.js new file mode 100644 index 0000000..0785ec7 --- /dev/null +++ b/addon/components/action-list/organization-filter.js @@ -0,0 +1,30 @@ +// Generated by CoffeeScript 1.12.5 +import Ember from 'ember'; + +export default Ember.Component.extend({ + tagName: 'span', + organizations: [], + actions: { + apply_filters: function() { + var base; + return typeof (base = this.get('apply_filters')) === "function" ? base() : void 0; + }, + set_scope_organization: function(organization) { + var organization_id; + if (organization != null) { + organization_id = (organization != null ? organization.get('id') : void 0) || ''; + this.set('scope_organization', organization); + this.set('filters.workflow_action_definition_organization_id', organization_id); + this.$('.select-organization .dropdown.button').addClass('orange'); + return this.send('apply_filters'); + } else { + this.setProperties({ + scope_organization: null, + scope_scenario: null, + 'filters.workflow_action_definition_organization_id': '' + }, this.$('.select-organization .dropdown.button').removeClass('orange')); + return this.send('apply_filters'); + } + } + } +}); diff --git a/addon/components/action-list/project-filter.coffee b/addon/components/action-list/project-filter.coffee new file mode 100644 index 0000000..53d74b9 --- /dev/null +++ b/addon/components/action-list/project-filter.coffee @@ -0,0 +1,42 @@ +import Ember from 'ember' +{get} = Ember + +export default Ember.Component.extend + tagName: 'span' + scope_project: null + scope_scenario: null + projects: [] + actions: + apply_filters: -> + @get('apply_filters')?() + + edit_project_instance_action: (project_instance_action) -> + @modal 'project/edit-instance-action', model: project_instance_action + set_scope_project: (project) -> + if project? + project.reload().then => + project_id = project?.get('id') || '' + @set 'scope_project', project + @set 'filters.workflow_action_definition_title_cont', '' + @set 'filters.project_id', project_id + @$('.select-project .dropdown.button').addClass('orange') + @send 'apply_filters' + else + @setProperties + scope_project: null + scope_scenario: null + 'filters.project_id': '' + 'filters.project_instance_project_scenario_id': '' + @$('.select-project .dropdown.button').removeClass('orange') + @send 'apply_filters' + + set_scope_scenario: (project_scenario) -> + if project_scenario + scenario_id = get(project_scenario, 'id') + @$('.select-project-scenario .dropdown.button').addClass('orange') + else + @$('.select-project-scenario .dropdown.button').removeClass('orange') + scenario_id = '' + @set 'filters.project_instance_project_scenario_id', scenario_id + @set 'scope_scenario', project_scenario + @send 'apply_filters' diff --git a/addon/components/action-list/project-filter.js b/addon/components/action-list/project-filter.js new file mode 100644 index 0000000..6241d84 --- /dev/null +++ b/addon/components/action-list/project-filter.js @@ -0,0 +1,60 @@ +// Generated by CoffeeScript 1.12.5 +var get; + +import Ember from 'ember'; + +get = Ember.get; + +export default Ember.Component.extend({ + tagName: 'span', + scope_project: null, + scope_scenario: null, + projects: [], + actions: { + apply_filters: function() { + var base; + return typeof (base = this.get('apply_filters')) === "function" ? base() : void 0; + }, + edit_project_instance_action: function(project_instance_action) { + return this.modal('project/edit-instance-action', { + model: project_instance_action + }); + }, + set_scope_project: function(project) { + if (project != null) { + return project.reload().then((function(_this) { + return function() { + var project_id; + project_id = (project != null ? project.get('id') : void 0) || ''; + _this.set('scope_project', project); + _this.set('filters.workflow_action_definition_title_cont', ''); + _this.set('filters.project_id', project_id); + _this.$('.select-project .dropdown.button').addClass('orange'); + return _this.send('apply_filters'); + }; + })(this)); + } else { + this.setProperties({ + scope_project: null, + scope_scenario: null, + 'filters.project_id': '', + 'filters.project_instance_project_scenario_id': '' + }, this.$('.select-project .dropdown.button').removeClass('orange')); + return this.send('apply_filters'); + } + }, + set_scope_scenario: function(project_scenario) { + var scenario_id; + if (project_scenario) { + scenario_id = get(project_scenario, 'id'); + this.$('.select-project-scenario .dropdown.button').addClass('orange'); + } else { + this.$('.select-project-scenario .dropdown.button').removeClass('orange'); + scenario_id = ''; + } + this.set('filters.project_instance_project_scenario_id', scenario_id); + this.set('scope_scenario', project_scenario); + return this.send('apply_filters'); + } + } +}); diff --git a/addon/components/panda-image.coffee b/addon/components/panda-image.coffee new file mode 100644 index 0000000..cffee92 --- /dev/null +++ b/addon/components/panda-image.coffee @@ -0,0 +1,13 @@ +import Ember from 'ember' +import config from 'ember-get-config' + +export default Ember.Component.extend + tagName: 'img' + init: -> + src = if config.environment is 'development' or config.environment is 'test' + "#{config.rootURL}ember-panda/assets/#{@get('path')}" + else + "#{config.rootURL}assets/#{@get('path')}" + @set 'full_src', src + @_super arguments... + attributeBindings: ['full_src:src'] diff --git a/addon/components/panda-image.js b/addon/components/panda-image.js new file mode 100644 index 0000000..c127f3e --- /dev/null +++ b/addon/components/panda-image.js @@ -0,0 +1,15 @@ +// Generated by CoffeeScript 1.12.5 +import Ember from 'ember'; + +import config from 'ember-get-config'; + +export default Ember.Component.extend({ + tagName: 'img', + init: function() { + var src; + src = config.environment === 'development' || config.environment === 'test' ? config.rootURL + "ember-panda/assets/" + (this.get('path')) : config.rootURL + "assets/" + (this.get('path')); + this.set('full_src', src); + return this._super.apply(this, arguments); + }, + attributeBindings: ['full_src:src'] +}); diff --git a/addon/components/project/edit-active-project-scenario.coffee b/addon/components/project/edit-active-project-scenario.coffee new file mode 100644 index 0000000..78b9bd9 --- /dev/null +++ b/addon/components/project/edit-active-project-scenario.coffee @@ -0,0 +1,18 @@ +import Ember from 'ember' + +export default Ember.Component.extend + classNames: ['ui', 'small', 'buttons', 'active-project-scenario-selector-component'] + project: null + active_project_scenario: null + + actions: + show_project_scenarios_grid: (project) -> + @modal 'project/project-scenarios-show-grid', + model: project + edit_selected_project_scenario: -> + return unless project_scenario = @get('active_project_scenario') + @modal 'project/edit-project-scenario', + model: project_scenario + closeOnOverlay: false + destroy_callback: => + @set 'active_project_scenario', null diff --git a/addon/components/project/edit-active-project-scenario.js b/addon/components/project/edit-active-project-scenario.js new file mode 100644 index 0000000..7a60cec --- /dev/null +++ b/addon/components/project/edit-active-project-scenario.js @@ -0,0 +1,30 @@ +// Generated by CoffeeScript 1.12.5 +import Ember from 'ember'; + +export default Ember.Component.extend({ + classNames: ['ui', 'small', 'buttons', 'active-project-scenario-selector-component'], + project: null, + active_project_scenario: null, + actions: { + show_project_scenarios_grid: function(project) { + return this.modal('project/project-scenarios-show-grid', { + model: project + }); + }, + edit_selected_project_scenario: function() { + var project_scenario; + if (!(project_scenario = this.get('active_project_scenario'))) { + return; + } + return this.modal('project/edit-project-scenario', { + model: project_scenario, + closeOnOverlay: false, + destroy_callback: (function(_this) { + return function() { + return _this.set('active_project_scenario', null); + }; + })(this) + }); + } + } +}); diff --git a/addon/components/project/instance-action-value-display.coffee b/addon/components/project/instance-action-value-display.coffee new file mode 100644 index 0000000..3cef8a9 --- /dev/null +++ b/addon/components/project/instance-action-value-display.coffee @@ -0,0 +1,53 @@ +import Ember from 'ember' + +export default Ember.Component.extend + classNames: ['project-instance-action-value-display'] + eventIO: null + project_instance_action: null + value_type: Ember.computed.alias 'project_instance_action.workflow_action_definition.value_type' + save: -> + + # https://stackoverflow.com/questions/26967855/handling-click-and-doubleclick-on-same-view-in-emberjs + doubleClick: -> + eventIO = @get 'eventIO' + if eventIO? + Ember.run.cancel eventIO + @set 'eventIO', null + + if @get('value_type') is 'boolean' + #@toggleProperty 'project_instance_action.boolean_value' + #@get('project_instance_action').save() + + # always modal, otherwise implement authorizations overhere + @modal 'project/edit-instance-action', + model: @get('project_instance_action') + save: @save + close: @save + else + # fallback to single click behaviour + @modal 'project/edit-instance-action', + model: @get('project_instance_action') + save: @save + close: @save + + + click: -> + if @get('eventIO') is null + eventIO = Ember.run.later @, -> + @modal 'project/edit-instance-action', + model: @get('project_instance_action') + save: @save + close: @save + if @get('eventIO') isnt null + Ember.run.cancel @get('eventIO') + @set 'eventIO', null + , 500 + @set 'eventIO', eventIO + + actions: + right_click: -> + debugger + false + #edit_record: -> + # @modal 'project/edit-instance-action', model: @get('project_instance_action') + # false diff --git a/addon/components/project/instance-action-value-display.js b/addon/components/project/instance-action-value-display.js new file mode 100644 index 0000000..cde28ed --- /dev/null +++ b/addon/components/project/instance-action-value-display.js @@ -0,0 +1,54 @@ +// Generated by CoffeeScript 1.12.5 +import Ember from 'ember'; + +export default Ember.Component.extend({ + classNames: ['project-instance-action-value-display'], + eventIO: null, + project_instance_action: null, + value_type: Ember.computed.alias('project_instance_action.workflow_action_definition.value_type'), + save: function() {}, + doubleClick: function() { + var eventIO; + eventIO = this.get('eventIO'); + if (eventIO != null) { + Ember.run.cancel(eventIO); + this.set('eventIO', null); + } + if (this.get('value_type') === 'boolean') { + return this.modal('project/edit-instance-action', { + model: this.get('project_instance_action'), + save: this.save, + close: this.save + }); + } else { + return this.modal('project/edit-instance-action', { + model: this.get('project_instance_action'), + save: this.save, + close: this.save + }); + } + }, + click: function() { + var eventIO; + if (this.get('eventIO') === null) { + eventIO = Ember.run.later(this, function() { + this.modal('project/edit-instance-action', { + model: this.get('project_instance_action'), + save: this.save, + close: this.save + }); + if (this.get('eventIO') !== null) { + Ember.run.cancel(this.get('eventIO')); + return this.set('eventIO', null); + } + }, 500); + return this.set('eventIO', eventIO); + } + }, + actions: { + right_click: function() { + debugger; + return false; + } + } +}); diff --git a/addon/components/state-badge.coffee b/addon/components/state-badge.coffee new file mode 100644 index 0000000..d5886d2 --- /dev/null +++ b/addon/components/state-badge.coffee @@ -0,0 +1,27 @@ +import Ember from 'ember' + +Component = Ember.Component.extend + tagName: 'span' + classNames: ['ui', 'label', 'state-badge'] + classNameBindings: ['ui_class', 'having_action:with-action', 'basic:basic'] + action: null + state: '…' + human_state: Ember.computed 'state', -> + return '-' unless state = @get('state') + t("attributes.state.#{state}", default: state) + having_action: Ember.computed.bool 'action' + click: -> + @action?() + ui_class: Ember.computed 'state', -> + switch @get('state') + when 'overdue' then 'yellow' + when 'completed', 'all_completed' then 'green' + when 'rejected', 'any_rejected' then 'red' + when 'active', 'processing' then 'blue' + when 'inquiry' then 'pink' + else 'grey' + +Component.reopenClass + positionalParams: ['state'] + +export default Component diff --git a/addon/components/state-badge.js b/addon/components/state-badge.js new file mode 100644 index 0000000..b03abb3 --- /dev/null +++ b/addon/components/state-badge.js @@ -0,0 +1,50 @@ +// Generated by CoffeeScript 1.12.5 +var Component; + +import Ember from 'ember'; + +Component = Ember.Component.extend({ + tagName: 'span', + classNames: ['ui', 'label', 'state-badge'], + classNameBindings: ['ui_class', 'having_action:with-action', 'basic:basic'], + action: null, + state: '…', + human_state: Ember.computed('state', function() { + var state; + if (!(state = this.get('state'))) { + return '-'; + } + return t("attributes.state." + state, { + "default": state + }); + }), + having_action: Ember.computed.bool('action'), + click: function() { + return typeof this.action === "function" ? this.action() : void 0; + }, + ui_class: Ember.computed('state', function() { + switch (this.get('state')) { + case 'overdue': + return 'yellow'; + case 'completed': + case 'all_completed': + return 'green'; + case 'rejected': + case 'any_rejected': + return 'red'; + case 'active': + case 'processing': + return 'blue'; + case 'inquiry': + return 'pink'; + default: + return 'grey'; + } + }) +}); + +Component.reopenClass({ + positionalParams: ['state'] +}); + +export default Component; diff --git a/addon/components/ui-multi-select.coffee b/addon/components/ui-multi-select.coffee new file mode 100644 index 0000000..5609528 --- /dev/null +++ b/addon/components/ui-multi-select.coffee @@ -0,0 +1,13 @@ +import Ember from 'ember' + +export default Ember.Component.extend + classNames: ['ui-multi-select-container'] + labelProperty: 'name' + placeholder: 'Select' # can be a text or translation string: organization.select_users.placeholder => # 'Select ${models.plural.user} (%{count})' + options: [] + selected: [] + placeholder_text: Ember.computed 'placeholder', -> + placeholder = @get('placeholder') + placeholder = t(placeholder, count: @get('options.length')) if is_translation_path(placeholder) + placeholder.htmlSafe() + diff --git a/addon/components/ui-multi-select.js b/addon/components/ui-multi-select.js new file mode 100644 index 0000000..a044f6a --- /dev/null +++ b/addon/components/ui-multi-select.js @@ -0,0 +1,20 @@ +// Generated by CoffeeScript 1.12.5 +import Ember from 'ember'; + +export default Ember.Component.extend({ + classNames: ['ui-multi-select-container'], + labelProperty: 'name', + placeholder: 'Select', + options: [], + selected: [], + placeholder_text: Ember.computed('placeholder', function() { + var placeholder; + placeholder = this.get('placeholder'); + if (is_translation_path(placeholder)) { + placeholder = t(placeholder, { + count: this.get('options.length') + }); + } + return placeholder.htmlSafe(); + }) +}); diff --git a/addon/components/value-type-display.coffee b/addon/components/value-type-display.coffee new file mode 100644 index 0000000..b9ccf0f --- /dev/null +++ b/addon/components/value-type-display.coffee @@ -0,0 +1,34 @@ +import Ember from 'ember' + +export default Ember.Component.extend + tagName: 'span' + type: null + classNames: ['value-type-display'] + classNameBindings: ['typeClass'] + typeClass: Ember.computed 'type', -> @get('type') || 'no-typ' + iconClass: Ember.computed 'type', -> + switch @get('type') + when 'boolean' then 'checkmark box' + when 'date' then 'calendar' + when 'select' then 'list' + when 'string' then 'terminal' + when 'text' then 'sticky note outline' + when 'number' then 'hashtag' + when 'timestamp' then 'wait' + else 'red warning circle' +# &.date +# @extend .fa-calendar +# &.number +# @extend .fa-hashtag +# &.select +# @extend .fa-list-ul +# &.string +# @extend .fa-terminal +# &.text +# @extend .fa-sticky-note-o +# &.window_from +# @extend .fa-clock-o +# &.window_to +# @extend .fa-clock-o +# &.timestamp +# @extend .fa-clock-o diff --git a/addon/components/value-type-display.js b/addon/components/value-type-display.js new file mode 100644 index 0000000..e65ef4b --- /dev/null +++ b/addon/components/value-type-display.js @@ -0,0 +1,32 @@ +// Generated by CoffeeScript 1.12.5 +import Ember from 'ember'; + +export default Ember.Component.extend({ + tagName: 'span', + type: null, + classNames: ['value-type-display'], + classNameBindings: ['typeClass'], + typeClass: Ember.computed('type', function() { + return this.get('type') || 'no-typ'; + }), + iconClass: Ember.computed('type', function() { + switch (this.get('type')) { + case 'boolean': + return 'checkmark box'; + case 'date': + return 'calendar'; + case 'select': + return 'list'; + case 'string': + return 'terminal'; + case 'text': + return 'sticky note outline'; + case 'number': + return 'hashtag'; + case 'timestamp': + return 'wait'; + default: + return 'red warning circle'; + } + }) +}); diff --git a/addon/controllers/action-list.coffee b/addon/controllers/action-list.coffee new file mode 100644 index 0000000..a8c9ec1 --- /dev/null +++ b/addon/controllers/action-list.coffee @@ -0,0 +1,48 @@ +import Ember from 'ember' +import config from 'ember-get-config' + +export default Ember.Controller.extend + session: Ember.inject.service('session') + filters: {} + scope_project: null + scope_scenario: null + scope_organization: null + filter_query: Ember.computed 'filters.filter_calls', -> + @get('filters')?.toProperties() || {} + export_human_url: Ember.computed 'filters.filter_calls', -> + model_name = 'panda/project-instance-action' + base_url = @store.adapterFor(model_name).buildURL(model_name) + filter_addition = $.param q: @get('filter_query') + "#{base_url}/export_human.csv?#{filter_addition}" + export_technical_url: Ember.computed 'filters.filter_calls', -> + model_name = 'panda/project-instance-action' + base_url = @store.adapterFor(model_name).buildURL(model_name) + filter_addition = $.param q: @get('filter_query') + "#{base_url}/export_technical.csv?#{filter_addition}" + apply_filters: -> + @incrementProperty 'filters.filter_calls' + @set 'model.project_instance_actions.page', 1 + @set 'model.project_instance_actions.lastPage', null + @get('model.project_instance_actions').setOtherParam('q', @get('filter_query')) + false + actions: + refresh_list: -> @apply_filters() + edit_project_instance_action: (project_instance_action) -> + @modal 'project/edit-instance-action', model: project_instance_action + + toggle_filter: (key, value, checked) -> + new_value = [] + if checked + new_value = new_value.concat(@get("filters.#{key}")).concat(value) + else + @get("filters.#{key}").forEach (present_in_filter) -> + new_value.push present_in_filter unless present_in_filter is value + @set "filters.#{key}", new_value + @apply_filters() + clear_filter: (keys...) -> + keys.forEach (key) => + if Ember.isArray(@get("filters.#{key}")) + @set "filters.#{key}", [] + else + @set "filters.#{key}", null + @apply_filters() diff --git a/addon/controllers/action-list.js b/addon/controllers/action-list.js new file mode 100644 index 0000000..117e2b1 --- /dev/null +++ b/addon/controllers/action-list.js @@ -0,0 +1,82 @@ +// Generated by CoffeeScript 1.12.5 +var slice = [].slice; + +import Ember from 'ember'; + +import config from 'ember-get-config'; + +export default Ember.Controller.extend({ + session: Ember.inject.service('session'), + filters: {}, + scope_project: null, + scope_scenario: null, + scope_organization: null, + filter_query: Ember.computed('filters.filter_calls', function() { + var ref; + return ((ref = this.get('filters')) != null ? ref.toProperties() : void 0) || {}; + }), + export_human_url: Ember.computed('filters.filter_calls', function() { + var base_url, filter_addition, model_name; + model_name = 'panda/project-instance-action'; + base_url = this.store.adapterFor(model_name).buildURL(model_name); + filter_addition = $.param({ + q: this.get('filter_query') + }); + return base_url + "/export_human.csv?" + filter_addition; + }), + export_technical_url: Ember.computed('filters.filter_calls', function() { + var base_url, filter_addition, model_name; + model_name = 'panda/project-instance-action'; + base_url = this.store.adapterFor(model_name).buildURL(model_name); + filter_addition = $.param({ + q: this.get('filter_query') + }); + return base_url + "/export_technical.csv?" + filter_addition; + }), + apply_filters: function() { + this.incrementProperty('filters.filter_calls'); + this.set('model.project_instance_actions.page', 1); + this.set('model.project_instance_actions.lastPage', null); + this.get('model.project_instance_actions').setOtherParam('q', this.get('filter_query')); + return false; + }, + actions: { + refresh_list: function() { + return this.apply_filters(); + }, + edit_project_instance_action: function(project_instance_action) { + return this.modal('project/edit-instance-action', { + model: project_instance_action + }); + }, + toggle_filter: function(key, value, checked) { + var new_value; + new_value = []; + if (checked) { + new_value = new_value.concat(this.get("filters." + key)).concat(value); + } else { + this.get("filters." + key).forEach(function(present_in_filter) { + if (present_in_filter !== value) { + return new_value.push(present_in_filter); + } + }); + } + this.set("filters." + key, new_value); + return this.apply_filters(); + }, + clear_filter: function() { + var keys; + keys = 1 <= arguments.length ? slice.call(arguments, 0) : []; + keys.forEach((function(_this) { + return function(key) { + if (Ember.isArray(_this.get("filters." + key))) { + return _this.set("filters." + key, []); + } else { + return _this.set("filters." + key, null); + } + }; + })(this)); + return this.apply_filters(); + } + } +}); diff --git a/addon/mixins/project/active-workflow-action-definitions.coffee b/addon/mixins/project/active-workflow-action-definitions.coffee new file mode 100644 index 0000000..cf16473 --- /dev/null +++ b/addon/mixins/project/active-workflow-action-definitions.coffee @@ -0,0 +1,44 @@ +import Ember from 'ember' + +export default Ember.Mixin.create + selected_project_scenarios: [] # null + selected_project_scenarios_set_operation: 'union' # union or intersection + active_workflow_action_definitions: [] + active_workflow_action_definition_ids: [] + + set_active_workflow_action_definitions: -> + # initialize unused working workflow-action-definitions + if @get('selected_project_scenarios.length') + if @get('selected_project_scenarios_set_operation') is 'intersection' + @set_active_workflow_action_definitions_using_intersection() + else + @set_active_workflow_action_definitions_using_union() + else + @set 'active_workflow_action_definitions', @get('project.workflow_action_definitions') || [] + @set 'active_workflow_action_definition_ids', @get('active_workflow_action_definitions').mapBy('id').toArray() + + set_active_workflow_action_definitions_using_union: -> + selected_project_scenario_workflow_action_definition_ids = [] + working_workflow_action_definitions = [] + @get('selected_project_scenarios').forEach (selected_project_scenario) -> + selected_project_scenario.get('workflow_action_definitions').forEach (workflow_action_definition) -> + if selected_project_scenario_workflow_action_definition_ids.indexOf(workflow_action_definition.get('id')) < 0 + selected_project_scenario_workflow_action_definition_ids.push workflow_action_definition.get('id') + working_workflow_action_definitions.push workflow_action_definition + @set 'active_workflow_action_definitions', working_workflow_action_definitions + @set 'active_workflow_action_definition_ids', selected_project_scenario_workflow_action_definition_ids + + set_active_workflow_action_definitions_using_intersection: -> + selected_project_scenario_workflow_action_definition_ids = [] + first_run = true + @get('selected_project_scenarios').forEach (selected_project_scenario) -> + current_workflow_action_definition_ids = selected_project_scenario.get('workflow_action_definitions').mapBy('id').toArray() + if first_run + selected_project_scenario_workflow_action_definition_ids = current_workflow_action_definition_ids + first_run = false + return + selected_project_scenario_workflow_action_definition_ids = selected_project_scenario_workflow_action_definition_ids.filter (id) -> id in current_workflow_action_definition_ids + + @set 'active_workflow_action_definition_ids', selected_project_scenario_workflow_action_definition_ids + @set 'active_workflow_action_definitions', @get('project.workflow_action_definitions').filter((r) -> r.get('id') in selected_project_scenario_workflow_action_definition_ids) + diff --git a/addon/mixins/project/active-workflow-action-definitions.js b/addon/mixins/project/active-workflow-action-definitions.js new file mode 100644 index 0000000..4c2ff89 --- /dev/null +++ b/addon/mixins/project/active-workflow-action-definitions.js @@ -0,0 +1,60 @@ +// Generated by CoffeeScript 1.12.5 +var indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }; + +import Ember from 'ember'; + +export default Ember.Mixin.create({ + selected_project_scenarios: [], + selected_project_scenarios_set_operation: 'union', + active_workflow_action_definitions: [], + active_workflow_action_definition_ids: [], + set_active_workflow_action_definitions: function() { + if (this.get('selected_project_scenarios.length')) { + if (this.get('selected_project_scenarios_set_operation') === 'intersection') { + return this.set_active_workflow_action_definitions_using_intersection(); + } else { + return this.set_active_workflow_action_definitions_using_union(); + } + } else { + this.set('active_workflow_action_definitions', this.get('project.workflow_action_definitions') || []); + return this.set('active_workflow_action_definition_ids', this.get('active_workflow_action_definitions').mapBy('id').toArray()); + } + }, + set_active_workflow_action_definitions_using_union: function() { + var selected_project_scenario_workflow_action_definition_ids, working_workflow_action_definitions; + selected_project_scenario_workflow_action_definition_ids = []; + working_workflow_action_definitions = []; + this.get('selected_project_scenarios').forEach(function(selected_project_scenario) { + return selected_project_scenario.get('workflow_action_definitions').forEach(function(workflow_action_definition) { + if (selected_project_scenario_workflow_action_definition_ids.indexOf(workflow_action_definition.get('id')) < 0) { + selected_project_scenario_workflow_action_definition_ids.push(workflow_action_definition.get('id')); + return working_workflow_action_definitions.push(workflow_action_definition); + } + }); + }); + this.set('active_workflow_action_definitions', working_workflow_action_definitions); + return this.set('active_workflow_action_definition_ids', selected_project_scenario_workflow_action_definition_ids); + }, + set_active_workflow_action_definitions_using_intersection: function() { + var first_run, selected_project_scenario_workflow_action_definition_ids; + selected_project_scenario_workflow_action_definition_ids = []; + first_run = true; + this.get('selected_project_scenarios').forEach(function(selected_project_scenario) { + var current_workflow_action_definition_ids; + current_workflow_action_definition_ids = selected_project_scenario.get('workflow_action_definitions').mapBy('id').toArray(); + if (first_run) { + selected_project_scenario_workflow_action_definition_ids = current_workflow_action_definition_ids; + first_run = false; + return; + } + return selected_project_scenario_workflow_action_definition_ids = selected_project_scenario_workflow_action_definition_ids.filter(function(id) { + return indexOf.call(current_workflow_action_definition_ids, id) >= 0; + }); + }); + this.set('active_workflow_action_definition_ids', selected_project_scenario_workflow_action_definition_ids); + return this.set('active_workflow_action_definitions', this.get('project.workflow_action_definitions').filter(function(r) { + var ref; + return ref = r.get('id'), indexOf.call(selected_project_scenario_workflow_action_definition_ids, ref) >= 0; + })); + } +}); diff --git a/addon/mixins/project/active-workflow-groups.coffee b/addon/mixins/project/active-workflow-groups.coffee new file mode 100644 index 0000000..631cd6d --- /dev/null +++ b/addon/mixins/project/active-workflow-groups.coffee @@ -0,0 +1,46 @@ +import Ember from 'ember' + +export default Ember.Mixin.create + get_active_workflow_groups: (workflow_groups, project_scenarios, set_operation = 'union')-> + project_scenarios = Ember.makeArray(project_scenarios) + workflow_groups = workflow_groups.toArray() + # initialize unused working workflow-groups + if project_scenarios.length + if set_operation is 'intersection' + @get_active_workflow_groups_using_intersection(workflow_groups, project_scenarios) + else + @get_active_workflow_groups_using_union(project_scenarios) + else + workflow_groups + + #@set 'active_workflow_groups', workflow_groups || [] + #@set 'active_workflow_group_ids', @get('active_workflow_groups').mapBy('id').toArray() + + get_active_workflow_groups_using_union: (project_scenarios) -> + selected_project_scenario_workflow_group_ids = [] + working_workflow_groups = [] + project_scenarios.forEach (selected_project_scenario) -> + selected_project_scenario.get('workflow_groups').forEach (workflow_group) -> + if selected_project_scenario_workflow_group_ids.indexOf(workflow_group.get('id')) < 0 + selected_project_scenario_workflow_group_ids.push workflow_group.get('id') + working_workflow_groups.push workflow_group + #working_workflow_groups = working_workflow_groups + #@set 'active_workflow_groups', working_workflow_groups + #@set 'active_workflow_group_ids', selected_project_scenario_workflow_group_ids + working_workflow_groups + + get_active_workflow_groups_using_intersection: (workflow_groups, project_scenarios) -> + selected_project_scenario_workflow_group_ids = [] + first_run = true + project_scenarios.forEach (selected_project_scenario) -> + current_workflow_group_ids = selected_project_scenario.get('workflow_groups').mapBy('id').toArray() + if first_run + selected_project_scenario_workflow_group_ids = current_workflow_group_ids + first_run = false + return + selected_project_scenario_workflow_group_ids = selected_project_scenario_workflow_group_ids.filter (id) -> id in current_workflow_group_ids + + #@set 'active_workflow_group_ids', selected_project_scenario_workflow_group_ids + #@set 'active_workflow_groups', workflow_groups.filter((r) -> r.get('id') in selected_project_scenario_workflow_group_ids) + workflow_groups.filter((r) -> r.get('id') in selected_project_scenario_workflow_group_ids) + diff --git a/addon/mixins/project/active-workflow-groups.js b/addon/mixins/project/active-workflow-groups.js new file mode 100644 index 0000000..8870d7e --- /dev/null +++ b/addon/mixins/project/active-workflow-groups.js @@ -0,0 +1,58 @@ +// Generated by CoffeeScript 1.12.5 +var indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }; + +import Ember from 'ember'; + +export default Ember.Mixin.create({ + get_active_workflow_groups: function(workflow_groups, project_scenarios, set_operation) { + if (set_operation == null) { + set_operation = 'union'; + } + project_scenarios = Ember.makeArray(project_scenarios); + workflow_groups = workflow_groups.toArray(); + if (project_scenarios.length) { + if (set_operation === 'intersection') { + return this.get_active_workflow_groups_using_intersection(workflow_groups, project_scenarios); + } else { + return this.get_active_workflow_groups_using_union(project_scenarios); + } + } else { + return workflow_groups; + } + }, + get_active_workflow_groups_using_union: function(project_scenarios) { + var selected_project_scenario_workflow_group_ids, working_workflow_groups; + selected_project_scenario_workflow_group_ids = []; + working_workflow_groups = []; + project_scenarios.forEach(function(selected_project_scenario) { + return selected_project_scenario.get('workflow_groups').forEach(function(workflow_group) { + if (selected_project_scenario_workflow_group_ids.indexOf(workflow_group.get('id')) < 0) { + selected_project_scenario_workflow_group_ids.push(workflow_group.get('id')); + return working_workflow_groups.push(workflow_group); + } + }); + }); + return working_workflow_groups; + }, + get_active_workflow_groups_using_intersection: function(workflow_groups, project_scenarios) { + var first_run, selected_project_scenario_workflow_group_ids; + selected_project_scenario_workflow_group_ids = []; + first_run = true; + project_scenarios.forEach(function(selected_project_scenario) { + var current_workflow_group_ids; + current_workflow_group_ids = selected_project_scenario.get('workflow_groups').mapBy('id').toArray(); + if (first_run) { + selected_project_scenario_workflow_group_ids = current_workflow_group_ids; + first_run = false; + return; + } + return selected_project_scenario_workflow_group_ids = selected_project_scenario_workflow_group_ids.filter(function(id) { + return indexOf.call(current_workflow_group_ids, id) >= 0; + }); + }); + return workflow_groups.filter(function(r) { + var ref; + return ref = r.get('id'), indexOf.call(selected_project_scenario_workflow_group_ids, ref) >= 0; + }); + } +}); diff --git a/addon/models/panda/action-event.coffee b/addon/models/panda/action-event.coffee new file mode 100644 index 0000000..04179c6 --- /dev/null +++ b/addon/models/panda/action-event.coffee @@ -0,0 +1,12 @@ +import DS from 'ember-data' + +export default DS.Model.extend + client_identifier: DS.attr('string') + identifier: DS.attr('string') + event_key: DS.attr('string') + event_value: DS.attr('string') + event_timestamp: DS.attr('moment') + source: DS.attr('string') + + project_instance_action: DS.belongsTo('panda/project-instance-action', inverse: 'events') + diff --git a/addon/models/panda/action-event.js b/addon/models/panda/action-event.js new file mode 100644 index 0000000..f19cdaa --- /dev/null +++ b/addon/models/panda/action-event.js @@ -0,0 +1,14 @@ +// Generated by CoffeeScript 1.12.5 +import DS from 'ember-data'; + +export default DS.Model.extend({ + client_identifier: DS.attr('string'), + identifier: DS.attr('string'), + event_key: DS.attr('string'), + event_value: DS.attr('string'), + event_timestamp: DS.attr('moment'), + source: DS.attr('string'), + project_instance_action: DS.belongsTo('panda/project-instance-action', { + inverse: 'events' + }) +}); diff --git a/addon/models/panda/project-instance-action-connection.coffee b/addon/models/panda/project-instance-action-connection.coffee new file mode 100644 index 0000000..7b95eb6 --- /dev/null +++ b/addon/models/panda/project-instance-action-connection.coffee @@ -0,0 +1,7 @@ +import DS from 'ember-data' + +export default DS.Model.extend + project_instance_action: DS.belongsTo('panda/project-instance-action') + base_project_instance_action: DS.belongsTo('panda/project-instance-action', inverse: 'pointing_project_instance_action_connections') + conditional_base_value: DS.attr('string') + connection_type: DS.attr('string') diff --git a/addon/models/panda/project-instance-action-connection.js b/addon/models/panda/project-instance-action-connection.js new file mode 100644 index 0000000..9644c3b --- /dev/null +++ b/addon/models/panda/project-instance-action-connection.js @@ -0,0 +1,11 @@ +// Generated by CoffeeScript 1.12.5 +import DS from 'ember-data'; + +export default DS.Model.extend({ + project_instance_action: DS.belongsTo('panda/project-instance-action'), + base_project_instance_action: DS.belongsTo('panda/project-instance-action', { + inverse: 'pointing_project_instance_action_connections' + }), + conditional_base_value: DS.attr('string'), + connection_type: DS.attr('string') +}); diff --git a/addon/models/panda/workflow-action-connection.coffee b/addon/models/panda/workflow-action-connection.coffee new file mode 100644 index 0000000..e0e0a81 --- /dev/null +++ b/addon/models/panda/workflow-action-connection.coffee @@ -0,0 +1,8 @@ +import DS from 'ember-data' + +export default DS.Model.extend + conditional_base_value: DS.attr('string', defaultValue: '') + connection_type: DS.attr('string') # attached, constraint, info + + workflow_action_definition: DS.belongsTo('panda/workflow-action-definition', inverse: 'workflow_action_connections') + base_workflow_action_definition: DS.belongsTo('panda/workflow-action-definition', inverse: 'pointing_workflow_action_connections') diff --git a/addon/models/panda/workflow-action-connection.js b/addon/models/panda/workflow-action-connection.js new file mode 100644 index 0000000..64a87e0 --- /dev/null +++ b/addon/models/panda/workflow-action-connection.js @@ -0,0 +1,15 @@ +// Generated by CoffeeScript 1.12.5 +import DS from 'ember-data'; + +export default DS.Model.extend({ + conditional_base_value: DS.attr('string', { + defaultValue: '' + }), + connection_type: DS.attr('string'), + workflow_action_definition: DS.belongsTo('panda/workflow-action-definition', { + inverse: 'workflow_action_connections' + }), + base_workflow_action_definition: DS.belongsTo('panda/workflow-action-definition', { + inverse: 'pointing_workflow_action_connections' + }) +}); diff --git a/addon/models/panda/workflow-action-definition.coffee b/addon/models/panda/workflow-action-definition.coffee new file mode 100644 index 0000000..7a601e4 --- /dev/null +++ b/addon/models/panda/workflow-action-definition.coffee @@ -0,0 +1,50 @@ +import Ember from 'ember' +import DS from 'ember-data' +import add_business_day_computations from 'ember-panda/utils/business-day-computations' + +export value_type_options = ['boolean', 'date', 'number', 'select', 'string', 'text', 'timestamp'] +Model = + identifier: DS.attr('string') + title: DS.attr('string') + name: Ember.computed.alias 'title' + description: DS.attr('string') + isEditing: DS.attr('boolean', defaultValue: false) #TODO: remove, is from PMBC + done: DS.attr('boolean', defaultValue: false) #TODO: remove, is from PMBC + position: DS.attr('number', defaultValue: 0) + value_type: DS.attr('string', defaultValue: 'boolean') # means checkbox progress type + value_type_select_options: DS.attr(defaultValue: -> []) + extra_info: DS.attr('string') + offset_type: DS.attr('string', defaultValue: 'none') # none, attached, configured + overdue_coulance: DS.attr('number', defaultValue: 0) + completes_group: DS.attr('boolean', defaultValue: false) + + # Offset tree + #action_offset_base: DS.belongsTo('workflow-action-definition', inverse: 'action_offset_dependents') + #action_offset_dependents: DS.hasMany('workflow-action-definition', inverse: null) # do not give with api, will clear inverse if not given + + workflow_group: DS.belongsTo('panda/workflow-group') + project: DS.belongsTo('panda/project', inverse: 'workflow_action_definitions') + organization: DS.belongsTo('panda/organization', inverse: 'workflow_action_definitions') + workflow_action_constraints: DS.hasMany('panda/workflow-action-constraint', inverse: 'workflow_action_definition') + workflow_action_offsets: DS.hasMany('panda/workflow-action-offset', inverse: 'workflow_action_definition') + dependent_workflow_action_offsets: DS.hasMany('panda/workflow-action-offset', inverse: 'base_workflow_action_definition') + #project_scenarios: DS.hasMany('panda/project-scenario', inverse: 'workflow_action_definitions') + project_scenarios: DS.hasMany('panda/project-scenario', inverse: null) + project_instance_actions: DS.hasMany('panda/project-instance-action', inverse: 'workflow_action_definition') + confirms_workflow_action_definition: DS.belongsTo('panda/workflow-action-definition', inverse: 'confirmed_by_workflow_action_definition') + confirmed_by_workflow_action_definition: DS.belongsTo('panda/workflow-action-definition', inverse: 'confirms_workflow_action_definition') + + workflow_action_connections: DS.hasMany('panda/workflow-action-connection', inverse: 'workflow_action_definition') + pointing_workflow_action_connections: DS.hasMany('panda/workflow-action-connection', inverse: 'base_workflow_action_definition') + + absolute_position: Ember.computed 'workflow_group.position', 'position', -> + (@get('workflow_group.position') || 99) * 100 + (@get('position') || 99 ) + + #drag_identifier: Ember.computed 'id', 'workflow_group.id', 'position', -> + # "#{@get('id')}-#{@get('workflow_group.id')}-#{@get('position')}" + #smart_position: Ember.computed 'done', 'position', -> + # num = @get('position') + # if @get('done') then num + 1000 else num +add_business_day_computations Model, 'offset', add_attributes: true +add_business_day_computations Model, 'batch_element_threshold', add_attributes: true #, default_unit: 'businessday' +export default DS.Model.extend(Model) diff --git a/addon/models/panda/workflow-action-definition.js b/addon/models/panda/workflow-action-definition.js new file mode 100644 index 0000000..d6f1bbc --- /dev/null +++ b/addon/models/panda/workflow-action-definition.js @@ -0,0 +1,91 @@ +// Generated by CoffeeScript 1.12.5 +var Model; + +import Ember from 'ember'; + +import DS from 'ember-data'; + +import add_business_day_computations from 'ember-panda/utils/business-day-computations'; + +export var value_type_options = ['boolean', 'date', 'number', 'select', 'string', 'text', 'timestamp']; + +Model = { + identifier: DS.attr('string'), + title: DS.attr('string'), + name: Ember.computed.alias('title'), + description: DS.attr('string'), + isEditing: DS.attr('boolean', { + defaultValue: false + }), + done: DS.attr('boolean', { + defaultValue: false + }), + position: DS.attr('number', { + defaultValue: 0 + }), + value_type: DS.attr('string', { + defaultValue: 'boolean' + }), + value_type_select_options: DS.attr({ + defaultValue: function() { + return []; + } + }), + extra_info: DS.attr('string'), + offset_type: DS.attr('string', { + defaultValue: 'none' + }), + overdue_coulance: DS.attr('number', { + defaultValue: 0 + }), + completes_group: DS.attr('boolean', { + defaultValue: false + }), + workflow_group: DS.belongsTo('panda/workflow-group'), + project: DS.belongsTo('panda/project', { + inverse: 'workflow_action_definitions' + }), + organization: DS.belongsTo('panda/organization', { + inverse: 'workflow_action_definitions' + }), + workflow_action_constraints: DS.hasMany('panda/workflow-action-constraint', { + inverse: 'workflow_action_definition' + }), + workflow_action_offsets: DS.hasMany('panda/workflow-action-offset', { + inverse: 'workflow_action_definition' + }), + dependent_workflow_action_offsets: DS.hasMany('panda/workflow-action-offset', { + inverse: 'base_workflow_action_definition' + }), + project_scenarios: DS.hasMany('panda/project-scenario', { + inverse: null + }), + project_instance_actions: DS.hasMany('panda/project-instance-action', { + inverse: 'workflow_action_definition' + }), + confirms_workflow_action_definition: DS.belongsTo('panda/workflow-action-definition', { + inverse: 'confirmed_by_workflow_action_definition' + }), + confirmed_by_workflow_action_definition: DS.belongsTo('panda/workflow-action-definition', { + inverse: 'confirms_workflow_action_definition' + }), + workflow_action_connections: DS.hasMany('panda/workflow-action-connection', { + inverse: 'workflow_action_definition' + }), + pointing_workflow_action_connections: DS.hasMany('panda/workflow-action-connection', { + inverse: 'base_workflow_action_definition' + }), + absolute_position: Ember.computed('workflow_group.position', 'position', function() { + return (this.get('workflow_group.position') || 99) * 100 + (this.get('position') || 99); + }) +}; + +add_business_day_computations(Model, 'offset', { + add_attributes: true +}); + +add_business_day_computations(Model, 'batch_element_threshold', { + add_attributes: true +}); + +export default DS.Model.extend(Model); diff --git a/addon/routes/action-list.coffee b/addon/routes/action-list.coffee new file mode 100644 index 0000000..e8ebaac --- /dev/null +++ b/addon/routes/action-list.coffee @@ -0,0 +1,67 @@ +import Ember from 'ember' +import PagedRemoteArray from 'ember-cli-pagination/remote/paged-remote-array' +export default_filter = Ember.Object.create + filter_calls: 0 + workflow_action_definition_id_in: [] + project_instance_group_workflow_group_id: '' + project_id: '' + project_instance_project_scenario_id: '' + workflow_action_definition_organization_id: '' + offset_type_not_eq: 'attached' + state_in: [] + target_date_gteq: null + target_date_lteq: null + s: 'target_date asc' + +export default Ember.Route.extend + queryParams: + q: + refreshModel: true + setupController: (controller, model) -> + controller.set 'filters', default_filter + @_super arguments... + + model: (params) -> + #@store.findAll('epots/migration-window-planning') + otherParams = {q: default_filter.toProperties()} + includes = [ + 'workflow_action_definition.~workflow_action_connections' + 'workflow_action_definition.~pointing_workflow_action_connections' + 'workflow_action_definition.~organization' + 'workflow_action_definition.~workflow_action_offsets' + 'project_instance_group.~project' + 'project_instance_group.~project_instance' + 'project_instance_group.~project_instance_actions' + 'project_instance_group.workflow_group' + 'project_instance.~project' + 'project_instance.project_scenario.~project' + 'project_instance.project_scenario.~workflow_groups' + 'project_instance.project_scenario.~workflow_action_definitions' + 'pointing_project_instance_action_connections.project_instance_action.~project' + 'pointing_project_instance_action_connections.project_instance_action.~project_instance' + 'pointing_project_instance_action_connections.project_instance_action.~project_instance_group' + 'pointing_project_instance_action_connections.project_instance_action.~pointing_project_instance_action_connections' + 'pointing_project_instance_action_connections.project_instance_action.workflow_action_definition.~workflow_action_connections' + 'pointing_project_instance_action_connections.project_instance_action.workflow_action_definition.~pointing_workflow_action_connections' + 'pointing_project_instance_action_connections.project_instance_action.workflow_action_definition.~organization' + 'pointing_project_instance_action_connections.project_instance_action.workflow_action_definition.~workflow_action_offsets' + ].join(',') + #includes += ',' + #includes += [ + # 'project.users' + #].join(',') + includes = 'action-list' + otherParams.include = includes + Ember.RSVP.hash + projects: @store.findAll('panda/project') + organizations: @store.findAll('panda/organization') + project_instance_actions: + PagedRemoteArray.create + modelName: 'panda/project-instance-action' + store: @store + page: params.page || 1 + perPage: params.perPage || 30 + otherParams: otherParams + paramMapping: + total_pages: 'total-pages' + total_count: 'total-count' diff --git a/addon/routes/action-list.js b/addon/routes/action-list.js new file mode 100644 index 0000000..aec9bf6 --- /dev/null +++ b/addon/routes/action-list.js @@ -0,0 +1,54 @@ +// Generated by CoffeeScript 1.12.5 +import Ember from 'ember'; + +import PagedRemoteArray from 'ember-cli-pagination/remote/paged-remote-array'; + +export var default_filter = Ember.Object.create({ + filter_calls: 0, + workflow_action_definition_id_in: [], + project_instance_group_workflow_group_id: '', + project_id: '', + project_instance_project_scenario_id: '', + workflow_action_definition_organization_id: '', + offset_type_not_eq: 'attached', + state_in: [], + target_date_gteq: null, + target_date_lteq: null, + s: 'target_date asc' +}); + +export default Ember.Route.extend({ + queryParams: { + q: { + refreshModel: true + } + }, + setupController: function(controller, model) { + controller.set('filters', default_filter); + return this._super.apply(this, arguments); + }, + model: function(params) { + var includes, otherParams; + otherParams = { + q: default_filter.toProperties() + }; + includes = ['workflow_action_definition.~workflow_action_connections', 'workflow_action_definition.~pointing_workflow_action_connections', 'workflow_action_definition.~organization', 'workflow_action_definition.~workflow_action_offsets', 'project_instance_group.~project', 'project_instance_group.~project_instance', 'project_instance_group.~project_instance_actions', 'project_instance_group.workflow_group', 'project_instance.~project', 'project_instance.project_scenario.~project', 'project_instance.project_scenario.~workflow_groups', 'project_instance.project_scenario.~workflow_action_definitions', 'pointing_project_instance_action_connections.project_instance_action.~project', 'pointing_project_instance_action_connections.project_instance_action.~project_instance', 'pointing_project_instance_action_connections.project_instance_action.~project_instance_group', 'pointing_project_instance_action_connections.project_instance_action.~pointing_project_instance_action_connections', 'pointing_project_instance_action_connections.project_instance_action.workflow_action_definition.~workflow_action_connections', 'pointing_project_instance_action_connections.project_instance_action.workflow_action_definition.~pointing_workflow_action_connections', 'pointing_project_instance_action_connections.project_instance_action.workflow_action_definition.~organization', 'pointing_project_instance_action_connections.project_instance_action.workflow_action_definition.~workflow_action_offsets'].join(','); + includes = 'action-list'; + otherParams.include = includes; + return Ember.RSVP.hash({ + projects: this.store.findAll('panda/project'), + organizations: this.store.findAll('panda/organization'), + project_instance_actions: PagedRemoteArray.create({ + modelName: 'panda/project-instance-action', + store: this.store, + page: params.page || 1, + perPage: params.perPage || 30, + otherParams: otherParams, + paramMapping: { + total_pages: 'total-pages', + total_count: 'total-count' + } + }) + }); + } +}); diff --git a/addon/styles/addon.scss b/addon/styles/addon.scss new file mode 100644 index 0000000..36f1a31 --- /dev/null +++ b/addon/styles/addon.scss @@ -0,0 +1,45 @@ +$sort-background-color: #566573; + +$button-tny: 2px; +$primary-color: blue; +$success-color: green; +$warning-color: yellow; +$alert-color: red; +$secondary-color: grey; +$info-color: lightblue; +$non-default-attention-color: #f2711c; + +@import './semantic-ui-additions'; + +//@import './mixins/animations'; +//@import './mixins/buttons'; + +@import './components/buttons'; +@import './components/format-json'; +@import './components/forms'; +//@import './components/flash-message'; +//@import './components/modal'; +@import './components/multiselect'; +@import './components/project-instance-action-value-display'; +//@import './components/page-title'; +@import './components/resources'; +@import './components/sortable'; +@import './components/select-duration'; +@import './components/state-badge'; +@import './components/tables'; +@import './components/title-box'; + +//@import './resources/login-form'; +@import './resources/businessdays'; +@import './resources/projects'; +@import './resources/project-instance'; +@import './resources/tracker-subject'; +@import './resources/workflow-action-definition'; +@import './resources/workflow-groups'; +@import './resources/workflow-group-constraints'; +@import './resources/manual'; + +// DIAGRAMS ARE SVGs +@import './diagrams/project/workflow-action-offsets-graph'; +@import './diagrams/project/workflow-group-offset-diagram'; +@import './diagrams/project/action-execution-offset-diagram'; diff --git a/addon/styles/components/_buttons.sass b/addon/styles/components/_buttons.sass new file mode 100644 index 0000000..474822e --- /dev/null +++ b/addon/styles/components/_buttons.sass @@ -0,0 +1,50 @@ +//=push-action-button($color: $primary-color) +// +button($bg: $color, $padding: $button-tny) +// margin-bottom: 0 +// &.right +// margin-left: 8px + +//.push-action +// $push-action-padding: $button-tny +// +push-action-button +// .icon +// @extend .fa +// &.new +// +push-action-button($primary-color) +// .icon +// @extend .fa-plus +// &.edit +// +push-action-button($warning-color) +// .icon +// @extend .fa-pencil +// &.destroy +// +push-action-button($alert-color) +// .icon +// @extend .fa-trash +// &.save +// +push-action-button($success-color) +// .icon +// @extend .fa-save +// &.rollback +// +push-action-button($secondary-color) +// .icon +// @extend .fa-undo +// &.show +// +push-action-button($info-color) +// .icon +// @extend .fa-eye +// &.sort +// +push-action-button($sort-background-color) +// .icon +// @extend .fa-sort + +// &.new, &.edit, &.save, &.rollback, &.destroy, &.show, &.info +// margin-right: rem-calc(20) +// &:last-of-type +// margin-right: 0 +// .push-action-content +// padding-left: rem-calc(10) + +.workflow-instance-workflow-group-title, .workflow-action-definition-item, .project-header + .push-action + margin: 0 diff --git a/addon/styles/components/_format-json.sass b/addon/styles/components/_format-json.sass new file mode 100644 index 0000000..9c2b4ab --- /dev/null +++ b/addon/styles/components/_format-json.sass @@ -0,0 +1,8 @@ +.format-json-container + max-height: 500px + overflow: scroll + .json-formatter-top-node-name + cursor: pointer + .json-formatter-constructor-name + // Do not show the names Object and Array + display: none diff --git a/addon/styles/components/_forms.sass b/addon/styles/components/_forms.sass new file mode 100644 index 0000000..8dd0285 --- /dev/null +++ b/addon/styles/components/_forms.sass @@ -0,0 +1,42 @@ +.ui.input + input[type=number] + text-align: right +//.form-row +// +grid-row +// .form-label +// @media #{$small-only} +// +grid-column($columns:10, $center:true) +// @media #{$medium-only} +// +grid-column($columns:4, $offset:1) +// @media #{$large-up} +// +grid-column(3) +// &.half +// +grid-column(6) +// .form-field +// @media #{$small-only} +// +grid-column($columns:10, $center:true, $last-column:true) +// @media #{$medium-only} +// +grid-column($columns:4, $last-column:false) +// @media #{$large-up} +// +grid-column($columns: 4, $last-column:false) +// &.full +// @media #{$small-only} +// +grid-column($columns:10, $center:true, $last-column:true) +// @media #{$medium-only} +// +grid-column($columns:7, $last-column:true) +// @media #{$large-up} +// +grid-column($columns: 9, $last-column:true) +// &.half +// +grid-column(6) +// .error +// color: $alert-color +// .form-info +// @media #{$small-only} +// +grid-column($columns:10, $center:true) +// @media #{$medium-only} +// +grid-column($columns:4, $offset:1) +// @media #{$large-up} +// +grid-column(3) +// .error-message +// color: $alert-color + diff --git a/addon/styles/components/_multiselect.sass b/addon/styles/components/_multiselect.sass new file mode 100644 index 0000000..c118038 --- /dev/null +++ b/addon/styles/components/_multiselect.sass @@ -0,0 +1,2 @@ +.multiselect-checkboxes + list-style: none diff --git a/addon/styles/components/_project-instance-action-value-display.sass b/addon/styles/components/_project-instance-action-value-display.sass new file mode 100644 index 0000000..6a92028 --- /dev/null +++ b/addon/styles/components/_project-instance-action-value-display.sass @@ -0,0 +1,3 @@ +.project-instance-action-value-display + .value-missing + color: #aaa diff --git a/addon/styles/components/_resources.sass b/addon/styles/components/_resources.sass new file mode 100644 index 0000000..0cbf2ee --- /dev/null +++ b/addon/styles/components/_resources.sass @@ -0,0 +1,13 @@ +.resource-row + margin-bottom: rem-calc(10) +h2, .content + .resource-descriptor + padding-right: 8px + &:after + content: ':' + .resource-name + text-decoration: underline + .resource-suffix + font-weight: normal + padding-left: 8px + font-size: 0.8em diff --git a/addon/styles/components/_select-duration.sass b/addon/styles/components/_select-duration.sass new file mode 100644 index 0000000..f85676e --- /dev/null +++ b/addon/styles/components/_select-duration.sass @@ -0,0 +1,6 @@ +.select-duration + .ui.input + &.left.labeled + color: green + &+ .dropdown + margin-left: 51px diff --git a/addon/styles/components/_sortable.sass b/addon/styles/components/_sortable.sass new file mode 100644 index 0000000..af42736 --- /dev/null +++ b/addon/styles/components/_sortable.sass @@ -0,0 +1,2 @@ +.sortable-list + list-style: none diff --git a/addon/styles/components/_state-badge.sass b/addon/styles/components/_state-badge.sass new file mode 100644 index 0000000..5d216ca --- /dev/null +++ b/addon/styles/components/_state-badge.sass @@ -0,0 +1,3 @@ +.state-badge + &.with-action + cursor: pointer diff --git a/addon/styles/components/_tables.sass b/addon/styles/components/_tables.sass new file mode 100644 index 0000000..ec26318 --- /dev/null +++ b/addon/styles/components/_tables.sass @@ -0,0 +1,9 @@ +tr.pointing-record + + .record-with-pointing-records + td + border-top: 3px solid #aaa + td + &:first-child + border-left: 8px solid #aaa + &:last-child + border-right: 8px solid #aaa diff --git a/addon/styles/components/_title-box.sass b/addon/styles/components/_title-box.sass new file mode 100644 index 0000000..5cd5222 --- /dev/null +++ b/addon/styles/components/_title-box.sass @@ -0,0 +1,71 @@ +/* Based on the bootstrap 3+ panel, of course under a different name. + The conversion is done based on: + http://stackoverflow.com/questions/19220027/can-you-use-panels-in-bootstrap-2-3 +.title-box + padding: 15px + margin-bottom: 20px + background-color: #ffffff + border: 1px solid #dddddd + border-radius: 4px + -webkit-box-shadow: 0 1px 1px rgba(0, 0, 0, 0.05) + box-shadow: 0 1px 1px rgba(0, 0, 0, 0.05) + +.title-box-heading + padding: 10px 15px + margin: -15px -15px 15px + font-size: 17.5px + font-weight: 500 + background-color: #f5f5f5 + border-bottom: 1px solid #dddddd + border-top-right-radius: 3px + border-top-left-radius: 3px + +.title-box-footer + padding: 10px 15px + margin: 15px -15px -15px + background-color: #f5f5f5 + border-top: 1px solid #dddddd + border-bottom-right-radius: 3px + border-bottom-left-radius: 3px + +.title-box-primary + border-color: #428bca + .title-box-heading + color: #ffffff + background-color: #428bca + border-color: #428bca + +.title-box-success + border-color: #d6e9c6 + + .title-box-heading + color: #468847 + background-color: #dff0d8 + border-color: #d6e9c6 + +.title-box-warning + border-color: #fbeed5 + + .title-box-heading + color: #c09853 + background-color: #fcf8e3 + border-color: #fbeed5 + +.title-box-danger + border-color: #eed3d7 + + .title-box-heading + color: #b94a48 + background-color: #f2dede + border-color: #eed3d7 + +.title-box-info + border-color: #bce8f1 + + .title-box-heading + color: #3a87ad + background-color: #d9edf7 + border-color: #bce8f1 + +//State specific additions + diff --git a/addon/styles/diagrams/project/_action-execution-offset-diagram.sass b/addon/styles/diagrams/project/_action-execution-offset-diagram.sass new file mode 100644 index 0000000..b36abbc --- /dev/null +++ b/addon/styles/diagrams/project/_action-execution-offset-diagram.sass @@ -0,0 +1,19 @@ +.action-execution-offset-diagram-component, .action-execution-offset-diagram-component-old2 + .noUi-tooltip + white-space: nowrap + .range-slider-container + padding: 18px 30px +.action-execution-offset-diagram-container + .offset-line + stroke: #adadad + stroke-width: 1px + .base-line + stroke: #2185d0 + stroke-width: 3px + .action-descriptions + .action-description-text + text-anchor: start +// within .tooltipstered +.tooltipster-base + .edit-workflow-action-definition + margin: -3px 0 5px 10px !important diff --git a/addon/styles/diagrams/project/_workflow-action-offsets-graph.sass b/addon/styles/diagrams/project/_workflow-action-offsets-graph.sass new file mode 100644 index 0000000..4d63ca4 --- /dev/null +++ b/addon/styles/diagrams/project/_workflow-action-offsets-graph.sass @@ -0,0 +1,3 @@ +.workflow-action-offsets-graph-component + .range-slider-container + padding: 18px 30px diff --git a/addon/styles/diagrams/project/_workflow-group-offset-diagram.sass b/addon/styles/diagrams/project/_workflow-group-offset-diagram.sass new file mode 100644 index 0000000..979b4a9 --- /dev/null +++ b/addon/styles/diagrams/project/_workflow-group-offset-diagram.sass @@ -0,0 +1,31 @@ +.workflow-group-offset-diagram-component + .noUi-tooltip + white-space: nowrap + .range-slider-container + padding: 18px 30px + +// SVG +.workflow-group-offset-diagram-container + .groups-crossing-left-axis + text + fill: black + font-weight: bold + .groups-crossing-right-axis + text + fill: black + font-weight: bold + text-anchor: end + .bar + fill: #5f89ad + &.lead-time-none + fill: #f2711c + &.extra-duration + fill: #fff + fill-opacity: 0.2 + .offset-line + stroke: #adadad + stroke-width: 1px + .base-line + stroke: #2185d0 + stroke-width: 3px + diff --git a/addon/styles/resources/_businessdays.sass b/addon/styles/resources/_businessdays.sass new file mode 100644 index 0000000..a415180 --- /dev/null +++ b/addon/styles/resources/_businessdays.sass @@ -0,0 +1,6 @@ +.default-business-days +.non-default-business-days + .select-businessdays-in-week + color: $non-default-attention-color + .ui.header + color: $non-default-attention-color diff --git a/addon/styles/resources/_login-form.sass b/addon/styles/resources/_login-form.sass new file mode 100644 index 0000000..390c0fb --- /dev/null +++ b/addon/styles/resources/_login-form.sass @@ -0,0 +1,5 @@ +.login-form + &.ui.grid + margin-top: 20px + .column + max-width: 450px diff --git a/addon/styles/resources/_manual.sass b/addon/styles/resources/_manual.sass new file mode 100644 index 0000000..5bcacf3 --- /dev/null +++ b/addon/styles/resources/_manual.sass @@ -0,0 +1,7 @@ +.ui.grid.manual + .nav-column + padding-top: 2rem + padding-left: 2rem + .ui.sticky.fixed.top + // allow space for top navigation + padding-top: 50px diff --git a/addon/styles/resources/_project-instance.sass b/addon/styles/resources/_project-instance.sass new file mode 100644 index 0000000..da2f418 --- /dev/null +++ b/addon/styles/resources/_project-instance.sass @@ -0,0 +1,50 @@ +.project-instance-list-tab-container + .project-instance-list-workflow-group-container + display: table + border-bottom: 1px solid #aaa + width: 100% + &:first-child + border-top: 1px solid #aaa + .project-instance-list-workflow-group-row + display: table-row + font-size: 1.2em + font-weight: bold + background-color: #ddd + .project-instance-list-workflow-action-definition-row + display: table-row + .project-instance-list-workflow-group, .project-instance-list-workflow-action-definition + display: table-cell + width: 33% + padding: 7px + .project-instance-list-workflow-step-context, .project-instance-list-workflow-action-context + display: table-cell + padding: 7px + //.project-instance-list-workflow-group + //font-size: 1.2em + //font-weight: bold + //background-color: #ddd + //.project-instance-list-workflow-step-context + //line-height: 1.2em + //padding: 7px + //background-color: #dff + //border-top: 1px solid #bbb + //&:last-child + //border-bottom: 1px solid #bbb + //.project-instance-list-workflow-action-definition + //border-bottom: 1px solid #bbb + //&:first-child + //border-top: 1px solid #bbb + //.project-instance-list-workflow-action-context + //border-top: 1px solid #bbb + //&:last-child + //border-bottom: 1px solid #bbb +table.ui.definition.table + tr.pointing-action + &.constraint + &.uncompleted + td + font-weight: bold + color: #700 + i + font-size: 1.5em + color: #700 diff --git a/addon/styles/resources/_projects.sass b/addon/styles/resources/_projects.sass new file mode 100644 index 0000000..033afbd --- /dev/null +++ b/addon/styles/resources/_projects.sass @@ -0,0 +1,19 @@ +ul.project-user-select + workflow_group-style: none +.ui.header + .project-scenarios-dropdown-container + float: right + margin-top: -3px // visual + font-size: 0.7em + +.project-scenarios-dropdown-container + .ui.multiple.dropdown + i.dropdown.icon + margin-right: -20px + margin-top: 5px + .ui.button.logical-icon-button + padding: 2px 7px 0px + .logical-icon-image + height: 26px +.active-project-scenario-selector-component.ui.buttons + margin-left: 8px diff --git a/addon/styles/resources/_tracker-subject.sass b/addon/styles/resources/_tracker-subject.sass new file mode 100644 index 0000000..90d88bf --- /dev/null +++ b/addon/styles/resources/_tracker-subject.sass @@ -0,0 +1,34 @@ +.tracker-subject-tracker-groups-container + display: flex + flex-direction: row + flex-wrap: wrap + align-items: flex-start + justify-content: space-around + +.tracker-subject-tracker-group-container + @extend .title-box + //float: left + width: 412px + .tracker-subject-tracker-group-title + @extend .title-box-heading + .tracker-subject-tracker-group-body + .display-row + &.attribute-notes + display: none + .display-label + width: 65% + .display-field + width: 35% + .page-actions + //display: none + text-align: center + .collection-edit + margin: 0 + padding: 5px 12px + &.processing + @extend .title-box-primary + &.rejected + @extend .title-box-danger + &.completed + @extend .title-box-success + diff --git a/addon/styles/resources/_workflow-action-definition.sass b/addon/styles/resources/_workflow-action-definition.sass new file mode 100644 index 0000000..9ffffb9 --- /dev/null +++ b/addon/styles/resources/_workflow-action-definition.sass @@ -0,0 +1,50 @@ +.workflow-action-definition-item + color: #222 + &.is-editing + background-color: #FEF9E7 + &.done + text-decoration: line-through + color: #aaa + &:hover + color: white + .title + text-decoration: line-through + input + margin: 0 +//.workflow-action-definition-value-type-display +// @extend .fa, .fa-lg +// &.boolean +// @extend .checker +// &.date +// @extend .fa-calendar +// &.number +// @extend .fa-hashtag +// &.select +// @extend .fa-list-ul +// &.string +// @extend .fa-terminal +// &.text +// @extend .fa-sticky-note-o +// &.window_from +// @extend .fa-clock-o +// &.window_to +// @extend .fa-clock-o +// &.timestamp +// @extend .fa-clock-o +.ui.dropdown + .workflow-action-definition-value-type-display + padding: 0 + margin-right: 6px + margin-left: -10px +.ui.grid.workflow-action-definition-row + > .column + padding-top: 0.3rem + padding-bottom: 0.3rem + .right.floated.icon.buttons + // normal setting is margin-top: -0.5rem + margin-top: -0.2rem +.workflow-action-definition-workflow-action-offset-container + // below divider, pull a bit up + margin-top: -10px + .number-input + width: 82px diff --git a/addon/styles/resources/_workflow-group-constraints.sass b/addon/styles/resources/_workflow-group-constraints.sass new file mode 100644 index 0000000..a23c2b9 --- /dev/null +++ b/addon/styles/resources/_workflow-group-constraints.sass @@ -0,0 +1,4 @@ +.workflow-group-constraints-count + .count + display: inline-block + margin-right: 8px diff --git a/addon/styles/resources/_workflow-groups.sass b/addon/styles/resources/_workflow-groups.sass new file mode 100644 index 0000000..26fbdee --- /dev/null +++ b/addon/styles/resources/_workflow-groups.sass @@ -0,0 +1,71 @@ +.new-workflow_action_definition-button + display: inline-block + margin-left: 12px + .icon + margin-right: 12px + +.workflow_group-name + padding-right: rem-calc(10) + +.workflow-instance-workflow-groups-container + display: flex + flex-direction: row + flex-wrap: wrap + align-items: flex-start + justify-content: space-around + +.workflow-instance-workflow-group-container + @extend .title-box + //float: left + width: 412px + padding-bottom: 0 + .workflow-instance-workflow-group-title + @extend .title-box-heading + margin-bottom: 0 + .right.floated.buttons + margin-right: -12px + + .workflow-instance-workflow-group-body + .display-row + &.attribute-notes + display: none + .display-label + width: 65% + .display-field + width: 35% + .page-actions + //display: none + text-align: center + .collection-edit + margin: 0 + padding: 5px 12px + .workflow-action-container + border-bottom: 1px solid #eee + margin-left: -15px + margin-right: -15px + &:last-child + border-width: 0 + .ui.grid + margin: 0 + &.planned + background-color: #ddd + &.overdue + background-color: #f7da00 + &.processing + background-color: #cef + &.rejected + background-color: #ff4848 + &.completed + background-color: #f2ffdd + &.processing + @extend .title-box-primary + &.rejected + @extend .title-box-danger + &.completed + @extend .title-box-success + &.overdue + @extend .title-box-warning + +.modal + .workflow-instance-workflow-group-container + width: 242px diff --git a/addon/styles/semantic-ui-additions.sass b/addon/styles/semantic-ui-additions.sass new file mode 100644 index 0000000..b0c3f05 --- /dev/null +++ b/addon/styles/semantic-ui-additions.sass @@ -0,0 +1,29 @@ +//.ui.container +// width: 98% + +.ui.main + padding-top: 40px + +.hide + display: none + + +.right.floated.buttons + margin-top: -0.5rem + +.menu.fixed + .header.item + padding: 0 1rem + background-color: #444 !important + .grid + margin-top: -1rem + //margin-left: 0 # active in epots adapter + +.ui.header + .push-action.icon + padding: 0.6rem + margin-right: 0 + +.ui.segment.title-box + padding-top: 3rem + diff --git a/addon/templates/components/project/edit-active-project-scenario.emblem b/addon/templates/components/project/edit-active-project-scenario.emblem new file mode 100644 index 0000000..0a90f09 --- /dev/null +++ b/addon/templates/components/project/edit-active-project-scenario.emblem @@ -0,0 +1 @@ += yield diff --git a/addon/utils/business-day-computations.coffee b/addon/utils/business-day-computations.coffee new file mode 100644 index 0000000..905ea3c --- /dev/null +++ b/addon/utils/business-day-computations.coffee @@ -0,0 +1,14 @@ +import Ember from 'ember' +import DS from 'ember-data' +businessDayComputations = (target, prefix, options = {}) -> + if options.add_attributes + Ember.defineProperty target, "#{prefix}_value", DS.attr('number', defaultValue: 0) + Ember.defineProperty target, "#{prefix}_unit", DS.attr('string', defaultValue: options.default_unit || 'day') + Ember.defineProperty target, "#{prefix}_business_days", DS.attr(defaultValue: -> [ 1, 2, 3, 4, 5 ]) + #Ember.defineProperty target, "#{prefix}_business_days_index_sum", Ember.computed.sum "#{prefix}_business_days" + #Ember.defineProperty target, "has_default_#{prefix}_business_days", Ember.computed "#{prefix}_business_days.length", "#{prefix}_business_days_index_sum", -> + # @get("#{prefix}_business_days.length") is 5 and @get("#{prefix}_business_days_index_sum") is 15 + #Ember.defineProperty target, "#{prefix}_unit_businessday", Ember.computed.equal "#{prefix}_unit", 'businessday' + true + +export default businessDayComputations diff --git a/addon/utils/business-day-computations.js b/addon/utils/business-day-computations.js new file mode 100644 index 0000000..fe4645d --- /dev/null +++ b/addon/utils/business-day-computations.js @@ -0,0 +1,28 @@ +// Generated by CoffeeScript 1.12.5 +var businessDayComputations; + +import Ember from 'ember'; + +import DS from 'ember-data'; + +businessDayComputations = function(target, prefix, options) { + if (options == null) { + options = {}; + } + if (options.add_attributes) { + Ember.defineProperty(target, prefix + "_value", DS.attr('number', { + defaultValue: 0 + })); + Ember.defineProperty(target, prefix + "_unit", DS.attr('string', { + defaultValue: options.default_unit || 'day' + })); + Ember.defineProperty(target, prefix + "_business_days", DS.attr({ + defaultValue: function() { + return [1, 2, 3, 4, 5]; + } + })); + } + return true; +}; + +export default businessDayComputations; diff --git a/addon/utils/moment-business-days.coffee b/addon/utils/moment-business-days.coffee new file mode 100644 index 0000000..1dcb787 --- /dev/null +++ b/addon/utils/moment-business-days.coffee @@ -0,0 +1,148 @@ +'use strict' +# taken from https://github.com/kalmecak/moment-business-days +#if (typeof require === 'function') { +# var moment = require('moment'); +#} + +moment.fn.isHoliday = -> + locale = @localeData() + if locale._holidays + if locale._holidays.indexOf(@format(locale._holidayFormat)) >= 0 + return true + if locale.holiday + if locale.holiday(this) + return true + else + return false + false + +moment.fn.isBusinessDay = (options) -> + locale = @localeData() + defaultWorkingWeekdays = [ 1, 2, 3, 4, 5 ] + workingWeekdays = options.business_days or locale._workingWeekdays or defaultWorkingWeekdays + if @isHoliday() + return false + if workingWeekdays.indexOf(@day()) >= 0 + return true + false + +moment.fn.businessDaysIntoMonth = -> + businessDay = if @isBusinessDay() then this else @prevBusinessDay() + monthBusinessDays = businessDay.monthBusinessDays() + businessDaysIntoMonth = undefined + monthBusinessDays.map (day, index) -> + if day.format('M/DD/YY') == businessDay.format('M/DD/YY') + businessDaysIntoMonth = index + 1 + return + businessDaysIntoMonth + +moment.fn.businessDiff = (param) -> + d1 = @clone() + d2 = param.clone() + start = if d1 < d2 then d1 else d2 + end = if d2 > d1 then d2 else d1 + daysBetween = 0 + if start == end + return daysBetween + while start < end + if start.isBusinessDay() + daysBetween++ + start.add 1, 'd' + daysBetween + +moment.fn.businessAdd = (number, options = {}) -> + options.period ||= 'days' + day = @clone() + signal = if number < 0 then -1 else 1 + remaining = Math.abs(number) + while remaining + day.add signal, options.period + if day.isBusinessDay(options) + remaining-- + day + +moment.fn.businessSubtract = (number, options = {}) -> + @businessAdd -number, options + +moment.fn.nextBusinessDay = -> + iterator = 1 + limit = 7 + while iterator < limit + if @add(1, 'd').isBusinessDay() + break + iterator++ + this + +moment.fn.prevBusinessDay = -> + iterator = 1 + limit = 7 + while iterator < limit + if @subtract(1, 'd').isBusinessDay() + break + iterator++ + this + +moment.fn.monthBusinessDays = (partialEndDate) -> + me = @clone() + day = me.clone().startOf('month') + end = if partialEndDate then partialEndDate else me.clone().endOf('month') + daysArr = [] + done = false + while !done + if day.isBusinessDay() + daysArr.push day.clone() + if end.diff(day.add(1, 'd')) < 0 + done = true + daysArr + +moment.fn.monthNaturalDays = (fromToday) -> + me = @clone() + day = if fromToday then me.clone() else me.clone().startOf('month') + end = me.clone().endOf('month') + daysArr = [] + done = false + while !done + daysArr.push day.clone() + if end.diff(day.add(1, 'd')) < 0 + done = true + daysArr + +moment.fn.monthBusinessWeeks = (fromToday) -> + me = @clone() + day = if fromToday then me.clone() else me.clone().startOf('month') + end = me.clone().endOf('month') + weeksArr = [] + daysArr = [] + done = false + while !done + if day.day() >= 1 and day.day() < 6 + daysArr.push day.clone() + if day.day() == 5 + weeksArr.push daysArr + daysArr = [] + if end.diff(day.add(1, 'd')) < 0 + if daysArr.length < 5 + weeksArr.push daysArr + done = true + weeksArr + +moment.fn.monthNaturalWeeks = (fromToday) -> + me = @clone() + day = if fromToday then me.clone() else me.clone().startOf('month') + end = me.clone().endOf('month') + weeksArr = [] + daysArr = [] + done = false + while !done + daysArr.push day.clone() + if day.day() == 6 + weeksArr.push daysArr + daysArr = [] + if end.diff(day.add(1, 'd')) < 0 + if daysArr.length < 7 + weeksArr.push daysArr + done = true + weeksArr + +if typeof module isnt 'undefined' and module.exports + module.exports = moment diff --git a/addon/utils/moment-business-days.js b/addon/utils/moment-business-days.js new file mode 100644 index 0000000..b42bd7c --- /dev/null +++ b/addon/utils/moment-business-days.js @@ -0,0 +1,204 @@ +// Generated by CoffeeScript 1.12.5 +'use strict'; +moment.fn.isHoliday = function() { + var locale; + locale = this.localeData(); + if (locale._holidays) { + if (locale._holidays.indexOf(this.format(locale._holidayFormat)) >= 0) { + return true; + } + } + if (locale.holiday) { + if (locale.holiday(this)) { + return true; + } else { + return false; + } + } + return false; +}; + +moment.fn.isBusinessDay = function(options) { + var defaultWorkingWeekdays, locale, workingWeekdays; + locale = this.localeData(); + defaultWorkingWeekdays = [1, 2, 3, 4, 5]; + workingWeekdays = options.business_days || locale._workingWeekdays || defaultWorkingWeekdays; + if (this.isHoliday()) { + return false; + } + if (workingWeekdays.indexOf(this.day()) >= 0) { + return true; + } + return false; +}; + +moment.fn.businessDaysIntoMonth = function() { + var businessDay, businessDaysIntoMonth, monthBusinessDays; + businessDay = this.isBusinessDay() ? this : this.prevBusinessDay(); + monthBusinessDays = businessDay.monthBusinessDays(); + businessDaysIntoMonth = void 0; + monthBusinessDays.map(function(day, index) { + if (day.format('M/DD/YY') === businessDay.format('M/DD/YY')) { + businessDaysIntoMonth = index + 1; + } + }); + return businessDaysIntoMonth; +}; + +moment.fn.businessDiff = function(param) { + var d1, d2, daysBetween, end, start; + d1 = this.clone(); + d2 = param.clone(); + start = d1 < d2 ? d1 : d2; + end = d2 > d1 ? d2 : d1; + daysBetween = 0; + if (start === end) { + return daysBetween; + } + while (start < end) { + if (start.isBusinessDay()) { + daysBetween++; + } + start.add(1, 'd'); + } + return daysBetween; +}; + +moment.fn.businessAdd = function(number, options) { + var day, remaining, signal; + if (options == null) { + options = {}; + } + options.period || (options.period = 'days'); + day = this.clone(); + signal = number < 0 ? -1 : 1; + remaining = Math.abs(number); + while (remaining) { + day.add(signal, options.period); + if (day.isBusinessDay(options)) { + remaining--; + } + } + return day; +}; + +moment.fn.businessSubtract = function(number, options) { + if (options == null) { + options = {}; + } + return this.businessAdd(-number, options); +}; + +moment.fn.nextBusinessDay = function() { + var iterator, limit; + iterator = 1; + limit = 7; + while (iterator < limit) { + if (this.add(1, 'd').isBusinessDay()) { + break; + } + iterator++; + } + return this; +}; + +moment.fn.prevBusinessDay = function() { + var iterator, limit; + iterator = 1; + limit = 7; + while (iterator < limit) { + if (this.subtract(1, 'd').isBusinessDay()) { + break; + } + iterator++; + } + return this; +}; + +moment.fn.monthBusinessDays = function(partialEndDate) { + var day, daysArr, done, end, me; + me = this.clone(); + day = me.clone().startOf('month'); + end = partialEndDate ? partialEndDate : me.clone().endOf('month'); + daysArr = []; + done = false; + while (!done) { + if (day.isBusinessDay()) { + daysArr.push(day.clone()); + } + if (end.diff(day.add(1, 'd')) < 0) { + done = true; + } + } + return daysArr; +}; + +moment.fn.monthNaturalDays = function(fromToday) { + var day, daysArr, done, end, me; + me = this.clone(); + day = fromToday ? me.clone() : me.clone().startOf('month'); + end = me.clone().endOf('month'); + daysArr = []; + done = false; + while (!done) { + daysArr.push(day.clone()); + if (end.diff(day.add(1, 'd')) < 0) { + done = true; + } + } + return daysArr; +}; + +moment.fn.monthBusinessWeeks = function(fromToday) { + var day, daysArr, done, end, me, weeksArr; + me = this.clone(); + day = fromToday ? me.clone() : me.clone().startOf('month'); + end = me.clone().endOf('month'); + weeksArr = []; + daysArr = []; + done = false; + while (!done) { + if (day.day() >= 1 && day.day() < 6) { + daysArr.push(day.clone()); + } + if (day.day() === 5) { + weeksArr.push(daysArr); + daysArr = []; + } + if (end.diff(day.add(1, 'd')) < 0) { + if (daysArr.length < 5) { + weeksArr.push(daysArr); + } + done = true; + } + } + return weeksArr; +}; + +moment.fn.monthNaturalWeeks = function(fromToday) { + var day, daysArr, done, end, me, weeksArr; + me = this.clone(); + day = fromToday ? me.clone() : me.clone().startOf('month'); + end = me.clone().endOf('month'); + weeksArr = []; + daysArr = []; + done = false; + while (!done) { + daysArr.push(day.clone()); + if (day.day() === 6) { + weeksArr.push(daysArr); + daysArr = []; + } + if (end.diff(day.add(1, 'd')) < 0) { + if (daysArr.length < 7) { + weeksArr.push(daysArr); + } + done = true; + } + } + return weeksArr; +}; + +if (typeof module !== 'undefined' && module.exports) { + module.exports = moment; +} diff --git a/addon/utils/panda-route-setup.coffee b/addon/utils/panda-route-setup.coffee new file mode 100644 index 0000000..0e6db58 --- /dev/null +++ b/addon/utils/panda-route-setup.coffee @@ -0,0 +1,51 @@ +export default pandaRouteSetup = (context) -> + context.route 'project', -> + @route 'show', path: ':project_id', -> + @route 'dunlop' + @route 'tracker-subjects' + @route 'tracker-subject', path: 'tracker-subject/:tracker_subject_id' + @route 'add-progress' + #@route 'tracker-subject', -> + # @route 'show' + @route 'dunlop-generator' + @route 'analysis' + @route 'project-instance', -> + @route 'show', path: ':project_instance_id', -> + @route 'groups' + @route 'list' + @route 'actions' + @route 'graphs' + @route 'project-instance-group', -> + @route 'show', path: ':project_instance_group_id' + @route 'project-instance-action', -> + @route 'show', path: ':project_instance_action_id' + #@route 'project/views', path: 'views' + context.route 'login' + #@route 'organization', path: 'organizations/:organization_id' + context.route 'action-list' + + context.route 'organization', -> + @route 'show', path: ':organization_id', -> + @route 'project-actions' + + context.route 'user', -> + @route 'profile' + @route 'show', path: ':user_id' + + + context.route 'tracker-subject', -> + @route 'show', path: ':tracker_subject_id', -> + @route 'tracking-connections' + @route 'tracker-group', path: 'tracker-groups/:tracker_group_id' + @route 'project', path: 'projects/:project_id' + #@route 'tracker-subject/tracker-group', path: 'tracker-subjects/:tracker_subject_id/tracker-groups/:tracker_group_id' + context.route 'event' + + context.route 'meeting-reports' + + context.route 'manual', -> + @route 'project', -> + @route 'dunlop' + @route 'tracker', -> + @route 'tricks' # dummy/empty now, to genate the index + true diff --git a/addon/utils/panda-route-setup.js b/addon/utils/panda-route-setup.js new file mode 100644 index 0000000..81bf07b --- /dev/null +++ b/addon/utils/panda-route-setup.js @@ -0,0 +1,78 @@ +// Generated by CoffeeScript 1.12.5 +var pandaRouteSetup; + +export default pandaRouteSetup = function(context) { + context.route('project', function() { + return this.route('show', { + path: ':project_id' + }, function() { + this.route('dunlop'); + this.route('tracker-subjects'); + this.route('tracker-subject', { + path: 'tracker-subject/:tracker_subject_id' + }); + this.route('add-progress'); + this.route('dunlop-generator'); + this.route('analysis'); + this.route('project-instance', function() { + return this.route('show', { + path: ':project_instance_id' + }, function() { + this.route('groups'); + this.route('list'); + this.route('actions'); + return this.route('graphs'); + }); + }); + this.route('project-instance-group', function() { + return this.route('show', { + path: ':project_instance_group_id' + }); + }); + return this.route('project-instance-action', function() { + return this.route('show', { + path: ':project_instance_action_id' + }); + }); + }); + }); + context.route('login'); + context.route('action-list'); + context.route('organization', function() { + return this.route('show', { + path: ':organization_id' + }, function() { + return this.route('project-actions'); + }); + }); + context.route('user', function() { + this.route('profile'); + return this.route('show', { + path: ':user_id' + }); + }); + context.route('tracker-subject', function() { + return this.route('show', { + path: ':tracker_subject_id' + }, function() { + this.route('tracking-connections'); + this.route('tracker-group', { + path: 'tracker-groups/:tracker_group_id' + }); + return this.route('project', { + path: 'projects/:project_id' + }); + }); + }); + context.route('event'); + context.route('meeting-reports'); + context.route('manual', function() { + this.route('project', function() { + return this.route('dunlop'); + }); + return this.route('tracker', function() { + return this.route('tricks'); + }); + }); + return true; +}; diff --git a/addon/utils/project/active-workflow-action-definitions.coffee b/addon/utils/project/active-workflow-action-definitions.coffee new file mode 100644 index 0000000..7a4d85e --- /dev/null +++ b/addon/utils/project/active-workflow-action-definitions.coffee @@ -0,0 +1,45 @@ +import Ember from 'ember' +# get_active_workflow_action_definitions(workflow_action_definitions, project_scenarios, set_operation = 'union or intersection') +# this util takes as first arguments the workflow_action_definitions scope +# and tries to apply filtering based on the given project_scenarios and set_operation + +get_active_workflow_action_definitions_using_union = (project_scenarios) -> + selected_project_scenario_workflow_action_definition_ids = [] + working_workflow_action_definitions = [] + project_scenarios.forEach (selected_project_scenario) -> + selected_project_scenario.get('workflow_action_definitions').forEach (workflow_action_definition) -> + if selected_project_scenario_workflow_action_definition_ids.indexOf(workflow_action_definition.get('id')) < 0 + selected_project_scenario_workflow_action_definition_ids.push workflow_action_definition.get('id') + working_workflow_action_definitions.push workflow_action_definition + working_workflow_action_definitions + +get_active_workflow_action_definitions_using_intersection = (workflow_action_definitions, project_scenarios) -> + selected_project_scenario_workflow_action_definition_ids = [] + first_run = true + project_scenarios.forEach (selected_project_scenario) -> + current_workflow_action_definition_ids = selected_project_scenario.get('workflow_action_definitions').mapBy('id').toArray() + if first_run + selected_project_scenario_workflow_action_definition_ids = current_workflow_action_definition_ids + first_run = false + return + selected_project_scenario_workflow_action_definition_ids = selected_project_scenario_workflow_action_definition_ids.filter (id) -> id in current_workflow_action_definition_ids + workflow_action_definitions.filter((r) -> r.get('id') in selected_project_scenario_workflow_action_definition_ids) + +get_active_workflow_action_definitions = (workflow_action_definitions, project_scenarios, set_operation = 'union') -> + project_scenarios = Ember.makeArray(project_scenarios) + workflow_action_definitions = workflow_action_definitions.toArray() + # initialize unused working workflow-action-definitions + if project_scenarios.length + if set_operation is 'intersection' + get_active_workflow_action_definitions_using_intersection(workflow_action_definitions, project_scenarios) + else + get_active_workflow_action_definitions_using_union(project_scenarios) + else + # no filtering can be allplied, return all + workflow_action_definitions + +export { get_active_workflow_action_definitions_using_union } +export { get_active_workflow_action_definitions_using_intersection } +export { get_active_workflow_action_definitions } + +export default get_active_workflow_action_definitions diff --git a/addon/utils/project/active-workflow-action-definitions.js b/addon/utils/project/active-workflow-action-definitions.js new file mode 100644 index 0000000..37f8034 --- /dev/null +++ b/addon/utils/project/active-workflow-action-definitions.js @@ -0,0 +1,73 @@ +// Generated by CoffeeScript 1.12.5 +var get_active_workflow_action_definitions, get_active_workflow_action_definitions_using_intersection, get_active_workflow_action_definitions_using_union, + indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }; + +import Ember from 'ember'; + +get_active_workflow_action_definitions_using_union = function(project_scenarios) { + var selected_project_scenario_workflow_action_definition_ids, working_workflow_action_definitions; + selected_project_scenario_workflow_action_definition_ids = []; + working_workflow_action_definitions = []; + project_scenarios.forEach(function(selected_project_scenario) { + return selected_project_scenario.get('workflow_action_definitions').forEach(function(workflow_action_definition) { + if (selected_project_scenario_workflow_action_definition_ids.indexOf(workflow_action_definition.get('id')) < 0) { + selected_project_scenario_workflow_action_definition_ids.push(workflow_action_definition.get('id')); + return working_workflow_action_definitions.push(workflow_action_definition); + } + }); + }); + return working_workflow_action_definitions; +}; + +get_active_workflow_action_definitions_using_intersection = function(workflow_action_definitions, project_scenarios) { + var first_run, selected_project_scenario_workflow_action_definition_ids; + selected_project_scenario_workflow_action_definition_ids = []; + first_run = true; + project_scenarios.forEach(function(selected_project_scenario) { + var current_workflow_action_definition_ids; + current_workflow_action_definition_ids = selected_project_scenario.get('workflow_action_definitions').mapBy('id').toArray(); + if (first_run) { + selected_project_scenario_workflow_action_definition_ids = current_workflow_action_definition_ids; + first_run = false; + return; + } + return selected_project_scenario_workflow_action_definition_ids = selected_project_scenario_workflow_action_definition_ids.filter(function(id) { + return indexOf.call(current_workflow_action_definition_ids, id) >= 0; + }); + }); + return workflow_action_definitions.filter(function(r) { + var ref; + return ref = r.get('id'), indexOf.call(selected_project_scenario_workflow_action_definition_ids, ref) >= 0; + }); +}; + +get_active_workflow_action_definitions = function(workflow_action_definitions, project_scenarios, set_operation) { + if (set_operation == null) { + set_operation = 'union'; + } + project_scenarios = Ember.makeArray(project_scenarios); + workflow_action_definitions = workflow_action_definitions.toArray(); + if (project_scenarios.length) { + if (set_operation === 'intersection') { + return get_active_workflow_action_definitions_using_intersection(workflow_action_definitions, project_scenarios); + } else { + return get_active_workflow_action_definitions_using_union(project_scenarios); + } + } else { + return workflow_action_definitions; + } +}; + +export { + get_active_workflow_action_definitions_using_union +}; + +export { + get_active_workflow_action_definitions_using_intersection +}; + +export { + get_active_workflow_action_definitions +}; + +export default get_active_workflow_action_definitions; diff --git a/addon/utils/project/active-workflow-groups.coffee b/addon/utils/project/active-workflow-groups.coffee new file mode 100644 index 0000000..2214264 --- /dev/null +++ b/addon/utils/project/active-workflow-groups.coffee @@ -0,0 +1,45 @@ +import Ember from 'ember' +# get_active_workflow_groups(workflow_groups, project_scenarios, set_operation = 'union or intersection') +# this util takes as first arguments the workflow_groups scope +# and tries to apply filtering based on the given project_scenarios and set_operation + +get_active_workflow_groups_using_union = (project_scenarios) -> + selected_project_scenario_workflow_group_ids = [] + working_workflow_groups = [] + project_scenarios.forEach (selected_project_scenario) -> + selected_project_scenario.get('workflow_groups').forEach (workflow_group) -> + if selected_project_scenario_workflow_group_ids.indexOf(workflow_group.get('id')) < 0 + selected_project_scenario_workflow_group_ids.push workflow_group.get('id') + working_workflow_groups.push workflow_group + working_workflow_groups + +get_active_workflow_groups_using_intersection = (workflow_groups, project_scenarios) -> + selected_project_scenario_workflow_group_ids = [] + first_run = true + project_scenarios.forEach (selected_project_scenario) -> + current_workflow_group_ids = selected_project_scenario.get('workflow_groups').mapBy('id').toArray() + if first_run + selected_project_scenario_workflow_group_ids = current_workflow_group_ids + first_run = false + return + selected_project_scenario_workflow_group_ids = selected_project_scenario_workflow_group_ids.filter (id) -> id in current_workflow_group_ids + workflow_groups.filter((r) -> r.get('id') in selected_project_scenario_workflow_group_ids) + +get_active_workflow_groups = (workflow_groups, project_scenarios, set_operation = 'union') -> + project_scenarios = Ember.makeArray(project_scenarios) + workflow_groups = workflow_groups.toArray() + # initialize unused working workflow-groups + if project_scenarios.length + if set_operation is 'intersection' + get_active_workflow_groups_using_intersection(workflow_groups, project_scenarios) + else + get_active_workflow_groups_using_union(project_scenarios) + else + # no filtering can be allplied, return all + workflow_groups + +export { get_active_workflow_groups_using_union } +export { get_active_workflow_groups_using_intersection } +export { get_active_workflow_groups } + +export default get_active_workflow_groups diff --git a/addon/utils/project/active-workflow-groups.js b/addon/utils/project/active-workflow-groups.js new file mode 100644 index 0000000..e40bab7 --- /dev/null +++ b/addon/utils/project/active-workflow-groups.js @@ -0,0 +1,73 @@ +// Generated by CoffeeScript 1.12.5 +var get_active_workflow_groups, get_active_workflow_groups_using_intersection, get_active_workflow_groups_using_union, + indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }; + +import Ember from 'ember'; + +get_active_workflow_groups_using_union = function(project_scenarios) { + var selected_project_scenario_workflow_group_ids, working_workflow_groups; + selected_project_scenario_workflow_group_ids = []; + working_workflow_groups = []; + project_scenarios.forEach(function(selected_project_scenario) { + return selected_project_scenario.get('workflow_groups').forEach(function(workflow_group) { + if (selected_project_scenario_workflow_group_ids.indexOf(workflow_group.get('id')) < 0) { + selected_project_scenario_workflow_group_ids.push(workflow_group.get('id')); + return working_workflow_groups.push(workflow_group); + } + }); + }); + return working_workflow_groups; +}; + +get_active_workflow_groups_using_intersection = function(workflow_groups, project_scenarios) { + var first_run, selected_project_scenario_workflow_group_ids; + selected_project_scenario_workflow_group_ids = []; + first_run = true; + project_scenarios.forEach(function(selected_project_scenario) { + var current_workflow_group_ids; + current_workflow_group_ids = selected_project_scenario.get('workflow_groups').mapBy('id').toArray(); + if (first_run) { + selected_project_scenario_workflow_group_ids = current_workflow_group_ids; + first_run = false; + return; + } + return selected_project_scenario_workflow_group_ids = selected_project_scenario_workflow_group_ids.filter(function(id) { + return indexOf.call(current_workflow_group_ids, id) >= 0; + }); + }); + return workflow_groups.filter(function(r) { + var ref; + return ref = r.get('id'), indexOf.call(selected_project_scenario_workflow_group_ids, ref) >= 0; + }); +}; + +get_active_workflow_groups = function(workflow_groups, project_scenarios, set_operation) { + if (set_operation == null) { + set_operation = 'union'; + } + project_scenarios = Ember.makeArray(project_scenarios); + workflow_groups = workflow_groups.toArray(); + if (project_scenarios.length) { + if (set_operation === 'intersection') { + return get_active_workflow_groups_using_intersection(workflow_groups, project_scenarios); + } else { + return get_active_workflow_groups_using_union(project_scenarios); + } + } else { + return workflow_groups; + } +}; + +export { + get_active_workflow_groups_using_union +}; + +export { + get_active_workflow_groups_using_intersection +}; + +export { + get_active_workflow_groups +}; + +export default get_active_workflow_groups; diff --git a/addon/utils/project/contract.coffee b/addon/utils/project/contract.coffee new file mode 100644 index 0000000..af38167 --- /dev/null +++ b/addon/utils/project/contract.coffee @@ -0,0 +1,4 @@ +projectContract = () -> + true + +export default projectContract diff --git a/addon/utils/project/contract.js b/addon/utils/project/contract.js new file mode 100644 index 0000000..8381e31 --- /dev/null +++ b/addon/utils/project/contract.js @@ -0,0 +1,8 @@ +// Generated by CoffeeScript 1.12.5 +var projectContract; + +projectContract = function() { + return true; +}; + +export default projectContract; diff --git a/addon/utils/project/workflow-action-definition-offset-nodes.coffee b/addon/utils/project/workflow-action-definition-offset-nodes.coffee new file mode 100644 index 0000000..f975347 --- /dev/null +++ b/addon/utils/project/workflow-action-definition-offset-nodes.coffee @@ -0,0 +1,295 @@ +#projectGetWorkflowActionDefinitionOffsets = () -> +# true +#export default projectGetWorkflowActionDefinitionOffsets +import Ember from 'ember' + +RecordNode = Ember.Object.extend + resolved: false + title: Ember.computed 'record.title', 'value_shift', -> + title = @get('record.title') + title = " #{title} (#{value_shift}d)" if value_shift = @get('value_shift') + title + +OffsetNode = Ember.Object.extend + record: null + total_offset: 0 + value_shift: 0 + resulting_absolute_value: 0 + +WorkflowActionDefinitionOffsetNodes = Ember.Object.extend + iteration_context_counter: 0 + workflow_action_definitions: [] + reference_date: null + batch_size: 0 + active_project_scenario: null + + forEach: -> + @get('processed_workflow_action_definitions').forEach(arguments...) + + filterBy: -> + @get('processed_workflow_action_definitions').filterBy(arguments...) + + get_resolved: -> + @get('processed_workflow_action_definitions').filterBy('resolved') + + init: -> + @set 'scope_workflow_action_definition_ids', @get('workflow_action_definitions').mapBy('id').toArray() + @set 'processed_workflow_action_definitions', [] + @set 'working_workflow_action_definitions', [] + @get_workflow_action_definition_offsets() + + get_workflow_action_definition_offsets: -> + base = @ + active_scenario_identifier = @get('active_project_scenario.identifier') + scope_workflow_action_definition_ids = @get('scope_workflow_action_definition_ids') + first_loop_actions = [] + working_workflow_action_definitions = [] + @get('workflow_action_definitions').forEach (workflow_action_definition) -> + action_added = false + if workflow_action_definition.get('offset_type') is 'configured' + workflow_action_definition.get('workflow_action_offsets').forEach (workflow_action_offset) -> + return if action_added + return if active_scenario_identifier and workflow_action_offset.get('project_scenario.identifier') and workflow_action_offset.get('project_scenario.identifier') isnt active_scenario_identifier + if not workflow_action_offset.get('base_workflow_action_definition.content') # x_based + [offset_date, offset_value] = base.get_offset_for_target(workflow_action_offset, 0) + [date, value_shift] = base.get_dynamic_offset_for_target(workflow_action_offset, offset_value) + absolute_value = offset_value + value_shift + record_wrapper = RecordNode.create + record: workflow_action_definition + record_id: workflow_action_definition.get('id') + offset_wrappers: [ + OffsetNode.create(total_offset: absolute_value, value_shift: value_shift) + ] + absolute_value: absolute_value + value_shift: value_shift + date: date + resolve_type: 'x' + resolved: true + resolve_level: 0 + first_loop_actions.push record_wrapper + action_added = true + working_workflow_action_definitions.push workflow_action_definition unless action_added + + @set 'processed_workflow_action_definitions', first_loop_actions + @set 'working_workflow_action_definitions', working_workflow_action_definitions + + @incrementProperty 'iteration_context_counter' + #@set 'working_workflow_action_definitions', @get('workflow_action_definitions').filterBy('offset_type', 'configured') + @run_critical_path_resolve_loop() + resolve_level = @get('iteration_context_counter') + @get('working_workflow_action_definitions').forEach (unresolved_workflow_action_definition) => # add unresolved records + offset_wrappers = [] + unresolved_workflow_action_definition.get('workflow_action_offsets').forEach (action_offset) -> + return unless action_offset.get('base_workflow_action_definition.id') in scope_workflow_action_definition_ids # do not add offsets from other scenarios + offset_wrappers.push OffsetNode.create + total_offset: action_offset.get('offset_value') + record: action_offset + @get('processed_workflow_action_definitions').push RecordNode.create + record: unresolved_workflow_action_definition + record_id: unresolved_workflow_action_definition.get('id') + offset_wrappers: offset_wrappers + resolved: false + resolve_level: resolve_level + resolve_type: 'unresolved' + + get_dynamic_offset_for_target: (target, absolute_value) -> + base_date = @get('reference_date').clone().add(absolute_value, 'day') + empty = [base_date, 0] + return empty unless batch_size = @get('batch_size') + return empty unless threshold = target.get('batch_element_threshold_value') + return empty unless threshold_exceedance = Math.floor(batch_size / (0.1 + parseInt(threshold))) # + 0.1 (delta) to exclude exact match of batch_size and threshold. First day is action day + + period_unit = target.get('batch_element_threshold_unit') + offset_value = target.get('offset_value') + operation = if offset_value < 0 then 'subtract' else 'add' + if period_unit is 'businessday' + operation = "business#{operation.capitalize()}" + shifted_date = base_date.clone()[operation](threshold_exceedance, business_days: target.get('batch_element_threshold_business_days')) + else + shifted_date = base_date.clone()[operation](threshold_exceedance, period_unit) + value_shift = shifted_date.diff(base_date, 'day') + [shifted_date, value_shift] + + get_offset_for_target: (target, absolute_value) -> + base_date = @get('reference_date').clone().add(absolute_value, 'day') + empty = [base_date, 0] + return empty unless offset_value = target.get('offset_value') + period_unit = target.get('offset_unit') + operation = if offset_value < 0 then 'subtract' else 'add' + if period_unit is 'businessday' + operation = "business#{operation.capitalize()}" + shifted_date = base_date.clone()[operation](Math.abs(offset_value), business_days: target.get('offset_business_days')) + else + shifted_date = base_date.clone()[operation](Math.abs(offset_value), period_unit) + value_shift = shifted_date.diff(base_date, 'day') + #debugger if target.get('workflow_action_definition.title') is 'Initial check done' + [shifted_date, value_shift] + + run_critical_path_resolve_loop: -> + working_indices_to_be_removed = [] + active_scenario_identifier = @get('active_project_scenario.identifier') + processed_workflow_action_definitions = @get('processed_workflow_action_definitions') + scope_workflow_action_definition_ids = @get('scope_workflow_action_definition_ids') + get_dynamic_offset_for_target = @get('get_dynamic_offset_for_target').bind(@) + get_offset_for_target = @get('get_offset_for_target').bind(@) + resolve_level = @get('iteration_context_counter') + @get('working_workflow_action_definitions').forEach (working_workflow_action_definition, working_index) -> + return unless working_workflow_action_definition.get('offset_type') is 'configured' + farthest_absolute_value = 0 + farthest_absolute_value_value_shift = 0 + farthest_result_total_offset = 0 + date = null # scoping + resolved_offset_wrappers = [] + all_offsets_resolved = working_workflow_action_definition.get('workflow_action_offsets').every (workflow_action_offset) -> + return true if active_scenario_identifier and workflow_action_offset.get('project_scenario.identifier') and workflow_action_offset.get('project_scenario.identifier') isnt active_scenario_identifier + unless processed_base_workflow_action_definition_wrapper = processed_workflow_action_definitions.findBy 'record_id', workflow_action_offset.get('base_workflow_action_definition.id') + if workflow_action_offset.get('base_workflow_action_definition.id') not in scope_workflow_action_definition_ids # base is not present in this scenario, do not pursue this connection + return true + return false + processed_absolute_value = processed_base_workflow_action_definition_wrapper.get('absolute_value') + [offset_date, working_offset_value] = get_offset_for_target(workflow_action_offset, processed_absolute_value) + [date, current_connection_value_shift] = get_dynamic_offset_for_target(workflow_action_offset, processed_absolute_value + working_offset_value) + current_connection_total_offset_value = current_connection_value_shift + working_offset_value + current_connection_absolute_value = processed_absolute_value + current_connection_total_offset_value + #debugger if processed_base_workflow_action_definition_wrapper.get('record.title') is 'Initial check done' + #debugger if working_workflow_action_definition.get('title') is 'Initial check done' + if Math.abs(current_connection_absolute_value) > Math.abs(farthest_absolute_value) + farthest_absolute_value = current_connection_absolute_value + farthest_absolute_value_value_shift = current_connection_value_shift + farthest_result_total_offset = current_connection_total_offset_value + resolved_offset_wrappers.push OffsetNode.create + record: workflow_action_offset + value_shift: current_connection_value_shift + total_offset: current_connection_total_offset_value + resulting_absolute_value: current_connection_absolute_value + true # indicate processed_base_workflow_action_definition_wrapper found for all_offsets_resolved value + + if all_offsets_resolved and resolved_offset_wrappers.length + record_wrapper = RecordNode.create + record: working_workflow_action_definition + record_id: working_workflow_action_definition.get('id') + offset_wrappers: resolved_offset_wrappers + value_shift: farthest_absolute_value_value_shift + absolute_value: farthest_absolute_value + date: date + resolved: true + resolve_level: resolve_level + resolve_type: 'critical-path' + processed_workflow_action_definitions.push record_wrapper + working_indices_to_be_removed.push working_index + if working_indices_to_be_removed.length + @get('working_workflow_action_definitions').splice(working_indices_to_be_removed.pop(), 1) while working_indices_to_be_removed.length + @run_critical_path_resolve_loop() + else + @incrementProperty 'iteration_context_counter' + @run_unresolved_tree_resolve_loop() + + run_unresolved_tree_resolve_loop: -> + working_indices_to_be_removed = [] + active_scenario_identifier = @get('active_project_scenario.identifier') + processed_workflow_action_definitions = @get('processed_workflow_action_definitions') + scope_workflow_action_definition_ids = @get('scope_workflow_action_definition_ids') + get_offset_for_target = @get('get_offset_for_target').bind(@) + get_dynamic_offset_for_target = @get('get_dynamic_offset_for_target').bind(@) + resolve_level = @get('iteration_context_counter') + @get('working_workflow_action_definitions').forEach (working_workflow_action_definition, working_index) -> + return unless working_workflow_action_definition.get('offset_type') is 'configured' + farthest_absolute_value = 0 + farthest_absolute_value_value_shift = 0 + farthest_result_total_offset = 0 + date = null # scoping + resolved_offset_wrappers = [] + working_workflow_action_definition.get('workflow_action_offsets').forEach (workflow_action_offset) -> + return if active_scenario_identifier and workflow_action_offset.get('project_scenario.identifier') and workflow_action_offset.get('project_scenario.identifier') isnt active_scenario_identifier + return unless workflow_action_offset.get('base_workflow_action_definition.id') in scope_workflow_action_definition_ids + return unless processed_base_workflow_action_definition_wrapper = processed_workflow_action_definitions.findBy 'record_id', workflow_action_offset.get('base_workflow_action_definition.id') + processed_absolute_value = processed_base_workflow_action_definition_wrapper.get('absolute_value') + [offset_date, working_offset_value] = get_offset_for_target(workflow_action_offset, processed_absolute_value) + [date, current_connection_value_shift] = get_dynamic_offset_for_target(workflow_action_offset, processed_absolute_value + working_offset_value) + current_connection_total_offset_value = current_connection_value_shift + working_offset_value + current_connection_absolute_value = processed_absolute_value + current_connection_total_offset_value + #debugger if processed_base_workflow_action_definition_wrapper.get('record.title') is 'Initial check done' + #debugger if working_workflow_action_definition.get('title') is 'Initial check done' + if Math.abs(current_connection_absolute_value) > Math.abs(farthest_absolute_value) + farthest_absolute_value = current_connection_absolute_value + farthest_absolute_value_value_shift = current_connection_value_shift + farthest_result_total_offset = current_connection_total_offset_value + resolved_offset_wrappers.push OffsetNode.create + record: workflow_action_offset + value_shift: current_connection_value_shift + total_offset: current_connection_total_offset_value + resulting_absolute_value: current_connection_absolute_value + if resolved_offset_wrappers.length + record_wrapper = RecordNode.create + record: working_workflow_action_definition + record_id: working_workflow_action_definition.get('id') + offset_wrappers: resolved_offset_wrappers # .clone()? + value_shift: farthest_absolute_value_value_shift + absolute_value: farthest_absolute_value + date: date + resolved: true + resolve_level: resolve_level + resolve_type: 'with-non-rooted-offsets' + processed_workflow_action_definitions.push record_wrapper + working_indices_to_be_removed.push working_index + + if working_indices_to_be_removed.length + @get('working_workflow_action_definitions').splice(working_indices_to_be_removed.pop(), 1) while working_indices_to_be_removed.length + @run_critical_path_resolve_loop() + else + @run_offset_forwarding_loop() + + run_offset_forwarding_loop: -> + working_indices_to_be_removed = [] + active_scenario_identifier = @get('active_project_scenario.identifier') + processed_workflow_action_definitions = @get('processed_workflow_action_definitions') + scope_workflow_action_definition_ids = @get('scope_workflow_action_definition_ids') + get_offset_for_target = @get('get_offset_for_target').bind(@) + get_dynamic_offset_for_target = @get('get_dynamic_offset_for_target').bind(@) + resolve_level = @get('iteration_context_counter') + @get('working_workflow_action_definitions').forEach (working_workflow_action_definition, working_index) -> + farthest_absolute_value = 0 + farthest_absolute_value_value_shift = 0 + farthest_result_total_offset = 0 + date = null # scoping + resolved_offset_wrappers = [] + working_workflow_action_definition.get('dependent_workflow_action_offsets').forEach (workflow_action_offset) -> + return if active_scenario_identifier and workflow_action_offset.get('project_scenario.identifier') and workflow_action_offset.get('project_scenario.identifier') isnt active_scenario_identifier + return unless workflow_action_offset.get('workflow_action_definition.id') in scope_workflow_action_definition_ids + return unless processed_base_workflow_action_definition_wrapper = processed_workflow_action_definitions.findBy 'record_id', workflow_action_offset.get('workflow_action_definition.id') + #return if workflow_action_offset.get('workflow_action_definition.offset_type') is 'none' # do not resolve on none offset typed nodes + processed_absolute_value = processed_base_workflow_action_definition_wrapper.get('absolute_value') + [offset_date, working_offset_value] = get_offset_for_target(workflow_action_offset, processed_absolute_value) + [date, current_connection_value_shift] = get_dynamic_offset_for_target(workflow_action_offset, processed_absolute_value + working_offset_value) + current_connection_total_offset_value = current_connection_value_shift + working_offset_value + #NOTE MINUS!!!!!!!!!!!!!!!!!!!!! + current_connection_absolute_value = processed_absolute_value - current_connection_total_offset_value + #debugger if processed_base_workflow_action_definition_wrapper.get('record.title') is 'Initial check done' + #debugger if working_workflow_action_definition.get('title') is 'Initial check done' + if Math.abs(current_connection_absolute_value) > Math.abs(farthest_absolute_value) + farthest_absolute_value = current_connection_absolute_value + farthest_absolute_value_value_shift = current_connection_value_shift + farthest_result_total_offset = current_connection_total_offset_value + resolved_offset_wrappers.push OffsetNode.create + record: workflow_action_offset + value_shift: current_connection_value_shift + total_offset: current_connection_total_offset_value + resulting_absolute_value: current_connection_absolute_value + if resolved_offset_wrappers.length + record_wrapper = RecordNode.create + record: working_workflow_action_definition + record_id: working_workflow_action_definition.get('id') + offset_wrappers: resolved_offset_wrappers # .clone()? + value_shift: farthest_absolute_value_value_shift + absolute_value: farthest_absolute_value + date: date + resolved: true + resolve_level: resolve_level + resolve_type: 'offset-lookup-inversion' + processed_workflow_action_definitions.push record_wrapper + working_indices_to_be_removed.push working_index + + if working_indices_to_be_removed.length + @get('working_workflow_action_definitions').splice(working_indices_to_be_removed.pop(), 1) while working_indices_to_be_removed.length + @run_critical_path_resolve_loop() +export default WorkflowActionDefinitionOffsetNodes diff --git a/addon/utils/project/workflow-action-definition-offset-nodes.js b/addon/utils/project/workflow-action-definition-offset-nodes.js new file mode 100644 index 0000000..05e06a4 --- /dev/null +++ b/addon/utils/project/workflow-action-definition-offset-nodes.js @@ -0,0 +1,389 @@ +// Generated by CoffeeScript 1.12.5 +var OffsetNode, RecordNode, WorkflowActionDefinitionOffsetNodes, + indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }; + +import Ember from 'ember'; + +RecordNode = Ember.Object.extend({ + resolved: false, + title: Ember.computed('record.title', 'value_shift', function() { + var title, value_shift; + title = this.get('record.title'); + if (value_shift = this.get('value_shift')) { + title = " " + title + " (" + value_shift + "d)"; + } + return title; + }) +}); + +OffsetNode = Ember.Object.extend({ + record: null, + total_offset: 0, + value_shift: 0, + resulting_absolute_value: 0 +}); + +WorkflowActionDefinitionOffsetNodes = Ember.Object.extend({ + iteration_context_counter: 0, + workflow_action_definitions: [], + reference_date: null, + batch_size: 0, + active_project_scenario: null, + forEach: function() { + var ref; + return (ref = this.get('processed_workflow_action_definitions')).forEach.apply(ref, arguments); + }, + filterBy: function() { + var ref; + return (ref = this.get('processed_workflow_action_definitions')).filterBy.apply(ref, arguments); + }, + get_resolved: function() { + return this.get('processed_workflow_action_definitions').filterBy('resolved'); + }, + init: function() { + this.set('scope_workflow_action_definition_ids', this.get('workflow_action_definitions').mapBy('id').toArray()); + this.set('processed_workflow_action_definitions', []); + this.set('working_workflow_action_definitions', []); + return this.get_workflow_action_definition_offsets(); + }, + get_workflow_action_definition_offsets: function() { + var active_scenario_identifier, base, first_loop_actions, resolve_level, scope_workflow_action_definition_ids, working_workflow_action_definitions; + base = this; + active_scenario_identifier = this.get('active_project_scenario.identifier'); + scope_workflow_action_definition_ids = this.get('scope_workflow_action_definition_ids'); + first_loop_actions = []; + working_workflow_action_definitions = []; + this.get('workflow_action_definitions').forEach(function(workflow_action_definition) { + var action_added; + action_added = false; + if (workflow_action_definition.get('offset_type') === 'configured') { + workflow_action_definition.get('workflow_action_offsets').forEach(function(workflow_action_offset) { + var absolute_value, date, offset_date, offset_value, record_wrapper, ref, ref1, value_shift; + if (action_added) { + return; + } + if (active_scenario_identifier && workflow_action_offset.get('project_scenario.identifier') && workflow_action_offset.get('project_scenario.identifier') !== active_scenario_identifier) { + return; + } + if (!workflow_action_offset.get('base_workflow_action_definition.content')) { + ref = base.get_offset_for_target(workflow_action_offset, 0), offset_date = ref[0], offset_value = ref[1]; + ref1 = base.get_dynamic_offset_for_target(workflow_action_offset, offset_value), date = ref1[0], value_shift = ref1[1]; + absolute_value = offset_value + value_shift; + record_wrapper = RecordNode.create({ + record: workflow_action_definition, + record_id: workflow_action_definition.get('id'), + offset_wrappers: [ + OffsetNode.create({ + total_offset: absolute_value, + value_shift: value_shift + }) + ], + absolute_value: absolute_value, + value_shift: value_shift, + date: date, + resolve_type: 'x', + resolved: true, + resolve_level: 0 + }); + first_loop_actions.push(record_wrapper); + return action_added = true; + } + }); + } + if (!action_added) { + return working_workflow_action_definitions.push(workflow_action_definition); + } + }); + this.set('processed_workflow_action_definitions', first_loop_actions); + this.set('working_workflow_action_definitions', working_workflow_action_definitions); + this.incrementProperty('iteration_context_counter'); + this.run_critical_path_resolve_loop(); + resolve_level = this.get('iteration_context_counter'); + return this.get('working_workflow_action_definitions').forEach((function(_this) { + return function(unresolved_workflow_action_definition) { + var offset_wrappers; + offset_wrappers = []; + unresolved_workflow_action_definition.get('workflow_action_offsets').forEach(function(action_offset) { + var ref; + if (ref = action_offset.get('base_workflow_action_definition.id'), indexOf.call(scope_workflow_action_definition_ids, ref) < 0) { + return; + } + return offset_wrappers.push(OffsetNode.create({ + total_offset: action_offset.get('offset_value'), + record: action_offset + })); + }); + return _this.get('processed_workflow_action_definitions').push(RecordNode.create({ + record: unresolved_workflow_action_definition, + record_id: unresolved_workflow_action_definition.get('id'), + offset_wrappers: offset_wrappers, + resolved: false, + resolve_level: resolve_level, + resolve_type: 'unresolved' + })); + }; + })(this)); + }, + get_dynamic_offset_for_target: function(target, absolute_value) { + var base_date, batch_size, empty, offset_value, operation, period_unit, shifted_date, threshold, threshold_exceedance, value_shift; + base_date = this.get('reference_date').clone().add(absolute_value, 'day'); + empty = [base_date, 0]; + if (!(batch_size = this.get('batch_size'))) { + return empty; + } + if (!(threshold = target.get('batch_element_threshold_value'))) { + return empty; + } + if (!(threshold_exceedance = Math.floor(batch_size / (0.1 + parseInt(threshold))))) { + return empty; + } + period_unit = target.get('batch_element_threshold_unit'); + offset_value = target.get('offset_value'); + operation = offset_value < 0 ? 'subtract' : 'add'; + if (period_unit === 'businessday') { + operation = "business" + (operation.capitalize()); + shifted_date = base_date.clone()[operation](threshold_exceedance, { + business_days: target.get('batch_element_threshold_business_days') + }); + } else { + shifted_date = base_date.clone()[operation](threshold_exceedance, period_unit); + } + value_shift = shifted_date.diff(base_date, 'day'); + return [shifted_date, value_shift]; + }, + get_offset_for_target: function(target, absolute_value) { + var base_date, empty, offset_value, operation, period_unit, shifted_date, value_shift; + base_date = this.get('reference_date').clone().add(absolute_value, 'day'); + empty = [base_date, 0]; + if (!(offset_value = target.get('offset_value'))) { + return empty; + } + period_unit = target.get('offset_unit'); + operation = offset_value < 0 ? 'subtract' : 'add'; + if (period_unit === 'businessday') { + operation = "business" + (operation.capitalize()); + shifted_date = base_date.clone()[operation](Math.abs(offset_value), { + business_days: target.get('offset_business_days') + }); + } else { + shifted_date = base_date.clone()[operation](Math.abs(offset_value), period_unit); + } + value_shift = shifted_date.diff(base_date, 'day'); + return [shifted_date, value_shift]; + }, + run_critical_path_resolve_loop: function() { + var active_scenario_identifier, get_dynamic_offset_for_target, get_offset_for_target, processed_workflow_action_definitions, resolve_level, scope_workflow_action_definition_ids, working_indices_to_be_removed; + working_indices_to_be_removed = []; + active_scenario_identifier = this.get('active_project_scenario.identifier'); + processed_workflow_action_definitions = this.get('processed_workflow_action_definitions'); + scope_workflow_action_definition_ids = this.get('scope_workflow_action_definition_ids'); + get_dynamic_offset_for_target = this.get('get_dynamic_offset_for_target').bind(this); + get_offset_for_target = this.get('get_offset_for_target').bind(this); + resolve_level = this.get('iteration_context_counter'); + this.get('working_workflow_action_definitions').forEach(function(working_workflow_action_definition, working_index) { + var all_offsets_resolved, date, farthest_absolute_value, farthest_absolute_value_value_shift, farthest_result_total_offset, record_wrapper, resolved_offset_wrappers; + if (working_workflow_action_definition.get('offset_type') !== 'configured') { + return; + } + farthest_absolute_value = 0; + farthest_absolute_value_value_shift = 0; + farthest_result_total_offset = 0; + date = null; + resolved_offset_wrappers = []; + all_offsets_resolved = working_workflow_action_definition.get('workflow_action_offsets').every(function(workflow_action_offset) { + var current_connection_absolute_value, current_connection_total_offset_value, current_connection_value_shift, offset_date, processed_absolute_value, processed_base_workflow_action_definition_wrapper, ref, ref1, ref2, working_offset_value; + if (active_scenario_identifier && workflow_action_offset.get('project_scenario.identifier') && workflow_action_offset.get('project_scenario.identifier') !== active_scenario_identifier) { + return true; + } + if (!(processed_base_workflow_action_definition_wrapper = processed_workflow_action_definitions.findBy('record_id', workflow_action_offset.get('base_workflow_action_definition.id')))) { + if (ref = workflow_action_offset.get('base_workflow_action_definition.id'), indexOf.call(scope_workflow_action_definition_ids, ref) < 0) { + return true; + } + return false; + } + processed_absolute_value = processed_base_workflow_action_definition_wrapper.get('absolute_value'); + ref1 = get_offset_for_target(workflow_action_offset, processed_absolute_value), offset_date = ref1[0], working_offset_value = ref1[1]; + ref2 = get_dynamic_offset_for_target(workflow_action_offset, processed_absolute_value + working_offset_value), date = ref2[0], current_connection_value_shift = ref2[1]; + current_connection_total_offset_value = current_connection_value_shift + working_offset_value; + current_connection_absolute_value = processed_absolute_value + current_connection_total_offset_value; + if (Math.abs(current_connection_absolute_value) > Math.abs(farthest_absolute_value)) { + farthest_absolute_value = current_connection_absolute_value; + farthest_absolute_value_value_shift = current_connection_value_shift; + farthest_result_total_offset = current_connection_total_offset_value; + } + resolved_offset_wrappers.push(OffsetNode.create({ + record: workflow_action_offset, + value_shift: current_connection_value_shift, + total_offset: current_connection_total_offset_value, + resulting_absolute_value: current_connection_absolute_value + })); + return true; + }); + if (all_offsets_resolved && resolved_offset_wrappers.length) { + record_wrapper = RecordNode.create({ + record: working_workflow_action_definition, + record_id: working_workflow_action_definition.get('id'), + offset_wrappers: resolved_offset_wrappers, + value_shift: farthest_absolute_value_value_shift, + absolute_value: farthest_absolute_value, + date: date, + resolved: true, + resolve_level: resolve_level, + resolve_type: 'critical-path' + }); + processed_workflow_action_definitions.push(record_wrapper); + return working_indices_to_be_removed.push(working_index); + } + }); + if (working_indices_to_be_removed.length) { + while (working_indices_to_be_removed.length) { + this.get('working_workflow_action_definitions').splice(working_indices_to_be_removed.pop(), 1); + } + return this.run_critical_path_resolve_loop(); + } else { + this.incrementProperty('iteration_context_counter'); + return this.run_unresolved_tree_resolve_loop(); + } + }, + run_unresolved_tree_resolve_loop: function() { + var active_scenario_identifier, get_dynamic_offset_for_target, get_offset_for_target, processed_workflow_action_definitions, resolve_level, scope_workflow_action_definition_ids, working_indices_to_be_removed; + working_indices_to_be_removed = []; + active_scenario_identifier = this.get('active_project_scenario.identifier'); + processed_workflow_action_definitions = this.get('processed_workflow_action_definitions'); + scope_workflow_action_definition_ids = this.get('scope_workflow_action_definition_ids'); + get_offset_for_target = this.get('get_offset_for_target').bind(this); + get_dynamic_offset_for_target = this.get('get_dynamic_offset_for_target').bind(this); + resolve_level = this.get('iteration_context_counter'); + this.get('working_workflow_action_definitions').forEach(function(working_workflow_action_definition, working_index) { + var date, farthest_absolute_value, farthest_absolute_value_value_shift, farthest_result_total_offset, record_wrapper, resolved_offset_wrappers; + if (working_workflow_action_definition.get('offset_type') !== 'configured') { + return; + } + farthest_absolute_value = 0; + farthest_absolute_value_value_shift = 0; + farthest_result_total_offset = 0; + date = null; + resolved_offset_wrappers = []; + working_workflow_action_definition.get('workflow_action_offsets').forEach(function(workflow_action_offset) { + var current_connection_absolute_value, current_connection_total_offset_value, current_connection_value_shift, offset_date, processed_absolute_value, processed_base_workflow_action_definition_wrapper, ref, ref1, ref2, working_offset_value; + if (active_scenario_identifier && workflow_action_offset.get('project_scenario.identifier') && workflow_action_offset.get('project_scenario.identifier') !== active_scenario_identifier) { + return; + } + if (ref = workflow_action_offset.get('base_workflow_action_definition.id'), indexOf.call(scope_workflow_action_definition_ids, ref) < 0) { + return; + } + if (!(processed_base_workflow_action_definition_wrapper = processed_workflow_action_definitions.findBy('record_id', workflow_action_offset.get('base_workflow_action_definition.id')))) { + return; + } + processed_absolute_value = processed_base_workflow_action_definition_wrapper.get('absolute_value'); + ref1 = get_offset_for_target(workflow_action_offset, processed_absolute_value), offset_date = ref1[0], working_offset_value = ref1[1]; + ref2 = get_dynamic_offset_for_target(workflow_action_offset, processed_absolute_value + working_offset_value), date = ref2[0], current_connection_value_shift = ref2[1]; + current_connection_total_offset_value = current_connection_value_shift + working_offset_value; + current_connection_absolute_value = processed_absolute_value + current_connection_total_offset_value; + if (Math.abs(current_connection_absolute_value) > Math.abs(farthest_absolute_value)) { + farthest_absolute_value = current_connection_absolute_value; + farthest_absolute_value_value_shift = current_connection_value_shift; + farthest_result_total_offset = current_connection_total_offset_value; + } + return resolved_offset_wrappers.push(OffsetNode.create({ + record: workflow_action_offset, + value_shift: current_connection_value_shift, + total_offset: current_connection_total_offset_value, + resulting_absolute_value: current_connection_absolute_value + })); + }); + if (resolved_offset_wrappers.length) { + record_wrapper = RecordNode.create({ + record: working_workflow_action_definition, + record_id: working_workflow_action_definition.get('id'), + offset_wrappers: resolved_offset_wrappers, + value_shift: farthest_absolute_value_value_shift, + absolute_value: farthest_absolute_value, + date: date, + resolved: true, + resolve_level: resolve_level, + resolve_type: 'with-non-rooted-offsets' + }); + processed_workflow_action_definitions.push(record_wrapper); + return working_indices_to_be_removed.push(working_index); + } + }); + if (working_indices_to_be_removed.length) { + while (working_indices_to_be_removed.length) { + this.get('working_workflow_action_definitions').splice(working_indices_to_be_removed.pop(), 1); + } + return this.run_critical_path_resolve_loop(); + } else { + return this.run_offset_forwarding_loop(); + } + }, + run_offset_forwarding_loop: function() { + var active_scenario_identifier, get_dynamic_offset_for_target, get_offset_for_target, processed_workflow_action_definitions, resolve_level, scope_workflow_action_definition_ids, working_indices_to_be_removed; + working_indices_to_be_removed = []; + active_scenario_identifier = this.get('active_project_scenario.identifier'); + processed_workflow_action_definitions = this.get('processed_workflow_action_definitions'); + scope_workflow_action_definition_ids = this.get('scope_workflow_action_definition_ids'); + get_offset_for_target = this.get('get_offset_for_target').bind(this); + get_dynamic_offset_for_target = this.get('get_dynamic_offset_for_target').bind(this); + resolve_level = this.get('iteration_context_counter'); + this.get('working_workflow_action_definitions').forEach(function(working_workflow_action_definition, working_index) { + var date, farthest_absolute_value, farthest_absolute_value_value_shift, farthest_result_total_offset, record_wrapper, resolved_offset_wrappers; + farthest_absolute_value = 0; + farthest_absolute_value_value_shift = 0; + farthest_result_total_offset = 0; + date = null; + resolved_offset_wrappers = []; + working_workflow_action_definition.get('dependent_workflow_action_offsets').forEach(function(workflow_action_offset) { + var current_connection_absolute_value, current_connection_total_offset_value, current_connection_value_shift, offset_date, processed_absolute_value, processed_base_workflow_action_definition_wrapper, ref, ref1, ref2, working_offset_value; + if (active_scenario_identifier && workflow_action_offset.get('project_scenario.identifier') && workflow_action_offset.get('project_scenario.identifier') !== active_scenario_identifier) { + return; + } + if (ref = workflow_action_offset.get('workflow_action_definition.id'), indexOf.call(scope_workflow_action_definition_ids, ref) < 0) { + return; + } + if (!(processed_base_workflow_action_definition_wrapper = processed_workflow_action_definitions.findBy('record_id', workflow_action_offset.get('workflow_action_definition.id')))) { + return; + } + processed_absolute_value = processed_base_workflow_action_definition_wrapper.get('absolute_value'); + ref1 = get_offset_for_target(workflow_action_offset, processed_absolute_value), offset_date = ref1[0], working_offset_value = ref1[1]; + ref2 = get_dynamic_offset_for_target(workflow_action_offset, processed_absolute_value + working_offset_value), date = ref2[0], current_connection_value_shift = ref2[1]; + current_connection_total_offset_value = current_connection_value_shift + working_offset_value; + current_connection_absolute_value = processed_absolute_value - current_connection_total_offset_value; + if (Math.abs(current_connection_absolute_value) > Math.abs(farthest_absolute_value)) { + farthest_absolute_value = current_connection_absolute_value; + farthest_absolute_value_value_shift = current_connection_value_shift; + farthest_result_total_offset = current_connection_total_offset_value; + } + return resolved_offset_wrappers.push(OffsetNode.create({ + record: workflow_action_offset, + value_shift: current_connection_value_shift, + total_offset: current_connection_total_offset_value, + resulting_absolute_value: current_connection_absolute_value + })); + }); + if (resolved_offset_wrappers.length) { + record_wrapper = RecordNode.create({ + record: working_workflow_action_definition, + record_id: working_workflow_action_definition.get('id'), + offset_wrappers: resolved_offset_wrappers, + value_shift: farthest_absolute_value_value_shift, + absolute_value: farthest_absolute_value, + date: date, + resolved: true, + resolve_level: resolve_level, + resolve_type: 'offset-lookup-inversion' + }); + processed_workflow_action_definitions.push(record_wrapper); + return working_indices_to_be_removed.push(working_index); + } + }); + if (working_indices_to_be_removed.length) { + while (working_indices_to_be_removed.length) { + this.get('working_workflow_action_definitions').splice(working_indices_to_be_removed.pop(), 1); + } + return this.run_critical_path_resolve_loop(); + } + } +}); + +export default WorkflowActionDefinitionOffsetNodes; diff --git a/addon/validations/organization.coffee b/addon/validations/organization.coffee new file mode 100644 index 0000000..73362b4 --- /dev/null +++ b/addon/validations/organization.coffee @@ -0,0 +1,10 @@ +import { + validatePresence +} from 'ember-changeset-validations/validators' +import buildMessage from 'ember-changeset-validations/utils/validation-errors' + +validations = + name: validatePresence presence: true, message: (key, type) -> + buildMessage t("attributes.workflow_action_definition.#{key}"), type: type +export default validations + diff --git a/addon/validations/organization.js b/addon/validations/organization.js new file mode 100644 index 0000000..679e0d9 --- /dev/null +++ b/addon/validations/organization.js @@ -0,0 +1,21 @@ +// Generated by CoffeeScript 1.12.5 +var validations; + +import { + validatePresence +} from 'ember-changeset-validations/validators'; + +import buildMessage from 'ember-changeset-validations/utils/validation-errors'; + +validations = { + name: validatePresence({ + presence: true, + message: function(key, type) { + return buildMessage(t("attributes.workflow_action_definition." + key), { + type: type + }); + } + }) +}; + +export default validations; diff --git a/addon/validations/project-instance.coffee b/addon/validations/project-instance.coffee new file mode 100644 index 0000000..65e9893 --- /dev/null +++ b/addon/validations/project-instance.coffee @@ -0,0 +1,10 @@ +import { + validatePresence +} from 'ember-changeset-validations/validators' +import buildMessage from 'ember-changeset-validations/utils/validation-errors' + +validations = + client_identifier: validatePresence presence: true, message: (key, type) -> + buildMessage t("attributes.project_instance.#{key}"), type: type +export default validations + diff --git a/addon/validations/project-instance.js b/addon/validations/project-instance.js new file mode 100644 index 0000000..d323acb --- /dev/null +++ b/addon/validations/project-instance.js @@ -0,0 +1,21 @@ +// Generated by CoffeeScript 1.12.5 +var validations; + +import { + validatePresence +} from 'ember-changeset-validations/validators'; + +import buildMessage from 'ember-changeset-validations/utils/validation-errors'; + +validations = { + client_identifier: validatePresence({ + presence: true, + message: function(key, type) { + return buildMessage(t("attributes.project_instance." + key), { + type: type + }); + } + }) +}; + +export default validations; diff --git a/addon/validations/project.coffee b/addon/validations/project.coffee new file mode 100644 index 0000000..73362b4 --- /dev/null +++ b/addon/validations/project.coffee @@ -0,0 +1,10 @@ +import { + validatePresence +} from 'ember-changeset-validations/validators' +import buildMessage from 'ember-changeset-validations/utils/validation-errors' + +validations = + name: validatePresence presence: true, message: (key, type) -> + buildMessage t("attributes.workflow_action_definition.#{key}"), type: type +export default validations + diff --git a/addon/validations/project.js b/addon/validations/project.js new file mode 100644 index 0000000..679e0d9 --- /dev/null +++ b/addon/validations/project.js @@ -0,0 +1,21 @@ +// Generated by CoffeeScript 1.12.5 +var validations; + +import { + validatePresence +} from 'ember-changeset-validations/validators'; + +import buildMessage from 'ember-changeset-validations/utils/validation-errors'; + +validations = { + name: validatePresence({ + presence: true, + message: function(key, type) { + return buildMessage(t("attributes.workflow_action_definition." + key), { + type: type + }); + } + }) +}; + +export default validations; diff --git a/addon/validations/user.coffee b/addon/validations/user.coffee new file mode 100644 index 0000000..1ba4719 --- /dev/null +++ b/addon/validations/user.coffee @@ -0,0 +1,14 @@ +import { + validatePresence, + validateFormat +} from 'ember-changeset-validations/validators' +import buildMessage from 'ember-changeset-validations/utils/validation-errors' + +validations = + nickname: validatePresence presence: true, message: (key, type) -> + buildMessage t("attributes.user.#{key}"), type: type + email: validateFormat type: 'email', message: (key, type) -> + buildMessage t("attributes.user.#{key}"), type: type + +export default validations + diff --git a/addon/validations/user.js b/addon/validations/user.js new file mode 100644 index 0000000..1a4c226 --- /dev/null +++ b/addon/validations/user.js @@ -0,0 +1,30 @@ +// Generated by CoffeeScript 1.12.5 +var validations; + +import { + validatePresence, + validateFormat +} from 'ember-changeset-validations/validators'; + +import buildMessage from 'ember-changeset-validations/utils/validation-errors'; + +validations = { + nickname: validatePresence({ + presence: true, + message: function(key, type) { + return buildMessage(t("attributes.user." + key), { + type: type + }); + } + }), + email: validateFormat({ + type: 'email', + message: function(key, type) { + return buildMessage(t("attributes.user." + key), { + type: type + }); + } + }) +}; + +export default validations; diff --git a/addon/validations/workflow-action-definition.coffee b/addon/validations/workflow-action-definition.coffee new file mode 100644 index 0000000..5b9fde8 --- /dev/null +++ b/addon/validations/workflow-action-definition.coffee @@ -0,0 +1,10 @@ +import { + validatePresence +} from 'ember-changeset-validations/validators' +import buildMessage from 'ember-changeset-validations/utils/validation-errors' + +validations = + title: validatePresence presence: true, message: (key, type) -> + buildMessage t("attributes.workflow_action_definition.#{key}"), type: type +export default validations + diff --git a/addon/validations/workflow-action-definition.js b/addon/validations/workflow-action-definition.js new file mode 100644 index 0000000..27e1bfa --- /dev/null +++ b/addon/validations/workflow-action-definition.js @@ -0,0 +1,21 @@ +// Generated by CoffeeScript 1.12.5 +var validations; + +import { + validatePresence +} from 'ember-changeset-validations/validators'; + +import buildMessage from 'ember-changeset-validations/utils/validation-errors'; + +validations = { + title: validatePresence({ + presence: true, + message: function(key, type) { + return buildMessage(t("attributes.workflow_action_definition." + key), { + type: type + }); + } + }) +}; + +export default validations; diff --git a/app/.gitkeep b/app/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/app/components/action-list/organization-filter.js b/app/components/action-list/organization-filter.js new file mode 100644 index 0000000..26b1472 --- /dev/null +++ b/app/components/action-list/organization-filter.js @@ -0,0 +1 @@ +export { default } from 'ember-panda/components/action-list/organization-filter'; diff --git a/app/components/action-list/project-filter.js b/app/components/action-list/project-filter.js new file mode 100644 index 0000000..a59ea59 --- /dev/null +++ b/app/components/action-list/project-filter.js @@ -0,0 +1 @@ +export { default } from 'ember-panda/components/action-list/project-filter'; diff --git a/app/components/format-json.coffee b/app/components/format-json.coffee new file mode 100644 index 0000000..454725f --- /dev/null +++ b/app/components/format-json.coffee @@ -0,0 +1,14 @@ +import Ember from 'ember' +import JSONFormatter from 'npm:json-formatter-js' + +export default Ember.Component.extend + classNames: ['ui', 'secondary', 'segment', 'format-json-container'] + value: {} + nesting_level: Infinity + top_title: 'JSON' + didInsertElement: -> + formatter = new JSONFormatter.default(@get('value'), @get('nesting_level')) + v = formatter.render() + tree = $(v) + tree.find('.json-formatter-constructor-name:first').removeClass('json-formatter-constructor-name').addClass('json-formatter-top-node-name').text(@get('top_title')) + @$().append(v) diff --git a/app/components/labeled-content-choice.js b/app/components/labeled-content-choice.js new file mode 100644 index 0000000..926b613 --- /dev/null +++ b/app/components/labeled-content-choice.js @@ -0,0 +1,4 @@ +import Ember from 'ember'; + +export default Ember.Component.extend({ +}); diff --git a/app/components/modals/base-disabled.coffee b/app/components/modals/base-disabled.coffee new file mode 100644 index 0000000..8ffccc7 --- /dev/null +++ b/app/components/modals/base-disabled.coffee @@ -0,0 +1,85 @@ +import Ember from 'ember' +export default Ember.Controller.extend + alert_message: "" + modal_options: {} + layoutName: 'modals/layout' + willOpenModal: -> # can be implemented, is fired after loading attributes, before rendering + title: (-> + # return title if directly set by options + return @get('modal_options.title') if @get('modal_options.title') + # return translated title_path if directly set by controller + translation_params = {} + if title_params = @get('modal_options.title_params') + translation_params = title_params + else if model = @get('model') + translation_params = model.toJSON() if model.toJSON + + # return translated title_path if directly set by options + # return translated title_path if directly set by controller + if title_path = @get('modal_options.title_path') || @get('title_path') + return tspan(title_path, translation_params).htmlSafe() + # return translated title_path if directly set by options + #return tspan(@get('modal_options.title_path'), translation_params).htmlSafe() if @get('modal_options.title_path') + # return translated title_path if directly set by controller + #return tspan(@title_path, translation_params).htmlSafe() if @title_path + # infer title path based on controller name App.modals.AddSectionController => add_section + #underscored = `this.constructor.toString().substr(11).replace(/Controller$/, '').underscore()` + underscored = @constructor.toString().replace(/.*controller:/, '').replace(/\W+$/, '').underscore().replace('/', '.') + #underscored = @constructor.toString().replace(/[a-zA-Z_-]+\@controller:modals\//, '').replace(/:$/, '').underscore() # ember cli + # find translated title or humanize the controller name + if convention_translation = ttry("#{underscored}.title", translation_params) + tspan("#{underscored}.title", translation_params).htmlSafe() + else + underscored.replace(/.*\./,'').capitalize().replace(/_/, ' ') + ).property('model.id', 'modal_options.title_path') + body: (-> + @get('modal_options.body') + ).property('modal_options.body') + save_error: (error)-> + switch error.status + when 403 + @set 'alert_message', 'Unauthorized action' + else + @set 'alert_message', 'Something went wrong' + save_success: -> + if save_callback = @get('modal_options.save') + save_callback.apply(@) + @set 'alert_message', '' + @send 'closeModal' + + actions: + close: -> + if close = @get('modal_options.close') + close.apply(@) + unless @preventClose + @set 'alert_message', '' + @send 'closeModal' + closeOnOverlay: -> + @send('close') if @get('modal_options.closeOnOverlay') + false + modalClick: -> + @send('close') if @get('modal_options.closeOnModalClick') + false + ok: -> + if ok = @get('modal_options.ok') + ok.apply(@) + @set 'alert_message', '' + @send 'closeModal' unless @preventClose + confirm: -> @send('ok') + saveRecord: -> + if @get('model.isValid') + @get('model').save().then @save_success.bind(@), @save_error.bind(@) + #@send 'closeModal' unless @preventClose + rollback_and_close: -> + @get('model').rollbackAttributes() + @send 'close' + destroyRecord: -> + my_scope = @ + destroy_callback = @get('modal_options.destroy_callback') + @modal 'confirm', + title_path: @get('modal_options.destroy_text_path') || 'general.destroy.are_you_sure' + model: @get('model') + ok: -> + @get('model').destroyRecord() + destroy_callback.call(my_scope) if destroy_callback + @send 'closeModal' unless @preventClose diff --git a/app/components/modals/display-workflow-action-definitions.coffee b/app/components/modals/display-workflow-action-definitions.coffee new file mode 100644 index 0000000..39ec68c --- /dev/null +++ b/app/components/modals/display-workflow-action-definitions.coffee @@ -0,0 +1,12 @@ +import ModalBase from 'ember-cli-dunlop/mixins/modal-base' +export default ModalBase.extend + showAll: true + workflow_groups: Ember.computed.alias "model" + showTitles: Ember.computed "model.length", "title", -> + return false if @get("model.length") is 1 and @get('model.firstObject.name') is @get('title') + true + #workflow_action_definitions: Ember.computed 'model.[]', 'showAll', -> + # workflow_action_definitions = @get('model') + # unless @get('showAll') + # workflow_action_definitions = workflow_action_definitions.rejectBy('done') + # workflow_action_definitions.sortBy('workflow_group.name', 'smart_position') diff --git a/app/components/modals/edit-tracker-group.coffee b/app/components/modals/edit-tracker-group.coffee new file mode 100644 index 0000000..ebbc439 --- /dev/null +++ b/app/components/modals/edit-tracker-group.coffee @@ -0,0 +1,4 @@ +import Ember from 'ember' +import BaseController from './base' +export default BaseController.extend + a: 3 diff --git a/app/components/modals/edit-tracker-subject.coffee b/app/components/modals/edit-tracker-subject.coffee new file mode 100644 index 0000000..0c38df2 --- /dev/null +++ b/app/components/modals/edit-tracker-subject.coffee @@ -0,0 +1,18 @@ +import ModalBase from 'ember-cli-dunlop/mixins/modal-base' +export default ModalBase.extend + model_projects: Ember.A() + + willOpenModal: -> + @get("model.projects").then (projects)=> + @set "model_projects", projects.toArray() + + actions: + newViewItem: -> + record = @get('model.tracker_groups').createRecord(isEditing: true) + + saveRecord: -> + @set("model.projects", @get("model_projects")) + if @get("model.isNew") + @get("model").save() # do not close modal + else + @_super() diff --git a/app/components/modals/sort-records.coffee b/app/components/modals/sort-records.coffee new file mode 100644 index 0000000..387f15d --- /dev/null +++ b/app/components/modals/sort-records.coffee @@ -0,0 +1,10 @@ +import ModalBase from 'ember-cli-dunlop/mixins/modal-base' +export default ModalBase.extend + sorted_records: Ember.computed 'model.@each.position', -> + @get('model').sortBy 'position' + actions: + reorderItems: (records)-> + (records || []).forEach (record, position) -> + if record.get('position') isnt position + record.set 'position', position + record.save() diff --git a/app/components/number-input-with-panda.coffee b/app/components/number-input-with-panda.coffee new file mode 100644 index 0000000..ad85900 --- /dev/null +++ b/app/components/number-input-with-panda.coffee @@ -0,0 +1,23 @@ +import Ember from 'ember' + +# renamed from number-input to number-input-with-panda in favour of the ember-cli-dunlop version +# using number-input numericValue=path.to.value +export default Ember.Component.extend + classNames: ['number-input'] + step: 1 + stringValue: '' + value: null + didReceiveAttrs: -> + value = @get('value') + if value or value is 0 or value is '0' + @set 'stringValue', value.toString() + else + @set 'stringValue', '' + actions: + set_value: (value) -> + @set 'stringValue', value + if value or value is 0 or value is '0' + @set 'value', parseInt(value, 10) + else + @set 'value', null + false diff --git a/app/components/organization/modals/edit-organization.coffee b/app/components/organization/modals/edit-organization.coffee new file mode 100644 index 0000000..0f88d32 --- /dev/null +++ b/app/components/organization/modals/edit-organization.coffee @@ -0,0 +1,13 @@ +import ModalBase from 'ember-cli-dunlop/mixins/modal-base' +export default ModalBase.extend + selected_users: [] + possible_users: [] + willOpenModal: -> + @set 'possible_users', @store.peekAll('user') + @get('model.users').then (users)=> + @set 'selected_users', users.toArray() + + actions: + saveRecord: -> + @set('model.users', @get('selected_users')) + @_super() diff --git a/app/components/panda-image.js b/app/components/panda-image.js new file mode 100644 index 0000000..4b42b5d --- /dev/null +++ b/app/components/panda-image.js @@ -0,0 +1 @@ +export { default } from 'ember-panda/components/panda-image'; diff --git a/app/components/project-workflow-groups-graph.coffee b/app/components/project-workflow-groups-graph.coffee new file mode 100644 index 0000000..96ad7e4 --- /dev/null +++ b/app/components/project-workflow-groups-graph.coffee @@ -0,0 +1,29 @@ +import Ember from 'ember' +import Viz from 'npm:viz.js' # browserify +export default Ember.Component.extend + + #constraints: Ember.computed.alias "workflow_groups.@each.constraints" + #workflow_groups: Ember.computed.alias('project.sorted_workflow_groups') + #digraph_content: Ember.computed "constraints.@each.type", "constraints.@each.constraint_workflow_group.id", "workflow_groups.@each.name", -> + #digraph_content: Ember.computed "project.update_revision", -> + digraph_content: Ember.computed "project.update_revision", "project.workflow_groups.@each.constraints.@each.type", "project.workflow_groups.@each.constraints.@each.workflow_action_definitions.length", -> + txt = "digraph prof {\n ratio=fill;\n node [style=filled fillcolor=\"#A2D9CE\"];\n rankdir=TB;\n" + @get('project.sorted_workflow_groups').forEach (workflow_group)-> + workflow_group_id = "q#{workflow_group.id}" # start with a letter + txt += " #{workflow_group_id} [label=\"#{workflow_group.get('name')}\"];\n" + workflow_group.get('constraints').forEach (constraint)-> + constraint_workflow_group_id = "q#{constraint.get('constraint_workflow_group.id')}" # start with a letter + actions_label = "" + switch length = constraint.get('workflow_action_definitions.length') + when 0 then # nothing + when 1 then actions_label = " label=\"#{constraint.get('workflow_action_definitions.firstObject.name')}\"" + else actions_label = " label=\"#{length} actions\"" + if constraint.get('type') is "restrict" + txt += " #{constraint_workflow_group_id} -> #{workflow_group_id} [color=\"#C0392B\" penwidth=2 #{actions_label}];\n" + else + txt += " #{constraint_workflow_group_id} -> #{workflow_group_id} [color=\"#F5B041\" #{actions_label}];\n" + txt += "}" + svg_content: Ember.computed "digraph_content", -> + data = @get('digraph_content') + svg = Viz(data, format: 'svg') + svg.htmlSafe() diff --git a/app/components/project/action-execution-offset-diagram.coffee b/app/components/project/action-execution-offset-diagram.coffee new file mode 100644 index 0000000..e75da88 --- /dev/null +++ b/app/components/project/action-execution-offset-diagram.coffee @@ -0,0 +1,303 @@ +#import Component from 'ember-c3/components/c3-chart' +import Ember from 'ember' +import groupBy from 'ember-group-by' +#asdf +import WorkflowActionDefinitionOffsetNodes from 'ember-panda/utils/project/workflow-action-definition-offset-nodes' +{get} = Ember + +GraphWrapper = Ember.Object.extend + records: [] + absolute_value: 0 + title: Ember.computed 'records.length', 'records.firstObject.title', -> + length = @get('records.length') + if length > 1 + optional_text = @get('records').mapBy('title').join(' & ') + if optional_text.length <= 49 + optional_text + else + actions_name = t('models.plural.workflow_action_definition') + "#{length} #{actions_name}" + else + @get('records.firstObject.title') +#GraphRecordWrapper = Ember.Object.extend +# title: Ember.computed 'record.title', 'value_shift', -> +# title = @get('record.title') +# title = " #{title} (#{value_shift}d)" if value_shift = @get('value_shift') +# title + +#GraphOffsetWrapper = Ember.Object.extend {} +export default Ember.Component.extend + classNames: ['action-execution-offset-diagram-component'] + graph_height: 320 + bar_height: 20 + x_axis_space: 60 + left_space: 70 + svg_width: 1200 + window_min: null + window_max: null + + range_start_default: null + range_start: null + range_end: null + range_bounds: {min: -7, max: 7} + range_formatter: (-> [{to: @days_offset_format.bind(@)}, {to: @days_offset_format.bind(@)}]).property() + reference_date_day_of_week: Ember.computed.oneWay 'project.reference_date_day_of_week' + choose_reference_date: true # choose a specific date to display as planning. Use X with migration date at specific day otherwise + chosen_absolute_date: null + batch_size_simulation_range: Ember.computed 'project.{batch_size_lower_value,batch_size_upper_value}',-> + {min: @get('project.batch_size_lower_value'), max: @get('project.batch_size_upper_value')} + batch_size_simulation_value: 0 + batch_size_formatter: (-> [to: (v) -> Math.round(v)]).property() + + graph_title: Ember.computed 'window_min', 'window_max', 'range_start', 'range_end', 'chosen_absolute_date', -> + range_min = @days_offset_format @get('range_start') + range_max = @days_offset_format @get('range_end') + window_min = @days_offset_format @get('window_min') + window_max = @days_offset_format @get('window_max') + range_addition = '' + if range_min isnt window_min or range_max isnt window_max + range_addition = "(visible #{range_min} t/m #{range_max})" + base = "Showing #{t('models.workflow_action_definition')} offsets #{range_addition} having total window #{window_min} t/m #{window_max}" + base += " for #{t('attributes.project_instance.reference_date')} #{@days_offset_format(0)}" if @get('chosen_absolute_date') + base + + days_offset_format: (v) -> + v = Math.round(v) # could be float from slider + if @get('chosen_absolute_date') + date = moment(@get('chosen_absolute_date')) + date.add(v, 'day') if v + date.format("D-M-YYYY") + else + return '' unless v + return '' unless v and Number.isFinite(v) + if v % 7 is 0 + v = v / 7 + if v > 0 then "X+#{v}w" else "X#{v}w" + else + if v > 0 then "X+#{v}" else "X#{v}" + + actions: + range_changed: ([range_start, range_end]) -> + [range_start, range_end] = [Math.round(range_start), Math.round(range_end)] + @setProperties + range_start_default: [range_start, range_end] + range_start: range_start + range_end: range_end + @_setup_graph() # manual setup, do not use observable patter for range_start and range_end + + batch_size_simulation_change: (value) -> + @setProperties + range_start: null + range_end: null + batch_size_simulation_value: Math.round(value) + + hide_absolute_date_choice: -> + @set 'chosen_absolute_date', null # important! determinse wether the labels use X or dates + @set 'choose_reference_date', false + + init: -> + @_super arguments... + #@set 'batch_size_simulation_value', 0 + @get('project.batch_size_lower_value') if @get('project.batch_size_lower_value') + @set 'batch_size_simulation_value', @get('project.batch_size_lower_value') if not @get('batch_size_simulation_value') and @get('project.batch_size_lower_value') + + didReceiveAttrs: -> + console.log 'Reload workflow-action-offset-diagram call from didReceiveAttrs' + Ember.run.scheduleOnce 'afterRender', @, @_setup_graph + + #_graph_observer: Ember.observer 'active_workflow_action_definitions.@each.title', 'active_workflow_action_definitions.@each.offset_value', 'range_start', 'range_end', 'batch_size_simulation_value', 'batch_size_simulation_range', 'reference_date_day_of_week', 'chosen_absolute_date', 'active_workflow_action_definitions.@each.id', -> + _graph_observer: Ember.observer 'project.update_revision', -> + console.log 'Reload workflow-action-offset-diagram call from observer' + console.log "update_revision: #{@get('project.update_revision')}, range_start: #{@get('range_start')}, range_end: #{@get('range_end')}" + Ember.run.debounce @, @_setup_graph, 150 + + _setup_graph: -> + console.log 'Reload workflow-actions-offset-diagram' + container_selector = '.action-execution-offset-diagram-container' + @$(container_selector).html('') + component = @ + + return unless @get('active_workflow_action_definitions.length') + batch_size_simulation_value = @get('batch_size_simulation_value') || 0 + reference_date_day_of_week = @get('reference_date_day_of_week') + unless reference_date = @get('chosen_absolute_date') + reference_date = moment() + reference_date.day reference_date_day_of_week if reference_date_day_of_week # updates reference_date instance + result_nodes = WorkflowActionDefinitionOffsetNodes.create + workflow_action_definitions: @get('active_workflow_action_definitions') + reference_date: reference_date + batch_size: batch_size_simulation_value + resolved_record_nodes = result_nodes.get_resolved() + return unless get resolved_record_nodes, 'length' + wrappers = [] + x_min = -1 # at least include zero + x_max = 0 + batch_size_simulation_value = @get('batch_size_simulation_value') + resolved_record_nodes.forEach (record_wrapper) -> + if wrapper = wrappers.findBy 'absolute_value', record_wrapper.get('absolute_value') + wrapper.get('records').push record_wrapper + else + absolute_value = record_wrapper.get('absolute_value') + x_min = Math.min(x_min, absolute_value) + x_max = Math.max(x_max, absolute_value) + wrappers.push GraphWrapper.create + records: Ember.A([record_wrapper]) + absolute_value: absolute_value + date: record_wrapper.get('date') + @set 'window_min', x_min + @set 'window_max', x_max + + ##data = @get('workflow_action_definition_groups') + ##return unless data.length + #x_min = d3.min(data.mapBy('absolute_value')) + #x_min = -1 if x_min > 0 # add space for execution line + #x_max = d3.max(data.mapBy('absolute_value')) + #x_max = 0 if x_max < 0 + #x_max += 1 # last ending can have duration 1 when empty + + if @get('range_bounds.min') isnt x_min or @get('range_bounds.max') isnt x_max + @set 'range_bounds', min: x_min, max: x_max + @set 'range_start_default', [x_min, x_max] # bounds have changed, reset starts + else if not @get('range_start_default') # uninitialized + @set 'range_start_default', [x_min, x_max] + + @set 'range_start', x_min unless Number.isFinite(@get('range_start')) + @set 'range_end', x_max unless Number.isFinite(@get('range_end')) + graph_min = @get('range_start') + graph_max = @get('range_end') + + graph_range_width = @get('svg_width') - @get('left_space') - 22 + x = d3.scale.linear().domain([graph_min, graph_max]).range([0, graph_range_width]) + #yscale = d3.scale.linear().domain([0, @get('number_of_groups')]).range([0, @get('graph_height')]) + #y = d3.scale.ordinal().rangeRoundBands([@get('graph_height'), 0], 0.1).domain(workflow_groups.mapBy('name').reverse()) + + #x = d3.scale.linear().domain([x_min, x_max]).range([0, 872]) + y = d3.scale.linear().domain([0, 1]).range([0, @get('graph_height')]) + + x_ticks = wrappers.mapBy 'absolute_value' + #data.forEach (group) -> + # offset = Ember.get(group, 'value') + # x_ticks.push offset unless offset is 0 or x_ticks.indexOf(offset) > -1 + x_ticks = x_ticks.filter( (v) -> v >= graph_min and v <= graph_max).sort(d3.ascending) + displayed_x_ticks = [] + if x_ticks.length + displayed_x_ticks.push x_ticks[0] + latest_pixel_x_for_space = x(x_ticks[0]) + x_ticks.forEach (v) -> + current_pixel_x = x(v) + if current_pixel_x - latest_pixel_x_for_space > 14 + latest_pixel_x_for_space = current_pixel_x + displayed_x_ticks.push v + xAxis = d3.svg.axis() + .orient('bottom') + .scale(x) + .tickSize(1) + .tickValues(displayed_x_ticks) + .tickFormat component.days_offset_format.bind(component) + @$(container_selector).append '' + svg_height = @get('x_axis_space') + @get('graph_height') + canvas = d3.select("#{container_selector} svg") + .attr 'width', '100%' + .attr 'height', svg_height + .attr 'viewBox', "0 0 #{@get('svg_width')} #{svg_height}" + + lines = canvas.append('g') + .attr('class', 'offset-lines') + .attr('transform', "translate(#{@get('left_space')},0)") + .selectAll('line') + .data(x_ticks) # first element is y-axis + .enter() + .append('line') + .attr( + x1: (d) -> x(d) + y1: 0 + x2: (d) -> x(d) + y2: y(1) + class: 'offset-line' + ) + + offset_text = (record_wrapper) -> + title_text = "#{record_wrapper.get('record.workflow_group.name')} -> #{record_wrapper.get('title')}" + offset_texts = [] + record_wrapper.get('offset_wrappers').forEach (offset_wrapper) -> + given_offset = offset_wrapper.get('total_offset') || 0 + day_text = if Math.abs(given_offset) > 1 then t('date.unit.plural.day') else t('date.unit.day') + offset_spec = if given_offset then "#{given_offset} #{day_text}" else 'direct' + offset_spec += " (#{value_shift}d)" if value_shift = offset_wrapper.get('value_shift') + if offset_base_action_title = offset_wrapper.get('record.base_workflow_action_definition.title') + offset_base_group_title = offset_wrapper.get('record.base_workflow_action_definition.workflow_group.name') + offset_relation_text = "#{offset_spec}: #{offset_base_group_title} -> #{offset_base_action_title}" + else + offset_relation_text = "#{offset_spec} from X" + offset_texts.push "Offset: #{offset_relation_text}" + "#{title_text}
#{offset_texts.join('
')}" + texts = canvas.append('g') + .attr('class', 'action-descriptions') + .attr('transform', "translate(#{@get('left_space')},0)") + .selectAll('text') + .data(wrappers.filter((d) -> (d.get('absolute_value') >= graph_min) and (d.get('absolute_value') <= graph_max))) + .enter() + .append('text') + .attr 'class', 'action-description-text' + .attr('transform', 'rotate(-90)') + .html (wrapper) -> + wrapper.get('title') + #.html (group) -> + # if group.items.length <= 2 + # group.items.map( (record) -> + # "#{Ember.get(record, 'title')}" + # ).join(', ') + # else + # "#{group.items.length} #{actions_name}" + .attr + x: 10 - @get('graph_height') + y: (d) -> x(Ember.get(d, 'absolute_value')) + dy: -5 + dx: -5 + .on 'click', (wrapper) -> + target = $(@) + unless target.hasClass 'tooltipstered' + target.tooltipster + content: 'Loading...' + contentAsHTML: true + selfDestruction: false + trigger: 'click' + interactive: true + functionReady: -> + $('.tooltipster-base .edit-workflow-action-definition').on 'click', -> + return target.tooltipster('close') unless id = $(@).data('workflowActionDefinition') + return target.tooltipster('close') unless target_workflow_action_definition = component.store.peekRecord('panda/workflow-action-definition', id) + component.modal 'workflow-action-definition/edit-workflow-action-definition', + model: target_workflow_action_definition + target.tooltipster('close') + + record_offset_texts = wrapper.records.sortBy('record.absolute_position').map(offset_text) + if wrapper.records.length > 1 + target.tooltipster 'content', record_offset_texts.join('
') + else + target.tooltipster 'content', record_offset_texts[0] + target.tooltipster 'open' + + if graph_min <= 0 <= graph_max + base_line = canvas.append('line').attr + x1: x(0) + y1: 0 + x2: x(0) + y2: 15 + @get('graph_height') + class: 'base-line' + transform: "translate(#{@get('left_space')},0)" + + x_xis = canvas.append('g') + .attr('transform', "translate(#{@get('left_space')},#{@get('graph_height')})") + .attr('class','x-axis') + .call(xAxis) + .selectAll('text') + .style('text-anchor', 'end') + .attr('dx', '-.8em') + .attr('dy', '.15em') + .attr('transform', "rotate(-40)") + + canvas.selectAll('.action-description-action-part').on 'click', (d) -> + return unless workflow_action_definition_id = $(@).data('recordId') + return unless workflow_action_definition = component.store.peekRecord('panda/workflow-action-definition', workflow_action_definition_id) + component.modal 'workflow-action-definition/edit-workflow-action-definition', + model: workflow_action_definition diff --git a/app/components/project/edit-active-project-scenario.js b/app/components/project/edit-active-project-scenario.js new file mode 100644 index 0000000..2a88ab2 --- /dev/null +++ b/app/components/project/edit-active-project-scenario.js @@ -0,0 +1 @@ +export { default } from 'ember-panda/components/project/edit-active-project-scenario'; diff --git a/app/components/project/instance-action-value-display.js b/app/components/project/instance-action-value-display.js new file mode 100644 index 0000000..f50cd8b --- /dev/null +++ b/app/components/project/instance-action-value-display.js @@ -0,0 +1 @@ +export { default } from 'ember-panda/components/project/instance-action-value-display'; diff --git a/app/components/project/modals/contract.coffee b/app/components/project/modals/contract.coffee new file mode 100644 index 0000000..53f0a92 --- /dev/null +++ b/app/components/project/modals/contract.coffee @@ -0,0 +1,88 @@ +import ModalBase from 'ember-cli-dunlop/mixins/modal-base' +import DS from 'ember-data' +add_offset_to = (target, offsets, record, project) -> + Ember.makeArray(offsets).forEach (offset_name) -> + offset_object = + value: record.get("#{offset_name}_value") || 0 + unit: record.get("#{offset_name}_unit") + if offset_object.unit is 'businessday' + offset_object.business_days = record.get("#{offset_name}_business_days") || [1,2,3,4,5] + target[offset_name] = offset_object + target + +export default ModalBase.extend + contract: null + download_file_name: 'PANDA-contract.json' + download_href: Ember.computed 'contract.content', -> + return 'javascript:void(0)' unless content = @get('contract.content') + hrefContent = "data:text/json;charset=utf-8,#{encodeURIComponent(JSON.stringify(content))}" + hrefContent + + willOpenModal: -> + project = @get('model') + component = @ + project_name = String(project.get('name')).replace(/[\W_]/g, '-') + @set 'download_file_name', "PANDA-contract-#{project_name}.json" + @set 'contract', DS.PromiseObject.create + promise: new Ember.RSVP.Promise (resolve) -> + contract = + generating_project: project.get('id') + contract_version: 'v1' + instance_scope: project.get('instance_scope') + workflow_groups: [] + identifier: project.get('identifier') + workflow_actions: [] + scenarios: [] + project.get('sorted_workflow_groups').forEach (workflow_group) -> + workflow_group_object = + name: workflow_group.get('name') + identifier: workflow_group.get('identifier') + offset_type: workflow_group.get('offset_type') + offset_base: workflow_group.get('offset_base.identifier') || null # no undefined + scenarios: workflow_group.get('project_scenarios').mapBy('identifier') + add_offset_to workflow_group_object, ['offset', 'duration', 'batch_element_threshold'], workflow_group + if project.get('instance_scope') is 'batch' + add_offset_to workflow_group_object, 'batch_element_threshold', workflow_group + contract.workflow_groups.push workflow_group_object + project.get('sorted_workflow_action_definitions').forEach (workflow_action_definition) -> + workflow_action_definition_object = + identifier: workflow_action_definition.get('identifier') + title: workflow_action_definition.get('title') + extra_info: workflow_action_definition.get('extra_info') + value_type: workflow_action_definition.get('value_type') + value_type_select_options: workflow_action_definition.get('value_type_select_options') + overdue_coulance: workflow_action_definition.get('overdue_coulance') + offset_type: workflow_action_definition.get('offset_type') + workflow_group: workflow_action_definition.get('workflow_group.identifier') || null # no undefined + confirms_workflow_action: workflow_action_definition.get('confirms_workflow_action_definition.identifier') || null # no undefined + completes_group: workflow_action_definition.get('completes_group') + organization: workflow_action_definition.get('organization.identifier') + scenarios: workflow_action_definition.get('project_scenarios').mapBy('identifier') + contract.workflow_actions.push workflow_action_definition_object + + workflow_action_definition.get('workflow_action_offsets').forEach (workflow_action_offset) -> + workflow_action_definition_object.action_offsets ||= [] + offset_object = {} + if base_identifier = workflow_action_offset.get('base_workflow_action_definition.identifier') + offset_object.base_workflow_action = base_identifier + add_offset_to offset_object, 'offset', workflow_action_offset + if project.get('instance_scope') is 'batch' + add_offset_to offset_object, 'batch_element_threshold', workflow_action_offset + workflow_action_definition_object.action_offsets.push offset_object + + if workflow_action_definition.get('offset_type') is 'attached' + workflow_action_definition_object.attached_to = + identifier: workflow_action_definition.get('workflow_action_connection.base_workflow_action_definition.identifier') + conditional_value: workflow_action_definition.get('workflow_action_connection.conditional_base_value') + + workflow_action_definition.get('workflow_action_constraints').forEach (workflow_action_constraint) -> + workflow_action_definition_object.action_constraints ||= [] + workflow_action_definition_object.action_constraints.push + type: workflow_action_constraint.get('type') + base_workflow_action: workflow_action_constraint.get('constraint_workflow_action_definition.identifier') + project.get('project_scenarios').forEach (project_scenario) -> + project_scenario_object = + identifier: project_scenario.get('identifier') + name: project_scenario.get('name') + contract.scenarios.push project_scenario_object + resolve contract diff --git a/app/components/project/modals/edit-instance-action.coffee b/app/components/project/modals/edit-instance-action.coffee new file mode 100644 index 0000000..682aac1 --- /dev/null +++ b/app/components/project/modals/edit-instance-action.coffee @@ -0,0 +1,112 @@ +import ModalBase from 'ember-cli-dunlop/mixins/modal-base' +ESC_KEY = 27 +export default ModalBase.extend + title: Ember.computed 'model.workflow_action_definition.title', -> + t 'project.edit_instance_action.title', title: @get('model.workflow_action_definition.title') + value_type: Ember.computed.alias 'model.workflow_action_definition.value_type' + string_value: '' + number_value: null + text_value: '' + new_note_content: '' + new_note_content_is_blank: Ember.computed.not 'new_note_content.length' + has_uncompleted_constraints: false + + can_edit_action: Ember.computed 'model.workflow_action_definition.organization.users.[]', -> + return true if @get('globals.current_user.admin') + return true if @get('model.workflow_action_definition.offset_type') is 'attached' + role_names = @get('globals.current_user.role_names') || [] + return true if 'manage-app-panda' in role_names + return true if 'manage-model-panda/project-instance' in role_names + organization_user_ids = @get('model.workflow_action_definition.organization.users')?.mapBy('id') || [] + return true if @get('globals.current_user.id') in organization_user_ids + false + + _initEscListener: -> + closeOnEscapeKey = (ev) => + @send 'rollback_and_close' if ev.keyCode is ESC_KEY + Ember.$('body').on('keyup.modal-dialog', closeOnEscapeKey) + false + + note_events: Ember.computed 'model.events.@each.event_value', -> + @get('model.events').filterBy('event_key', 'note') + + misc_events: Ember.computed 'model.events.length', -> + @get('model.events').rejectBy('event_key', 'note') + + + + willOpenModal: -> + @_initEscListener() + @get('model.events').reload() + @set 'string_value', @get('model.string_value') + @set 'number_value', @get('model.number_value') + @set 'text_value', @get('model.text_value') + @set 'timestamp_value', @get('model.timestamp_value') + @get('model.pointing_project_instance_action_connections').filterBy('connection_type', 'constraint').some (pointing_connection) => + return @set 'has_uncompleted_constraints', true unless pointing_connection.get('project_instance_action.completed') + false + #@get('pointing_project_instance_action_connections').forEach (pointing_connection) -> + + select_class_names: Ember.computed 'model.string_value', -> + return ['green'] + if @get('model.string_value') then 'green button' else 'button' + + + has_changed_timestamp_value: Ember.computed 'timestamp_value', 'model.timestamp_value', -> + current_timestamp = @get('timestamp_value') + model_timestamp = @get('model.timestamp_value') + return false unless current_timestamp or model_timestamp # both false, nothing changed + return true unless current_timestamp and model_timestamp + # now we have two timestamps, lets compare + current_timestamp.toISOString() isnt model_timestamp.toISOString() + + actions: + set_value: (attribute, value) -> + @set "model.#{attribute}", value + @send 'saveRecord' + + set_string_value: -> + @send 'set_value', 'string_value', @get('string_value') + + set_text_value: -> + @send 'set_value', 'text_value', @get('text_value') + + set_number_value: -> + @send 'set_value', 'number_value', @get('number_value') + set_timestamp_value: -> + @send 'set_value', 'timestamp_value', @get('timestamp_value') + + close: -> + Ember.$('body').off('keyup.modal-dialog') + @_super arguments... + + open_current_modal: -> @modal 'project/edit-instance-action', model: @get('model') + + continue_from_inquiry: -> + record = @get('model') + if target_date = @get('model.target_date') + state = if target_date.format('YYYY-MM-DD') < moment().format('YYYY-MM-DD') then 'overdue' else 'active' + else + state = 'active' + record.set 'state', state + record.save().then -> + record.get('events').reload() + add_new_note: -> + return unless content = @get('new_note_content') + now = moment() + new_note_event = @store.createRecord 'panda/action-event', + client_identifier: @get('model.project_instance.client_identifier') + identifier: @get('model.workflow_action_definition.identifier') + event_key: 'note' + event_value: content + event_timestamp: now + new_note_event.save().then => + @set 'new_note_content', '' + @send 'close' + start_inquiry: -> + record = @get('model') + record.set 'state', 'inquiry' + record.save().then => @send 'add_new_note' + #record.save().then -> + # record.get('events').reload() + diff --git a/app/components/project/modals/edit-project-scenario.coffee b/app/components/project/modals/edit-project-scenario.coffee new file mode 100644 index 0000000..e21f0f6 --- /dev/null +++ b/app/components/project/modals/edit-project-scenario.coffee @@ -0,0 +1,67 @@ +import ModalBase from 'ember-cli-dunlop/mixins/modal-base' +export default ModalBase.extend + selected_workflow_action_definition_ids: [] + selected_workflow_group_ids: [] + initial_selected_workflow_action_definition_ids: [] + initial_selected_workflow_group_ids: [] + willOpenModal: -> + current_workflow_action_definition_ids = @get('model.workflow_action_definitions').mapBy('id').toArray() + current_workflow_group_ids = @get('model.workflow_groups').mapBy('id').toArray() + @set 'selected_workflow_action_definition_ids', current_workflow_action_definition_ids + @set 'selected_workflow_group_ids', current_workflow_group_ids + + @set 'initial_workflow_action_definition_ids', current_workflow_action_definition_ids.copy() + @set 'initial_workflow_group_ids', current_workflow_group_ids.copy() + actions: + check_all: -> + @set 'selected_workflow_action_definition_ids', @get('model.project.workflow_action_definitions').mapBy('id').toArray() + @set 'selected_workflow_group_ids', @get('model.project.workflow_groups').mapBy('id').toArray() + check_none: -> + @set 'selected_workflow_action_definition_ids', [] + @set 'selected_workflow_group_ids', [] + change_workflow_group_selection: (workflow_group, checked) -> + if checked + @get('selected_workflow_group_ids').addObject workflow_group.get('id') + @get('selected_workflow_action_definition_ids').addObjects workflow_group.get('workflow_action_definitions').mapBy('id') + else + @get('selected_workflow_group_ids').removeObject workflow_group.get('id') + @get('selected_workflow_action_definition_ids').removeObjects workflow_group.get('workflow_action_definitions').mapBy('id') + change_workflow_action_definition_selection: (workflow_action_definition, checked) -> + if checked + @get('selected_workflow_action_definition_ids').addObject workflow_action_definition.get('id') + #@get('model.workflow_action_definitions').addObject workflow_action_definition + else + @get('selected_workflow_action_definition_ids').removeObject workflow_action_definition.get('id') + #@get('model.workflow_action_definitions').removeObject workflow_action_definition + saveRecord: -> + selected_workflow_action_definition_ids = @get('selected_workflow_action_definition_ids') + selected_workflow_group_ids = @get('selected_workflow_group_ids') + initial_workflow_action_definition_ids = @get('initial_workflow_action_definition_ids') + initial_workflow_group_ids = @get('initial_workflow_group_ids') + + added_workflow_action_definition_ids = selected_workflow_action_definition_ids.filter (id) -> id not in initial_workflow_action_definition_ids + removed_workflow_action_definition_ids = initial_workflow_action_definition_ids.filter (id) -> id not in selected_workflow_action_definition_ids + + added_workflow_group_ids = selected_workflow_group_ids.filter (id) -> id not in initial_workflow_group_ids + removed_workflow_group_ids = initial_workflow_group_ids.filter (id) -> id not in selected_workflow_group_ids + + if added_workflow_action_definition_ids.length + added_workflow_action_definitions = @get('model.project.workflow_action_definitions').filter (record) -> + record.get('id') in added_workflow_action_definition_ids + @get('model.workflow_action_definitions').addObjects added_workflow_action_definitions + if removed_workflow_action_definition_ids.length + removed_workflow_action_definitions = @get('model.project.workflow_action_definitions').filter (record) -> + record.get('id') in removed_workflow_action_definition_ids + @get('model.workflow_action_definitions').removeObjects removed_workflow_action_definitions + + if added_workflow_group_ids.length + added_workflow_groups = @get('model.project.workflow_groups').filter (record) -> + record.get('id') in added_workflow_group_ids + @get('model.workflow_groups').addObjects added_workflow_groups + if removed_workflow_group_ids.length + removed_workflow_groups = @get('model.project.workflow_groups').filter (record) -> + record.get('id') in removed_workflow_group_ids + @get('model.workflow_groups').removeObjects removed_workflow_groups + + @_super arguments... + @incrementProperty 'model.project.update_revision' diff --git a/app/components/project/modals/edit-project.coffee b/app/components/project/modals/edit-project.coffee new file mode 100644 index 0000000..a03d0ab --- /dev/null +++ b/app/components/project/modals/edit-project.coffee @@ -0,0 +1,25 @@ +import ModalBase from 'ember-cli-dunlop/mixins/modal-base' +export default ModalBase.extend + project_users: Ember.A() + possible_users: [] + possible_workflow_instance_types: [] + willOpenModal: -> + @set 'possible_users', @store.peekAll('user') + @set 'possible_workflow_instance_types', @store.peekAll('panda/workflow-instance-type') + @get('model.users').then (users)=> + @set 'project_users', users.toArray() + #@set 'project_users', @get('model.users').toArray() + + actions: + saveRecord: -> + @set('model.users', @get('project_users')) + @_super() + + selectWorkflowInstanceType: (workflow_instance_type) -> + @set 'model.workflow_instance_type', workflow_instance_type + newProjectScenario: -> + record = @store.createRecord 'panda/project-scenario', project: @get('model') + @modal 'project/edit-project-scenario', + model: record + title: t('action.new_model', model: t('models.project_scenario')) + diff --git a/app/components/project/modals/edit-projects-context.coffee b/app/components/project/modals/edit-projects-context.coffee new file mode 100644 index 0000000..7ce8937 --- /dev/null +++ b/app/components/project/modals/edit-projects-context.coffee @@ -0,0 +1,7 @@ +import ModalBase from 'ember-cli-dunlop/mixins/modal-base' + +export default ModalBase.extend + workflow_instance_types: Ember.computed -> + @store.peekAll('panda/workflow-instance-type') + actions: + newWorkflowInstanceType: -> @store.createRecord('panda/workflow-instance-type') diff --git a/app/components/project/modals/edit-workflow-group.coffee b/app/components/project/modals/edit-workflow-group.coffee new file mode 100644 index 0000000..f60c520 --- /dev/null +++ b/app/components/project/modals/edit-workflow-group.coffee @@ -0,0 +1,71 @@ +import ModalBase from 'ember-cli-dunlop/mixins/modal-base' +import Changeset from 'ember-changeset' +export default ModalBase.extend + organization_tracker_subjects: Ember.A() + constraint_workflow_groups: Ember.A() + offset_selectable_workflow_groups: [] + + offset_type_none: Ember.computed.equal 'model.offset_type', 'none' + offset_type_connected_finish: Ember.computed.equal 'model.offset_type', 'connected_finish' + offset_type_connected_start: Ember.computed.equal 'model.offset_type', 'connected_start' + + willOpenModal: -> + @set "constraint_workflow_groups", @get('model.project.sorted_workflow_groups').rejectBy('id', @get('model.id')) + @set_offset_selectable_workflow_groups() + changeset = new Changeset(@get('model')) + @set 'model', changeset + @store.findAll('panda/tracker-subject', backgroundReload: false).then (tracker_subjects)=> + organization_tracker_subjects = Ember.A() + current_tracker_groups = @get('model.tracker_groups') + tracker_subjects.filterBy("edit_from_workflow_group").forEach (tracker_subject)-> + current_tracker_subject_item = current_tracker_groups.findBy("tracker_subject.id", tracker_subject.id) + organization_tracker_subjects.push Ember.Object.create + model: tracker_subject + current_tracker_group: current_tracker_subject_item + @set "organization_tracker_subjects", organization_tracker_subjects + + set_offset_selectable_workflow_groups: (offset_type) -> + offset_type ||= @get('model.offset_type') + return if offset_type is 'none' + selectable_workflow_groups = @get('model.project.sorted_workflow_groups') #.rejectBy('offset_type', 'none') allow all groups, more user friendly + @set 'offset_selectable_workflow_groups', selectable_workflow_groups + actions: + saveRecord: -> + #if offset_value = @get('model.offset_value') + # @set 'model.offset_value', parseInt(offset_value) + #if duration_value = @get('model.duration_value') + # @set 'model.duration_value', parseInt(duration_value) + #if @get("model.isNew") + # return @get("model").save() # add extra functions, do not close modal + @set("model.tracker_groups", @get("organization_tracker_subjects").mapBy("current_tracker_group").compact()) + @get('model.constraints').forEach (constraint)-> + constraint_workflow_group_id = constraint.get('constraint_workflow_group.id') + constraint.get('workflow_action_definitions').rejectBy('workflow_group.id', constraint_workflow_group_id).forEach (illigal_workflow_group_action)-> + constraint.get('workflow_action_definitions').removeObject illigal_workflow_group_action + + #workflow_action_definitions.forEach (workflow_action_definition)-> + # debugger + # workflow_action_definitions.removeObject(workflow_action_definition) unless workflow_action_definition.get("workflow_group.id") is constraint_workflow_group_id + # # remove non constraint_workflow_action_definition actions, switching the constraint_workflow_group does not remove these + # #constraint.set "workflow_action_definitions", workflow_action_definitions.filterBy("id", constraint_workflow_group_id) + #debugger + constraint.save() + @_super() + @incrementProperty('model.project.update_revision') + rollback_and_close: -> + constraints = @get('model.constraints') + constraints.filterBy('isNew').invoke 'unloadRecord' + constraints.rejectBy('isNew').invoke 'rollbackAttributes' # not rolling back belongsTo, known issue + @_super() + + set_offset_type: (offset_type) -> + offset_type ||= 'none' + @set 'model.offset_base', null + #TODO: CLEANUP DEPENDENCY TREE FARTHER AWAY FROM REFERENCE DATE DEPENDENT ON MODEL + @set 'model.offset_type', offset_type + @set_offset_selectable_workflow_groups offset_type + + newConstraint: -> + constraint = @store.createRecord('panda/workflow-group-constraint', workflow_group: @get('model')) + set_batch_element_threshold_unit: (unit) -> @set 'model.batch_element_threshold_unit', unit + diff --git a/app/components/project/modals/load-project-instances.coffee b/app/components/project/modals/load-project-instances.coffee new file mode 100644 index 0000000..b35ec5f --- /dev/null +++ b/app/components/project/modals/load-project-instances.coffee @@ -0,0 +1,116 @@ +import ModalBase from 'ember-cli-dunlop/mixins/modal-base' +import get_active_workflow_groups from 'ember-panda/utils/project/active-workflow-groups' +import get_active_workflow_action_definitions from 'ember-panda/utils/project/active-workflow-action-definitions' +import WorkflowActionDefinitionOffsetNodes from 'ember-panda/utils/project/workflow-action-definition-offset-nodes' +{get} = Ember +export default ModalBase.extend + loading_message: '' + finished_message: '' + willOpenModal: -> + @set 'loading_message', "Loading project's dependencies" + @get('model').reload().then => @set 'loading_message', '' + actions: + load_instance_list: (file) -> + component = @ + store = @store + now = moment() + @set 'alert_message', '' + @set 'finished_message', '' + failure = ((message) -> + @set 'alert_message', message + @set 'loading_message', '' + @set 'finished_message', '' + ).bind @ + progress_formatter = d3.format('%') + process_instance_line = ((remaining_lines, current_index, start_length)-> + unless remaining_lines.length + @set 'alert_message', '' + @set 'loading_message', '' + @set 'finished_message', "Finished loading #{start_length} records" + return + instance_line = remaining_lines.splice(0, 1)[0] + current_index += 1 + project = @get('model') + [identifier, reference_date_spec, scenario_identifier, batch_size] = instance_line.split(/\s*[,;\t]\s*/).map((str) -> str.trim()) + scenario_lookup_attribute = if scenario_identifier.match(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/) then 'identifier' else 'name' + batch_size = parseInt(batch_size) || 0 + @set 'loading_message', "Processing #{identifier} #{current_index} / #{start_length} (#{progress_formatter current_index / start_length}) of file #{file.get('name')}" + selected_scenarios = if scenario = @get('model.project_scenarios').findBy(scenario_lookup_attribute, scenario_identifier) then [scenario] else [] + @set 'selected_project_scenarios', selected_scenarios + workflow_groups = get_active_workflow_groups @get('model.workflow_groups'), selected_scenarios + workflow_action_definitions = get_active_workflow_action_definitions @get('model.workflow_action_definitions'), selected_scenarios + + if reference_date_spec + reference_date = moment(reference_date_spec) + state = 'uncategorized' + if scenario + state = 'unplanned' + if reference_date_spec + state = 'planned' + project_instance = store.createRecord 'panda/project-instance', + id: identifier + client_identifier: identifier + state: state + reference_date: reference_date + batch_size: batch_size + project: project + project_scenario: selected_scenarios.get('firstObject') + + # PROJECT GROUPS + new_project_instance_groups = [] + workflow_groups.forEach (workflow_group) -> + new_project_instance_groups.push store.createRecord 'panda/project-instance-group', + id: "pig-#{project_instance.id}-#{workflow_group.get('id')}" + project_instance: project_instance + workflow_group: workflow_group + project: project + + # PROJECT INSTANCES + if reference_date + result_nodes = WorkflowActionDefinitionOffsetNodes.create + workflow_action_definitions: workflow_action_definitions + reference_date: reference_date + batch_size: batch_size + result_nodes.forEach (node) -> + new_project_instance_action_date = get(node, 'date') + project_instance_group = new_project_instance_groups.findBy('workflow_group.id', node.get('record.workflow_group.id')) + store.createRecord 'panda/project-instance-action', + id: "pia-#{project_instance.id}-#{node.get('record_id')}" + project_instance: project_instance + date: new_project_instance_action_date + state: if new_project_instance_action_date then (if new_project_instance_action_date.isBefore(now) then 'overdue' else 'planned') else 'pending' + workflow_action_definition: get(node, 'record') + project_instance_group: project_instance_group + project: project + else + workflow_action_definitions.forEach (workflow_action_definition) -> + project_instance_group = new_project_instance_groups.findBy('workflow_group.id', workflow_action_definition.get('workflow_group.id')) + store.createRecord 'panda/project-instance-action', + id: "pia-#{project_instance.id}-#{workflow_action_definition.get('id')}" + project_instance: project_instance + state: 'pending' + workflow_action_definition: workflow_action_definition + project_instance_group: project_instance_group + project: project + process_instance_line(remaining_lines, current_index, start_length) + #setTimeout (-> process_instance_line(remaining_lines, current_index, start_length)), 2000 + ).bind @ + file_name = String(file.get('name')) + @set 'loading_message', "Uploading file '#{file_name}'..." + if file_name.match(/\.xlsx?$/) + return failure "Cannot upload excel files. Please use comma separated UTF-8 CSV files" + else unless file_name.match(/\.csv$/) + return failure "Please use comma separated UTF-8 CSV files" + file.readAsText().then (result) => + #instance_lines = result.split("\n").filter Ember.isPresent + #instance_lines.splice(0,1) # header + #process_instance_line instance_lines, 0, instance_lines.length + #return + try + return failure 'File does not have expected header: Batch identifier,Batch date,Scenario,Batch size' unless result.match(/^Batch identifier,Batch date,Scenario,Batch size\n/) + instance_lines = result.split("\n").filter Ember.isPresent + instance_lines.splice(0,1) # header + #setTimeout (-> process_instance_line instance_lines, 0, instance_lines.length), 3000 + process_instance_line instance_lines, 0, instance_lines.length + catch error + failure "Cannot load CSV
#{error?.message}" diff --git a/app/components/project/modals/new-project-from-contract.coffee b/app/components/project/modals/new-project-from-contract.coffee new file mode 100644 index 0000000..f44c9d1 --- /dev/null +++ b/app/components/project/modals/new-project-from-contract.coffee @@ -0,0 +1,53 @@ +import ModalBase from 'ember-cli-dunlop/mixins/modal-base' +import lookupValidator from 'ember-changeset-validations' +import Changeset from 'ember-changeset' +import ModelValidations from 'ember-panda/validations/project' +validator = lookupValidator(ModelValidations) +import config from 'ember-get-config' +{get} = Ember +export default ModalBase.extend + session: Ember.inject.service('session') + contract: null + willOpenModal: -> + dummy = Ember.Object.create + name: '' # new project name + changeset = new Changeset(dummy, validator, ModelValidations) + @set 'changeset', changeset + + actions: + set_contract: (file) -> + @set 'file', file + failure = ((message) -> + @set 'contract', null + @set 'alert_message', message + ).bind @ + file.readAsText().then (result) => + try + contract = JSON.parse(result) + return failure 'Contract has no version' unless contract.contract_version? + @set 'contract', contract + @set 'alert_message', null + catch error + failure 'Cannot parse JSON' + saveRecord: -> + @get('changeset').validate() + component = @ + if @get('changeset.isValid') + file = @get('file') + model_name = 'panda/project' + base_url = @store.adapterFor(model_name).buildURL(model_name) + endpoint = "#{base_url}/new_from_contract" + session = @get('session') + #TODO implement generic request feature, this is not the way to do it! Done because it works and no time + if get(session, 'isAuthenticated') + session.authorize 'authorizer:devise', (key, authorization) => + file.upload(endpoint, data: {name: @get('changeset.name')}, headers: "#{key}": authorization).then (response) -> + if response.body?.errors?.length + component.set 'alert_message', response.body.errors.join('
') + component.set 'contract', null + else + component.store.pushPayload response.body if response.body?.data?.id + component.send 'close' + else # assume same host authenticated rails session + file.upload(endpoint, data: {name: @get('changeset.name')}).then (response) -> + component.send 'close' diff --git a/app/components/project/modals/position-workflow-action-definition-in-project.coffee b/app/components/project/modals/position-workflow-action-definition-in-project.coffee new file mode 100644 index 0000000..867349b --- /dev/null +++ b/app/components/project/modals/position-workflow-action-definition-in-project.coffee @@ -0,0 +1,42 @@ +import ModalBase from 'ember-cli-dunlop/mixins/modal-base' +export default ModalBase.extend + actions: + position_workflow_action_definition_at_top_of: (workflow_action_definition, workflow_group) -> + Ember.run @, -> + workflow_action_definition.setProperties + position: 0 + workflow_group: workflow_group + workflow_action_definition.save() + working_position = 1 + workflow_group.get('sorted_workflow_action_definitions').forEach (group_workflow_action_definition) -> + return if group_workflow_action_definition.get('id') is workflow_action_definition.get('id') + if group_workflow_action_definition.get('position') isnt working_position + group_workflow_action_definition.set 'position', working_position + group_workflow_action_definition.save() + working_position += 1 + @send 'close' + + position_workflow_action_definition_below: (workflow_action_definition, workflow_action_definition_above) -> + workflow_group = workflow_action_definition_above.get('workflow_group') + working_position = 0 + Ember.run @, -> + workflow_group.get('sorted_workflow_action_definitions').forEach (group_workflow_action_definition) -> + return if group_workflow_action_definition.get('id') is workflow_action_definition.get('id') + if group_workflow_action_definition.get('position') isnt working_position + group_workflow_action_definition.set 'position', working_position + group_workflow_action_definition.save() + if group_workflow_action_definition.get('id') is workflow_action_definition_above.get('id') + working_position += 1 + workflow_action_definition.setProperties + position: working_position + workflow_group: workflow_group + workflow_action_definition.save() + working_position += 1 + @send 'close' + + unassign_workflow_action_definition: -> + workflow_action_definition = @get('model') + workflow_action_definition.set 'workflow_group', null + workflow_action_definition.save() + @send 'close' + diff --git a/app/components/project/modals/project-scenarios-show-grid.coffee b/app/components/project/modals/project-scenarios-show-grid.coffee new file mode 100644 index 0000000..236bb7f --- /dev/null +++ b/app/components/project/modals/project-scenarios-show-grid.coffee @@ -0,0 +1,12 @@ +import ModalBase from 'ember-cli-dunlop/mixins/modal-base' +export default ModalBase.extend + actions: + edit_project_scenario: (project_scenario) -> + redirect = => + @modal 'project/project-scenarios-show-grid', + model: project_scenario.get('project') + @modal 'project/edit-project-scenario', + model: project_scenario + close: redirect + save: redirect + diff --git a/app/components/project/scenario-edit-item.coffee b/app/components/project/scenario-edit-item.coffee new file mode 100644 index 0000000..7edf4c1 --- /dev/null +++ b/app/components/project/scenario-edit-item.coffee @@ -0,0 +1,9 @@ +import Ember from 'ember' + +export default Ember.Component.extend + classNames: ['ui', 'segment', 'project-scenario-edit-item-component'] + actions: + editProjectScenario: -> + @modal 'project/edit-project-scenario', + model: @get('model') + diff --git a/app/components/project/show/modals/dunlop-project-info.coffee b/app/components/project/show/modals/dunlop-project-info.coffee new file mode 100644 index 0000000..fce2857 --- /dev/null +++ b/app/components/project/show/modals/dunlop-project-info.coffee @@ -0,0 +1,12 @@ +import ModalBase from 'ember-cli-dunlop/mixins/modal-base' +import WorkflowActionDefinitionOffsetNodes from 'ember-panda/utils/project/workflow-action-definition-offset-nodes' + +export default ModalBase.extend + tab: 'index' + workflow_action_definition_nodes: Ember.computed 'model.workflow_action_definitions.@each.id', -> + result_nodes = WorkflowActionDefinitionOffsetNodes.create + workflow_action_definitions: @get('model.workflow_action_definitions') + reference_date: moment() + result_nodes.filterBy('resolved').sortBy('absolute_value') + actions: + set_tab: (tab_name) -> @set 'tab', tab_name diff --git a/app/components/project/show/project-instance/modals/new-project-instance.coffee b/app/components/project/show/project-instance/modals/new-project-instance.coffee new file mode 100644 index 0000000..43d4dbe --- /dev/null +++ b/app/components/project/show/project-instance/modals/new-project-instance.coffee @@ -0,0 +1,120 @@ +import ModalBase from 'ember-cli-dunlop/mixins/modal-base' +import lookupValidator from 'ember-changeset-validations' +import Changeset from 'ember-changeset' +import ModelValidations from 'ember-panda/validations/project-instance' + +import get_active_workflow_groups from 'ember-panda/utils/project/active-workflow-groups' +import get_active_workflow_action_definitions from 'ember-panda/utils/project/active-workflow-action-definitions' +import WorkflowActionDefinitionOffsetNodes from 'ember-panda/utils/project/workflow-action-definition-offset-nodes' + +export default ModalBase.extend + dependencies_loading: true + new_project_instance_groups: Ember.A() + new_project_instance_actions: Ember.A() + willOpenModal: -> + changeset = new Changeset(@get('model'), lookupValidator(ModelValidations), ModelValidations) + changeset.set 'project_scenario', null # otherwise non null promise object + @set 'model.changeset', changeset + @get('model').setProperties #TODO remove me, this is development logic + client_identifier: 'Ams-AAQ' + #id: "project-instance-#{@get('globals.modals_counter')}" + Ember.RSVP.all([ + @get('model.project.workflow_groups') + @get('model.project.workflow_action_definitions') + ]).then => + @set 'dependencies_loading', false + @send 'calculate_workflow_based_instances' + recalculate_workflow_based_instances: Ember.observer 'model.changeset.batch_size', -> + Ember.run.debounce @, @actions.calculate_workflow_based_instances, 350 + actions: + calculate_workflow_based_instances: -> + store = @store + Ember.run @, -> + workflow_groups = get_active_workflow_groups @get('model.project.workflow_groups'), @get('model.changeset.project_scenario') + workflow_action_definitions = get_active_workflow_action_definitions @get('model.project.workflow_action_definitions'), @get('model.changeset.project_scenario') + project_instance = @get('model') + project_instance.set 'client_identifier', project_instance.get('changeset.client_identifier') #TODO: REMOVE + #project = project_instance.get('project') + #project_instance.get('project_instance_groups').forEach (r) -> r and (r.rollbackAttributes(); r.unloadRecord()) + #project_instance.get('project_instance_groups').invoke 'unloadRecord' #TODO: REMOVE + #project_instance.get('project_instance_actions').invoke 'unloadRecord' #TODO: REMOVE + #project_instance.get('project_instance_groups').invoke 'rollbackAttributes' + #project_instance.get('project_instance_actions').invoke 'rollbackAttributes' + #@set 'model.project_instance_groups', Ember.A() + #@set 'model.project_instance_actions', Ember.A() + # PROJECT GROUPS + #new_project_instance_groups = [] + @set 'new_project_instance_groups', Ember.A() + @set 'new_project_instance_actions', Ember.A() + new_project_instance_groups = @get('new_project_instance_groups') + new_project_instance_actions = @get('new_project_instance_actions') + #new_project_instance_groups.invoke 'rollbackAttributes' + workflow_groups.forEach (workflow_group) -> + new_project_instance_groups.pushObject store.createRecord 'panda/project-instance-group', + #id: "#{project_instance.id}-project-instance-group-#{workflow_group.id}-#{Math.round(Math.random()*1000000)}" + #project_instance: project_instance #IMPORTANT add after close + workflow_group: workflow_group + + now = moment() + reference_date = if selected_date = @get('model.changeset.reference_date') then moment(selected_date) else null + batch_size = @get('model.changeset.batch_size') || 0 + # PROJECT INSTANCES + if reference_date + result_nodes = WorkflowActionDefinitionOffsetNodes.create + workflow_action_definitions: workflow_action_definitions + reference_date: reference_date + batch_size: batch_size + result_nodes.forEach (wrapper) -> + new_project_instance_action_date = Ember.get(wrapper, 'date') + project_instance_group = new_project_instance_groups.findBy('workflow_group.id', wrapper.get('record.workflow_group.id')) + new_project_instance_actions.pushObject store.createRecord 'panda/project-instance-action', + #id: "#{project_instance.id}-project-instance-action-#{wrapper.get('record_id')}-#{Math.round(Math.random()*1000000)}" + #project_instance: project_instance + date: new_project_instance_action_date + state: if new_project_instance_action_date then (if new_project_instance_action_date.toISOString().substring(0,10) < now.toISOString().substring(0, 10) then 'overdue' else 'planned') else 'pending' + workflow_action_definition: Ember.get(wrapper, 'record') + project_instance_group: project_instance_group + else + workflow_action_definitions.forEach (workflow_action_definition) -> + project_instance_group = new_project_instance_groups.findBy('workflow_group.id', workflow_action_definition.get('workflow_group.id')) + new_project_instance_actions.pushObject store.createRecord 'panda/project-instance-action', + #id: "#{project_instance.id}-project-instance-action-#{workflow_action_definition.id}-#{Math.round(Math.random()*1000000)}" + #project_instance: project_instance + state: 'pending' + workflow_action_definition: workflow_action_definition + project_instance_group: project_instance_group + set_project_scenario: (project_scenario) -> + @set 'model.changeset.project_scenario', project_scenario + @set 'model.project_scenario', project_scenario #TODO: REMOVE + @send 'calculate_workflow_based_instances' + set_reference_date: (reference_date) -> + @set 'model.changeset.reference_date', reference_date + #@set 'model.reference_date', reference_date #TODO: REMOVE + @send 'calculate_workflow_based_instances' + close: -> + project = @get('model.project') + project_instance = @get('model') + @get('new_project_instance_groups').forEach (project_instance_group) -> project_instance_group.setProperties project: project, project_instance: project_instance + @get('new_project_instance_actions').forEach (project_instance_action) -> project_instance_action.setProperties project: project, project_instance: project_instance + @_super arguments... + + rollback_and_close: -> + project_instance = @get('model') + project_instance.get('project_instance_groups').invoke 'rollbackAttributes' + project_instance.get('project_instance_actions').invoke 'rollbackAttributes' + project_instance.rollbackAttributes() + #@get('model.changeset').rollback() + @send 'close' + #saveRecord: -> + # project = @get('model.project') + # component = @ + # @get('model').save().then (project_instance) -> + # #@get('model').save().then (project_instance) -> + # # component.get('new_project_instance_groups').forEach((project_instance_group) -> + # # project_instance_group.setProperties project: project, project_instance: project_instance + # # project_instance_group.save() + # # ) + # # component.get('new_project_instance_actions').forEach (project_instance_action) -> + # # project_instance_action.setProperties project: project, project_instance: project_instance + # # project_instance_action.save() + # # component.send 'close' diff --git a/app/components/project/workflow-action-constraints-graph.coffee b/app/components/project/workflow-action-constraints-graph.coffee new file mode 100644 index 0000000..4a25b40 --- /dev/null +++ b/app/components/project/workflow-action-constraints-graph.coffee @@ -0,0 +1,54 @@ +import Ember from 'ember' +import { mapHasMany } from 'ember-cli-dunlop/utils/map-action' +import Viz from 'npm:viz.js' # browserify +export default Ember.Component.extend + #workflow_action_constraints: mapHasMany 'project.workflow_action_definitions', 'workflow_action_constraints' + has_content: true + selected_project_scenario: null + + digraph_content: Ember.computed 'project.update_revision', 'active_workflow_action_definitions.@each.id', -> + labels = '' + connections = '' + component = @ + return unless @get('active_workflow_action_definitions.length') + active_workflow_action_definition_ids = @get('active_workflow_action_definitions').mapBy('id').toArray() + @get('active_workflow_action_definitions').forEach (workflow_action_definition) -> + constraint_connections = workflow_action_definition.get('workflow_action_connections').filterBy('connection_type', 'constraint') + #return unless workflow_action_definition.get('workflow_action_constraints.length') + return unless constraint_connections.get('length') + workflow_action_definition_id = "q#{workflow_action_definition.id}" # start with a letter + unless labels.indexOf(workflow_action_definition_id) > -1 + labels += " #{workflow_action_definition_id} [label=\"#{workflow_action_definition.get('title')}\" tooltip=\"#{workflow_action_definition.get('workflow_group.name')}\"];\n" + constraint_connections.forEach (constraint) -> + return if constraint.get('isDeleted') + #return unless constraint.get('constraint_workflow_action_definition.id') in active_workflow_action_definition_ids + constraint_workflow_action_definition_id = "q#{constraint.get('base_workflow_action_definition.id')}" # start with a letter + unless labels.indexOf(constraint_workflow_action_definition_id) > -1 + labels += " #{constraint_workflow_action_definition_id} [label=\"#{constraint.get('base_workflow_action_definition.title')}\" tooltip=\"#{constraint.get('base_workflow_action_definition.workflow_group.name')}\"];\n" + #actions_label = " label=\"#{constraint.get('type')}\"" + actions_label = " label=\"☝\"" + #if constraint.get('type') is "restrict" + # connections += " #{constraint_workflow_action_definition_id} -> #{workflow_action_definition_id} [color=\"#C0392B\" penwidth=2 #{actions_label}];\n" + #else + # connections += " #{constraint_workflow_action_definition_id} -> #{workflow_action_definition_id} [color=\"#F5B041\" #{actions_label}];\n" + #connections += " #{constraint_workflow_action_definition_id} -> #{workflow_action_definition_id} [color=\"#F5B041\" #{actions_label}];\n" + connections += " #{workflow_action_definition_id} -> #{constraint_workflow_action_definition_id} [color=\"#F5B041\" #{actions_label}];\n" + #@set 'has_content', !!labels + "digraph \"Constraints diagram\" {\n ratio=fill;\n node [style=filled fillcolor=\"#A2D9CE\" shape=box];\n rankdir=TB;\n#{labels}#{connections}}" + + svg_content: Ember.computed "digraph_content", -> + data = @get('digraph_content') + svg = Viz(data, format: 'svg') + svg.htmlSafe() + + attach_events: Ember.on 'didRender', Ember.observer 'svg_content', -> + component = @ + @$('.edge').each (index, edge) => + edge = $(edge) + return unless title = edge.find('title').text() + target_workflow_action_definition_id = title.replace(/.*->q/, '') + return unless target_workflow_action_definition = @store.peekRecord('panda/workflow-action-definition', target_workflow_action_definition_id) + edge.click -> + component.modal 'workflow-action-definition/edit-workflow-action-definition', + model: target_workflow_action_definition + diff --git a/app/components/project/workflow-action-offsets-graph.coffee b/app/components/project/workflow-action-offsets-graph.coffee new file mode 100644 index 0000000..d62005d --- /dev/null +++ b/app/components/project/workflow-action-offsets-graph.coffee @@ -0,0 +1,227 @@ +import Ember from 'ember' +import { mapHasMany } from 'ember-cli-dunlop/utils/map-action' +import WorkflowActionDefinitionOffsetNodes from 'ember-panda/utils/project/workflow-action-definition-offset-nodes' +import Viz from 'npm:viz.js' # browserify +GraphWrapper = Ember.Object.extend {} +critical_path_box_color = '#ccfff2' +secondary_resolve_box_color = '#ffe1b8' +higher_resolve_box_color = '#ffe1b8' +base_x_box_color = '#b9e0ff' +unresolved_node_box_color = '#c2f9c0' +attached_node_box_color = '#dddddd' +attached_node_connection_color = '#aaaaaa' +info_node_box_color = '#f7f7f7' +info_node_connection_color = '#dddddd' +constraint_node_box_color = '#ffcc7d' +constraint_node_connection_color = '#ff5d5d' +{get} = Ember +export default Ember.Component.extend + digraph_contents: [] + classNames: ['workflow-action-offsets-graph-component'] + batch_size_simulation_range: Ember.computed 'project.{batch_size_lower_value,batch_size_upper_value}',-> + {min: @get('project.batch_size_lower_value'), max: @get('project.batch_size_upper_value')} + batch_size_simulation_value: 0 + batch_size_formatter: (-> [to: (v) -> Math.round(v)]).property() + reference_date_day_of_week: Ember.computed.oneWay 'project.reference_date_day_of_week' + has_content: false + choose_reference_date: true + graph_title: '' + show_attached: true + show_pointing_infos: false + show_pointing_constraints: false + active_workflow_action_definitions: [] + active_project_scenario: null + + days_offset_format: (v) -> + v = Math.round(v) # could be float from slider + if @get('chosen_absolute_date') + date = moment(@get('chosen_absolute_date')) + date.add(v, 'day') if v + date.format("D-M-YYYY") + else + return 'X' unless v + return '' unless v and Number.isFinite(v) + if v % 7 is 0 + v = v / 7 + if v > 0 then "X+#{v}w" else "X#{v}w" + else + if v > 0 then "X+#{v}" else "X#{v}" + + didReceiveAttrs: -> + console.log 'Reload workflow-action-offset-graph call from after render' + Ember.run.scheduleOnce 'afterRender', @, @_setup_graph + + #_graph_observer: Ember.observer 'project.update_revision', '{chosen_absolute_date,reference_date_day_of_week,batch_size_simulation_value,show_connected}', 'active_workflow_action_definitions.@each.id', -> + # console.log 'Reload workflow-action-offset-graph call from observer' + # Ember.run.debounce @, @_setup_graph, 150 + _graph_observer: Ember.observer 'project.update_revision', '{show_attached,show_pointing_infos,show_pointing_constraints}', -> + console.log 'Reload workflow-action-offset-graph call from observer' + Ember.run.debounce @, @_setup_graph, 150 + #init: -> + # @_super arguments... + # #@set 'batch_size_simulation_value', 0 + @get('project.batch_size_lower_value') if @get('project.batch_size_lower_value') + # @set 'batch_size_simulation_value', @get('project.batch_size_lower_value') if not @get('batch_size_simulation_value') and @get('project.batch_size_lower_value') + _setup_graph: -> + @set 'digraph_contents', [] + digraph_labels = '' + digraph_connections = '' + component = @ + scenario = @get('active_project_scenario') + show_pointing_infos = @get('show_pointing_infos') + show_pointing_constraints = @get('show_pointing_constraints') + show_attached = @get('show_attached') + return unless @get('active_workflow_action_definitions.length') + + if @get('chosen_absolute_date') + reference_date = moment(@get('chosen_absolute_date')) + else + reference_date = moment() + reference_date.day reference_date_day_of_week if reference_date_day_of_week = @get('reference_date_day_of_week') # updates reference_date instance + batch_size = @get('batch_size_simulation_value') + result_nodes = WorkflowActionDefinitionOffsetNodes.create + workflow_action_definitions: @get('active_workflow_action_definitions') + reference_date: reference_date + batch_size: batch_size + active_project_scenario: scenario + result_nodes.forEach (node) -> + return unless node.resolved or node.offset_wrappers?.length or node.get('record.dependent_workflow_action_offsets.length') + node.offset_wrappers ||= [] # iteration security + workflow_action_definition_id = "q#{node.get('record_id')}" # start with a letter + label_title = "#{node.get('record.title')}" + if node.resolved + label_title += "\\n#{component.days_offset_format(node.get('absolute_value'))}" + label_title += " (#{value_shift}d)" if value_shift = node.get('value_shift') # since X-based boxes don't have a connector displaying the value_shift. NOTE: not absolute + fill_color = if node.resolved then null else unresolved_node_box_color + farthest_absolute_value = Math.max(node.offset_wrappers.map((o) -> Math.abs(o.resulting_absolute_value))...) || 0 + inversed_lookup = node.resolve_type is 'offset-lookup-inversion' + #Ember.makeArray(node.offset_wrappers).forEach (node_offset) -> + node.offset_wrappers.forEach (node_offset) -> + base = if inversed_lookup then node_offset.get('record.workflow_action_definition.content') else node_offset.get('record.base_workflow_action_definition.content') + if base + base_id = "q#{base.get('id')}" + penwidth = if Math.abs(node_offset.resulting_absolute_value) is farthest_absolute_value then 2 else 1 + connection_label = "#{Math.abs(node_offset.total_offset)}d" + connection_label += " (#{Math.abs(node_offset.value_shift)}d)" if node_offset.value_shift + if node_offset.total_offset < 0 + connection_color = '#f5b042' + if not inversed_lookup + digraph_connections += "#{workflow_action_definition_id} -> #{base_id} [color=\"#{connection_color}\" label=\"#{connection_label}\" penwidth=#{penwidth}];\n" + else + digraph_connections += "#{base_id} -> #{workflow_action_definition_id} [color=\"#{connection_color}\" label=\"#{connection_label}\" penwidth=#{penwidth}];\n" + else + connection_color = '#f5b041' + if not inversed_lookup + digraph_connections += "#{base_id} -> #{workflow_action_definition_id} [color=\"#{connection_color}\" label=\"#{connection_label}\" penwidth=#{penwidth}];\n" + else + digraph_connections += "#{workflow_action_definition_id} -> #{base_id} [color=\"#{connection_color}\" label=\"#{connection_label}\" penwidth=#{penwidth}];\n" + if not fill_color + if node.resolve_level <= 1 + fill_color = critical_path_box_color + else if node.resolve_level is 2 + fill_color = secondary_resolve_box_color + else + fill_color = higher_resolve_box_color + + else + fill_color ||= base_x_box_color + digraph_labels += " #{workflow_action_definition_id} [label=\"#{label_title}\" fillcolor=\"#{fill_color}\" tooltip=\"#{node.get('record.workflow_group.name')}\"];\n" + + if show_pointing_infos + node.get('record.pointing_workflow_action_connections').filterBy('connection_type', 'info').forEach (info_connection) -> + pointing_action = info_connection.get('workflow_action_definition') + if scenario + return if info_connection.get('project_scenario.id') and info_connection.get('project_scenario.id') isnt scenario.get('id') + return if scenario.get('id') not in pointing_action.get('project_scenarios').mapBy('id') + info_id = "qi#{pointing_action.get('id')}" + connection_label = 'ℹ' + if digraph_labels.indexOf(info_id) < 0 + info_label_title = "#{pointing_action.get('title')}" + digraph_labels += " #{info_id} [label=\"#{info_label_title}\" fillcolor=\"#{info_node_box_color}\" tooltip=\"#{pointing_action.get('workflow_group.name')}\"];\n" + digraph_connections += "#{info_id} -> #{workflow_action_definition_id} [color=\"#{info_node_connection_color}\" label=\"#{connection_label}\" penwidth=2];\n" + + if show_pointing_constraints + node.get('record.pointing_workflow_action_connections').filterBy('connection_type', 'constraint').forEach (constraint_connection) -> + pointing_action = constraint_connection.get('workflow_action_definition') + if scenario + return if constraint_connection.get('project_scenario.id') and constraint_connection.get('project_scenario.id') isnt scenario.get('id') + return if scenario.get('id') not in pointing_action.get('project_scenarios').mapBy('id') + constraint_id = "qi#{pointing_action.get('id')}" + connection_label = '☝' + if digraph_labels.indexOf(constraint_id) < 0 + constraint_label_title = "#{pointing_action.get('title')}" + digraph_labels += " #{constraint_id} [label=\"#{constraint_label_title}\" fillcolor=\"#{constraint_node_box_color}\" tooltip=\"#{pointing_action.get('workflow_group.name')}\"];\n" + digraph_connections += "#{constraint_id} -> #{workflow_action_definition_id} [color=\"#{constraint_node_connection_color}\" label=\"#{connection_label}\" penwidth=2];\n" + + + if show_attached + # attached nodes might be pointed at, but never are pointing themselves. Whe have no knowledge about if these nodes or even the target nodes are already available on the canvas + # so do some checking. Attached nodes that are not yet available on the canvas will have no offset + @get('active_workflow_action_definitions').filterBy('offset_type', 'attached').forEach (attached_workflow_action_definition) => + # initialization + return unless connection = attached_workflow_action_definition.get('workflow_action_connections').findBy('connection_type', 'attached') + return unless connection_target_workflow_action_definition = connection.get('base_workflow_action_definition') + + # base label + attached_workflow_action_definition_id = "q#{attached_workflow_action_definition.get('id')}" + if digraph_labels.indexOf(attached_workflow_action_definition_id) < 0 + attached_label_title = "#{attached_workflow_action_definition.get('title')}" + digraph_labels += " #{attached_workflow_action_definition_id} [label=\"#{attached_label_title}\" fillcolor=\"#{attached_node_box_color}\" tooltip=\"#{attached_workflow_action_definition.get('workflow_group.name')}\"];\n" + + # target label + connection_target_workflow_action_definition_id = "q#{connection_target_workflow_action_definition.get('id')}" + if digraph_labels.indexOf(connection_target_workflow_action_definition_id) < 0 + connection_target_label_title = "#{connection_target_workflow_action_definition.get('title')}" + digraph_labels += " #{connection_target_workflow_action_definition_id} [label=\"#{connection_target_label_title}\" fillcolor=\"#{unresolved_node_box_color}\" tooltip=\"#{connection_target_workflow_action_definition.get('workflow_group.name')}\"];\n" + + # add the connection + connection_label = connection.get('conditional_base_value') || '' + connection_label = '✓' if connection_label is 'true' + digraph_connections += "#{attached_workflow_action_definition_id} -> #{connection_target_workflow_action_definition_id} [color=\"#{attached_node_connection_color}\" label=\"#{connection_label}\" penwidth=2];\n" + + content = "digraph \"Offset diagram\" {\n ratio=fill;\n node [style=filled fillcolor=\"#{critical_path_box_color}\" shape=box];\n rankdir=TB;\n#{digraph_labels}#{digraph_connections}}" + @set 'has_content', true + @get('digraph_contents').push content + + + svg_contents: Ember.computed "digraph_contents.length", -> + return '' unless @get('digraph_contents.length') + result = Ember.A() + data = @get('digraph_contents').forEach (data) -> + svg = Viz data, + format: 'svg' + engine: 'dot' + svg = svg.replace(/\ + component = @ + @$('.edge').each (index, edge_element) -> + edge = $(edge_element) + return unless title = edge.find('title').text() + return unless color = edge.find('path:first').attr('stroke') + return unless color in ['#f5b041', '#f5b042'] # two flavors + if color is '#f5b042' # determines what the defining action is + target_workflow_action_definition_id = title.replace(/->.*/, '').replace(/^q/, '') + else + target_workflow_action_definition_id = title.replace(/.*->q/, '') + return unless target_workflow_action_definition = component.store.peekRecord('panda/workflow-action-definition', target_workflow_action_definition_id) + edge.click -> + component.modal 'workflow-action-definition/edit-workflow-action-definition', + model: target_workflow_action_definition + @$('.node').each (index, node_element) -> + node = $(node_element) + return unless title = node.find('title').text() + target_workflow_action_definition_id = title.replace(/^qi?/, '') + return unless target_workflow_action_definition = component.store.peekRecord('panda/workflow-action-definition', target_workflow_action_definition_id) + node.click -> + component.modal 'workflow-action-definition/edit-workflow-action-definition', + model: target_workflow_action_definition + + actions: + batch_size_simulation_change: (value) -> + #@incrementProperty 'project.update_revision' + @set 'batch_size_simulation_value', Math.round(value) + hide_absolute_date_choice: -> + @set 'chosen_absolute_date', null # important! determinse wether the labels use X or dates + @set 'choose_reference_date', false diff --git a/app/components/project/workflow-group-offset-diagram.coffee b/app/components/project/workflow-group-offset-diagram.coffee new file mode 100644 index 0000000..abb13c3 --- /dev/null +++ b/app/components/project/workflow-group-offset-diagram.coffee @@ -0,0 +1,371 @@ +import Ember from 'ember' +import EmberCPM from 'ember-cpm' +{ Macros: { sum, difference, product } } = EmberCPM +# SOURCES +# http://bl.ocks.org/kiranml1/6872226 +# https://bost.ocks.org/mike/bar/2/ +# https://bl.ocks.org/hrecht/f84012ee860cb4da66331f18d588eee3 + +GraphWrapper = Ember.Object.extend + record: null + start: null + finish: null + init: -> + @_super arguments... + @set 'start', value: 0, date: null, has_variable_value_part: false + @set 'finish', value: 0, date: null, has_variable_value_part: false + duration: Ember.computed 'start.value', 'finish.value', -> + @get('finish.value') - @get('start.value') +export default Ember.Component.extend + classNames: ['workflow-group-offset-diagram-component'] + bar_height: 20 + x_axis_space: 60 + left_space: 250 + svg_width: 1200 + number_of_groups: Ember.computed.alias 'workflow_groups.length' + graph_height: product 'number_of_groups', 'bar_height' + all_workflow_groups: Ember.computed.alias 'active_workflow_groups' + workflow_groups: Ember.computed 'all_workflow_groups.@each.offset_type', 'all_workflow_groups.@each.duration_value', -> + @get('all_workflow_groups').filter((r) -> r.get('offset_type') isnt 'none').sortBy('position') + range_start_default: null + range_start: null + range_end: null + range_bounds: {min: -7, max: 7} + range_formatter: (-> [{to: @days_offset_format.bind(@)}, {to: @days_offset_format.bind(@)}]).property() + #range_formatter: [{to: ((v)->v)}, {to: ((v)->v)}] + reference_date_day_of_week: Ember.computed.oneWay 'project.reference_date_day_of_week' + choose_reference_date: true # choose a specific date to display as planning. Use X with migration date at specific day otherwise + chosen_absolute_date: null + batch_size_simulation_range: Ember.computed 'project.{batch_size_lower_value,batch_size_upper_value}',-> + {min: @get('project.batch_size_lower_value'), max: @get('project.batch_size_upper_value')} + batch_size_simulation_value: 0 + batch_size_formatter: (-> [to: (v) -> Math.round(v)]).property() + #all_offset_values: Ember.computed.mapBy 'workflow_groups', 'offset_values' + #uniq_offset_values: Ember.computed.uniq 'all_offset_values' + #offset_values: Ember.computed 'uniq_offset_values', -> @get('uniq_offset_values').sort() + #min_offset_value: Ember.computed.alias 'offset_values.firstObject' + #max_offset_value: Ember.computed.alias 'offset_values.lastObject' + #max_end_time: Ember.computed 'workflow_groups.@each.offset_value', 'workflow_groups.@each.duration_value', -> + # max_end_time = -1e-10 + # @get('workflow_groups').forEach (workflow_group) -> + # end_time = workflow_group.get('offset_value') + workflow_group.get('duration_value') + # max_end_time = end_time if end_time > max_end_time + # max_end_time + days_offset_format: (v) -> + v = Math.round(v) # could be float from slider + if @get('chosen_absolute_date') + date = moment(@get('chosen_absolute_date')) + date.add(v, 'day') if v + date.format("D-M-YYYY") + else + return '' unless v + return '' unless v and Number.isFinite(v) + if v % 7 is 0 + v = v / 7 + if v > 0 then "X+#{v}w" else "X#{v}w" + else + if v > 0 then "X+#{v}" else "X#{v}" + + actions: + range_changed: ([range_start, range_end]) -> + [range_start, range_end] = [Math.round(range_start), Math.round(range_end)] + @setProperties + range_start_default: [range_start, range_end] + range_start: range_start + range_end: range_end + + batch_size_simulation_change: (value) -> + @setProperties + range_start: null + range_end: null + batch_size_simulation_value: Math.round(value) + + hide_absolute_date_choice: -> + @set 'chosen_absolute_date', null # important! determinse wether the labels use X or dates + @set 'choose_reference_date', false + + #didReceiveAttrs: -> + init: -> + @_super arguments... + @set 'batch_size_simulation_value', @get('project.batch_size_lower_value') if not @get('batch_size_simulation_value') and @get('project.batch_size_lower_value') + didReceiveAttrs: -> + console.log 'Reload workflow-group-offset-diagram call from after render' + Ember.run.scheduleOnce 'afterRender', @, @_setup_graph + + _graph_observer: Ember.observer 'workflow_groups.@each.{offset_value,name,duration_value}', 'range_start', 'range_end', 'batch_size_simulation_value', 'batch_size_simulation_range', 'reference_date_day_of_week', 'chosen_absolute_date', -> + console.log 'Reload workflow-group-offset-diagram call from observer' + Ember.run.debounce @, @_setup_graph, 150 + + _setup_graph: -> + console.log 'Reload workflow-group-offset-diagram' + container_selector = '.workflow-group-offset-diagram-container' + component = @ + @$(container_selector).html('').append '' + workflow_groups = @get('workflow_groups').toArray() + return unless workflow_groups.length + active_workflow_group_ids = workflow_groups.mapBy('id').toArray() + + # create conditioned workflow_groups + reference_date_day_of_week = @get('reference_date_day_of_week') + if @get('chosen_absolute_date') + reference_date = moment(@get('chosen_absolute_date')) + else + reference_date = moment() + reference_date.day reference_date_day_of_week if reference_date_day_of_week # updates reference_date instance + + wrappers = [] + x_min = -1 # at least include zero + x_max = 0 + batch_size_simulation_value = @get('batch_size_simulation_value') + # recursive adder + add_dependent_wrapper_to_wrappers = (base_wrapper, record) -> + wrapper = GraphWrapper.create + record: record + if record.get('offset_type') is 'connected_finish' + wrapper.set 'finish.value', base_wrapper.get('start.value') - (record.get('offset_value') || 0) + wrapper.set 'start.constant_value', wrapper.get('finish.value') - (record.get('duration_value') || 0) + if batch_size_simulation_value and threshold = wrapper.get('record.batch_element_threshold_value') + # see how many times the threshold is exceeded. Within the threshold means no extra time + threshold_exceedance = Math.floor(batch_size_simulation_value / (0.1 + parseInt(threshold))) # + 0.1 (delta) to exclude exact match of batch_size and threshold + if threshold_exceedance + wrapper.set 'has_variable_value_part', true + period_unit = wrapper.get('record.batch_element_threshold_unit') + base_date = reference_date.clone().add(wrapper.get('start.constant_value'), 'day') + if period_unit is 'businessday' + wrapper.set 'start.date', base_date.clone().businessSubtract(threshold_exceedance) + else + wrapper.set 'start.date', base_date.clone().subtract(threshold_exceedance, period_unit) + wrapper.set 'start.value', wrapper.get('start.constant_value') - base_date.diff(wrapper.get('start.date'), 'day') + else + wrapper.set 'start.value', wrapper.get('start.constant_value') + else + wrapper.set 'start.value', wrapper.get('start.constant_value') + else #if record.get('offset_type') is 'connected_start' + wrapper.set 'start.value', base_wrapper.get('finish.value') + (record.get('offset_value') || 0) + wrapper.set 'finish.constant_value', wrapper.get('start.value') + (record.get('duration_value') || 0) + if batch_size_simulation_value and threshold = wrapper.get('record.batch_element_threshold_value') + if threshold_exceedance = Math.floor(batch_size_simulation_value / (0.1 + parseInt(threshold))) # + 0.1 (delta) to exclude exact match of batch_size and threshold + wrapper.set 'has_variable_value_part', true + period_unit = wrapper.get('record.batch_element_threshold_unit') + base_date = reference_date.clone().add(wrapper.get('finish.constant_value')) + if period_unit is 'businessday' + wrapper.set 'finish.date', base_date.clone().businessAdd(threshold_exceedance) + else + wrapper.set 'finish.date', base_date.clone().add(threshold_exceedance, period_unit) + wrapper.set 'finish.value', wrapper.get('finish.constant_value') + wrapper.get('finish.date').diff(base_date, 'day') + else + wrapper.set 'finish.value', wrapper.get('finish.constant_value') + else + wrapper.set 'finish.value', wrapper.get('finish.constant_value') + wrapper.set 'start.date', reference_date.clone().add(wrapper.get('start.value'), 'day') unless wrapper.get('start.date') + wrapper.set 'finish.date', reference_date.clone().add(wrapper.get('finish.value'), 'day') unless wrapper.get('finish.date') + x_min = Math.min(x_min, wrapper.get('start.value')) + x_max = Math.max(x_max, wrapper.get('finish.value')) + wrappers.push wrapper + record.get('offset_dependents').forEach (offset_dependent_record) -> + add_dependent_wrapper_to_wrappers wrapper, offset_dependent_record if offset_dependent_record.get('id') in active_workflow_group_ids + + workflow_groups.rejectBy('offset_base.content').forEach (record) -> + wrapper = GraphWrapper.create + record: record + add_dependent_wrapper_to_wrappers wrapper, record + wrappers = wrappers.sortBy('record.position') + x_max += 1 # design space at the right + + if @get('range_bounds.min') isnt x_min or @get('range_bounds.max') isnt x_max + @set 'range_bounds', min: x_min, max: x_max + @set 'range_start_default', [x_min, x_max] # bounds have changed, reset starts + else if not @get('range_start_default') # uninitialized + @set 'range_start_default', [x_min, x_max] + + graph_min = @get('range_start') || x_min + graph_max = @get('range_end') || x_max + + graph_range_width = @get('svg_width') - @get('left_space') - 22 + x = d3.scale.linear().domain([graph_min, graph_max]).range([0, graph_range_width]) + #yscale = d3.scale.linear().domain([0, @get('number_of_groups')]).range([0, @get('graph_height')]) + y = d3.scale.ordinal().rangeRoundBands([@get('graph_height'), 0], 0.1).domain(wrappers.mapBy('record.name').reverse()) + + x_ticks = [] + wrappers.forEach (wrapper) -> + [start, finish] = wrapper.getPropertyValues('start.value', 'finish.value') + x_ticks.push start if x_ticks.indexOf(start) < 0 + x_ticks.push finish if x_ticks.indexOf(finish) < 0 + #workflow_groups.forEach (workflow_action_definition) -> + # offset_value = workflow_action_definition.get('offset_value') + # x_ticks.push offset_value unless offset_value is 0 or x_ticks.indexOf(offset_value) > -1 + # if duration_value = workflow_action_definition.get('duration_value') + # end_time = offset_value + duration_value + # x_ticks.push end_time unless end_time is 0 or x_ticks.indexOf(end_time) > -1 + x_ticks = x_ticks.filter( (v) -> v >= graph_min and v <= graph_max).sort(d3.ascending) + # now remove the ticks not having enough space + displayed_x_ticks = [] + if x_ticks.length + displayed_x_ticks.push x_ticks[0] + latest_pixel_x_for_space = x(x_ticks[0]) + x_ticks.forEach (v) -> + current_pixel_x = x(v) + if current_pixel_x - latest_pixel_x_for_space > 14 + latest_pixel_x_for_space = current_pixel_x + displayed_x_ticks.push v + + svg_height = @get('x_axis_space') + @get('graph_height') + canvas = d3.select("#{container_selector} svg") + .attr 'width', '100%' + .attr 'height', svg_height + .attr 'viewBox', "0 0 #{@get('svg_width')} #{svg_height}" + + lines_data = x_ticks + lines_data = lines_data.slice(1) if x(lines_data[0]) is graph_min # first element is y-axis, so no tick + lines = canvas.append('g') + .attr('class', 'offset-lines') + .attr('transform', "translate(#{@get('left_space')},0)") + .selectAll('line') + .data(lines_data) + .enter() + .append('line') + .attr( + x1: (d) -> x(d) + y1: 0 + x2: (d) -> x(d) + y2: @get('graph_height') + 5 + class: 'offset-line' + ) + + if graph_max >= 0 and graph_min <= 0 # base line is zero + base_line = canvas.append('line').attr + x1: x(0) + y1: 0 + x2: x(0) + y2: 15 + @get('graph_height') + class: 'base-line' + transform: "translate(#{@get('left_space')},0)" + + bars_grid = canvas.append('g') + .attr('transform', "translate(#{@get('left_space')},0)") + .attr('class', 'bars') + + bars = bars_grid.selectAll('.bar') + .data(wrappers.filter((d) -> (d.get('finish.value') > graph_min) and (d.get('start.value') < graph_max))) + .enter() + bars.append('rect') + .attr('class', (d) -> "bar lead-time-#{d.get('duration') || 'none'}") + .attr('x', (d) -> x(Math.max(d.get('start.value'), graph_min))) + .attr('y', (d) -> y(d.get('record.name'))) + .attr('height', y.rangeBand()) + .attr 'width', (d) -> + if duration = d.get('duration') + display_range = duration + if d.get('start.value') < graph_min # remove difference from width + display_range = display_range + d.get('start.value') - graph_min + if d.get('finish.value') > graph_max # remove difference from width right threshold + display_range = display_range + graph_max - d.get('finish.value') + graph_range_width * display_range / (graph_max - graph_min) + else + # no duration to display, clickable width, gets different styling + 5 + .on 'click', (wrapper) => + return unless wrapper?.get('record') + @modal 'project/edit-workflow-group', + model: wrapper.get('record') + + wrappers_having_extra_duration_due_to_batch_simulation = wrappers.filter (d) -> + return false unless d.get('has_variable_value_part') + if d.get('record.offset_type') is 'connected_finish' + d.get('start.constant_value') > graph_min and d.get('start.value') < graph_max + else + d.get('finish.value') > graph_min and d.get('finish.constant_value') < graph_max + if wrappers_having_extra_duration_due_to_batch_simulation.length + canvas.append('g') + .attr('transform', "translate(#{@get('left_space')},0)") + .attr('class', 'bars-parts-indicating-extra-duration-due-to-batch-size') + .selectAll('.bar') + .data(wrappers_having_extra_duration_due_to_batch_simulation) + .enter() + .append('rect') + .attr('class', "bar extra-duration") + .attr('x', (d) -> + if d.get('record.offset_type') is 'connected_finish' + x(Math.max(d.get('start.value'), graph_min)) + else + x(Math.max(d.get('finish.constant_value'), graph_min)) + ) + .attr('y', (d) -> y(d.get('record.name'))) + .attr('height', y.rangeBand()) + .attr 'width', (d) -> + if d.get('record.offset_type') is 'connected_finish' + display_range = Math.abs(d.get('start.constant_value') - d.get('start.value')) + if d.get('start.value') < graph_min # remove difference from width + display_range = display_range + d.get('start.value') - graph_min + if d.get('start.constant_value') > graph_max # remove difference from width right threshold + display_range = display_range + graph_max - d.get('start.constant_value') + graph_range_width * display_range / (graph_max - graph_min) + else + display_range = Math.abs(d.get('finish.value') - d.get('finish.constant_value')) + if d.get('finish.constant_value') < graph_min # remove difference from width + display_range = display_range + d.get('finish.constant_value') - graph_min + if d.get('finish.value') > graph_max # remove difference from width right threshold + display_range = display_range + graph_max - d.get('finish.value') + graph_range_width * display_range / (graph_max - graph_min) + + + # bars crossing the left y-axis due to slider restrictions. Show text + groups_crossing_left_axis = wrappers.filter (d) -> + d.get('start.value') < graph_min and (d.get('finish.value') >= graph_min) # allow connecting edge, therefore >= in stead of > + if groups_crossing_left_axis.length + canvas.append('g') + .attr('transform', "translate(#{@get('left_space')},0)") + .attr('class', 'groups-crossing-left-axis') + .selectAll('text') + .data(groups_crossing_left_axis) + .enter() + .append('text') + .attr('y', (d) -> 14 + y(d.get('record.name'))) # 14 is visually chosen, a bit less than bar_height + .html((d) -> "← #{component.days_offset_format(d.get('start.value'))}") + + # bars crossing the right y-axis due to slider restrictions. Show text + groups_crossing_right_axis = wrappers.filter (d) -> + d.get('start.value') < graph_max and (d.get('finish.value') > graph_max) + if groups_crossing_right_axis.length + canvas.append('g') + .attr('transform', "translate(#{@get('left_space')},0)") + .attr('class', 'groups-crossing-right-axis') + .selectAll('text') + .data(groups_crossing_right_axis) + .enter() + .append('text') + .attr('y', (d) -> 14 + y(d.get('record.name'))) # 14 is visually chosen, a bit less than bar_height + .attr('x', graph_range_width) + .html((d) -> "#{component.days_offset_format(d.get('finish.value'))} →") + + yAxis = d3.svg.axis() + yAxis + .orient('left') + .scale(y) + .tickSize(1) + y_xis = canvas.append('g') + .attr('transform', "translate(#{@get('left_space')},0)") + .attr('class','y-axis') + .call(yAxis) + xAxis = d3.svg.axis() + .orient('bottom') + .scale(x) + .tickSize(1) + .tickValues(displayed_x_ticks) + .tickFormat component.days_offset_format.bind(component) + if displayed_x_ticks.length + x_xis = canvas.append('g') + .attr('transform', "translate(#{@get('left_space')},#{@get('graph_height')})") + .attr('class','x-axis') + .call(xAxis) + .selectAll('text') + .style('text-anchor', 'end') + .attr('dx', '-.8em') + .attr('dy', '.15em') + .attr('transform', "rotate(-40)") + canvas.selectAll('.y-axis .tick').on 'click', (d) => + return unless workflow_group = workflow_groups.findBy 'name', d + @modal 'project/edit-workflow-group', + model: workflow_group + + @set 'd3graph', canvas diff --git a/app/components/select-duration.coffee b/app/components/select-duration.coffee new file mode 100644 index 0000000..6864b3d --- /dev/null +++ b/app/components/select-duration.coffee @@ -0,0 +1,20 @@ +import Ember from 'ember' + +export default Ember.Component.extend + classNames: ['select-duration'] + value: 0 + unit: 'day' + week_days: (-> [1,2,3,4,5]).property() + unit_businessday: Ember.computed.equal 'unit', 'businessday' + week_days_index_sum: Ember.computed.sum 'week_days' + value_date_scope: Ember.computed 'value', -> + if Math.round(Math.abs(@get('value'))) is 1 then 'date.unit' else 'date.unit.plural' + has_default_business_days: Ember.computed 'week_days.length', 'week_days_index_sum', -> + @get("week_days.length") is 5 and @get("week_days_index_sum") is 15 + actions: + toggle_week_day_presence: (target, week_day, selected) -> + #target = target.get('batch_element_threshold_business_days') unless Ember.isArray(target) + if selected + target.addObject week_day.index + else + target.removeObject week_day.index diff --git a/app/components/select-week-days.coffee b/app/components/select-week-days.coffee new file mode 100644 index 0000000..664bc3c --- /dev/null +++ b/app/components/select-week-days.coffee @@ -0,0 +1,5 @@ +import Ember from 'ember' + +export default Ember.Component.extend + abc: 3 + diff --git a/app/components/set-workflow-group-tracker-group.coffee b/app/components/set-workflow-group-tracker-group.coffee new file mode 100644 index 0000000..3f6875b --- /dev/null +++ b/app/components/set-workflow-group-tracker-group.coffee @@ -0,0 +1,20 @@ +import Ember from 'ember' +export default Ember.Component.extend + isEditing: false + selectedViewItem: null + doubleClick: -> @toggleProperty("isEditing") + actions: + selectViewItem: (tracker_group, select_component)-> + if current_tracker_group = @get("current_tracker_group") + @get("workflow_group.tracker_groups").then (tracker_groups)-> + tracker_groups.removeObject current_tracker_group + unless tracker_group + @get("workflow_group").save() + return + + @get("workflow_group.tracker_groups").then (tracker_groups)-> + tracker_groups.pushObject(tracker_group) + @get("workflow_group").save() + set_selected: (-> + @set("selectedViewItem", @get("current_tracker_group")) + ).on("didReceiveAttrs") diff --git a/app/components/state-badge.js b/app/components/state-badge.js new file mode 100644 index 0000000..bba6080 --- /dev/null +++ b/app/components/state-badge.js @@ -0,0 +1 @@ +export { default } from 'ember-panda/components/state-badge'; diff --git a/app/components/ui-multi-select.js b/app/components/ui-multi-select.js new file mode 100644 index 0000000..b9a18c6 --- /dev/null +++ b/app/components/ui-multi-select.js @@ -0,0 +1 @@ +export { default } from 'ember-panda/components/ui-multi-select'; diff --git a/app/components/user/modals/edit-user.coffee b/app/components/user/modals/edit-user.coffee new file mode 100644 index 0000000..6129f4a --- /dev/null +++ b/app/components/user/modals/edit-user.coffee @@ -0,0 +1,17 @@ +import ModalBase from 'ember-cli-dunlop/mixins/modal-base' +import lookupValidator from 'ember-changeset-validations' +import Changeset from 'ember-changeset' +import ModelValidations from 'ember-panda/validations/project' +validator = lookupValidator(ModelValidations) +export default ModalBase.extend + selected_organizations: [] + possible_organizations: [] + willOpenModal: -> + @set 'possible_organizations', @store.peekAll('panda/organization') + @get("model.organizations").then (organizations)=> + @set "selected_organizations", organizations.toArray() + + actions: + saveRecord: -> + @set("model.organizations", @get('selected_organizations')) + @_super() diff --git a/app/components/user/modals/new-user.coffee b/app/components/user/modals/new-user.coffee new file mode 100644 index 0000000..f542f66 --- /dev/null +++ b/app/components/user/modals/new-user.coffee @@ -0,0 +1,15 @@ +import ModalBase from 'ember-cli-dunlop/mixins/modal-base' +import lookupValidator from 'ember-changeset-validations' +import Changeset from 'ember-changeset' +import ModelValidations from 'ember-panda/validations/user' +validator = lookupValidator(ModelValidations) +export default ModalBase.extend + selected_organizations: [] + possible_organizations: [] + willOpenModal: -> + @set 'possible_organizations', @store.peekAll('panda/organization') + @get("model.organizations").then (organizations)=> + @set "selected_organizations", organizations.toArray() + user = @get('model') + changeset = new Changeset(user, validator, ModelValidations) + user.set 'changeset', changeset diff --git a/app/components/value-type-display.js b/app/components/value-type-display.js new file mode 100644 index 0000000..e365ff9 --- /dev/null +++ b/app/components/value-type-display.js @@ -0,0 +1 @@ +export { default } from 'ember-panda/components/value-type-display'; diff --git a/app/components/workflow-action-definition-item.coffee b/app/components/workflow-action-definition-item.coffee new file mode 100644 index 0000000..2625513 --- /dev/null +++ b/app/components/workflow-action-definition-item.coffee @@ -0,0 +1,69 @@ +import Ember from 'ember' +export default Ember.Component.extend + classNames: ["workflow-action-definition-item", 'workflow-action-container'] + classNameBindings: ["workflow_action_definition.done:done", "isEditing:is-editing:is-display"] + selectedValueType: Ember.computed.oneWay "workflow_action_definition.value_type" + isEditing: false + + make_editable: (-> + @set 'isEditing', true + ).on "doubleClick" + + didRender: -> + @_super arguments... + #@set 'isEditing', true if @get('workflow_action_definition.isNew') + + actions: + saveWorkflowActionDefinition: -> + record = @get("workflow_action_definition") + first_save = record.get('isNew') + record.setProperties + value_type: @get("selectedValueType") + record.save().then => + @set 'isEditing', false + if first_save + @send('editWorkflowActionDefinition', record) + else + # fist save has no graph impact, so only overhere + @incrementProperty 'model.project.update_revision' + + destroyWorkflowActionDefinition: (workflow_action_definition)-> + @modal "confirm", + title_path: "general.destroy.are_you_sure" + model: @get("workflow_action_definition") + ok: -> + @set 'isEditing', false + workflow_action_definition = @get('model') + if workflow_action_definition.get('isNew') + workflow_action_definition.deleteRecord() + else + workflow_action_definition.rollbackAttributes() + workflow_action_definition.destroyRecord() + @incrementProperty 'model.project.update_revision' + rollbackWorkflowActionDefinition: -> + @set 'isEditing', false + @get('workflow_action_definition').rollbackAttributes() + + positionInProject: -> + @modal 'project/position-workflow-action-definition-in-project', + model: @get('workflow_action_definition') + @set 'isEditing', false + + authorize_users: -> + @modal 'workflow-action-definition/authorize-users', + title_path: 'workflow_action_definition.authorize_users.title' + model: @get('workflow_action_definition') + + editWorkflowActionDefinition: -> + @modal 'workflow-action-definition/edit-workflow-action-definition', + model: @get('workflow_action_definition') + @set 'isEditing', false # usability + + #select_on_edit: Ember.computed "workflow_action_definition.isEditing", -> + # @$('.workflow-action-definition-title-edit').select() + + updateWorkflowActionDefinitionIfNeeded: (-> + workflow_action_definition = @get('workflow_action_definition') + if workflow_action_definition.changedAttributes().done + workflow_action_definition.save() + ).observes('workflow_action_definition.done') diff --git a/app/components/workflow-action-definition/modal-header.coffee b/app/components/workflow-action-definition/modal-header.coffee new file mode 100644 index 0000000..01f82c2 --- /dev/null +++ b/app/components/workflow-action-definition/modal-header.coffee @@ -0,0 +1,4 @@ +import Ember from 'ember' + +export default Ember.Component.extend + classNames: ['ui', 'message'] diff --git a/app/components/workflow-action-definition/modals/edit-workflow-action-definition.coffee b/app/components/workflow-action-definition/modals/edit-workflow-action-definition.coffee new file mode 100644 index 0000000..5a2a6d5 --- /dev/null +++ b/app/components/workflow-action-definition/modals/edit-workflow-action-definition.coffee @@ -0,0 +1,333 @@ +import ModalBase from 'ember-cli-dunlop/mixins/modal-base' + +import lookupValidator from 'ember-changeset-validations' +import Changeset from 'ember-changeset' +import ModelValidations from 'ember-panda/validations/workflow-action-definition' +import add_business_day_computations from 'ember-panda/utils/business-day-computations' +import { value_type_options } from 'ember-panda/models/panda/workflow-action-definition' +ESC_KEY = 27 +Component = + _initEscListener: -> + closeOnEscapeKey = (ev) => + @send 'rollback_and_close' if ev.keyCode is ESC_KEY + Ember.$('body').on('keyup.modal-dialog', closeOnEscapeKey) + value_type_options: value_type_options + constraint_types: Ember.A(['warn', 'restrict']) + lastSelectedWorkflowGroup: null + lastSelectedWorkflowActionDefinition: null + selectedConstraintType: 'warn' + possible_organizations: [] + connection_types: ['info', 'constraint'] # could be warning in future as well + + new_workflow_action_constraint_warning: '' + new_workflow_action_offset_warning: '' + #new_workflow_action_offset_offset_value: 0 + #new_workflow_action_offset_offset_unit: 'day' + #new_workflow_action_offset_offset_business_days: (-> [ 1, 2, 3, 4, 5 ]).property() + #new_workflow_action_offset_batch_element_threshold_value: 0 + #new_workflow_action_offset_batch_element_threshold_unit: 'businessday' + #new_workflow_action_offset_batch_element_threshold_business_days: (-> [ 1, 2, 3, 4, 5 ]).property() + #select_action_offset_base_alert: '' + + attached_to_workflow_action: null + attached_to_workflow_action_workflow_group: null + possible_attached_to_workflow_action_workflow_action_definitions: [] + attached_to_connection_conditional_base_value: '' + attached_to_connection_warning: '' + + working_workflow_action_offsets: Ember.A() + non_deleted_working_workflow_action_offsets: Ember.computed 'working_workflow_action_offsets.@each.isDeleted', -> @get('working_workflow_action_offsets').rejectBy('isDeleted') + + working_pointing_workflow_action_connections: Ember.A() + non_deleted_working_pointing_workflow_action_connections: Ember.computed 'working_pointing_workflow_action_connections.@each.isDeleted', -> @get('working_pointing_workflow_action_connections').rejectBy('isDeleted') + + #working_workflow_action_constraints: Ember.A() + #used_workflow_action_definitions_for_offset: Ember.computed 'working_workflow_action_offsets.@each.isDeleted', -> + # @get('working_workflow_action_offsets').rejectBy('isDeleted').mapBy('base_workflow_action_definition.id') + #used_workflow_action_definitions_for_constraint: Ember.computed 'working_workflow_action_constraints.@each.isDeleted', -> + # @get('working_workflow_action_constraints').rejectBy('isDeleted').mapBy('constraint_workflow_action_definition.id') + + offset_type_none: Ember.computed.equal 'model.changeset.offset_type', 'none' + offset_type_attached: Ember.computed.equal 'model.changeset.offset_type', 'attached' + offset_type_configured: Ember.computed.equal 'model.changeset.offset_type', 'configured' + + willOpenModal: -> + @_initEscListener() + @get('model.workflow_group').then (workflow_group) => + @set 'lastSelectedWorkflowGroup', workflow_group + @set 'possible_organizations', @store.peekAll('panda/organization') + + # CONNECTION SETUP + if workflow_action_connection = @get('model.workflow_action_connections').findBy('connection_type', 'attached') + @set 'attached_to_workflow_action', workflow_action_connection.get('base_workflow_action_definition.content') + @set 'attached_to_workflow_action_workflow_group', @get('attached_to_workflow_action.workflow_group.content') + @set 'attached_to_connection_conditional_base_value', workflow_action_connection.get('conditional_base_value') + else # same group of configuring action + @set 'attached_to_workflow_action_workflow_group', @get('model.workflow_group.content') + unless @get('attached_to_workflow_action_workflow_group') # fallback to first available group + @set 'attached_to_workflow_action_workflow_group', @get('model.project.workflow_groups.firstObject') + if connection_group = @get('attached_to_workflow_action_workflow_group') + @set 'possible_attached_to_workflow_action_workflow_action_definitions', connection_group.get('sorted_workflow_action_definitions').rejectBy('id', @get('model.id')) + + + changeset = new Changeset(@get('model'), lookupValidator(ModelValidations), ModelValidations) + add_business_day_computations changeset, 'offset' + add_business_day_computations changeset, 'batch_element_threshold' + @set 'model.changeset', changeset + model_id = @get('model.id') + @get('model.workflow_action_offsets').then (workflow_action_offsets) => + @set('working_workflow_action_offsets', workflow_action_offsets.toArray()) + workflow_action_offsets.forEach (workflow_action_offset) -> + if selector_workflow_group = workflow_action_offset.get('base_workflow_action_definition.workflow_group.content') + workflow_action_offset.set 'selector_workflow_group', selector_workflow_group + workflow_action_offset.set 'filtered_workflow_action_definitions', selector_workflow_group.get('sorted_workflow_action_definitions').rejectBy('id', model_id) + base_action = workflow_action_offset.get('base_workflow_action_definition.content') + workflow_action_offset.set 'x_based', !base_action + workflow_action_offset.set 'original_base_workflow_action_definition', base_action + @get('model.pointing_workflow_action_connections').then (pointing_workflow_action_connections) => + @set('working_pointing_workflow_action_connections', pointing_workflow_action_connections.toArray()) + pointing_workflow_action_connections.forEach (pointing_workflow_action_connection) -> + if selector_workflow_group = pointing_workflow_action_connection.get('workflow_action_definition.workflow_group.content') + pointing_workflow_action_connection.set 'selector_workflow_group', selector_workflow_group + pointing_workflow_action_connection.set 'filtered_workflow_action_definitions', selector_workflow_group.get('sorted_workflow_action_definitions').rejectBy('id', model_id) + original_action = pointing_workflow_action_connection.get('workflow_action_definition.content') + pointing_workflow_action_connection.set 'original_workflow_action_definition', original_action + #@get('model.workflow_action_constraints').then => @set('working_workflow_action_constraints', arguments[0].toArray()) + + actions: + #newWorkflowActionConstraint: (constraint_workflow_action_definition, event) -> + # @set 'new_workflow_action_constraint_warning', '' + # return unless constraint_workflow_action_definition + # $(event.target).val('') + # if @get('used_workflow_action_definitions_for_constraint').includes(constraint_workflow_action_definition.get('id')) + # @set 'new_workflow_action_constraint_warning', + # t('workflow_action_definition.edit_workflow_action_definition.workflow_action_definition_already_used', constraint_workflow_action_definition.toJSON?()) + # return + # constraint = @store.createRecord 'panda/workflow-action-constraint', + # type: @get('selectedConstraintType') + # workflow_action_definition: @get('model') + # constraint_workflow_action_definition: constraint_workflow_action_definition + # @get('working_workflow_action_constraints').addObject constraint + + #newWorkflowActionOffset: (base_workflow_action_definition, event) -> + # base_workflow_action_definition = @get('attached_to_workflow_action') + # offset_value = parseInt(@get('new_workflow_action_offset_offset_value')) || 0 + # if @get('offset_type_workflow_action_definition') and not base_workflow_action_definition + # @set 'new_workflow_action_offset_warning', + # t('workflow_action_definition.edit_workflow_action_definition.no_action_offset_base_given_alert') + # return + # if @get('used_workflow_action_definitions_for_offset').includes(base_workflow_action_definition.get('id')) + # @set 'new_workflow_action_offset_warning', + # t('workflow_action_definition.edit_workflow_action_definition.workflow_action_definition_already_used', base_workflow_action_definition.toJSON?()) + # @set 'attached_to_workflow_action', null + # return + # @set 'new_workflow_action_offset_warning', '' + # offset = @store.createRecord 'workflow-action-offset', + # offset_value: offset_value + # offset_unit: @get('new_workflow_action_offset_offset_unit') + # offset_business_days: @get('new_workflow_action_offset_offset_business_days') + # batch_element_threshold_value: @get('new_workflow_action_offset_batch_element_threshold_value') + # batch_element_threshold_unit: @get('new_workflow_action_offset_batch_element_threshold_unit') + # batch_element_threshold_business_days: @get('new_workflow_action_offset_batch_element_threshold_business_days') + # workflow_action_definition: @get('model') + # base_workflow_action_definition: base_workflow_action_definition + # @get('working_workflow_action_offsets').addObject offset + # @set 'attached_to_workflow_action', null # required for empty validation + + add_workflow_action_offset: -> + @set 'new_workflow_action_offset_warning', '' + workflow_action_offset = @store.createRecord 'panda/workflow-action-offset', + workflow_action_definition: @get('model') + x_based: true + @actions.set_selector_workflow_group.bind(@) workflow_action_offset, @get('model.workflow_group'), force_refresh: true + @get('working_workflow_action_offsets').pushObject workflow_action_offset + + set_base_workflow_action_definition: (workflow_action_offset, workflow_action_definition) -> + workflow_action_offset.set 'base_workflow_action_definition_warning', '' + workflow_action_offset.set 'base_workflow_action_definition', workflow_action_definition + + set_connection_workflow_action_definition: (connection, workflow_action_definition) -> + connection.set 'workflow_action_definition_warning', '' + connection.set 'workflow_action_definition', workflow_action_definition + + add_workflow_action_connection: -> + connection = @store.createRecord 'panda/workflow-action-connection', + base_workflow_action_definition: @get('model') + connection_type: @get('connection_types.firstObject') + @actions.set_connection_selector_workflow_group.bind(@) connection, @get('model.workflow_group'), force_refresh: true + @get('working_pointing_workflow_action_connections').pushObject connection + + + workflow_group_change: (workflow_group) -> + @set 'lastSelectedWorkflowGroup', workflow_group + @set 'lastSelectedWorkflowActionDefinition', null + + #destroyWorkflowActionConstraint: (workflow_action_constraint) -> + # if workflow_action_constraint.get('isNew') + # workflow_action_constraint.rollbackAttributes() + # @get('working_workflow_action_constraints').removeObject workflow_action_constraint + # else + # workflow_action_constraint.deleteRecord() + # @set 'new_workflow_action_constraint_warning', '' + + destroyWorkflowActionOffset: (workflow_action_offset) -> + if workflow_action_offset.get('isNew') + @get('working_workflow_action_offsets').removeObject workflow_action_offset + workflow_action_offset.rollbackAttributes() + else + workflow_action_offset.deleteRecord() + #@set 'new_workflow_action_offset_warning', '' + + destroyWorkflowActionConnection: (connection) -> + if connection.get('isNew') + @get('working_pointing_workflow_action_connections').removeObject connection + connection.rollbackAttributes() + else + connection.deleteRecord() + + selectOrganization: (organization) -> @set('model.organization', organization) + selectProjectScenario: (project_scenario) -> @set('model.project_scenario', project_scenario) + + saveRecord: -> + changeset = @get('model.changeset') + changeset.validate() + if @get('offset_type_configured') + ##if action_offset_base = @get('attached_to_workflow_action') + ## @set 'model.action_offset_base', action_offset_base + ##else + ## @set 'select_action_offset_base_alert', t('workflow_action_definition.edit_workflow_action_definition.no_action_offset_base_given_alert') + ## return + #if action_offset_base = @get('attached_to_workflow_action') + # @set 'new_workflow_action_offset_warning', t('workflow_action_definition.edit_workflow_action_definition.chosen_but_non_added_offset_workflow_action_definition') + # return + unless @get('non_deleted_working_workflow_action_offsets.length') + @set 'new_workflow_action_offset_warning', t('workflow_action_definition.edit_workflow_action_definition.offset_type_configured_but_no_offsets') + return + all_workflow_action_definitions_valid = true + @get('non_deleted_working_workflow_action_offsets').forEach (workflow_action_offset) -> + workflow_action_offset.set 'base_workflow_action_definition_warning', '' + if !workflow_action_offset.get('x_based') and !workflow_action_offset.get('base_workflow_action_definition.id') + workflow_action_offset.set 'base_workflow_action_definition_warning', t('workflow_action_definition.edit_workflow_action_definition.base_workflow_action_definition_not_present') + all_workflow_action_definitions_valid = false + return unless all_workflow_action_definitions_valid + else + @get('model.workflow_action_offsets').invoke 'rollbackAttributes' # only offsets if offset_type is 'configured' + #@set 'model.action_offset_base', null + #@set 'select_action_offset_base_alert', '' + + if @get('offset_type_attached') + unless @get('attached_to_workflow_action') + @set 'attached_to_connection_warning', t('workflow_action_definition.edit_workflow_action_definition.offset_type_attached_but_no_connection') + return + else + @set 'attached_to_connection_warning', '' + @set 'attached_to_workflow_action', null + + all_connections_valid = true + @get('working_pointing_workflow_action_connections').forEach (pointing_connection) -> + pointing_connection.set 'workflow_action_definition_warning', '' + unless pointing_connection.get('workflow_action_definition.content') + pointing_connection.set 'workflow_action_definition_warning', t('workflow_action_definition.edit_workflow_action_definition.workflow_action_definition_not_present') + all_connections_valid = false + return unless all_connections_valid + + # This was validation phase, now perform persisting in proper order + return unless changeset.get('isValid') + changeset.save().then (workflow_action_definition) => + if @get('offset_type_configured') + @get('working_workflow_action_offsets').invoke 'save' + else + @get('working_workflow_action_offsets').invoke 'destroyRecord' + + @get('working_pointing_workflow_action_connections').invoke 'save' + + existing_attached_connection = @get('model.workflow_action_connections').findBy('connection_type', 'attached') + if @get('offset_type_attached') + if existing_attached_connection + existing_attached_connection.setProperties + base_workflow_action_definition: @get('attached_to_workflow_action') + conditional_base_value: @get('attached_to_connection_conditional_base_value') + existing_attached_connection.save() + else + new_attached_connection = @store.createRecord 'panda/workflow-action-connection', + workflow_action_definition: @get('model') + base_workflow_action_definition: @get('attached_to_workflow_action') + conditional_base_value: @get('attached_to_connection_conditional_base_value') + connection_type: 'attached' + new_attached_connection.save() + else if existing_attached_connection # cleanup + if existing_attached_connection.get('isNew') + existing_attached_connection.rollbackAttributes() + else + existing_attached_connection.destroyRecord() + + #@get('working_workflow_action_constraints').invoke 'save' + @incrementProperty 'model.project.update_revision' + @send 'close' + + rollback_and_close: -> + #@set 'select_action_offset_base_alert', '' + @get('working_workflow_action_offsets').invoke 'rollbackAttributes' + @get('working_pointing_workflow_action_connections').invoke 'rollbackAttributes' + #@get('working_workflow_action_constraints').invoke 'rollbackAttributes' + @_super arguments... + + close: -> + Ember.$('body').off('keyup.modal-dialog') + @_super arguments... + set_batch_element_threshold_unit: (unit) -> @set 'model.changeset.batch_element_threshold_unit', unit + positionInProject: -> + @modal 'project/position-workflow-action-definition-in-project', + model: @get('model') + set_offset_type_none: -> + @set 'model.changeset.offset_type', 'none' + set_offset_type_attached: -> + @set 'model.changeset.offset_type', 'attached' + set_offset_type_configured: -> + @set 'model.changeset.offset_type', 'configured' + # Add an initial offset if activated and none are present yet + @send 'add_workflow_action_offset' unless @get('model.workflow_action_offsets.length') + + set_attached_to_workflow_action_workflow_group: (workflow_group) -> + @set 'attached_to_workflow_action_workflow_group', workflow_group + @set 'attached_to_workflow_action', null + @set 'possible_attached_to_workflow_action_workflow_action_definitions', workflow_group.get('sorted_workflow_action_definitions').rejectBy('id', @get('model.id')) + false + + set_connection_selector_workflow_group: (workflow_action_offset, workflow_group, options = {}) -> + if workflow_action_offset.get('connection_type') is 'attached' + return if workflow_action_offset.get('base_workflow_action_definition.workflow_group.id') is workflow_group.get('id') and not options.force_refresh # do not clear unchanged select trigger + workflow_action_offset.set 'base_workflow_action_definition', null + else + return if workflow_action_offset.get('workflow_action_definition.workflow_group.id') is workflow_group.get('id') and not options.force_refresh # do not clear unchanged select trigger + workflow_action_offset.set 'workflow_action_definition', null + workflow_action_offset.set 'selector_workflow_group', workflow_group + workflow_action_offset.set 'filtered_workflow_action_definitions', workflow_group.get('sorted_workflow_action_definitions').rejectBy('id', @get('model.id')) + + set_selector_workflow_group: (workflow_action_offset, workflow_group, options = {}) -> + return if workflow_action_offset.get('base_workflow_action_definition.workflow_group.id') is workflow_group.get('id') and not options.force_refresh # do not clear unchanged select trigger + workflow_action_offset.set 'base_workflow_action_definition', null + workflow_action_offset.set 'selector_workflow_group', workflow_group + workflow_action_offset.set 'filtered_workflow_action_definitions', workflow_group.get('sorted_workflow_action_definitions').rejectBy('id', @get('model.id')) + + toggle_offset_x_based: (offset, x_based) -> + if x_based + offset.set 'base_workflow_action_definition', null + else + # do not loose original on back and forth toggle + offset.set 'base_workflow_action_definition', offset.get('original_base_workflow_action_definition') + offset.set 'x_based', x_based + false + + set_attached_to_workflow_action: (workflow_action_definition) -> + @set 'attached_to_workflow_action', workflow_action_definition + @set 'attached_to_connection_conditional_base_value', '' + false + #toggle_week_day_presence: (target, week_day, selected) -> + # #target = target.get('batch_element_threshold_business_days') unless Ember.isArray(target) + # if selected + # target.addObject week_day.index + # else + # target.removeObject week_day.index +add_business_day_computations Component, 'new_workflow_action_offset_batch_element_threshold' +export default ModalBase.extend(Component) diff --git a/app/components/workflow-group-edit-constraint.coffee b/app/components/workflow-group-edit-constraint.coffee new file mode 100644 index 0000000..d37b2aa --- /dev/null +++ b/app/components/workflow-group-edit-constraint.coffee @@ -0,0 +1,14 @@ +import Ember from 'ember' +export default Ember.Component.extend + #tagName: 'h4' + constraint_types: Ember.A(['warn', 'restrict']) + classNames: ['row'] + actions: + destroyConstraint: -> + #@get('constraint').destroyRecord() + @modal 'confirm', + title_path: 'general.destroy.are_you_sure' + model: @get('constraint') + ok: -> + @get('model').destroyRecord() + diff --git a/app/components/workflow-group-header.coffee b/app/components/workflow-group-header.coffee new file mode 100644 index 0000000..0009fb8 --- /dev/null +++ b/app/components/workflow-group-header.coffee @@ -0,0 +1,65 @@ +import Ember from 'ember' +export default Ember.Component.extend + #tagName: 'h4' + classNames: ['workflow-instance-workflow-group-title'] + make_editable: (-> + @set('workflow_group.isEditing', true) + @$('.workflow-group-title-edit').focus() + ).on "doubleClick" + #focusOut: -> @send('saveWorkflowStep') if @get('workflow_group.isEditing') + workflow_group_name: Ember.computed.oneWay 'workflow_group.name' + select_title: (-> + @$('.workflow-group-title-edit').focus() if @get('workflow_group.isEditing') + ).on('didRender') + + actions: + newWorkflowActionDefinition: -> + workflow_group = @get('workflow_group') + #title = "WorkflowActionDefinition #{workflow_group.store.peekAll('panda/workflow-action-definition').get('length') + 1}" + title = "" + if last_workflow_action_definition = workflow_group.get('sorted_workflow_action_definitions.lastObject') + position = 1 + last_workflow_action_definition.get('position') # can be a gap due to deleted actions not updating the rest + else + position = 0 + + workflow_action_definition = @store.createRecord 'panda/workflow_action_definition', + title: title + position: position + workflow_group: workflow_group + project: workflow_group.get('project') + @modal 'workflow-action-definition/edit-workflow-action-definition', model: workflow_action_definition + + saveWorkflowStep: -> + return unless workflow_group_name = @get('workflow_group_name') + workflow_group = @get('workflow_group') + workflow_group.set 'name', workflow_group_name + workflow_group.set 'isEditing', false + workflow_group.save() + rollbackWorkflowStep: -> + #@get('workflow_group').rollbackAttributes() + @set 'workflow_groupName', @get('workflow_group.name') + @set 'workflow_group.isEditing', false + destroyWorkflowStep: -> + @modal 'confirm', + title_path: 'general.destroy.are_you_sure' + model: @get('workflow_group') + ok: -> + workflow_group = @get('model') + workflow_group.rollbackAttributes() + workflow_group.destroyRecord() + close: -> + workflow_group = @get('model') + workflow_group.set "isEditing", false + displayWorkflowActionDefinitions: -> + @modal 'display-workflow-action-definitions', + model: Ember.A([@get('workflow_group')]) + title: @get('workflow_group.name') + + sortWorkflowActionDefinitions: -> + @modal 'sort-records', + model: @get('workflow_group.workflow_action_definitions') + title: "Arrange workflow actions for step #{@get('workflow_group.name')} of project #{@get('workflow_group.project.name')}" + + editWorkflowGroup: -> + @modal 'project/edit-workflow-group', + model: @get('workflow_group') diff --git a/app/controllers/action-list.js b/app/controllers/action-list.js new file mode 100644 index 0000000..2fb8ded --- /dev/null +++ b/app/controllers/action-list.js @@ -0,0 +1 @@ +export { default } from 'ember-panda/controllers/action-list'; diff --git a/app/controllers/organization.coffee b/app/controllers/organization.coffee new file mode 100644 index 0000000..3d53adf --- /dev/null +++ b/app/controllers/organization.coffee @@ -0,0 +1,16 @@ +import Ember from 'ember' +export default Ember.Controller.extend + newProjectName: '' + is_manager: Ember.computed 'model.manager_ids', -> + return false unless manager_ids = @get('model.manager_ids') + return false unless user_id = @get('globals.current_user.id') + manager_ids.includes user_id + + tracker_subjects_for_user: Ember.computed.alias('model.tracker_subjects') + chart_data: [ + ['Year', 'Sales', 'Expenses'] + ['2004', 1000, 400] + ['2005', 1170, 460] + ['2006', 660, 1120] + ['2007', 1030, 540] + ] diff --git a/app/controllers/project.coffee b/app/controllers/project.coffee new file mode 100644 index 0000000..ce67a02 --- /dev/null +++ b/app/controllers/project.coffee @@ -0,0 +1,3 @@ +import Ember from 'ember' +export default Ember.Controller.extend + a: 3 diff --git a/app/controllers/project/index.coffee b/app/controllers/project/index.coffee new file mode 100644 index 0000000..2d25310 --- /dev/null +++ b/app/controllers/project/index.coffee @@ -0,0 +1,15 @@ +import Ember from 'ember' +export default Ember.Controller.extend + is_manager: Ember.computed 'model.manager_ids', -> + return false unless manager_ids = @get('model.manager_ids') + return false unless user_id = @get('globals.current_user.id') + manager_ids.includes user_id + projects_for_user: Ember.computed "model.[]", "model.@each.users.[]", -> + user_id = localStorage.getItem('user_id') + projects = @get('sorted_projects') + return projects if @get('is_manager') # Do not filter, sorry, managers have to see all the projects + projects.filter (project)-> + project.get('users').mapBy('id').includes user_id + #sorted_projects: Ember.computed 'projects.@each.name', -> @get('projects').sortBy('name') + projectSorting: ["name:asc"] + sorted_projects: Ember.computed.sort("model", "projectSorting") diff --git a/app/controllers/project/show/dunlop-generator.coffee b/app/controllers/project/show/dunlop-generator.coffee new file mode 100644 index 0000000..0ce467a --- /dev/null +++ b/app/controllers/project/show/dunlop-generator.coffee @@ -0,0 +1,12 @@ +import Ember from 'ember' +possible_modules = ['kbt_endpoint', 'wba_mailbox'] +export default Ember.Controller.extend + possible_source_files: ["net_order", "net_task"] + selected_source_files: [] + kbt_endpoint: Ember.Object.create(active: false, config: {}) + wba_mailbox: Ember.Object.create(active: true, config: {}) + activated_modules: Ember.computed "kbt_endpoint.active", "wba_mailbox.active", -> + activated = Ember.A() + for key in possible_modules + activated.push Ember.Object.create(name: key) if @get("#{key}.active") + activated diff --git a/app/controllers/project/show/dunlop.coffee b/app/controllers/project/show/dunlop.coffee new file mode 100644 index 0000000..3a012de --- /dev/null +++ b/app/controllers/project/show/dunlop.coffee @@ -0,0 +1,94 @@ +import Ember from 'ember' +import ActiveWorkflowActionDefinitions from 'ember-panda/mixins/project/active-workflow-action-definitions' +import get_active_workflow_groups from 'ember-panda/utils/project/active-workflow-groups' +import get_active_workflow_action_definitions from 'ember-panda/utils/project/active-workflow-action-definitions' +export default Ember.Controller.extend + newWorkflowStepName: '' + batch_size_simulation_range: Ember.computed 'model.{batch_size_lower_value,batch_size_upper_value}',-> + {min: @get('model.batch_size_lower_value'), max: @get('model.batch_size_upper_value')} + batch_size_simulation_value: 0 + chosen_absolute_date: null + reference_date_day_of_week: Ember.computed.oneWay 'model.reference_date_day_of_week' + project: Ember.computed.alias 'model' + + # Multiple project scenarios (currently inactive) + selected_project_scenarios: [] + has_multiple_selected_project_scenarios: Ember.computed.gt 'selected_project_scenarios.length', 1 + has_one_selected_project_scenario: Ember.computed.equal 'selected_project_scenarios.length', 1 + selected_project_scenarios_set_operation: 'union' + + active_project_scenario: null + + init: -> + @_super arguments... + #@get 'active_workflow_action_definitions_identity' + #@set_active_workflow_action_definitions() + + #_active_workflow_action_definitions_obsever: Ember.observer 'selected_project_scenarios.@each.id', 'selected_project_scenarios_set_operation', 'project.workflow_action_definitions.@each.id', 'project.workflow_groups.@each.id', 'project.update_revision', -> + _active_workflow_action_definitions_obsever: Ember.observer 'active_project_scenario', 'project.workflow_action_definitions.@each.id', 'project.workflow_groups.@each.id', 'project.update_revision', -> + Ember.run.debounce @, (-> + #[project_scenarios, set_operation] = @getPropertyValues('selected_project_scenarios', 'selected_project_scenarios_set_operation') + set_operation = 'union' + if active_project_scenario = @get('active_project_scenario') + @set 'active_workflow_action_definitions', get_active_workflow_action_definitions(@get('model.workflow_action_definitions'), [active_project_scenario], set_operation) + @set 'active_workflow_groups', get_active_workflow_groups(@get('model.workflow_groups'), [active_project_scenario], set_operation) + else + @set 'active_workflow_action_definitions', @get('model.workflow_action_definitions') + @set 'active_workflow_groups', @get('model.workflow_groups') + ), 350 + + actions: + newWorkflowStep: -> + new_name = @get('newWorkflowStepName') + workflow_group = @get("model.workflow_groups").createRecord(name: new_name, position: @get('model.workflow_groups.length')) + @set 'newWorkflowStepName', '' + @modal "project/edit-workflow-group", + model: workflow_group + destroyWorkflowStep: (workflow_group) -> workflow_group.destroyRecord() + toggle_sidebar: (id) -> + $("##{id}").sidebar('toggle') + positionWorkflowActionDefinition: (dragged_workflow_action_definition, options = {})-> + workflow_group = options.target.content + items = options.target.$('.draggableItem') + top_offsets = items.map (i, item) -> $(item).offset().top + dragged_offset = options.event.originalEvent.clientY + my_position = 0 + compare_offset = top_offsets[0] + while compare_offset and compare_offset < dragged_offset + my_position += 1 + compare_offset = top_offsets[my_position] + position_counter = 0 + workflow_group.get('sorted_workflow_action_definitions').forEach (workflow_action_definition)-> + if position_counter is my_position + position_counter += 1 + workflow_action_definition.set 'position', position_counter + workflow_action_definition.save() + position_counter += 1 + # Just before the last one, so the + + dragged_workflow_action_definition.set 'workflow_group', workflow_group + dragged_workflow_action_definition.set 'position', my_position + dragged_workflow_action_definition.save() + displayWorkflowActionDefinitions: -> + #workflow_action_definitions = Ember.A() + #@get('model.sorted_workflow_groups').forEach (workflow_group, i)-> + # workflow_action_definitions.addObjects(workflow_group.get('workflow_action_definitions')) + @modal 'display-workflow-action-definitions', + #model: workflow_action_definitions + model: @get('model.sorted_workflow_groups') + title: @get('model.name') + show_project_info: -> + @modal 'project/show/dunlop-project-info', model: @get('model') + sortWorkflowSteps: -> + @modal 'sort-records', + model: @get('model.workflow_groups') + title: "Sort workflow steps for project #{@get('model.name')}" + + positionWorkflowActionDefinitionInProject: (workflow_action_definition) -> + @modal 'project/position-workflow-action-definition-in-project', + model: workflow_action_definition + + select_project_scenario: (project_scenario) -> + @set 'selected_project_scenarios', project_scenario + + diff --git a/app/controllers/project/show/index.coffee b/app/controllers/project/show/index.coffee new file mode 100644 index 0000000..7821103 --- /dev/null +++ b/app/controllers/project/show/index.coffee @@ -0,0 +1,3 @@ +import Ember from 'ember' +export default Ember.Controller.extend + tracker_subjects: (-> @store.peekAll('panda/tracker-subject')).property() diff --git a/app/controllers/project/show/project-instance-action/index.coffee b/app/controllers/project/show/project-instance-action/index.coffee new file mode 100644 index 0000000..7844d9d --- /dev/null +++ b/app/controllers/project/show/project-instance-action/index.coffee @@ -0,0 +1,89 @@ +import Ember from 'ember' +import {create_date_sort} from 'ember-cli-dunlop/utils/date-sort' +date_sort = create_date_sort('date') +export default Ember.Controller.extend + #filter: Ember.Object.create + # workflow_action_definition_id_in: [] + # project_instance_group_workflow_group_id_in: [] + # state_in: [] + # date_gteq: null + # date_lteq: null + filtered_project_instance_actions_inline: Ember.computed 'project.project_instance_actions.@each.id', 'filter.{workflow_action_definition_id_in.length,state_in.length,project_instance_group_workflow_group_id_in.length,date_gteq,date_lteq}', -> + base = @get('project.project_instance_actions').toArray() + if @get('filter.workflow_action_definition_id_in.length') + base = base.filter (record) => record.get('workflow_action_definition.id') in @get('filter.workflow_action_definition_id_in') + if @get('filter.project_instance_group_workflow_group_id_in.length') + base = base.filter (record) => record.get('project_instance_group.workflow_group.id') in @get('filter.project_instance_group_workflow_group_id_in') + if date_gteq = @get('filter.date_gteq') + base = base.filter (record) -> if date = record.get('date') then date.toISOString().substring(0, 10) >= date_gteq.toISOString().substring(0, 10) else false + if date_lteq = @get('filter.date_lteq') + base = base.filter (record) -> if date = record.get('date') then date.toISOString().substring(0, 10) <= date_lteq.toISOString().substring(0, 10) else false + if @get('filter.state_in.length') + base = base.filter (record) => record.get('state') in @get('filter.state_in') + base.sort date_sort + filtered_project_instance_actions: Ember.computed.alias 'model' + + #date_filter_active: Ember.computed.or 'filter.{date_gteq,date_lteq}' + #date_filter_text: Ember.computed 'filter.date_gteq', 'filter.date_lteq', -> + # if gteq = @get('filter.date_gteq') + # gteq = gteq.format('ll') + # else + # gteq = '-' + # if lteq = @get('filter.date_lteq') + # lteq = lteq.format('ll') + # else + # lteq = '-' + # "#{gteq} / #{lteq}" + + filter_workflow_action_definitions: Ember.computed 'filters.workflow_action_definition_id_in.[]', -> + @get('project.workflow_action_definitions').filter((r) => r.get('id') in @get('filters.workflow_action_definition_id_in')) + workflow_action_definitions_filter_html: Ember.computed 'filters.workflow_action_definition_id_in.[]', -> + length = @get('filters.workflow_action_definition_id_in.length') + return '' unless length + tooltip = @get('project.workflow_action_definitions').filter((r) => r.get('id') in @get('filters.workflow_action_definition_id_in')).mapBy('title').join("<br>") + model_name = if length is 1 then t('models.workflow_action_definition') else t('models.plural.workflow_action_definition') + "#{length} #{model_name}" + + actions: + toggle_filter: (key, value, checked) -> + new_value = [] + if checked + new_value = new_value.concat(@get("filters.#{key}")).concat(value) + else + @get("filters.#{key}").forEach (present_in_filter) -> + new_value.push present_in_filter unless present_in_filter is value + @set "filters.#{key}", new_value + query = @get('filters')?.toProperties() || {} + @set('model.page', 1) + @set('model.lastPage', null) + @get('model').setOtherParam('q', query) + false + + #toggle_filter: (key, value, checked) -> + # if checked + # @get("filters.#{key}").addObject value + # else + # @get("filters.#{key}").removeObject value + # query = @get('filters')?.toProperties() || {} + # @get('model').setOtherParam('q', query) + clear_filter: (keys...) -> + keys.forEach (key) => + if Ember.isArray(@get("filters.#{key}")) + @set "filters.#{key}", [] + else + @set "filters.#{key}", null + query = @get('filters')?.toProperties() || {} + @set('model.page', 1) + @set('model.lastPage', null) + @get('model').setOtherParam('q', query) + false + #set_filter_date_today: -> + # today = moment() + # @setProperties + # 'filters.target_date_gteq': today + # 'filters.target_date_lteq': today + #set_filter_date_this_week: -> + # today = moment() + # @setProperties + # 'filters.target_date_gteq': today.clone().startOf('isoWeek') + # 'filters.target_date_lteq': today.endOf('isoWeek') diff --git a/app/controllers/project/show/project-instance/index.coffee b/app/controllers/project/show/project-instance/index.coffee new file mode 100644 index 0000000..69eddfc --- /dev/null +++ b/app/controllers/project/show/project-instance/index.coffee @@ -0,0 +1,47 @@ +import Ember from 'ember' +import {create_date_sort} from 'ember-cli-dunlop/utils/date-sort' +{get} = Ember +date_sort = create_date_sort('reference_date') +export default Ember.Controller.extend + possible_states: ['uncategorized', 'unplanned', 'planned', 'active', 'overdue', 'any_rejected', 'all_completed'] + #filter: Ember.Object.create + # project_scenario_id_in: [] + # state_in: [] + # reference_date_gteq: null + # reference_date_lteq: null + #filter_project_scenarios: Ember.computed 'filter.project_scenario_id_in.[]', -> + # @get('model.sorted_project_scenarios').filter((r) => r.get('id') in @get('filter.project_scenario_id_in')) + #reference_date_filter_active: Ember.computed.or 'filter.{reference_date_gteq,reference_date_lteq}' + #reference_date_filter_text: Ember.computed 'filter.reference_date_gteq', 'filter.reference_date_lteq', -> + # if gteq = @get('filter.reference_date_gteq') + # gteq = gteq.format('ll') + # else + # gteq = '-' + # if lteq = @get('filter.reference_date_lteq') + # lteq = lteq.format('ll') + # else + # lteq = '-' + # "#{gteq} / #{lteq}" + #filtered_project_instances: Ember.computed 'model.project_instances.@each.id', 'filter.{project_scenario_id_in.length,state_in.length,reference_date_gteq,reference_date_lteq}', -> + # base = @get('model.project_instances').toArray() + # if @get('filter.project_scenario_id_in.length') + # base = base.filter (record) => record.get('project_scenario.id') in @get('filter.project_scenario_id_in') + # if reference_date_gteq = @get('filter.reference_date_gteq') + # base = base.filter (record) -> if reference_date = record.get('reference_date') then reference_date.toISOString().substring(0, 10) >= reference_date_gteq.toISOString().substring(0, 10) else false + # if reference_date_lteq = @get('filter.reference_date_lteq') + # base = base.filter (record) -> if reference_date = record.get('reference_date') then reference_date.toISOString().substring(0, 10) <= reference_date_lteq.toISOString().substring(0, 10) else false + # if @get('filter.state_in.length') + # base = base.filter (record) => record.get('state') in @get('filter.state_in') + # base.sort date_sort + filtered_project_instances: Ember.computed.alias 'model' + actions: + toggle_filter: (key, value, checked) -> + if checked + @get("filter.#{key}").addObject value + else + @get("filter.#{key}").removeObject value + clear_filter: (key) -> + if Ember.isArray(@get("filter.#{key}")) + @set "filter.#{key}", [] + else + @set "filter.#{key}", null diff --git a/app/controllers/project/show/project-instance/show.coffee b/app/controllers/project/show/project-instance/show.coffee new file mode 100644 index 0000000..71e0a8f --- /dev/null +++ b/app/controllers/project/show/project-instance/show.coffee @@ -0,0 +1,11 @@ +import Ember from 'ember' +{get} = Ember +export default Ember.Controller.extend + breadcrumb_detail: Ember.computed 'globals.current_route.path', -> + return 'grid??' unless currentPath = @get('globals.current_route.path') + route_specific = currentPath.split('.')[4] + switch route_specific + when 'index' then t('project_instance.show.grid') + when 'groups' then t('models.plural.project_instance_group') + when 'actions' then t('models.plural.workflow_action_definition') + else t("project_instance.show.#{route_specific}") diff --git a/app/controllers/project/show/tracker-subject.coffee b/app/controllers/project/show/tracker-subject.coffee new file mode 100644 index 0000000..ca27665 --- /dev/null +++ b/app/controllers/project/show/tracker-subject.coffee @@ -0,0 +1,53 @@ +import Ember from 'ember' +export default Ember.Controller.extend + grouped_tracker_groups: [] + uncategorized_workflow_groups: [] + #grouped_tracker_groups: Ember.computed.alias("model.tracker_subject.sorted_tracker_groups") + + #grouped_tracker_groups_setter: (-> + # base = @ + # group = Ember.A() + # project_workflow_groups = @get('model.project.workflow_groups') + # @get("model.tracker_subject.tracker_groups").then (tracker_groups)-> + # tracker_groups.forEach (tracker_group)-> + # item_group = Ember.Object.extend( + # workflow_groups: Ember.computed.intersect("model.workflow_groups", "project_workflow_groups") + # ).create + # model: tracker_group + # project_workflow_groups: project_workflow_groups + # c = base + # debugger + # #item_group = Ember.Object.create + # # model: tracker_group + # # workflow_groups: workflow_groups.filterBy("") + # #tracker_group.get('workflow_groups').then (workflow_groups)-> + # # workflow_groups.forEach (workflow_group)-> + # # hasProject = existing_project_group = item_group.get("projects").findBy("model.id", workflow_group.get("project.id")) + # # if hasProject + # # existing_project_group.get("workflow_groups").push(workflow_group) + # # else + # # item_group.get("projects").pushObject Ember.Object.create(model: workflow_group.get('project'), workflow_groups: Ember.A([workflow_group])) + # # base.incrementProperty "change_iteration" + # group.push item_group + # @set("grouped_tracker_groups", group) + #).observes "model.tracker_subject.tracker_groups.@each.workflow_groups.[]" + + grouped_tracker_groups_setter: (-> + base = @ + group = Ember.A() + tracker_subject_id = @get("model.tracker_subject.id") + categorized_workflow_group_ids = [] + @get("model.tracker_subject.tracker_groups").forEach (tracker_group)-> + group.pushObject + model: tracker_group + workflow_groups: Ember.A() + @get('model.project.workflow_groups').forEach (workflow_group)-> + return unless project_tracker_group = workflow_group.get('tracker_groups').findBy("tracker_subject.id", tracker_subject_id) + return unless item_group = group.findBy("model.id", project_tracker_group.id) + categorized_workflow_group_ids.push workflow_group.id + item_group.workflow_groups.pushObject workflow_group + item_group.workflow_groups = item_group.workflow_groups.sortBy("position") + @set("grouped_tracker_groups", group) + uncategorized_workflow_groups = @get('model.project.workflow_groups').reject((ws) -> categorized_workflow_group_ids.includes(ws.id)) + @set("uncategorized_workflow_groups", uncategorized_workflow_groups.sortBy("position")) + ).observes "model.project.workflow_groups.@each.tracker_groups.[]", "model.tracker_subject.tracker_groups.[]" diff --git a/app/controllers/project/show/tracker-subjects.coffee b/app/controllers/project/show/tracker-subjects.coffee new file mode 100644 index 0000000..337e8fb --- /dev/null +++ b/app/controllers/project/show/tracker-subjects.coffee @@ -0,0 +1,3 @@ +import Ember from 'ember' +export default Ember.Controller.extend + tracker_subjects_for_project: (-> @store.peekAll('panda/tracker-subject')).property() diff --git a/app/controllers/tracker-subject/index.coffee b/app/controllers/tracker-subject/index.coffee new file mode 100644 index 0000000..401c3d5 --- /dev/null +++ b/app/controllers/tracker-subject/index.coffee @@ -0,0 +1,3 @@ +import Ember from 'ember' +export default Ember.Controller.extend + tracker_subjects_for_user: Ember.computed.alias 'model' diff --git a/app/controllers/tracker-subject/show/index.coffee b/app/controllers/tracker-subject/show/index.coffee new file mode 100644 index 0000000..aad2a10 --- /dev/null +++ b/app/controllers/tracker-subject/show/index.coffee @@ -0,0 +1,22 @@ +import Ember from 'ember' +export default Ember.Controller.extend + actions: + sortTrackerGroups: -> + @modal "sort-records", model: @get("model.tracker_groups") + + newTrackerGroup: -> + tracker_groups = @get("model.tracker_groups") + @modal "edit-tracker-group", + model: tracker_groups.createRecord(position: tracker_groups.get('length')) + title: t('action.new_model', model: t('models.tracker_group')) + + editTrackerGroup: (tracker_group) -> + @modal "edit-tracker-group", model: tracker_group + + newTrackerItem: (tracker_group)-> + tracker_items = tracker_group.get("tracker_items") + tracker_items.createRecord(position: tracker_items.get("length"), isEditing: true) + + sortTrackerItems: (tracker_group) -> + @modal "sort-records", model: tracker_group.get("tracker_items") + diff --git a/app/controllers/tracker-subject/show/project.coffee b/app/controllers/tracker-subject/show/project.coffee new file mode 100644 index 0000000..1e099ae --- /dev/null +++ b/app/controllers/tracker-subject/show/project.coffee @@ -0,0 +1,54 @@ +import Ember from 'ember' +export default Ember.Controller.extend + grouped_tracker_groups: Ember.A() + uncategorized_workflow_groups: Ember.A() + #grouped_tracker_groups: Ember.computed.alias("model.tracker_subject.sorted_tracker_groups") + + #grouped_tracker_groups_setter: (-> + # base = @ + # group = Ember.A() + # project_workflow_groups = @get('model.project.workflow_groups') + # @get("model.tracker_subject.tracker_groups").then (tracker_groups)-> + # tracker_groups.forEach (tracker_group)-> + # item_group = Ember.Object.extend( + # workflow_groups: Ember.computed.intersect("model.workflow_groups", "project_workflow_groups") + # ).create + # model: tracker_group + # project_workflow_groups: project_workflow_groups + # c = base + # debugger + # #item_group = Ember.Object.create + # # model: tracker_group + # # workflow_groups: workflow_groups.filterBy("") + # #tracker_group.get('workflow_groups').then (workflow_groups)-> + # # workflow_groups.forEach (workflow_group)-> + # # hasProject = existing_project_group = item_group.get("projects").findBy("model.id", workflow_group.get("project.id")) + # # if hasProject + # # existing_project_group.get("workflow_groups").push(workflow_group) + # # else + # # item_group.get("projects").pushObject Ember.Object.create(model: workflow_group.get('project'), workflow_groups: Ember.A([workflow_group])) + # # base.incrementProperty "change_iteration" + # group.push item_group + # @set("grouped_tracker_groups", group) + #).observes "model.tracker_subject.tracker_groups.@each.workflow_groups.[]" + + grouped_tracker_groups_setter: (-> + base = @ + group = Ember.A() + tracker_subject_id = @get("model.tracker_subject.id") + categorized_workflow_group_ids = [] + @get("model.tracker_subject.tracker_groups").forEach (tracker_group) -> + group.pushObject + model: tracker_group + workflow_groups: Ember.A() + + @get('model.project.workflow_groups').forEach (workflow_group)-> + return unless project_tracker_group = workflow_group.get('tracker_groups').findBy("tracker_subject.id", tracker_subject_id) + return unless item_group = group.findBy("model.id", project_tracker_group.id) + categorized_workflow_group_ids.push workflow_group.id + item_group.workflow_groups.pushObject workflow_group + item_group.workflow_groups = item_group.workflow_groups.sortBy("position") + @set("grouped_tracker_groups", group) + uncategorized_workflow_groups = @get('model.project.workflow_groups').reject((ws) -> categorized_workflow_group_ids.includes(ws.id)) + @set("uncategorized_workflow_groups", uncategorized_workflow_groups.sortBy("position")) + ).observes "model.project.workflow_groups.@each.tracker_groups.[]", "model.tracker_subject.tracker_groups.[]" diff --git a/app/controllers/tracker-subject/show/tracker-group.coffee b/app/controllers/tracker-subject/show/tracker-group.coffee new file mode 100644 index 0000000..04cd8ce --- /dev/null +++ b/app/controllers/tracker-subject/show/tracker-group.coffee @@ -0,0 +1,14 @@ +import Ember from 'ember' +export default Ember.Controller.extend + grouped_tracker_groups_setter: (-> + project_groups = Ember.A() + @get("model.workflow_groups").then (workflow_groups) -> + workflow_groups.forEach (workflow_group) -> + if existing_group = project_groups.findBy("model.id", workflow_group.get("project.id")) + existing_group.get("workflow_groups").pushObject(workflow_group) + else + project_groups.push Ember.Object.create + model: workflow_group.get('project') + workflow_groups: Ember.A([workflow_group]) + @set("grouped_projects", project_groups) + ).observes("model.workflow_groups.[]") diff --git a/app/controllers/tracker-subject/show/tracking-connections.coffee b/app/controllers/tracker-subject/show/tracking-connections.coffee new file mode 100644 index 0000000..93045ce --- /dev/null +++ b/app/controllers/tracker-subject/show/tracking-connections.coffee @@ -0,0 +1,64 @@ +import Ember from 'ember' +import Viz from 'npm:viz.js' # browserify +export default Ember.Controller.extend + change_iteration: 0 + + grouped_tracker_groups_setter: (-> + base = @ + group = Ember.A() + @get("model.tracker_groups").then (tracker_groups)-> + tracker_groups.forEach (tracker_group)-> + item_group = Ember.Object.create + model: tracker_group + projects: Ember.A() + tracker_group.get('workflow_groups').then (workflow_groups)-> + workflow_groups.forEach (workflow_group)-> + hasProject = existing_project_group = item_group.get("projects").findBy("model.id", workflow_group.get("project.id")) + if hasProject + existing_project_group.get("workflow_groups").push(workflow_group) + else + item_group.get("projects").pushObject Ember.Object.create(model: workflow_group.get('project'), workflow_groups: Ember.A([workflow_group])) + base.incrementProperty "change_iteration" + group.push item_group + @set("grouped_tracker_groups", group) + ).observes "model.tracker_groups.@each.workflow_groups.@each.project" + + #graph_content: Ember.computed "grouped_tracker_groups", -> + #graph_content: Ember.computed "model.tracker_groups.@each.workflow_groups.@each.project", -> + graph_content: Ember.computed "change_iteration", -> + txt = "digraph prof {\n ratio=fill;\n node [style=filled fillcolor=\"#A2D9CE\"];\n rankdir=TB;\n" + labeled_projects = {} + item_projects = {} + tracker_group_color = '"#AAAAAA"' + project_color = '"#AAFFAA"' + workflow_group_color = '"#AAAAFF"' + + grouped_tracker_groups = @get("grouped_tracker_groups") + grouped_tracker_groups.forEach (tracker_group_group)-> + tracker_group_id = "q#{tracker_group_group.get("model.id")}" # start with a letter + txt += " #{tracker_group_id} [label=\"#{tracker_group_group.get('model.name')}\" fillcolor=#{tracker_group_color}];\n" + tracker_group_group.get("projects").forEach (project)-> + project_id = "q#{project.get("model.id")}" + unless labeled_projects[project_id] + txt += " #{project_id} [label=\"#{project.get('model.name')}\" fillcolor=#{project_color}];\n" + labeled_projects[project_id] = 1 + project.workflow_groups.forEach (workflow_group)-> + workflow_group_id = "q#{workflow_group.id}" + txt += " #{workflow_group_id} [label=\"#{workflow_group.get('name')}\" fillcolor=#{workflow_group_color}];\n" + + unless item_projects["#{tracker_group_id}-#{project_id}"] + txt += " #{tracker_group_id} -> #{project_id} [color=\"#AAAAAA\" penwidth=1];\n" + item_projects["#{tracker_group_id}-#{project_id}"] = 1 + txt += " #{project_id } -> #{workflow_group_id} [color=\"#AAAAAA\" penwidth=1];\n" + #txt += " #{tracker_group_id} -> #{project_id} -> #{workflow_group_id};\n" + + txt += "}" + txt + svg_content: Ember.computed 'model.id', 'graph_content', -> + data = @get('graph_content') + try + svg = Viz(data, format: 'svg') + svg.htmlSafe() + catch error + console.log "Error parsing\n\n#{data}" + diff --git a/app/controllers/user/show.coffee b/app/controllers/user/show.coffee new file mode 100644 index 0000000..dfa9051 --- /dev/null +++ b/app/controllers/user/show.coffee @@ -0,0 +1,24 @@ +import Ember from 'ember' +export default Ember.Controller.extend + adapters: [] + panda_authorizations: [] # set by route + + active_authorizations: [] + + actions: + set_permission: (authorization, permission) -> + new_roles = [] + @get('active_authorizations').forEach (role) -> + if role.match(new RegExp("-#{authorization.type}-#{authorization.target}$")) + new_roles.push "#{permission}-#{authorization.type}-#{authorization.target}" + else + # different authorization + new_roles.push role + @set 'active_authorizations', new_roles + save_permissions: -> + user_roles = [] + @get('active_authorizations').forEach (role) -> + user_roles.push role unless role.match(/^ban-/) + user = @get('model') + user.set 'role_names', user_roles + user.save().then => @set 'globals.flash_message', 'Permissions saved' diff --git a/app/helpers/can.coffee b/app/helpers/can.coffee new file mode 100644 index 0000000..25031eb --- /dev/null +++ b/app/helpers/can.coffee @@ -0,0 +1,7 @@ +import Ember from 'ember' +import DunlopCan from 'ember-cli-dunlop/helpers/dunlop-can' + +export default DunlopCan.extend + custom_compute: (permission, type, target, roles) -> + return true if 'manage-app-panda' in roles + false diff --git a/app/helpers/date.coffee b/app/helpers/date.coffee new file mode 100644 index 0000000..6b9d40f --- /dev/null +++ b/app/helpers/date.coffee @@ -0,0 +1,15 @@ +import Ember from 'ember' + +# This function receives the params `params, hash` +date = ([date], options={}) -> + return '' unless date + return date if typeof date is 'string' + date = moment(date) unless date._isAMomentObject + date.format(options.format || 'll') + +DateHelper = Ember.Helper.helper date + +export { date } + +export default DateHelper + diff --git a/app/helpers/dynamic-path-t.coffee b/app/helpers/dynamic-path-t.coffee new file mode 100644 index 0000000..330b376 --- /dev/null +++ b/app/helpers/dynamic-path-t.coffee @@ -0,0 +1,7 @@ +import Ember from 'ember' +export default Ember.Helper.helper ([path_part, rest...], options)-> + toptions = options + path = "#{path_part}.#{options.postpath}" + text = t(path, toptions) + tag = if options.bare then text else "#{text}" + tag.htmlSafe() diff --git a/app/helpers/l.coffee b/app/helpers/l.coffee new file mode 100644 index 0000000..50b2468 --- /dev/null +++ b/app/helpers/l.coffee @@ -0,0 +1,14 @@ +import Ember from 'ember' + +# This function receives the params `params, hash` +l = ([date]) -> + return '' unless date + return date if typeof date is 'string' + return date.format('ll') if date._isAMomentObject + date.toISOString().substring(0,10) + +LHelper = Ember.Helper.helper l + +export { l } + +export default LHelper diff --git a/app/models/panda/action-event.js b/app/models/panda/action-event.js new file mode 100644 index 0000000..1f4b7d2 --- /dev/null +++ b/app/models/panda/action-event.js @@ -0,0 +1 @@ +export { default } from 'ember-panda/models/panda/action-event'; diff --git a/app/models/panda/adapter.coffee b/app/models/panda/adapter.coffee new file mode 100644 index 0000000..77c37cb --- /dev/null +++ b/app/models/panda/adapter.coffee @@ -0,0 +1,6 @@ +import DS from 'ember-data' + +export default DS.Model.extend + title: DS.attr('string') + authorizations: DS.attr(defaultValue: -> []) + diff --git a/app/models/panda/organization.coffee b/app/models/panda/organization.coffee new file mode 100644 index 0000000..f86b084 --- /dev/null +++ b/app/models/panda/organization.coffee @@ -0,0 +1,40 @@ +import DS from 'ember-data' +import lookupValidator from 'ember-changeset-validations' +import Changeset from 'ember-changeset' +import ModelValidations from 'ember-panda/validations/organization' +validator = lookupValidator(ModelValidations) +import config from 'ember-get-config' + +export default DS.Model.extend + name: DS.attr('string') + logo: DS.attr() + changeset: (-> + new Changeset @, validator, ModelValidations + ).property() + + #TODO: Move as general implementation to ember-cli-dunlop + api_url: Ember.computed 'id', -> + return unless id = @get('id') + modelName = @get('constructor.modelName') + adapter = @store.adapterFor(modelName) + adapter.buildURL modelName, id + action_url: (action) -> + return unless api_url = @get('api_url') + "#{api_url}/#{action}" + + logo_url: Ember.computed 'logo', -> + urls = {} + return urls unless logo = @get('logo') + for key, relative_url of logo + urls[key] = "#{config.apiHost}#{relative_url}" + urls + + #projects: DS.hasMany('project', inverse: 'organizations') + manager_ids: DS.attr(defaultValue: -> []) + isEditing: DS.attr('boolean', defaultValue: false) + + users: DS.hasMany('user', serialize: 'ids', inverse: 'organizations') + tracker_subjects: DS.hasMany('panda/tracker-subject') + #workflow_instance_types: DS.hasMany("workflow-instance-type") + workflow_action_definitions: DS.hasMany('panda/workflow-action-definition', inverse: 'organization') + diff --git a/app/models/panda/project-instance-action-connection.js b/app/models/panda/project-instance-action-connection.js new file mode 100644 index 0000000..cf9855c --- /dev/null +++ b/app/models/panda/project-instance-action-connection.js @@ -0,0 +1 @@ +export { default } from 'ember-panda/models/panda/project-instance-action-connection'; diff --git a/app/models/panda/project-instance-action.coffee b/app/models/panda/project-instance-action.coffee new file mode 100644 index 0000000..0c8358c --- /dev/null +++ b/app/models/panda/project-instance-action.coffee @@ -0,0 +1,49 @@ +import DS from 'ember-data' +export default DS.Model.extend + state: DS.attr('string', defaultValue: 'pending') # pending, planned, (active), overdue, rejected, completed + pending_at: DS.attr('moment') + planned_at: DS.attr('moment') + overdue_at: DS.attr('moment') + inquiry_at: DS.attr('moment') + rejected_at: DS.attr('moment') + completed_at: DS.attr('moment') + target_date: DS.attr('moment') + target_date_lock: DS.attr('boolean', defaultValue: false) + + string_value: DS.attr('string', defaultValue: '') + text_value: DS.attr('string', defaultValue: '') + date_value: DS.attr('moment') + timestamp_value: DS.attr('moment') + number_value: DS.attr('number') + boolean_value: DS.attr('boolean', defaultValue: false) + #date, type: Date + notes: DS.attr('string') + + completed: Ember.computed.equal 'state', 'completed' + + value_display: Ember.computed 'workflow_action_definition.value_type', -> + return '' unless value_type = @get('workflow_action_definition.value_type') + switch value_type + when 'string', 'select' + @get('string_value') + when 'number' + @get('number_value') + when 'text' + @get('text_value') + when 'date' + @get('date_value')?.format('LL') + when 'timestamp' + @get('timestamp_value')?.format('LLL') + when 'boolean' + cls = if @get('boolean_value') then 'checkmark box' else 'square outline' + "".htmlSafe() + + project: DS.belongsTo('panda/project', inverse: 'project_instance_actions') + workflow_action_definition: DS.belongsTo('panda/workflow-action-definition', inverse: 'project_instance_actions') + project_instance_group: DS.belongsTo('panda/project-instance-group', inverse: 'project_instance_actions') + project_instance: DS.belongsTo('panda/project-instance', inverse: 'project_instance_actions') + + #project_instance_action_connections: DS.hasMany('panda/project-instance-action-connection') # use for workflow, not actual instance handling + pointing_project_instance_action_connections: DS.hasMany('panda/project-instance-action-connection', inverse: 'base_project_instance_action') + + events: DS.hasMany('panda/action-event', inverse: 'project_instance_action') diff --git a/app/models/panda/project-instance-group.coffee b/app/models/panda/project-instance-group.coffee new file mode 100644 index 0000000..7d77189 --- /dev/null +++ b/app/models/panda/project-instance-group.coffee @@ -0,0 +1,10 @@ +import DS from 'ember-data' + +export default DS.Model.extend + state: DS.attr('string', defaultValue: 'pending') + notes: DS.attr('string') + + project: DS.belongsTo('panda/project', inverse: 'project_instance_groups') + project_instance: DS.belongsTo('panda/project-instance', inverse: 'project_instance_groups') + workflow_group: DS.belongsTo('panda/workflow-group') + project_instance_actions: DS.hasMany( 'panda/project-instance-action', inverse: 'project_instance_group' ) diff --git a/app/models/panda/project-instance.coffee b/app/models/panda/project-instance.coffee new file mode 100644 index 0000000..f891fb7 --- /dev/null +++ b/app/models/panda/project-instance.coffee @@ -0,0 +1,14 @@ +import DS from 'ember-data' + +export default DS.Model.extend + state: DS.attr('string', defaultValue: 'uncategorized') + client_identifier: DS.attr('string') + reference_date: DS.attr('moment') + batch_size: DS.attr('number') + notes: DS.attr('string') + + project: DS.belongsTo('panda/project', inverse: 'project_instances') + project_scenario: DS.belongsTo('panda/project-scenario', inverse: 'project_instances') + project_instance_groups: DS.hasMany('panda/project-instance-group', inverse: 'project_instance') + project_instance_actions: DS.hasMany('panda/project-instance-action', inverse: 'project_instance') + diff --git a/app/models/panda/project-scenario.coffee b/app/models/panda/project-scenario.coffee new file mode 100644 index 0000000..1c02f84 --- /dev/null +++ b/app/models/panda/project-scenario.coffee @@ -0,0 +1,11 @@ +import DS from 'ember-data' + +export default DS.Model.extend + identifier: DS.attr('string') + name: DS.attr('string') + project: DS.belongsTo('panda/project', inverse: 'project_scenarios') + + workflow_groups: DS.hasMany('panda/workflow-group', serialize: 'ids') + workflow_action_definitions: DS.hasMany('panda/workflow-action-definition', inverse: 'project_scenarios', serialize: 'ids') + + project_instances: DS.hasMany('panda/project-instance', inverse: 'project_scenario') diff --git a/app/models/panda/project.coffee b/app/models/panda/project.coffee new file mode 100644 index 0000000..d7bc19c --- /dev/null +++ b/app/models/panda/project.coffee @@ -0,0 +1,66 @@ +import DS from 'ember-data' +export default DS.Model.extend + update_revision: 0 + + manager_ids: DS.attr(defaultValue: -> []) + name: DS.attr('string') + identifier: DS.attr('string') + start_date: DS.attr('moment') + end_date: DS.attr('moment') + percent_done: DS.attr('number', defaultValue: 0) + expected_number_of_workflow_instances: DS.attr('number') + instance_scope: DS.attr('string', defaultValue: 'instance') + batch_size_lower_value: DS.attr('number', defaultValue: 0) + batch_size_upper_value: DS.attr('number', defaultValue: 200) + reference_date_day_of_week: DS.attr('string') + + #organizations: DS.hasMany('organization', inverse: 'projects', serialize: 'ids') + workflow_groups: DS.hasMany('panda/workflow-group') + users: DS.hasMany('user', serialize: 'ids') + workflow_instance_type: DS.belongsTo('panda/workflow-instance-type', inverse: 'projects') + tracker_subjects: DS.hasMany('panda/tracker-subject') + workflow_action_definitions: DS.hasMany('panda/workflow-action-definition', inverse: 'project') + project_scenarios: DS.hasMany('panda/project-scenario', inverse: 'project') + + workflowGroupSort: ['position:asc'] + sorted_workflow_groups: Ember.computed.sort('workflow_groups', 'workflowGroupSort') + sorted_workflow_action_definitions: Ember.computed 'workflow_groups.@each.position', 'workflow_action_definitions.@each.position', -> + @get('workflow_action_definitions').map((r) -> + base = r.get('workflow_group.position') || 100000 + r.set('absolute_sorter', base + r.get('position')) + r + ).sortBy 'absolute_sorter' + unassigned_workflow_action_definitions: Ember.computed 'workflow_action_definitions.@each.workflow_group.id', -> + @get('workflow_action_definitions').rejectBy('workflow_group.id').sortBy 'title' + + instance_scope_batch: Ember.computed.equal 'instance_scope', 'batch' + + project_instances: DS.hasMany('panda/project-instance', inverse: 'project') + project_instance_groups: DS.hasMany('panda/project-instance-group', inverse: 'project') + project_instance_actions: DS.hasMany('panda/project-instance-action', inverse: 'project') + + project_scenarios_sort: ['name:asc'] + sorted_project_scenarios: Ember.computed.sort 'project_scenarios', 'project_scenarios_sort' + #unassigned_workflow_action_definitions: Ember.computed -> + # promise = new Ember.RSVP.Promise (resolve) => + # @get('workflow_action_definitions').then (workflow_action_definitions) -> + # Ember.RSVP.all(workflow_action_definitions.mapBy 'workflow_group').then -> + # resolve workflow_action_definitions.sortBy 'title' + # DS.PromiseArray.create + # promise: promise + #unassigned_workflow_action_definitions: ( -> + # [@store.createRecord('panda/workflow-action-definition', id: 'asdf', title: 'asdf2')] + #).property() + #unassigned_workflow_action_definitions: Ember.computed -> + # promise = @get('workflow_action_definitions') + # promise.then (workflow_action_definitions) -> + # workflow_action_definitions.rejectBy('workflow_group').sortBy 'title' + # promise + #unassigned_workflow_action_definitions: Ember.computed -> + # DS.PromiseArray.create + # promise: Ember.RSVP.filter @get('workflow_action_definitions'), (workflow_action_definitions) -> + # debugger + # workflow_action_definitions.sortBy 'title' + + #@get('workflow_action_definitions').rejectBy('workflow_group.id').sortBy 'title' + diff --git a/app/models/panda/tracker-group.coffee b/app/models/panda/tracker-group.coffee new file mode 100644 index 0000000..362c5ce --- /dev/null +++ b/app/models/panda/tracker-group.coffee @@ -0,0 +1,12 @@ +import DS from 'ember-data' +export default DS.Model.extend + name: DS.attr('string') + position: DS.attr('number') + tracker_subject: DS.belongsTo('panda/tracker-subject') + tracker_items: DS.hasMany('panda/tracker-item') + workflow_groups: DS.hasMany('panda/workflow-group') + + trackerItemSort: ['position:asc'] + #sorted_tracker_items: Ember.computed.sort('tracker_items', 'trackerItemSort') + sorted_tracker_items: Ember.computed 'tracker_items.@each.position', -> + @get('tracker_items').sortBy('position') diff --git a/app/models/panda/tracker-item.coffee b/app/models/panda/tracker-item.coffee new file mode 100644 index 0000000..536e7bb --- /dev/null +++ b/app/models/panda/tracker-item.coffee @@ -0,0 +1,5 @@ +import DS from 'ember-data' +export default DS.Model.extend + name: DS.attr('string') + position: DS.attr('number') + tracker_group: DS.belongsTo('tracker-group') diff --git a/app/models/panda/tracker-subject.coffee b/app/models/panda/tracker-subject.coffee new file mode 100644 index 0000000..99de897 --- /dev/null +++ b/app/models/panda/tracker-subject.coffee @@ -0,0 +1,11 @@ +import DS from 'ember-data' +export default DS.Model.extend + name: DS.attr('string') + edit_from_workflow_group: DS.attr('boolean') + + organization: DS.belongsTo('panda/organization') + tracker_groups: DS.hasMany('panda/tracker-group') + projects: DS.hasMany('panda/project', serialize: 'ids') + + trackerGroupSort: ['position:asc'] + sorted_tracker_groups: Ember.computed.sort('tracker_groups', 'trackerGroupSort') diff --git a/app/models/panda/workflow-action-connection.js b/app/models/panda/workflow-action-connection.js new file mode 100644 index 0000000..5d86aa5 --- /dev/null +++ b/app/models/panda/workflow-action-connection.js @@ -0,0 +1 @@ +export { default } from 'ember-panda/models/panda/workflow-action-connection'; diff --git a/app/models/panda/workflow-action-constraint.coffee b/app/models/panda/workflow-action-constraint.coffee new file mode 100644 index 0000000..7c41d44 --- /dev/null +++ b/app/models/panda/workflow-action-constraint.coffee @@ -0,0 +1,9 @@ +import DS from 'ember-data' + +export default DS.Model.extend + type: DS.attr('string', defaultValue: 'warn') + + workflow_action_definition: DS.belongsTo('panda/workflow-action-definition', inverse: 'workflow_action_constraints') + constraint_workflow_action_definition: DS.belongsTo('panda/workflow-action-definition') + + restrict: Ember.computed.equal 'type', 'restrict' diff --git a/app/models/panda/workflow-action-definition.coffee b/app/models/panda/workflow-action-definition.coffee new file mode 100644 index 0000000..98ddc75 --- /dev/null +++ b/app/models/panda/workflow-action-definition.coffee @@ -0,0 +1 @@ +export { default } from 'ember-panda/models/panda/workflow-action-definition' diff --git a/app/models/panda/workflow-action-offset.coffee b/app/models/panda/workflow-action-offset.coffee new file mode 100644 index 0000000..69d7c29 --- /dev/null +++ b/app/models/panda/workflow-action-offset.coffee @@ -0,0 +1,10 @@ +import DS from 'ember-data' +import add_business_day_computations from 'ember-panda/utils/business-day-computations' + +Model = + workflow_action_definition: DS.belongsTo('panda/workflow-action-definition', inverse: 'workflow_action_offsets') + base_workflow_action_definition: DS.belongsTo('panda/workflow-action-definition') + project_scenario: DS.belongsTo('panda/project-scenario') +add_business_day_computations Model, 'offset', add_attributes: true +add_business_day_computations Model, 'batch_element_threshold', add_attributes: true #, default_unit: 'businessday' +export default DS.Model.extend(Model) diff --git a/app/models/panda/workflow-group-constraint.coffee b/app/models/panda/workflow-group-constraint.coffee new file mode 100644 index 0000000..f4dc37d --- /dev/null +++ b/app/models/panda/workflow-group-constraint.coffee @@ -0,0 +1,7 @@ +import DS from 'ember-data' +export default DS.Model.extend + type: DS.attr('string') + + workflow_group: DS.belongsTo('panda/workflow-group', inverse: 'constraints') + constraint_workflow_group: DS.belongsTo('panda/workflow-group') + workflow_action_definitions: DS.hasMany('panda/workflow-action-definition', serialize: 'ids') diff --git a/app/models/panda/workflow-group.coffee b/app/models/panda/workflow-group.coffee new file mode 100644 index 0000000..70c7a56 --- /dev/null +++ b/app/models/panda/workflow-group.coffee @@ -0,0 +1,24 @@ +import DS from 'ember-data' +import add_business_day_computations from 'ember-panda/utils/business-day-computations' + +Model = + identifier: DS.attr('string') + name: DS.attr('string') + position: DS.attr('number', defaultValue: 0) + offset_type: DS.attr('string', defaultValue: 'none') # none, connected_finish, connected_start + + project: DS.belongsTo('panda/project') + constraints: DS.hasMany('panda/workflow-group-constraint', inverse: 'workflow_group') + tracker_groups: DS.hasMany('panda/tracker-group', serialize: 'ids') + workflow_action_definitions: DS.hasMany('panda/workflow_action_definition') + project_scenarios: DS.hasMany('panda/project-scenario') + + offset_base: DS.belongsTo('panda/workflow-group', inverse: 'offset_dependents') + offset_dependents: DS.hasMany('panda/workflow-group', inverse: null) # do not give with api, will clear inverse if not given + + workflowActionDefinitionSort: ['position:asc'] + sorted_workflow_action_definitions: Ember.computed.sort('workflow_action_definitions', 'workflowActionDefinitionSort') +add_business_day_computations Model, 'offset', add_attributes: true +add_business_day_computations Model, 'duration', add_attributes: true +add_business_day_computations Model, 'batch_element_threshold', add_attributes: true #, default_unit: 'businessday' +export default DS.Model.extend(Model) diff --git a/app/models/panda/workflow-instance-type.coffee b/app/models/panda/workflow-instance-type.coffee new file mode 100644 index 0000000..29da88f --- /dev/null +++ b/app/models/panda/workflow-instance-type.coffee @@ -0,0 +1,6 @@ +import DS from 'ember-data' + +export default DS.Model.extend + name: DS.attr('string') + #organization: DS.belongsTo('organization') + projects: DS.hasMany('panda/project', inverse: 'workflow_instance_type') diff --git a/app/routes/action-list.js b/app/routes/action-list.js new file mode 100644 index 0000000..6831a2a --- /dev/null +++ b/app/routes/action-list.js @@ -0,0 +1 @@ +export { default } from 'ember-panda/routes/action-list'; diff --git a/app/routes/organization.coffee b/app/routes/organization.coffee new file mode 100644 index 0000000..2e7d03c --- /dev/null +++ b/app/routes/organization.coffee @@ -0,0 +1,33 @@ +import Ember from 'ember' +{get} = Ember +export default Ember.Route.extend + session: Ember.inject.service('session') + actions: + editOrganization: (organization)-> + @modal 'organization/edit-organization', + model: organization.get('changeset') + + newOrganization: -> + organization = @store.createRecord('panda/organization') + @modal 'organization/edit-organization', + model: organization.get('changeset') + title: t('action.new_model', model: t('models.organization')) + + setLogo: (organization, file) -> + return unless file + base = @ + session = @get('session') + #TODO implement generic request feature, this is not the way to do it! Done because it works and no time + if get(session, 'isAuthenticated') + session.authorize 'authorizer:devise', (key, authorization) -> + file.upload organization.action_url('logo'), + headers: + "#{key}": authorization + #authorization: "Token token=\"#{@get('session.session.content.authenticated.token')}\", email=\"#{@get('session.session.content.authenticated.email')}\"" + .then (response) -> + organization.store.pushPayload response.body if response?.body?.data + else # assume same host authenticated rails session + file.upload(organization.action_url('logo')).then (response) -> + organization.store.pushPayload response.body if response?.body?.data + + @send 'closeModal' diff --git a/app/routes/organization/index.coffee b/app/routes/organization/index.coffee new file mode 100644 index 0000000..ef924fa --- /dev/null +++ b/app/routes/organization/index.coffee @@ -0,0 +1,3 @@ +import Ember from 'ember' +export default Ember.Route.extend + model: -> @store.peekAll('panda/organization') diff --git a/app/routes/organization/show.coffee b/app/routes/organization/show.coffee new file mode 100644 index 0000000..d7e3545 --- /dev/null +++ b/app/routes/organization/show.coffee @@ -0,0 +1,3 @@ +import Ember from 'ember' +export default Ember.Route.extend + model: (options)-> @store.findRecord('panda/organization', options.organization_id, reload: false) diff --git a/app/routes/project.coffee b/app/routes/project.coffee new file mode 100644 index 0000000..66975f5 --- /dev/null +++ b/app/routes/project.coffee @@ -0,0 +1,25 @@ +import Ember from 'ember' +export default Ember.Route.extend + actions: + editProject: (project)-> + @modal 'project/edit-project', + model: project + + newProject: -> + project = @store.createRecord 'panda/project' + @modal 'project/edit-project', + model: project + title: t('action.new_model', model: t('models.project')) + + projectContract: (project) -> + @modal 'project/contract', + model: project + + new_project_from_contract: (project) -> + #project ||= @store.createRecord('panda/project') + project.rollbackAttributes() if project?.get('isNew') + @modal 'project/new-project-from-contract' + + show_project_scenarios_grid: (project) -> + @modal 'project/project-scenarios-show-grid', + model: project diff --git a/app/routes/project/index.coffee b/app/routes/project/index.coffee new file mode 100644 index 0000000..e315d98 --- /dev/null +++ b/app/routes/project/index.coffee @@ -0,0 +1,6 @@ +import Ember from 'ember' +export default Ember.Route.extend + model: -> @store.findAll('panda/project') + actions: + editProjectsContext: -> + @modal 'project/edit-projects-context' diff --git a/app/routes/project/show.coffee b/app/routes/project/show.coffee new file mode 100644 index 0000000..9cbb2c4 --- /dev/null +++ b/app/routes/project/show.coffee @@ -0,0 +1,9 @@ +import Ember from 'ember' +export default Ember.Route.extend + model: (params) -> @store.findRecord('panda/project', params.project_id) + #model: (params) -> + # @store.findRecord('panda/project', params.project_id, backgroundReload: false).then (project) -> + # unless project.get('project_show_reloaded') + # console.log "Trigger project show reloading for #{project.get('id')}" + # project.reload().then project.set('project_show_reloaded', true) + # project diff --git a/app/routes/project/show/add-progress.coffee b/app/routes/project/show/add-progress.coffee new file mode 100644 index 0000000..6c3360f --- /dev/null +++ b/app/routes/project/show/add-progress.coffee @@ -0,0 +1,3 @@ +import Ember from 'ember' +export default Ember.Route.extend + model: -> @modelFor('project.show') diff --git a/app/routes/project/show/dunlop.coffee b/app/routes/project/show/dunlop.coffee new file mode 100644 index 0000000..046810a --- /dev/null +++ b/app/routes/project/show/dunlop.coffee @@ -0,0 +1,16 @@ +import Ember from 'ember' +export default Ember.Route.extend + model: -> @modelFor('project.show') + + afterModel: (model, transition) -> + model.reload() unless @controllerFor('application').get('currentRouteName') in ['loading', 'project.show.dunlop'] + + actions: + edit_selected_project_scenario: -> + #return unless project_scenario = @get('controller.selected_project_scenarios.firstObject') + return unless project_scenario = @get('controller.active_project_scenario') + @modal 'project/edit-project-scenario', + model: project_scenario + closeOnOverlay: false + destroy_callback: => + @set 'controller.selected_project_scenario', null diff --git a/app/routes/project/show/project-instance-action/index.coffee b/app/routes/project/show/project-instance-action/index.coffee new file mode 100644 index 0000000..eb138c1 --- /dev/null +++ b/app/routes/project/show/project-instance-action/index.coffee @@ -0,0 +1,33 @@ +import Ember from 'ember' +import PagedRemoteArray from 'ember-cli-pagination/remote/paged-remote-array' +export default_filter = Ember.Object.create + workflow_action_definition_id_in: [] + project_instance_group_workflow_group_id: '' + state_in: [] + target_date_gteq: null + target_date_lteq: null + s: 'target_date asc' + +export default Ember.Route.extend + queryParams: + q: + refreshModel: true + setupController: (controller, model) -> + controller.set 'filters', default_filter + controller.set 'project', @modelFor('project.show') + @_super arguments... + + model: (params) -> + project = @modelFor 'project.show' + #@store.findAll('epots/migration-window-planning') + default_filter.project_id = project.id + otherParams = {q: default_filter.toProperties()} + PagedRemoteArray.create + modelName: 'panda/project-instance-action' + store: @store + page: params.page || 1 + perPage: params.perPage || 30 + otherParams: otherParams + paramMapping: + total_pages: 'total-pages' + total_count: 'total-count' diff --git a/app/routes/project/show/project-instance-action/show.coffee b/app/routes/project/show/project-instance-action/show.coffee new file mode 100644 index 0000000..249a28c --- /dev/null +++ b/app/routes/project/show/project-instance-action/show.coffee @@ -0,0 +1,3 @@ +import Ember from 'ember' +export default Ember.Route.extend + model: (options) -> @store.peekRecord('panda/project-instance-action', options.project_instance_action_id) diff --git a/app/routes/project/show/project-instance-group/index.coffee b/app/routes/project/show/project-instance-group/index.coffee new file mode 100644 index 0000000..3fc6547 --- /dev/null +++ b/app/routes/project/show/project-instance-group/index.coffee @@ -0,0 +1,8 @@ +import Ember from 'ember' +export default Ember.Route.extend + model: (params) -> + #project = @modelFor('project.show') + #Ember.RSVP.hash + # project: project + # project_instance_groups: @store.peekAll('panda/project-instance-group').filterBy('project_instance.project.id', project.get('id')) + @modelFor 'project.show' diff --git a/app/routes/project/show/project-instance-group/show.coffee b/app/routes/project/show/project-instance-group/show.coffee new file mode 100644 index 0000000..545ab9d --- /dev/null +++ b/app/routes/project/show/project-instance-group/show.coffee @@ -0,0 +1,3 @@ +import Ember from 'ember' +export default Ember.Route.extend + model: (options) -> @store.peekRecord('panda/project-instance-group', options.project_instance_group_id) diff --git a/app/routes/project/show/project-instance/index.coffee b/app/routes/project/show/project-instance/index.coffee new file mode 100644 index 0000000..0da9f33 --- /dev/null +++ b/app/routes/project/show/project-instance/index.coffee @@ -0,0 +1,39 @@ +import Ember from 'ember' +import PagedRemoteArray from 'ember-cli-pagination/remote/paged-remote-array' +export default_filter = Ember.Object.create + s: 'reference_date asc' + +export default Ember.Route.extend + queryParams: + q: + refreshModel: true + setupController: (controller, model) -> + controller.set 'filters', default_filter + controller.set 'project', @modelFor('project.show') + @_super arguments... + + model: (params) -> + project = @modelFor 'project.show' + #@store.findAll('epots/migration-window-planning') + default_filter.project_id = project.id + otherParams = {q: default_filter.toProperties()} + PagedRemoteArray.create + modelName: 'panda/project-instance' + store: @store + page: params.page || 1 + perPage: params.perPage || 30 + otherParams: otherParams + paramMapping: + total_pages: 'total-pages' + total_count: 'total-count' + + actions: + new_project_instance: -> + route = @ + @modal 'project/show/project-instance/new-project-instance', + model: @store.createRecord('panda/project-instance', project: @modelFor('project.show')) + title: t('action.new_model', model: t('models.project_instance')) + save: (record) -> + route.transitionTo('project.show.project-instance.show', record.id) if record.id + + diff --git a/app/routes/project/show/project-instance/show.coffee b/app/routes/project/show/project-instance/show.coffee new file mode 100644 index 0000000..d3845e6 --- /dev/null +++ b/app/routes/project/show/project-instance/show.coffee @@ -0,0 +1,9 @@ +import Ember from 'ember' +export default Ember.Route.extend + model: (options) -> + @store.findRecord('panda/project-instance', options.project_instance_id) + afterModel: (model, transition) -> + model.reload() if @controllerFor('application').get('currentRouteName') in ['action-list', 'project.show.project-instance.index'] + setupController: (controller, model) -> + controller.set 'project', @modelFor('project.show') + @_super arguments... diff --git a/app/routes/project/show/tracker-subject.coffee b/app/routes/project/show/tracker-subject.coffee new file mode 100644 index 0000000..3043eb6 --- /dev/null +++ b/app/routes/project/show/tracker-subject.coffee @@ -0,0 +1,6 @@ +import Ember from 'ember' +export default Ember.Route.extend + model: (options) -> + Ember.RSVP.hash + project: @modelFor('project.show') + tracker_subject: @store.findRecord('panda/tracker-subject', options.tracker_subject_id) diff --git a/app/routes/tracker-subject/index.coffee b/app/routes/tracker-subject/index.coffee new file mode 100644 index 0000000..503814a --- /dev/null +++ b/app/routes/tracker-subject/index.coffee @@ -0,0 +1,3 @@ +import Ember from 'ember' +export default Ember.Route.extend + model: -> @store.peekAll('panda/tracker-subject') diff --git a/app/routes/tracker-subject/show/project.coffee b/app/routes/tracker-subject/show/project.coffee new file mode 100644 index 0000000..3a35eba --- /dev/null +++ b/app/routes/tracker-subject/show/project.coffee @@ -0,0 +1,6 @@ +import Ember from 'ember' +export default Ember.Route.extend + model: (options)-> + Ember.RSVP.hash + project: @store.findRecord('panda/project', options.project_id) + tracker_subject: @modelFor('tracker-subject.show') diff --git a/app/routes/tracker_subject.coffee b/app/routes/tracker_subject.coffee new file mode 100644 index 0000000..8c95ba1 --- /dev/null +++ b/app/routes/tracker_subject.coffee @@ -0,0 +1,12 @@ +import Ember from 'ember' +export default Ember.Route.extend + actions: + newTrackerSubject: -> + tracker_subject = @store.createRecord 'panda/tracker-subject' + @modal "edit-tracker-subject", + model: tracker_subject + title: t('action.new_model', model: t('models.tracker_subject')) + + editTrackerSubject: (tracker_subject)-> + @modal 'edit-tracker-subject', + model: tracker_subject diff --git a/app/routes/user.coffee b/app/routes/user.coffee new file mode 100644 index 0000000..8522fca --- /dev/null +++ b/app/routes/user.coffee @@ -0,0 +1,11 @@ +import Ember from 'ember' +export default Ember.Route.extend + actions: + editUser: (user)-> + @modal 'user/edit-user', + model: user + newUser: -> + new_user = @store.createRecord('user') + @modal 'user/new-user', + model: new_user + save: => @transitionTo 'user.show', new_user diff --git a/app/routes/user/index.coffee b/app/routes/user/index.coffee new file mode 100644 index 0000000..ab24a7b --- /dev/null +++ b/app/routes/user/index.coffee @@ -0,0 +1,3 @@ +import Ember from 'ember' +export default Ember.Route.extend + model: -> @store.peekAll('user') # fetched in application route diff --git a/app/routes/user/profile.coffee b/app/routes/user/profile.coffee new file mode 100644 index 0000000..fd34537 --- /dev/null +++ b/app/routes/user/profile.coffee @@ -0,0 +1,3 @@ +import Ember from 'ember' +export default Ember.Route.extend + model: -> @get('router.globals.current_user') diff --git a/app/routes/user/show.coffee b/app/routes/user/show.coffee new file mode 100644 index 0000000..91c7c4e --- /dev/null +++ b/app/routes/user/show.coffee @@ -0,0 +1,34 @@ +import Ember from 'ember' +export panda_authorizations = [ + {type: 'model', target: 'panda/organization', permissions: ['manage']} + {type: 'model', target: 'panda/project', permissions: ['read', 'manage']} + {type: 'model', target: 'panda/project-instance', permissions: ['read', 'manage']} +] +export default Ember.Route.extend + setupController: (controller, model) -> + adapters = @store.peekAll('panda/adapter') + active_authorizations = model.get('role_names') + + active_authorizations.push "ban-app-panda" unless active_authorizations.some (permission) -> + permission.match new RegExp('-app-panda$') + panda_authorizations.forEach (authorization) -> + match_found = false + authorization.permissions.forEach (permission) -> + match_found = true if "#{permission}-#{authorization.type}-#{authorization.target}" in active_authorizations + active_authorizations.push "ban-#{authorization.type}-#{authorization.target}" unless match_found + + # explicitly add ban-model-epots/work-order if no permission is present + adapters.forEach (adapter) -> + adapter.get('authorizations').forEach (authorization) -> + match_found = false + authorization.permissions.forEach (permission) -> + match_found = true if "#{permission}-#{authorization.type}-#{authorization.target}" in active_authorizations + active_authorizations.push "ban-#{authorization.type}-#{authorization.target}" unless match_found + has_adapter_authorization = active_authorizations.some (permission) -> + permission.match(new RegExp("-adapter-#{adapter.id}$")) + active_authorizations.push "ban-adapter-#{adapter.id}" unless has_adapter_authorization + + controller.set 'active_authorizations', active_authorizations + controller.set 'adapters', adapters + controller.set 'panda_authorizations', panda_authorizations + @_super arguments... diff --git a/app/templates/action-list.emblem b/app/templates/action-list.emblem new file mode 100644 index 0000000..a11dbf2 --- /dev/null +++ b/app/templates/action-list.emblem @@ -0,0 +1,124 @@ +.ui.top-right.pagination.menu= page-numbers content=model.project_instance_actions showFL=true += page-title 'action_list.title' + = pagination-progress content=model.project_instance_actions class='between-brackets' +.ui.top.attached.segment + = ui-dropdown class='violet button pull-right' + .default.text Export + i.dropdown.icon + .menu + .item + a href=export_human_url: span Human + .item + a href=export_technical_url: span Technical + = action-list/project-filter apply_filters=(action 'refresh_list') filters=filters scope_project=scope_project scope_scenario=scope_scenario projects=model.projects + = action-list/organization-filter apply_filters=(action 'refresh_list') filters=filters scope_organization=scope_organization organizations=model.organizations + ' + .ui.small.icon.button{action 'refresh_list'} + i.refresh.icon +table.ui.attached.stiped.table + thead + tr + unless scope_project + th= t 'models.project' + unless scope_scenario + th= t 'models.project_scenario' + unless scope_organization + th= t 'models.organization' + th= t 'models.project_instance_group' + th= t 'models.workflow_action_definition' + th= t 'models.project_instance' + th= t 'attributes.project_instance_action.state' + th= t 'attributes.project_instance_action.notes' + th= t 'attributes.project_instance_action.target_date' + th= t 'attributes.project_instance_action.value' + table-filters content=model.project_instance_actions filters=filters as |filter| + unless scope_project + th + unless scope_scenario + th + unless scope_organization + th + if (and scope_project scope_project.isLoaded) + = filter.dropdown-select key='project_instance_group_workflow_group_id' options=scope_project.sorted_workflow_groups labelPath='name' + = filter.dropdown-select-multiple key='workflow_action_definition_id_in' options=scope_project.sorted_workflow_action_definitions labelPath='title' + else + th + = filter.input key='workflow_action_definition_title_cont' + = filter.input key='project_instance_client_identifier_cont' + = filter.dropdown-select key='state' options=globals.project_instance_action.states translationPath='attributes.state' + = filter.input key='notes_cont' + = filter.date-range key='target_date' expandingTime=true weekSelector=true + th + tbody + if model.project_instance_actions.loading + tr + td colspan='99' + .ui.active.inline.centered.loader + else + each model.project_instance_actions as |project_instance_action| + tr.record-with-pointing-records + unless scope_project + td= project_instance_action.project.name + unless scope_scenario + td= project_instance_action.project_instance.project_scenario.name + unless scope_organization + td= project_instance_action.workflow_action_definition.organization.name + td + if project_instance_action.project_instance_group.id + if (can 'read' 'model' 'panda/project-instance') + = link-to 'project.show.project-instance-group.show' project_instance_action.project_instance.project.id project_instance_action.project_instance_group.id + = project_instance_action.project_instance_group.workflow_group.name + else + = project_instance_action.project_instance_group.workflow_group.name + td= project_instance_action.workflow_action_definition.title + td + if (can 'read' 'model' 'panda/project-instance') + = link-to 'project.show.project-instance.show' project_instance_action.project_instance.project.id project_instance_action.project_instance.id + = project_instance_action.project_instance.client_identifier + else + = project_instance_action.project_instance.client_identifier + td= state-badge project_instance_action.state action=(action 'edit_project_instance_action' project_instance_action) + td + if (gt project_instance_action.notes.length 14) + = ui-markdown-popup tagName='i' class='sticky note outline icon' markdown=project_instance_action.notes + else if project_instance_action.notes + = project_instance_action.notes + if project_instance_action.workflow_action_definition.extra_info + ' + = ui-markdown-popup tagName='i' class='info circle icon' markdown=project_instance_action.workflow_action_definition.extra_info + td= format-date project_instance_action.target_date + td= project/instance-action-value-display project_instance_action=project_instance_action + each project_instance_action.pointing_project_instance_action_connections as |project_instance_action_connection| + if (eq project_instance_action_connection.connection_type 'attached') + tr.pointing-record + unless scope_project + td= project_instance_action_connection.project_instance_action.project.name + unless scope_scenario + td= project_instance_action_connection.project_instance_action.project_instance.project_scenario.name + unless scope_organization + td= project_instance_action_connection.project_instance_action.workflow_action_definition.organization.name + td + if project_instance_action_connection.project_instance_action.project_instance_group.id + if (can 'read' 'model' 'panda/project-instance') + = link-to 'project.show.project-instance-group.show' project_instance_action.project_instance.project.id project_instance_action.project_instance_group.id + = project_instance_action.project_instance_group.workflow_group.name + else + = project_instance_action.project_instance_group.workflow_group.name + td= project_instance_action_connection.project_instance_action.workflow_action_definition.title + td + if (can 'read' 'model' 'panda/project-instance') + = link-to 'project.show.project-instance.show' project_instance_action.project_instance.project.id project_instance_action.project_instance.id + = project_instance_action.project_instance.client_identifier + else + = project_instance_action.project_instance.client_identifier + td= state-badge project_instance_action_connection.project_instance_action.state action=(action 'edit_project_instance_action' project_instance_action_connection.project_instance_action) + td + if (gt project_instance_action_connection.project_instance_action.notes.length 14) + = ui-markdown-popup tagName='i' class='sticky note outline icon' markdown=project_instance_action_connection.project_instance_action.notes + else if project_instance_action_connection.project_instance_action.notes + = project_instance_action_connection.project_instance_action.notes + if project_instance_action_connection.project_instance_action.workflow_action_definition.extra_info + ' + = ui-markdown-popup tagName='i' class='info circle icon' markdown=project_instance_action_connection.project_instance_action.workflow_action_definition.extra_info + td= format-date project_instance_action_connection.project_instance_action.target_date + td= project/instance-action-value-display project_instance_action=project_instance_action_connection.project_instance_action diff --git a/app/templates/components/action-list/organization-filter.emblem b/app/templates/components/action-list/organization-filter.emblem new file mode 100644 index 0000000..f3ee00e --- /dev/null +++ b/app/templates/components/action-list/organization-filter.emblem @@ -0,0 +1,12 @@ +.ui.small.buttons.select-organization + = ui-dropdown class='button' selected=scope_organization onChange=(action 'set_scope_organization') as |execute mapper| + .default.text= t 'action.select_model' modelPath='models.organization' + i.dropdown.icon + .menu + each organizations as |organization| + .item data-value='{{map-value mapper organization}}' + = organization.name + if scope_organization + button.ui.icon.button{action 'set_scope_organization' null} + i.close.icon += yield diff --git a/app/templates/components/action-list/project-filter.emblem b/app/templates/components/action-list/project-filter.emblem new file mode 100644 index 0000000..c482a6b --- /dev/null +++ b/app/templates/components/action-list/project-filter.emblem @@ -0,0 +1,26 @@ +.ui.small.buttons.select-project + = ui-dropdown class='button' selected=scope_project onChange=(action 'set_scope_project') as |execute mapper| + .default.text= t 'action.select_model' modelPath='models.project' + i.dropdown.icon + .menu + each (sort-by 'name' projects) as |project| + .item data-value='{{map-value mapper project}}' + = project.name + if scope_project + button.ui.icon.button{action 'set_scope_project' null} + i.close.icon +' +if scope_project + .ui.small.buttons.select-project-scenario + = ui-dropdown class='button' selected=scope_scenario onChange=(action 'set_scope_scenario') as |execute mapper| + .default.text= t 'action.select_model' modelPath='models.project_scenario' + i.dropdown.icon + .menu + each scope_project.project_scenarios as |project_scenario| + .item data-value='{{map-value mapper project_scenario}}' + = project_scenario.name + if scope_scenario + button.ui.icon.button{action 'set_scope_scenario' null} + i.close.icon + ' += yield diff --git a/app/templates/components/edit-attribute-item.emblem b/app/templates/components/edit-attribute-item.emblem new file mode 100644 index 0000000..144e4b0 --- /dev/null +++ b/app/templates/components/edit-attribute-item.emblem @@ -0,0 +1,22 @@ +/.ui.form + .ui.right.floated.icon.buttons + if isEditing + = push-action type='rollback' action='rollbackRecord' + = push-action type='destroy' action='destroyRecord' + = push-action type='save' action='saveRecord' + else + if can_make_editable + /= push-action type='edit' action='editRecord' + if isEditing + = input value=model.name enter='saveRecord' + else + span= model.name +if isEditing + .ui.action.input + = input value=model.name enter='saveRecord' + = push-action type='rollback' action='rollbackRecord' + = push-action type='destroy' action='destroyRecord' + = push-action type='save' action='saveRecord' +else + span= model.name + = yield diff --git a/app/templates/components/flash-message.emblem b/app/templates/components/flash-message.emblem new file mode 100644 index 0000000..754ec50 --- /dev/null +++ b/app/templates/components/flash-message.emblem @@ -0,0 +1 @@ += message diff --git a/app/templates/components/format-json.emblem b/app/templates/components/format-json.emblem new file mode 100644 index 0000000..0a90f09 --- /dev/null +++ b/app/templates/components/format-json.emblem @@ -0,0 +1 @@ += yield diff --git a/app/templates/components/labeled-content-choice.emblem b/app/templates/components/labeled-content-choice.emblem new file mode 100644 index 0000000..0a90f09 --- /dev/null +++ b/app/templates/components/labeled-content-choice.emblem @@ -0,0 +1 @@ += yield diff --git a/app/templates/components/number-input-with-value.emblem b/app/templates/components/number-input-with-value.emblem new file mode 100644 index 0000000..02832f6 --- /dev/null +++ b/app/templates/components/number-input-with-value.emblem @@ -0,0 +1,2 @@ +one-way-input stringValue type='number' step=step update='set_value' += yield diff --git a/app/templates/components/project-workflow-groups-graph.emblem b/app/templates/components/project-workflow-groups-graph.emblem new file mode 100644 index 0000000..40a8112 --- /dev/null +++ b/app/templates/components/project-workflow-groups-graph.emblem @@ -0,0 +1,2 @@ +pre.hide= digraph_content += svg_content diff --git a/app/templates/components/project/action-execution-offset-diagram.emblem b/app/templates/components/project/action-execution-offset-diagram.emblem new file mode 100644 index 0000000..1a820e0 --- /dev/null +++ b/app/templates/components/project/action-execution-offset-diagram.emblem @@ -0,0 +1,26 @@ +.ui.grid + .nine.wide.column= graph_title + .right.aligned.seven.wide.column + if choose_reference_date + i.map.signs.flipped.icon{action 'hide_absolute_date_choice'} + .ui.small.icon.input= ui-calendar-moment type='date' momentValue=(readonly chosen_absolute_date) onChange=(action (mut chosen_absolute_date)) placeholder='X' allowClear=true + else + i.map.signs.icon{ action (mut choose_reference_date) true} + = ui-dropdown class='selection' selected=project.reference_date_day_of_week onChange=(action (mut reference_date_day_of_week)) + .default.text= t 'project.select_reference_date_day_of_week' + i.dropdown.icon + .menu + /.item data-value='': span -- + each globals.week_days as |week_day| + .item data-value=week_day.name + = t week_day.name scope='date.week_day' +.action-execution-offset-diagram-container.diagram-container +.range-slider-container + if range_start_default + range-slider start=range_start_default range=range_bounds on-change='range_changed' tooltips=range_formatter step=1 connect=true +.range-slider-container + if (and project.instance_scope_batch project.batch_size_upper_value) + .ui.form.grid + .sixteen.wide.column: h4 Batch size simulation + + range-slider start=batch_size_simulation_value range=batch_size_simulation_range on-change='batch_size_simulation_change' tooltips=batch_size_formatter step=1 diff --git a/app/templates/components/project/edit-active-project-scenario.emblem b/app/templates/components/project/edit-active-project-scenario.emblem new file mode 100644 index 0000000..26b205e --- /dev/null +++ b/app/templates/components/project/edit-active-project-scenario.emblem @@ -0,0 +1,15 @@ += ui-dropdown class='button' selected=active_project_scenario onChange=(action (mut active_project_scenario)) as |execute mapper| + .default.text= t 'action.select_model' modelPath='models.project_scenario' + i.dropdown.icon + .menu + .header + i.grid.layout.icon + a.pointable{action 'show_project_scenarios_grid' project} Show activation grid + each (sort-by 'name' project.project_scenarios) as |project_scenario| + .item data-value='{{map-value mapper project_scenario}}' + = project_scenario.name +if active_project_scenario + button.ui.icon.button{action (mut active_project_scenario) null} + i.close.icon + button.ui.edit.icon.button{action 'edit_selected_project_scenario'} + i.edit.icon diff --git a/app/templates/components/project/instance-action-value-display.emblem b/app/templates/components/project/instance-action-value-display.emblem new file mode 100644 index 0000000..73f8811 --- /dev/null +++ b/app/templates/components/project/instance-action-value-display.emblem @@ -0,0 +1,42 @@ +if (eq value_type 'boolean') + if project_instance_action.boolean_value + i.checkmark.box.icon + else + i.square.outline.value-missing.icon +else if (eq value_type 'date') + if project_instance_action.date_value + = format-date project_instance_action.date_value format=globals.date_formatting + else + i.calendar.value-missing.icon +else if (eq value_type 'timestamp') + if project_instance_action.timestamp_value + = format-date project_instance_action.timestamp_value format=globals.time_formatting + else + i.wait.value-missing.icon +else if (eq value_type 'number') + if project_instance_action.number_value + = project_instance_action.number_value + else + i.hashtag.value-missing.icon +else if (eq value_type 'select') + if project_instance_action.string_value + = project_instance_action.string_value + else + i.list.value-missing.icon +else if (eq value_type 'string') + if project_instance_action.string_value + = project_instance_action.string_value + else + i.terminal.value-missing.icon +else if (eq value_type 'text') + if project_instance_action.text_value + if (gt project_instance_action.text_value.length 14) + = ui-markdown-popup tagName='i' class='sticky note icon' markdown=project_instance_action.text_value + else + = project_instance_action.text_value + else + i.sticky.note.outline.value-missing.icon +else + span= value_type + i.red.warning.circle.icon + = project_instance_action.value_display diff --git a/app/templates/components/project/scenario-edit-item.emblem b/app/templates/components/project/scenario-edit-item.emblem new file mode 100644 index 0000000..7e58e57 --- /dev/null +++ b/app/templates/components/project/scenario-edit-item.emblem @@ -0,0 +1,3 @@ +.ui.right.floated.icon.buttons + = push-action type='edit' action='editProjectScenario' +p= model.name diff --git a/app/templates/components/project/workflow-action-constraints-graph.emblem b/app/templates/components/project/workflow-action-constraints-graph.emblem new file mode 100644 index 0000000..5b8d75d --- /dev/null +++ b/app/templates/components/project/workflow-action-constraints-graph.emblem @@ -0,0 +1,5 @@ +/if has_content + .ui.header: .content= t 'models.plural.workflow_action_constraint' + /pre.hide2= digraph_content + /h1= workflow_action_constraints.length += svg_content diff --git a/app/templates/components/project/workflow-action-offsets-graph.emblem b/app/templates/components/project/workflow-action-offsets-graph.emblem new file mode 100644 index 0000000..13f5cb9 --- /dev/null +++ b/app/templates/components/project/workflow-action-offsets-graph.emblem @@ -0,0 +1,34 @@ +/if has_content + .ui.header: .content= t 'models.plural.workflow_action_offset' + pre= digraph_contents.firstObject +.ui.grid + .eleven.wide.column + = graph_title + = ui-checkbox checked=show_attached label=(t 'workflow_action_definition.offset_graph.show_attached') onChange=(action (mut show_attached)) + ' + = ui-checkbox checked=show_pointing_infos label=(t 'workflow_action_definition.offset_graph.show_pointing_infos') onChange=(action (mut show_pointing_infos)) + ' + = ui-checkbox checked=show_pointing_constraints label=(t 'workflow_action_definition.offset_graph.show_pointing_constraints') onChange=(action (mut show_pointing_constraints)) + if project.project_scenarios.length + = project/edit-active-project-scenario project=project active_project_scenario=active_project_scenario + .right.aligned.five.wide.column + if choose_reference_date + i.map.signs.flipped.icon{action 'hide_absolute_date_choice'} + .ui.small.icon.input= ui-calendar-moment type='date' momentValue=(readonly chosen_absolute_date) onChange=(action (mut chosen_absolute_date)) placeholder='X' allowClear=true + else + i.map.signs.icon{ action (mut choose_reference_date) true} + = ui-dropdown class='selection' selected=project.reference_date_day_of_week onChange=(action (mut reference_date_day_of_week)) + .default.text= t 'project.select_reference_date_day_of_week' + i.dropdown.icon + .menu + /.item data-value='': span -- + each globals.week_days as |week_day| + .item data-value=week_day.name + = t week_day.name scope='date.week_day' +each svg_contents as |svg_content| + == svg_content +.range-slider-container + if (and project.instance_scope_batch project.batch_size_upper_value) + .ui.form.grid + .sixteen.wide.column: h4 Batch size simulation + range-slider start=batch_size_simulation_value range=batch_size_simulation_range on-change='batch_size_simulation_change' tooltips=batch_size_formatter step=1 diff --git a/app/templates/components/project/workflow-group-offset-diagram.emblem b/app/templates/components/project/workflow-group-offset-diagram.emblem new file mode 100644 index 0000000..fb4f30d --- /dev/null +++ b/app/templates/components/project/workflow-group-offset-diagram.emblem @@ -0,0 +1,27 @@ +.ui.grid.without-bottom-margin + .nine.wide.column= graph_title + .right.aligned.seven.wide.column + if choose_reference_date + i.map.signs.flipped.icon{action 'hide_absolute_date_choice'} + .ui.small.icon.input= ui-calendar-moment type='date' momentValue=(readonly chosen_absolute_date) onChange=(action (mut chosen_absolute_date)) placeholder='X' allowClear=true + else + i.map.signs.icon{ action (mut choose_reference_date) true} + = ui-dropdown class='selection' selected=project.reference_date_day_of_week onChange=(action (mut reference_date_day_of_week)) + .default.text= t 'project.select_reference_date_day_of_week' + i.dropdown.icon + .menu + /.item data-value='': span -- + each globals.week_days as |week_day| + .item data-value=week_day.name + = t week_day.name scope='date.week_day' +.workflow-group-offset-diagram-container.diagram-container +.range-slider-container + if range_start_default + range-slider start=range_start_default range=range_bounds on-change='range_changed' tooltips=range_formatter step=1 connect=true + +.range-slider-container + if (and project.instance_scope_batch project.batch_size_upper_value) + .ui.form.grid + .sixteen.wide.column: h4 Batch size simulation + + range-slider start=batch_size_simulation_value range=batch_size_simulation_range on-change='batch_size_simulation_change' tooltips=batch_size_formatter step=1 diff --git a/app/templates/components/select-duration.emblem b/app/templates/components/select-duration.emblem new file mode 100644 index 0000000..c93970a --- /dev/null +++ b/app/templates/components/select-duration.emblem @@ -0,0 +1,27 @@ +if threshold + .ui.left.labeled.input + .ui.label Max + = number-input numericValue=value step=1 +else + .ui.input= number-input numericValue=value step=1 += ui-dropdown class='selection' selected=unit onChange=(action (mut unit)) + /.default.text= t model.batch_element_threshold_unit scope='date.unit.plural' bare=true + .text + i.dropdown.icon + .menu + each globals.workflow_group.batch_element_threshold_unit_options as |batch_element_threshold_unit| + .item data-value=batch_element_threshold_unit + if threshold + span per + = t batch_element_threshold_unit scope='date.unit' + else + = t batch_element_threshold_unit scope=value_date_scope +if unit_businessday + span class=has_default_business_days:default-business-days:non-default-business-days + = ui-dropdown class='right basic labeled icon select-businessdays-in-week' action='nothing' + i.dropdown.icon + span.ui.tiny.header= t 'date.unit.plural.day' + .menu + .scrolling.menu + each globals.week_days as |week_day| + ui-checkbox class='item' label=(t week_day.name scope='date.week_day') checked=(contains week_day.index week_days) onChange=(action 'toggle_week_day_presence' week_days week_day) diff --git a/app/templates/components/select-week-days.emblem b/app/templates/components/select-week-days.emblem new file mode 100644 index 0000000..0a90f09 --- /dev/null +++ b/app/templates/components/select-week-days.emblem @@ -0,0 +1 @@ += yield diff --git a/app/templates/components/set-workflow-group-tracker-group.emblem b/app/templates/components/set-workflow-group-tracker-group.emblem new file mode 100644 index 0000000..54ca1e0 --- /dev/null +++ b/app/templates/components/set-workflow-group-tracker-group.emblem @@ -0,0 +1,9 @@ +if isEditing + span= workflow_group.name + x-select value=selectedViewItem action="selectViewItem" as |xs| + option -- + each tracker_subject.sorted_tracker_groups as |tracker_group| + xs.option value=tracker_group + = tracker_group.name +else + span= workflow_group.name diff --git a/app/templates/components/state-badge.emblem b/app/templates/components/state-badge.emblem new file mode 100644 index 0000000..80e6b50 --- /dev/null +++ b/app/templates/components/state-badge.emblem @@ -0,0 +1,2 @@ += human_state += yield diff --git a/app/templates/components/ui-multi-select.emblem b/app/templates/components/ui-multi-select.emblem new file mode 100644 index 0000000..04f77f9 --- /dev/null +++ b/app/templates/components/ui-multi-select.emblem @@ -0,0 +1,6 @@ +ui-dropdown class="fluid multiple selection" selected=selected onChange=(action (mut selected)) as |execute mapper| + i.dropdown.icon + .default.text= placeholder_text + .menu + each options as |option| + .item data-value="{{map-value mapper option}}"= get option labelProperty diff --git a/app/templates/components/value-type-display.emblem b/app/templates/components/value-type-display.emblem new file mode 100644 index 0000000..4f1c26f --- /dev/null +++ b/app/templates/components/value-type-display.emblem @@ -0,0 +1 @@ +i.icon class=iconClass diff --git a/app/templates/components/workflow-action-definition-item.emblem b/app/templates/components/workflow-action-definition-item.emblem new file mode 100644 index 0000000..a041976 --- /dev/null +++ b/app/templates/components/workflow-action-definition-item.emblem @@ -0,0 +1,31 @@ +if isEditing + .ui.one.column.grid + .ui.form + .column + .ui.tiny.right.floated.icon.buttons + = push-action type='rollback' action='rollbackWorkflowActionDefinition' + = push-action type='destroy' action='destroyWorkflowActionDefinition' + = push-action type='position' action='positionInProject' + = push-action type='edit' action='editWorkflowActionDefinition' + unless workflow_action_definition.isNew + = push-action type='authorization' action='authorize_users' + = push-action type='save' action='saveWorkflowActionDefinition' + = input value=workflow_action_definition.title enter='saveWorkflowActionDefinition' class='workflow-action-definition-title-edit' placeholder='name' + .column + = textarea value=workflow_action_definition.extra_info class='workflow-action-definition-extra-info-edit' placeholder='extra info' +else + .ui.grid.workflow-action-definition-row + .sixteen.wide.column + value-type-display type=workflow_action_definition.value_type + .ui.tiny.right.floated.icon.buttons + if workflow_action_definition.organization.logo + button.ui.unpadded.button{action 'editWorkflowActionDefinition'} + img src=workflow_action_definition.organization.logo_url.small + /workflow_action_definition.position + else + button.ui.button{action 'editWorkflowActionDefinition'}= workflow_action_definition.position + span.title= workflow_action_definition.title + if workflow_action_definition.extra_info + ' + = ui-markdown-popup tagName='i' class='info icon' markdown=workflow_action_definition.extra_info + diff --git a/app/templates/components/workflow-action-definition/modal-header.emblem b/app/templates/components/workflow-action-definition/modal-header.emblem new file mode 100644 index 0000000..cee9aec --- /dev/null +++ b/app/templates/components/workflow-action-definition/modal-header.emblem @@ -0,0 +1,11 @@ +.header + if model.workflow_group.id + span= model.workflow_group.name + else + span (unassigned) + i.right.arrow.icon + if model.isNew + span (new) + else + span= model.title += yield diff --git a/app/templates/components/workflow-group-edit-constraint.emblem b/app/templates/components/workflow-group-edit-constraint.emblem new file mode 100644 index 0000000..5aee47a --- /dev/null +++ b/app/templates/components/workflow-group-edit-constraint.emblem @@ -0,0 +1,17 @@ +.ui.grid + .four.wide.column + /x-select model=constraint_types value=constraint valueKey="value.type" required=true canSearch=false onSelect=(action 'selectConstraintType') + x-select value=constraint.type on-change=(action (mut constraint.type)) as |xs| + each constraint_types as |constraint_type| + xs.option value=constraint_type + = constraint_type + /select + each constraint_types as |constraint_type| + option value=constraint_type selected={eq constraint_type constraint.type} + .eight.wide.column + x-select value=constraint.constraint_workflow_group.content on-change=(action (mut constraint.constraint_workflow_group)) as |xs| + each constraint_workflow_groups as |constraint_workflow_group| + xs.option value=constraint_workflow_group + = constraint_workflow_group.name + ui-multi-select options=constraint.constraint_workflow_group.sorted_workflow_action_definitions labelProperty='name' selected=constraint.workflow_action_definitions + .two.wide.column= push-action type="destroy" action="destroyConstraint" diff --git a/app/templates/components/workflow-group-header.emblem b/app/templates/components/workflow-group-header.emblem new file mode 100644 index 0000000..a93f77d --- /dev/null +++ b/app/templates/components/workflow-group-header.emblem @@ -0,0 +1,13 @@ +if workflow_group.isEditing + .ui.small.right.floated.icon.buttons + = push-action type='rollback' action='rollbackWorkflowStep' + = push-action type='save' action='saveWorkflowStep' + = push-action type='destroy' action='destroyWorkflowStep' + = input value=workflow_group_name enter='saveWorkflowStep' class='workflow-group-title-edit' +else + .ui.small.right.floated.icon.buttons + = push-action type='new' action='newWorkflowActionDefinition' + = push-action type='edit' action='editWorkflowGroup' + = push-action type='sort' action='sortWorkflowActionDefinitions' + = push-action type='show' action='displayWorkflowActionDefinitions' + = workflow_group.name diff --git a/app/templates/event.emblem b/app/templates/event.emblem new file mode 100644 index 0000000..f2ee372 --- /dev/null +++ b/app/templates/event.emblem @@ -0,0 +1,74 @@ +d-breadcrumbs as |breadcrumb| + breadcrumb.item active=true + span= t 'models.plural.event' +.ui.container + .ui.header + /.ui.right.floated.icon.buttons + .content= t 'models.plural.event' + .ui.divider + .ui.top.attached.segment + .ui.right.floated.icon.buttons + button.ui.small.icon.button + i.upload.icon + .header + h3 Events files (proposal) + .ui.bottom.attached.secondary.segment + table style='width:100%' + thead + tr + td Type, + td Instance Identifier, + td Event Identifier, + td Event key, + td Event value, + td Event timestamp + td Source + tbody + tr + td action, + td CFK42664, + td (action)8bc03e74-45eb-11e7-ae9b-02429821d4da, + td value, + td NEW12345, + td 2017-06-05T13:11:50Z, + td + tr + td action, + td CFK42664, + td (action)8bc03e74-45eb-11e7-ae9b-02429821d4da, + td state, + td completed, + td 2017-06-05T13:11:50Z, + td + tr + td action, + td CFK42664, + td (action)8bc03e74-45eb-11e7-ae9b-02429821d4da, + td date, + td 2017-09-28, + td 2017-06-05T13:11:50Z, + td + tr + td group, + td CFK42665, + td (group)5d4d6a60-46b0-11e7-93e6-02429821d4da, + td state, + td overdue, + td 2017-06-05T13:11:50Z, + td + tr + td instance, + td CFK42665, + td (project)9898343a-45eb-11e7-ae9b-02429821d4da, + td state, + td overdue, + td 2017-06-05T13:11:50Z, + td + tr + td instance-update, + td CFK42665, + td (project)9898343a-45eb-11e7-ae9b-02429821d4da, + td new_xdf_service_id, + td CFK42666, + td 2017-06-05T13:11:50Z, + td diff --git a/app/templates/manual.emblem b/app/templates/manual.emblem new file mode 100644 index 0000000..1e992b4 --- /dev/null +++ b/app/templates/manual.emblem @@ -0,0 +1,16 @@ +.ui.grid.manual + .four.wide.column.nav-column + ui-sticky + .ui.vertical.menu + .header.item= link-to 'manual': span= t 'manual.title' + .item + .header= t 'models.plural.project' + .menu + = link-to 'manual.project.dunlop' class='item' + span= t 'project.dunlop.title' + .item + .header= t 'models.plural.tracker_subject' + .menu + = link-to 'manual.tracker.index' class='item' + span Basis + .twelve.wide.column= outlet diff --git a/app/templates/manual/index.emblem b/app/templates/manual/index.emblem new file mode 100644 index 0000000..f753a2a --- /dev/null +++ b/app/templates/manual/index.emblem @@ -0,0 +1,41 @@ +.ui.container + = page-title 'manual.title' + p= t 'manual.introduction1' + + h1.ui.header Conceptueel project design + .ui.horizontal.divider Definitie (nvt) + .ui.horizontal.divider Design (nvt) + .ui.horizontal.divider Initialisatie (nvt) + .ui.horizontal.divider Forecasting + .ui.horizontal.divider Resource distribution + p Een optimale doelplanning maken op basis van de forecast waarbij een geoptimaliseerde inzet van resources het doel is. + p + | Mogelijke voorbeelden zijn: + ul + li Het zo gelijkmatig werk verdelen over de week + li Zo veel mogelijk aan het begin van de week om meer tijd over te houden voor incidenten aan het einde van de week + li In afstemming met eventuele externe resource bronnen + + .ui.horizontal.divider Planning + p Het daadwerkelijk vastleggen van een beoogde planning (harde plan datum) + + .ui.horizontal.divider Aansturing + p Aansturen van betrokken organisaties + + .ui.horizontal.divider Progress consumption + p Het verwerken van de daadwerkelijke acties binnen het project. Ofwel via bronnen van externe organisaties, danwel direct via user input + + .ui.horizontal.divider Prognosis and velocity projections + p ... + + .ui.horizontal.divider Project insights + p Het kunnen inzien van de project voortgang en eventuele acties die te laat zijn afgemeld + + .ui.horizontal.divider Detection + p Systeem triggers op bekende mogelijke afwijkingen met actieve gebruikers handeling als gevolg (attention-incident) + + .ui.horizontal.divider Investigation + p Het kunnen bepalen waar onverwachte problemen ontstaan + .ui.horizontal.divider Analysis + .ui.horizontal.divider Reporting +img src="./assets/manual/manual-index.png" diff --git a/app/templates/manual/project/dunlop.emblem b/app/templates/manual/project/dunlop.emblem new file mode 100644 index 0000000..a4b903a --- /dev/null +++ b/app/templates/manual/project/dunlop.emblem @@ -0,0 +1,38 @@ +d-breadcrumbs as |breadcrumb| + breadcrumb.item + = link-to 'Manual' 'manual.index' + breadcrumb.item + = t 'models.project' + breadcrumb.item active=true + = t 'project.dunlop.title' += page-title 'project.dunlop.title' + +h2.ui.dividing.header= t 'models.plural.workflow_group' +p= t 'manual.project.dunlop.workflow_group.text' modelPath='models.workflow_group' modelsPath='models.plural.workflow_group' +h3.ui.block.header= t 'manual.project.dunlop.workflow_group.duration_value.header' + +h2.ui.dividing.header= t 'models.plural.workflow_action_definition' +h3.ui.block.header= t 'manual.project.dunlop.workflow_action_definition.action_confirmation.header' +p= t 'manual.project.dunlop.workflow_action_definition.action_confirmation.text' + +h2.ui.dividing.header= t 'models.plural.workflow_action_offset' +h3.ui.block.header Offset diagram +p + | The offset diagram is a (digraph) representation of the actions. The direction top to bottom indicates time. This diagram is meant to indicate relative order, so this indication is for + one action relative to the other in time. Different action trees can be at different times and at the same horizontal level. +p The following diagram shows a configuration where B is configured to be 5 days before A (-5) and C 2 days after B +img src="../../assets/manual/action-offsets/non-rooted.png" title="non-rooted" +p + | Since none of these actions has any connection with the intended reference date (X), this tree is non-rooted. This green color is an indication of that.
Now lets root action A + to be 1 day before the intended reference date (X). +img src="../../assets/manual/action-offsets/rooted-critical-path.png" title="rooted-critical-path" +p + | The rooted action is indicated in blue, and the actions connected to rooted elements according to the critical path algorithm are indicated by a softer color green then the non-rooted actions. + The critical path indication here means that the configuration of action B being 5 days before A (-5) is located on action B. In other words, on the action having the longest path distance + to the X-rooted action. So the fact that action C is soft green indicates that the configuration of it being 2 days after B is located on action C. +p Another assumption for an action to belong to the critical path solution is that all actions at the other end of the connections configured in that action are also critical path actions. +p + | Configuring action B to be 2 days before C (-2) instead of C being 2 days after B will break the critical path assumption. Action C will be not connected to any other action, so being a + non-rooted tree in itself and action B will have a connection to this non-rooted tree (consisting only of 1 action in this case). +img src="../../assets/manual/action-offsets/rooted-non-critical-path.png" title="rooted-critical-path" +p The C action in this case also is a non critical path connection since it's connection with B is defined on B, so not the farthest away from the X-rooted (or specifically planned) action. diff --git a/app/templates/manual/tracker/index.emblem b/app/templates/manual/tracker/index.emblem new file mode 100644 index 0000000..0d1695a --- /dev/null +++ b/app/templates/manual/tracker/index.emblem @@ -0,0 +1,11 @@ +d-breadcrumbs as |breadcrumb| + breadcrumb.item + = link-to 'Manual' 'manual.index' + breadcrumb.item + = t 'models.plural.tracker_subject' + breadcrumb.item active=true + span Basis += page-title 'models.plural.tracker_subject' +p + | + Trackers zijn het mechanisme om project onafhankelijk analyses te kunnen instellen en inzien over alle voortgansdata. diff --git a/app/templates/modals/-display-workflow-action-definitions.emblem b/app/templates/modals/-display-workflow-action-definitions.emblem new file mode 100644 index 0000000..8055298 --- /dev/null +++ b/app/templates/modals/-display-workflow-action-definitions.emblem @@ -0,0 +1,15 @@ +if displayNumbered + ol + each workflow_action_definitions as |workflow_action_definition| + li= workflow_action_definition.title +else + each workflow_groups as |workflow_group| + if showTitles + h3= workflow_group.name + ul + each workflow_group.sorted_workflow_action_definitions as |workflow_action_definition| + li class=workflow_action_definition.done:done:active: span= workflow_action_definition.title + +.modal-actions.sticky + button.ui.basic.button.modal-close.pull-right{ action "close"}= t 'general.close' + /= input type="checkbox" checked=showAll diff --git a/app/templates/modals/-edit-tracker-group.emblem b/app/templates/modals/-edit-tracker-group.emblem new file mode 100644 index 0000000..7edbc91 --- /dev/null +++ b/app/templates/modals/-edit-tracker-group.emblem @@ -0,0 +1,7 @@ +.form-row + .form-label= t 'attributes.organization.name' + .form-field= input value=model.name enter="saveRecord" +.modal-actions.sticky + button.ui.primary.button.modal-save.pull-right{action "saveRecord"}= t 'general.save' + button.ui.negative.button.modal-destroy.pull-right{action "destroyRecord"}= t 'general.destroy.button' + button.ui.basic.button.modal-rollback{action "rollback_and_close"}= t 'general.rollback' diff --git a/app/templates/modals/-edit-tracker-subject.emblem b/app/templates/modals/-edit-tracker-subject.emblem new file mode 100644 index 0000000..335f0ec --- /dev/null +++ b/app/templates/modals/-edit-tracker-subject.emblem @@ -0,0 +1,19 @@ +.form-row + .form-label= t 'attributes.tracker_subject.name' + .form-field= input value=model.name enter="saveRecord" +.form-row + .form-label= t 'attributes.tracker_subject.edit_from_workflow_group' + .form-field= input checked=model.edit_from_workflow_group type="checkbox" +.form-row + .form-label= t 'models.plural.project' + .form-field= ui-multi-select options=model.organization.projects labelProperty="name" selected=model_projects +unless model.isNew + h3 + push-action type="new" action="newViewItem" class="right" + = t 'models.plural.tracker_group' + each model.tracker_groups as |tracker_group| + editable-attribute model=tracker_group +.modal-actions.sticky + button.ui.primary.button.modal-save.pull-right{action "saveRecord"}= t 'general.save' + button.ui.negative.button.modal-destroy.pull-right{action "destroyRecord"}= t 'general.destroy.button' + button.ui.basic.button.modal-rollback{action "rollback_and_close"}= t 'general.rollback' diff --git a/app/templates/organization/-edit-organization.emblem b/app/templates/organization/-edit-organization.emblem new file mode 100644 index 0000000..70b2586 --- /dev/null +++ b/app/templates/organization/-edit-organization.emblem @@ -0,0 +1,26 @@ +table.ui.definition.table + tr + td= t 'attributes.organization.name' + td= validated-input attribute='name' enter="saveRecord" changeset=model + unless model.isNew + tr + td= t 'attributes.organization.logo' + td + if model._content.logo + img src=model._content.logo_url.small + = file-upload name='logo' onfileadd=(route-action "setLogo" model._content) + a.ui.button id="upload-image" tabindex=0 + i.upload.icon + span Replace logo + else + = file-upload name='logo' onfileadd=(route-action "setLogo" model._content) + a.ui.button id="upload-image" tabindex=0 + i.upload.icon + span Add logo + tr + td= t 'models.plural.user' + td= ui-multi-select options=possible_users labelProperty='nickname' selected=selected_users placeholder='organization.select_users.placeholder' +.modal-actions.sticky + button.ui.primary.button.modal-save.pull-right{ action "saveRecord"}= t 'general.save' + button.ui.negative.button.modal-destroy.pull-right{ action "destroyRecord"}= t 'general.destroy.button' + button.ui.basic.button.modal-rollback{ action "rollback_and_close"}= t 'general.rollback' diff --git a/app/templates/organization/index.emblem b/app/templates/organization/index.emblem new file mode 100644 index 0000000..ba0ed53 --- /dev/null +++ b/app/templates/organization/index.emblem @@ -0,0 +1,22 @@ +d-breadcrumbs as |breadcrumb| + breadcrumb.item active=true + = t 'models.plural.organization' +.ui.container + h3.ui.top.attached.header + .ui.right.floated.icon.buttons + /= push-action type='edit' action='editOrganizationContext' + = push-action type='new' action='newOrganization' description='Add organization' + = t 'models.plural.organization' + table.ui.unstackable.bottom.attached.table + /thead + tr + th= t 'attributes.organization.name' + th.actions + tbody + each (sort-by 'name' model) as |organization| + tr + td.compact.collapsing + if organization.logo + img src=organization.logo_url.small + td= link-to organization.name 'organization.show' organization + td.actions= push-action type='edit' action='editOrganization' model=organization diff --git a/app/templates/organization/show/index.emblem b/app/templates/organization/show/index.emblem new file mode 100644 index 0000000..aad7b7f --- /dev/null +++ b/app/templates/organization/show/index.emblem @@ -0,0 +1,26 @@ +/.row: .small-12.columns: ul.breadcrumbs + li.current: a= model.name +d-breadcrumbs as |breadcrumb| + breadcrumb.item + = link-to 'organization': span= t 'models.plural.organization' + breadcrumb.item active=true + = model.name + +.ui.container + .ui.header + .ui.right.floated.icon.buttons + if is_manager + = push-action type='edit' action='editOrganization' + h2 + span.resource-descriptor= t 'models.organization' + span.resource-name= model.name + .ui.segment + if model.logo + img src=model.logo_url.large + ul.ui.list + li= link-to 'organization.show.project-actions' model + span= t 'organization.project_actions.title' + +/= line-chart data=chart_data +/= gantt-chart organization=model + diff --git a/app/templates/organization/show/project-actions.emblem b/app/templates/organization/show/project-actions.emblem new file mode 100644 index 0000000..4993a36 --- /dev/null +++ b/app/templates/organization/show/project-actions.emblem @@ -0,0 +1,9 @@ +d-breadcrumbs as |breadcrumb| + breadcrumb.item + = link-to 'organization.index': span= t 'models.plural.organization' + breadcrumb.item + span= t 'models.organization' + span : + = link-to 'organization.show' model: span= model.name + breadcrumb.item active=true + span= t 'organization.project_actions.title' diff --git a/app/templates/project/-contract.emblem b/app/templates/project/-contract.emblem new file mode 100644 index 0000000..be1e306 --- /dev/null +++ b/app/templates/project/-contract.emblem @@ -0,0 +1,16 @@ +.ui.visible.message + | A project's contract is a technical description of the project meant for communication, project updates + and project cloning. + In addition to this some other important objectives will be backup and project history logging. +if contract.isFulfilled + = format-json value=contract.content top_title='Contract' +else + .ui.icon.message + i.notched.circle.loading.icon + .content + .header Loading contract... +.modal-actions.sticky + if contract.isFulfilled + a.ui.primary.button.pull-right href=download_href download=download_file_name + span Download + button.ui.basic.button.modal-rollback{action 'close'}= t 'general.close' diff --git a/app/templates/project/-edit-instance-action.emblem b/app/templates/project/-edit-instance-action.emblem new file mode 100644 index 0000000..af93eaf --- /dev/null +++ b/app/templates/project/-edit-instance-action.emblem @@ -0,0 +1,147 @@ +table.ui.top.attached.compact.definition.table + tr + td= t 'models.workflow_action_definition' + td= model.workflow_action_definition.title + tr + td= t 'attributes.project_instance_action.state' + td= state-badge model.state + if model.target_date + tr + td= t 'attributes.project_instance_action.target_date' + td= format-date model.target_date format=globals.date_formatting + tr + td= t 'models.project_instance' + td + = model.project_instance.client_identifier + if model.project_instance.reference_date + ' + span.between-brackets= format-date model.project_instance.reference_date format=globals.date_formatting + ' + = state-badge model.project_instance.state basic=true + tr + td= t 'models.workflow_group' + td + = model.workflow_action_definition.workflow_group.name + ' + = state-badge model.project_instance_group.state basic=true + if model.notes + tr + td= t 'attributes.project_instance_action.notes' + td= markdown-to-html model.notes + if model.workflow_action_definition.extra_info + tr + td= t 'attributes.workflow_action_definition.extra_info' + td= markdown-to-html model.workflow_action_definition.extra_info + if model.overdue_at + tr + td= t 'attributes.project_instance_action.overdue_at' + td= format-date model.overdue_at format=globals.time_formatting + if model.completed_at + tr + td= t 'attributes.project_instance_action.completed_at' + td= format-date model.completed_at format=globals.time_formatting + each model.pointing_project_instance_action_connections as |pointing_connection| + if (eq pointing_connection.connection_type 'constraint') + tr.pointing-action.constraint class=pointing_connection.project_instance_action.completed:completed:uncompleted + td= pointing_connection.project_instance_action.workflow_action_definition.title + td= project/instance-action-value-display project_instance_action=pointing_connection.project_instance_action save=(action 'open_current_modal') + else + tr.pointing-action class=pointing_connection.connection_type + td= pointing_connection.project_instance_action.workflow_action_definition.title + td= project/instance-action-value-display project_instance_action=pointing_connection.project_instance_action save=(action 'open_current_modal') +if (eq value_type 'text') + .ui.bottom.attached.segment + = textarea value=text_value +/minimal space for calendar at bottom right +if misc_events.length + .ui.horizontal.divider events + table.ui.striped.table + each misc_events as |event| + tr + td= event.source + td= event.event_key + td= event.event_value + td= format-date event.event_timestamp format=globals.time_formatting + +.ui.horizontal.divider notes +table.ui.top.attached.compact.definition.table + each note_events as |note_event| + tr + td.collapsing + = note_event.source + br + = format-date note_event.event_timestamp format=globals.time_formatting + td: pre= note_event.event_value +.ui.attached.segment= textarea value=new_note_content rows=3 +.ui.bottom.attached.segment + if can_edit_action + unless (contains model.state globals.project_instance_action.final_states) + button.ui.pink.button.pull-right click='start_inquiry' disabled=new_note_content_is_blank: span= t 'action_list.start_inquiry' + button.ui.primary.button click="add_new_note" disabled=new_note_content_is_blank: span= t 'action_list.add_note' + +.modal-actions.sticky + /button.ui.primary.button.modal-save.pull-right{action "saveRecord"}= t 'general.save' + /button.ui.negative.button.modal-destroy.pull-right{action "destroyRecord"}= t 'general.destroy.button' + if can_edit_action + if has_uncompleted_constraints + .pull-right.ui.negative.message= t 'project_instance_action.has_uncompleted_constraints_message' + else + if (eq model.state 'inquiry') + button.ui.pink.button.pull-right{action 'continue_from_inquiry'} Continue… + else + if (eq value_type 'boolean') + if model.boolean_value + button.ui.yellow.button.pull-right{action 'set_value' 'boolean_value' false}= t 'project_instance_action.revoke' + else + button.ui.primary.button.pull-right{action 'set_value' 'boolean_value' true}= t 'project_instance_action.done' + else if (eq value_type 'date') + .pull-right: .ui.revoke.action.input + = ui-calendar-moment type='date' momentValue=model.date_value onChange=(action 'set_value' 'date_value') placeholder="Select date" popupOptions=(hash position='top right' maxSearchDepth=0) + if model.date_value + button.ui.yellow.button.pull-right{action 'set_value' 'date_value' null}= t 'project_instance_action.revoke' + else if (eq value_type 'timestamp') + if has_changed_timestamp_value + button.ui.primary.icon.button.pull-right{action 'set_timestamp_value'}: i.save.icon + if model.timestamp_value + button.ui.yellow.button.pull-right{action 'set_value' 'timestamp_value' null}= t 'project_instance_action.revoke' + .pull-right: .ui.revoke.action.input + = ui-calendar-moment type='datetime' ampm=false momentValue=timestamp_value onChange=(action (mut timestamp_value)) placeholder="Select timestamp" popupOptions=(hash position='top right' maxSearchDepth=0) + else if (eq value_type 'string') + .pull-right: .ui.revoke.action.input + = input value=string_value enter='set_string_value' + if (not-eq string_value model.string_value) + button.ui.primary.icon.button{action 'set_string_value'}: i.save.icon + if model.string_value + button.ui.yellow.button{action 'set_value' 'string_value' ''}= t 'project_instance_action.revoke' + else if (eq value_type 'number') + .pull-right: .ui.revoke.action.input + = number-input numericValue=number_value step=1 enter='set_number_value' + if (not-eq number_value model.number_value) + button.ui.primary.icon.button{action 'set_number_value'}: i.save.icon + if model.number_value + button.ui.yellow.button.pull-right{action 'set_value' 'number_value' ''}= t 'project_instance_action.revoke' + else if (eq value_type 'select') + if model.workflow_action_definition.value_type_select_options.length + .ui.buttons.pull-right + = ui-dropdown class='button' selected=string_value onChange=(action 'set_value' 'string_value') direction='upward' + .default.text select… + i.dropdown.icon + .upward.menu + each model.workflow_action_definition.value_type_select_options as |option| + .item= option + if model.string_value + button.ui.yellow.button{action 'set_value' 'string_value' ''}= t 'project_instance_action.revoke' + + else + .pull-right: .ui.revoke.action.input + = input value=string_value enter='set_string_value' + if model.string_value + button.ui.yellow.button{action 'set_value' 'string_value' ''}= t 'project_instance_action.revoke' + else if (eq value_type 'text') + .ui.buttons.pull-right + if (not-eq text_value model.text_value) + button.ui.primary.button{action 'set_text_value'}= t 'project_instance_action.done' + if model.text_value + button.ui.yellow.button{action 'set_value' 'text_value' ''}= t 'project_instance_action.revoke' + + button.ui.basic.button.modal-rollback{action "close"}= t 'general.close' diff --git a/app/templates/project/-edit-project-scenario.emblem b/app/templates/project/-edit-project-scenario.emblem new file mode 100644 index 0000000..847a0f2 --- /dev/null +++ b/app/templates/project/-edit-project-scenario.emblem @@ -0,0 +1,20 @@ +table.ui.definition.table + tr + td= t 'attributes.project_scenario.name' + td= input value=model.name enter='saveRecord' +button.ui.button{action 'check_all'} Check all +button.ui.button{action 'check_none'} Check none +.workflow-instance-workflow-groups-container + each model.project.sorted_workflow_groups as |workflow_group| + .workflow-instance-workflow-group-container + .workflow-instance-workflow-group-title + ui-checkbox class='toggle' label=workflow_group.name checked=(contains workflow_group.id selected_workflow_group_ids) onChange=(action 'change_workflow_group_selection' workflow_group) + .workflow-instance-workflow-group-body + each workflow_group.sorted_workflow_action_definitions as |workflow_action_definition| + .workflow-action-definition-row + ui-checkbox class='toggle' label=workflow_action_definition.title checked=(contains workflow_action_definition.id selected_workflow_action_definition_ids) onChange=(action 'change_workflow_action_definition_selection' workflow_action_definition) + +.modal-actions.sticky + button.ui.primary.button.modal-save.pull-right{action 'saveRecord'}= t 'general.save' + button.ui.negative.button.modal-destroy.pull-right{action 'destroyRecord'}= t 'general.destroy.button' + button.ui.basic.button.modal-rollback{action 'rollback_and_close'}= t 'general.rollback' diff --git a/app/templates/project/-edit-project.emblem b/app/templates/project/-edit-project.emblem new file mode 100644 index 0000000..69d9ee1 --- /dev/null +++ b/app/templates/project/-edit-project.emblem @@ -0,0 +1,99 @@ +/.form-row + .form-label= t 'attributes.project.name' + .form-field= input value=model.name enter='saveRecord' +/.form-row + .form-label= t 'attributes.project.start_date' + .form-field= pick-a-date date=model.start_date on-selected=(action (mut model.start_date)) +/.form-row + .form-label= t 'attributes.project.end_date' + .form-field= pick-a-date date=model.end_date on-selected=(action (mut model.end_date)) +/.form-row + .form-label= t 'attributes.project.instance_scope' + .form-field + x-select value=model.instance_scope action=(action (mut model.instance_scope)) as |xs| + each globals.project.instance_scope_options as |instance_scope| + xs.option value=instance_scope + = instance_scope +/.form-row + .form-label= t 'attributes.project.expected_number_of_workflow_instances' + .form-field= number-input numericValue=model.expected_number_of_workflow_instances +/.form-row + .form-label= t 'models.plural.user' + .form-field= ui-multi-select options=possible_users labelProperty='nickname' selected=project_users +/.form-row + .form-label= t 'models.workflow_instance_type' + .form-field + x-select value=model.workflow_instance_type.content on-change=(action 'selectWorkflowInstanceType') as |xs| + option -- + each possible_workflow_instance_types as |record| + xs.option value=record + = record.name +table.ui.compact.definition.table + tr + td= t 'attributes.project.name' + td= input value=model.name enter='saveRecord' + tr + td= t 'attributes.project.start_date' + td= ui-calendar-moment type='date' momentValue=(readonly model.start_date) onChange=(action (mut model.start_date)) + tr + td= t 'attributes.project.end_date' + td= ui-calendar-moment type='date' momentValue=(readonly model.end_date) onChange=(action (mut model.end_date)) + tr + td= t 'attributes.project.expected_number_of_workflow_instances' + td= number-input numericValue=model.expected_number_of_workflow_instances + /tr + td= t 'models.plural.user' + td= ui-multi-select options=possible_users labelProperty='nickname' selected=project_users + tr + td= t 'models.workflow_instance_type' + td + x-select value=model.workflow_instance_type.content on-change=(action 'selectWorkflowInstanceType') as |xs| + option -- + each possible_workflow_instance_types as |record| + xs.option value=record + = record.name + tr + td= t 'attributes.project.instance_scope' + td + x-select value=model.instance_scope action=(action (mut model.instance_scope)) as |xs| + each globals.project.instance_scope_options as |instance_scope| + xs.option value=instance_scope + = instance_scope + if model.instance_scope_batch + tr + td= t 'attributes.project.batch_size_lower_value' + td= number-input numericValue=model.batch_size_lower_value + tr + td= t 'attributes.project.batch_size_upper_value' + td= number-input numericValue=model.batch_size_upper_value + tr + td= t 'attributes.project.reference_date_day_of_week' + td + = ui-dropdown class='selection' selected=model.reference_date_day_of_week onChange=(action (mut model.reference_date_day_of_week)) + .default.text= t 'project.select_reference_date_day_of_week' + i.dropdown.icon + .menu + .item data-value='': span -- + each globals.week_days as |week_day| + .item data-value=week_day.name + = t week_day.name scope='date.week_day' + +unless model.isNew + h3.ui.top.attached.header + .ui.right.floated.icon.buttons + = push-action type='new' action='newProjectScenario' description=(t 'action.add_model' modelPath='models.project_scenario') + if model.project_scenarios.length + = push-action type='raster' action=(route-action 'show_project_scenarios_grid' model) + = t 'models.plural.project_scenario' + .ui.bottom.attached.segment + .ui.segments + each (sort-by 'name' model.project_scenarios) as |project_scenario| + project/scenario-edit-item model=project_scenario + else + .ui.segment No scenarios defined +.modal-actions.sticky + button.ui.primary.button.modal-save.pull-right{action 'saveRecord'}= t 'general.save' + button.ui.negative.button.modal-destroy.pull-right{action 'destroyRecord'}= t 'general.destroy.button' + button.ui.basic.button.modal-rollback{action 'rollback_and_close'}= t 'general.rollback' + if model.isNew + button.ui.secondary.button{action (route-action 'new_project_from_contract' model)}= t 'project.new_project_from_contract.title' diff --git a/app/templates/project/-edit-projects-context.emblem b/app/templates/project/-edit-projects-context.emblem new file mode 100644 index 0000000..2754674 --- /dev/null +++ b/app/templates/project/-edit-projects-context.emblem @@ -0,0 +1,12 @@ +.ui.container + .ui.visible.message= t 'project.modals.edit_projects_context.message' + h3.ui.top.attached.header + .ui.right.floated.icon.buttons= push-action type="new" action="newWorkflowInstanceType" + = t 'models.plural.workflow_instance_type' + .ui.bottom.attached.segment + .ui.segments + each workflow_instance_types as |workflow_instance_type| + .ui.segment + editable-attribute model=workflow_instance_type +.modal-actions.sticky + button.ui.basic.button.modal-close.pull-right{ action "close"}= t 'general.close' diff --git a/app/templates/project/-edit-workflow-group.emblem b/app/templates/project/-edit-workflow-group.emblem new file mode 100644 index 0000000..e101193 --- /dev/null +++ b/app/templates/project/-edit-workflow-group.emblem @@ -0,0 +1,121 @@ +/.form-row + .form-label= t 'attributes.workflow_group.name' + .form-field= input value=model.name enter="saveRecord" + /.small-12.medium-9.columns= input value=model.name +/.form-row + .form-label= t 'attributes.workflow_group.offset_value' + .form-field + .ui.right.labeled.action.input + = number-input numericValue=model.offset_value step=1 + /button.ui.grey.icon.button{action (mut model.offset_value)}: i.remove.icon + .ui.label days +/.form-row + .form-label= t 'attributes.workflow_group.duration_value' + .form-field + .ui.right.labeled.action.input + = number-input numericValue=model.duration_value step=1 + /button.ui.grey.icon.button{action (mut model.duration_value)}: i.remove.icon + .ui.label days +table.ui.definition.table + tr + td= t 'attributes.workflow_group.name' + td= input value=model.name enter="saveRecord" + tr + td= t 'attributes.workflow_group.offset_type' + td + .ui.buttons + .ui.button class=offset_type_none:active click="'set_offset_type' 'none'" + = t 'workflow_group.offset_type.none.label' + .or + .ui.button class=offset_type_connected_finish:active click="'set_offset_type' 'connected_finish'" + = t 'workflow_group.offset_type.connected_finish.label' + /.or + /.ui.button class=offset_type_reference_date:active click="'set_offset_type' 'reference_date'" + = t 'workflow_group.offset_type.reference_date.label' + .or + .ui.button class=offset_type_connected_start:active click="'set_offset_type' 'connected_start'" + = t 'workflow_group.offset_type.connected_start.label' + /.inline.fields + .field: .ui.radio.checkbox + ui-checkbox checked=offset_type_none onChange=(action 'set_offset_type' 'none') label=(t 'workflow_group.offset_type.none.label') + .field: .ui.radio.checkbox + ui-checkbox checked=offset_type_connected_finish onChange=(action 'set_offset_type' 'connected_finish') label=(t 'workflow_group.offset_type.connected_finish.label') + .field: .ui.radio.checkbox + ui-checkbox checked=offset_type_reference_date onChange=(action 'set_offset_type' 'reference_date') label=(t 'workflow_group.offset_type.reference_date.label') + .field: .ui.radio.checkbox + ui-checkbox checked=offset_type_connected_start onChange=(action 'set_offset_type' 'connected_start') label=(t 'workflow_group.offset_type.connected_start.label') + unless offset_type_none + tr + td + = t model.offset_type scope='workflow_group.offset_type' suffix='offset_value_label' + /= t 'attributes.workflow_group.offset_value' + td + .ui.right.labeled.action.input + = number-input numericValue=model.offset_value step=1 + /button.ui.grey.icon.button{action (mut model.offset_value)}: i.remove.icon + .ui.label= t model.duration_unit scope='date.unit.plural' + tr + td + = t model.offset_type scope='workflow_group.offset_type' suffix='offset_base_label' + /= t 'attributes.workflow_group.offset_base' + td + x-select value=model.offset_base.content on-change=(action (mut model.offset_base)) as |xs| + option X + each offset_selectable_workflow_groups as |workflow_group| + unless (eq workflow_group.id model.id) + xs.option value=workflow_group + span= t model.offset_type scope='workflow_group.offset_type' suffix='offset_base_prefix' + span= workflow_group.name + tr + td= t 'attributes.workflow_group.duration_value' + td + .ui.right.labeled.action.input + = number-input numericValue=model.duration_value step=1 + /button.ui.grey.icon.button{action (mut model.duration_value)}: i.remove.icon + .ui.label= t model.duration_unit scope='date.unit.plural' + if model.project.instance_scope_batch + tr + td= t 'attributes.workflow_group.batch_element_threshold_value' + td + .ui.right.labeled.action.input + = number-input numericValue=model.batch_element_threshold_value step=1 + if model.batch_element_threshold_value + button.ui.grey.icon.button{action (mut model.batch_element_threshold_value)}: i.remove.icon + /.ui.basic.dropdown.button + = ui-dropdown class='selection' selected=model.batch_element_threshold_unit onChange=(action 'set_batch_element_threshold_unit') + /.default.text= t model.batch_element_threshold_unit scope='date.unit.plural' bare=true + .text + i.dropdown.icon + .menu + each globals.workflow_group.batch_element_threshold_unit_options as |batch_element_threshold_unit| + .item data-value=batch_element_threshold_unit + span per + = t batch_element_threshold_unit scope='date.unit' + /.ui.label + /= t model.batch_element_threshold_unit scope='date.unit.plural' + /x-select value=model.batch_element_threshold_unit on-change=(action (mut model.batch_element_threshold_unit)) as |xs| + each globals.workflow_group.batch_element_threshold_unit_options as |batch_element_threshold_unit| + xs.option value=batch_element_threshold_unit + = t batch_element_threshold_unit scope='date.unit.plural' +unless model.isNew + each organization_tracker_subjects as |tracker_subject| + .form-row + .form-label= tracker_subject.model.name + .form-field + x-select value=tracker_subject.current_tracker_group on-change=(action (mut tracker_subject.current_tracker_group)) as |xs| + option -- + each tracker_subject.model.tracker_groups as |tracker_group| + xs.option value=tracker_group + = tracker_group.name + /h3 + = push-action type="new" action="newConstraint" class="right" + = t 'models.plural.workflow_group_constraint' + /.clearfix + /each model.constraints as |constraint| + workflow-group-edit-constraint constraint=constraint constraint_workflow_groups=constraint_workflow_groups + +.modal-actions.sticky + button.ui.primary.button.modal-save.pull-right{ action "saveRecord"}= t 'general.save' + unless model.isNew + button.ui.negative.button.modal-destroy.pull-right{ action "destroyRecord"}= t 'general.destroy.button' + button.ui.basic.button.modal-rollback{ action "rollback_and_close"}= t 'general.rollback' diff --git a/app/templates/project/-load-project-instances.emblem b/app/templates/project/-load-project-instances.emblem new file mode 100644 index 0000000..a78333b --- /dev/null +++ b/app/templates/project/-load-project-instances.emblem @@ -0,0 +1,76 @@ +if loading_message + .ui.info.icon.message + i.notched.circle.loading.icon + .content= loading_message +if finished_message + .ui.positive.icon.message + i.flag.checkered.icon + .content= finished_message +unless (or loading_message finished_message) + if model.instance_scope_batch + .ui.top.attached.segment + .ui.right.floated.icon.buttons + = file-upload name='instance-list' onfileadd=(action 'load_instance_list') + a.ui.button id='upload-image' tabindex=0 + i.file.icon + span Upload CSV + .header + h3 Batch files (example) + .ui.bottom.attached.secondary.segment + table style='width:100%' + thead + tr + td Batch identifier, + td Batch date, + td Scenario, + td Batch size + tbody + tr + td Ams-AAC, + td 2017-11-23, + td e585c580-2a48-4fcd-a852-63f92c6a9f1a, + td 700 + else + .ui.top.attached.segment + .ui.right.floated.icon.buttons + button.ui.small.icon.button + i.upload.icon + .header + h3 Instance files (example) + .ui.bottom.attached.secondary.segment + table style='width:100%' + thead + tr + td Instance identifier, + td Instance date, + td Scenario + td Global instance identifier + tbody + tr + td CFK42664, + td 2017-11-23, + td e585c580-2a48-4fcd-a852-63f92c6a9f1b + td CFK42664, + tr + td CFK42665, + td 2017-11-25, + td e585c580-2a48-4fcd-a852-63f92c6a9f1c + td CFK42665, + tr + td CDQ87810, + td , + td , + td + + if model.project_scenarios.length + .ui.top.attached.header + = t 'models.project_scenario' + span  identifiers + table.ui.definition.bottom.attached.table + tbody + each model.project_scenarios as |project_scenario| + tr + td= project_scenario.name + td= project_scenario.identifier +.modal-actions.sticky + button.ui.basic.button.modal-close.pull-right{action 'close'}= t 'general.close' diff --git a/app/templates/project/-new-project-from-contract.emblem b/app/templates/project/-new-project-from-contract.emblem new file mode 100644 index 0000000..4fd6fb3 --- /dev/null +++ b/app/templates/project/-new-project-from-contract.emblem @@ -0,0 +1,17 @@ +.form-row + .form-label= t 'attributes.project.name' + .form-field= validated-input attribute='name' changeset=changeset enter='saveRecord' +.form-row + .form-label= t 'attributes.project.contract' + .form-field + = file-upload name='contract' onfileadd=(action "set_contract") + a.ui.button id="upload-contract" tabindex=0 + i.upload.icon + span Upload contract +if contract + = format-json value=contract top_title='Contract' +.modal-actions.sticky + if contract + button.ui.primary.button.modal-save.pull-right{action 'saveRecord'}= t 'general.save' + button.ui.basic.button.modal-rollback{action 'rollback_and_close'}= t 'general.rollback' + diff --git a/app/templates/project/-position-workflow-action-definition-in-project.emblem b/app/templates/project/-position-workflow-action-definition-in-project.emblem new file mode 100644 index 0000000..ed5a015 --- /dev/null +++ b/app/templates/project/-position-workflow-action-definition-in-project.emblem @@ -0,0 +1,26 @@ +workflow-action-definition/modal-header model=model +.ui.two.column.grid + each model.project.sorted_workflow_groups as |workflow_group| + .column + .ui.top.attached.block.header: .content= workflow_group.name + .ui.bottom.attached.segment + .ui.list + unless (and (eq model.workflow_group.id workflow_group.id) (eq model.position 0)) + .item + button.ui.tiny.button{action 'position_workflow_action_definition_at_top_of' model workflow_group} + i.right.arrow.icon + span   place at the top + each workflow_group.sorted_workflow_action_definitions as |workflow_action_definition| + .item + .name= workflow_action_definition.name + .target + i.vertically.flipped.share.icon + if (eq workflow_action_definition.id model.id) + button.ui.tiny.button disabled=true self + else + button.ui.tiny.button{action 'position_workflow_action_definition_below' model workflow_action_definition} + span place at this position + +.modal-actions.sticky + button.ui.basic.button.modal-rollback{action 'close'}= t 'general.close' + button.ui.negative.button.pull-right{action 'unassign_workflow_action_definition'} Unassign diff --git a/app/templates/project/-project-scenarios-show-grid.emblem b/app/templates/project/-project-scenarios-show-grid.emblem new file mode 100644 index 0000000..2729529 --- /dev/null +++ b/app/templates/project/-project-scenarios-show-grid.emblem @@ -0,0 +1,28 @@ +with (sort-by 'name' model.project_scenarios) as |project_scenarios| + table.ui.definition.table + thead + tr + th + each project_scenarios as |project_scenario| + th: a.pointable{action 'edit_project_scenario' project_scenario}= project_scenario.name + tbody + each model.sorted_workflow_groups as |workflow_group| + tr.active + td= workflow_group.name + each project_scenarios as |project_scenario| + td + if (contains workflow_group project_scenario.workflow_groups) + i.square.icon + each workflow_group.sorted_workflow_action_definitions as |workflow_action_definition| + tr + td= workflow_action_definition.title + each project_scenarios as |project_scenario| + td + if (contains workflow_action_definition project_scenario.workflow_action_definitions) + i.circle.icon + + + +.modal-actions.sticky + button.ui.basic.button.modal-rollback{action 'close'}= t 'general.close' + diff --git a/app/templates/project/index.emblem b/app/templates/project/index.emblem new file mode 100644 index 0000000..b601f1f --- /dev/null +++ b/app/templates/project/index.emblem @@ -0,0 +1,24 @@ +d-breadcrumbs as |breadcrumb| + breadcrumb.item active=true + = t 'models.plural.project' + +.ui.container + h3.ui.top.attached.header + if (can 'manage' 'model' 'panda/project') + .ui.right.floated.icon.buttons + = push-action type='edit' action='editProjectsContext' + = push-action type='new' action='newProject' description='Add project' + = t 'models.plural.project' + table.ui.unstackable.bottom.attached.table + /thead + tr + th= t 'attributes.project.name' + th.actions + tbody + each (sort-by 'name' model) as |project| + tr + td= link-to project.name 'project.show' project + td= project.workflow_instance_type.name + /td.actions= push-action type='edit' action='editProject' model=project + +/button.ui.secondary.button{action (route-action 'new_project_from_contract')}= t 'project.modals.new_project_from_contract.title' diff --git a/app/templates/project/show/-dunlop-project-info.emblem b/app/templates/project/show/-dunlop-project-info.emblem new file mode 100644 index 0000000..f238993 --- /dev/null +++ b/app/templates/project/show/-dunlop-project-info.emblem @@ -0,0 +1,40 @@ +.ui.top.attached.two.item.stackable.tabs.menu + .item.pointable click="'set_tab' 'index'" class="{{if (eq tab 'index') 'active'}}" + a Offsets + .item.pointable click="'set_tab' 'identifiers'" class="{{if (eq tab 'identifiers') 'active'}}" + a Identifiers +if (eq tab 'index') + table.ui.bottom.attached.compact.celled.table + thead + tr + th= t 'models.workflow_group' + th= t 'models.workflow_action_definition' + th Offset + tbody + each workflow_action_definition_nodes as |workflow_action_definition_node| + tr + td= workflow_action_definition_node.record.workflow_group.name + td= workflow_action_definition_node.record.title + td= workflow_action_definition_node.absolute_value +if (eq tab 'identifiers') + table.ui.bottom.attached.compact.definition.table + thead + tr + th + th= t 'attributes.workflow_action_definition.identifier' + th= t 'attributes.workflow_action_definition.completes_group' + tbody + each model.sorted_workflow_groups as |workflow_group| + tr.active + td.collapsing= workflow_group.name + td= workflow_group.identifier + td + each workflow_group.sorted_workflow_action_definitions as |workflow_action_definition| + tr + td= workflow_action_definition.title + td= workflow_action_definition.identifier + td + if workflow_action_definition.completes_group + i.checkmark.box.icon +.modal-actions.sticky + button.ui.basic.button.modal-rollback{action 'close'}= t 'general.close' diff --git a/app/templates/project/show/add-progress.emblem b/app/templates/project/show/add-progress.emblem new file mode 100644 index 0000000..d7a7838 --- /dev/null +++ b/app/templates/project/show/add-progress.emblem @@ -0,0 +1,79 @@ +d-breadcrumbs as |breadcrumb| + breadcrumb.item + = link-to 'project.index': span= t 'models.plural.project' + breadcrumb.item + span= t 'models.project' + span : + = link-to 'project.show' model: span= model.name + breadcrumb.item active=true + span= t 'project.add_progress.title' +.ui.container + .ui.dividing.header + /.ui.right.floated.icon.buttons + .content + span.resource-descriptor= t 'models.project' + span.resource-name= model.name + span.resource-suffix= t 'project.add_progress.title' + + /if model.project_scenarios.length + .ui.top.attached.header + = t 'models.plural.project_scenario' + table.ui.definition.bottom.attached.table + tbody + each model.project_scenarios as |project_scenario| + tr + td= project_scenario.name + td= project_scenario.identifier + if model.instance_scope_batch + .ui.top.attached.segment + .ui.right.floated.icon.buttons + button.ui.small.icon.button{action 'openModal' 'project/load-project-instances' (hash model=model)} + i.upload.icon + .header + h3 Batch files (example) + .ui.bottom.attached.secondary.segment + table style='width:100%' + thead + tr + td Batch identifier, + td Batch date, + td Scenario, + td Batch size + tbody + tr + td Ams-AAC, + td 2017-11-23, + td e585c580-2a48-4fcd-a852-63f92c6a9f1a, + td 700 + else + .ui.top.attached.segment + .ui.right.floated.icon.buttons + button.ui.small.icon.button + i.upload.icon + .header + h3 Instance files (example) + .ui.bottom.attached.secondary.segment + table style='width:100%' + thead + tr + td Instance identifier, + td Instance date, + td Scenario + td Global instance identifier + tbody + tr + td CFK42664, + td 2017-11-23, + td e585c580-2a48-4fcd-a852-63f92c6a9f1b + td CFK42664, + tr + td CFK42665, + td 2017-11-25, + td e585c580-2a48-4fcd-a852-63f92c6a9f1c + td CFK42665, + tr + td CDQ87810, + td , + td , + td + diff --git a/app/templates/project/show/analysis.emblem b/app/templates/project/show/analysis.emblem new file mode 100644 index 0000000..7da6e99 --- /dev/null +++ b/app/templates/project/show/analysis.emblem @@ -0,0 +1,19 @@ +d-breadcrumbs as |breadcrumb| + breadcrumb.item + = link-to 'project.index': span= t 'models.plural.project' + breadcrumb.item + span= t 'models.project' + span : + = link-to 'project.show' model: span= model.name + breadcrumb.item active=true + span= t 'project.analysis.title' +.ui.container + .ui.header + .content + span.resource-descriptor= t 'models.project' + span.resource-name= model.name + span.resource-suffix= t 'project.analysis.title' + .ui.divider + br + br + = panda-image path="under-construction.svg" diff --git a/app/templates/project/show/dunlop-generator.emblem b/app/templates/project/show/dunlop-generator.emblem new file mode 100644 index 0000000..998b161 --- /dev/null +++ b/app/templates/project/show/dunlop-generator.emblem @@ -0,0 +1,42 @@ +d-breadcrumbs as |breadcrumb| + breadcrumb.item + = link-to 'project.index': span= t 'models.plural.project' + breadcrumb.item + span= t 'models.project' + span : + = link-to 'project.show' model: span= model.name + breadcrumb.item active=true + span= t 'project.dunlop_generator.title' +.ui.container + .ui.header + h2 + span.resource-descriptor= t 'models.project' + span.resource-name= model.name + span.resource-suffix= t 'project.dunlop_generator.title' + .ui.three.column.grid + .column + .title-box + .title-box-heading= t 'models.plural.source_file' + = ui-multi-select options=possible_source_files selection=selected_source_files + .column + .title-box + .title-box-heading Modules + .kbt-endpoint + .module-row + label + = input type='checkbox' checked=kbt_endpoint.active + span= t 'dunlop.module.kbt_endpoint' + if kbt_endpoint.active + h3 Special KBT config settings + .kbt-endpoint + .module-row + label + = input type='checkbox' checked=wba_mailbox.active + span= t 'dunlop.module.wba_mailbox' + if wba_mailbox.active + h3 Special WBA Mailbox config settings + + if activated_modules + h3.ui.header Geselecteerde modules + each activated_modules as |activated_module| + h5= dynamic-path-t 'dunlop.module' postpath=activated_module.name diff --git a/app/templates/project/show/dunlop.emblem b/app/templates/project/show/dunlop.emblem new file mode 100644 index 0000000..c4b10b9 --- /dev/null +++ b/app/templates/project/show/dunlop.emblem @@ -0,0 +1,93 @@ +d-breadcrumbs as |breadcrumb| + breadcrumb.item + = link-to 'project.index': span= t 'models.plural.project' + breadcrumb.item + span= t 'models.project' + span : + = link-to 'project.show' model: span= model.name + breadcrumb.item active=true + span= t 'project.dunlop.title' +.ui.header + .ui.right.floated.icon.buttons + = push-action type='new' action='newWorkflowStep' + = push-action type='edit' action='editProject' model=model + = push-action type='sort' action='sortWorkflowSteps' + = push-action type='contract' action='projectContract' model=model + /= push-action type='show' action='displayWorkflowActionDefinitions' + = push-action type='show' action='show_project_info' + if model.project_scenarios.length + .project-scenarios-dropdown-container + / MULTIPLE INTERSECTIONABLE SCENARIOS + /.ui.small.buttons + = ui-dropdown class='multiple button' selected=selected_project_scenarios onChange=(action 'select_project_scenario') as |execute mapper| + .default.text= t 'action.select_model' modelPath='models.project_scenario' + i.dropdown.icon + .menu + .header + i.grid.layout.icon + a.pointable{action 'show_project_scenarios_grid' model} Show activation grid + each (sort-by 'name' model.project_scenarios) as |project_scenario| + .item data-value='{{map-value mapper project_scenario}}' + = project_scenario.name + if has_multiple_selected_project_scenarios + if (eq selected_project_scenarios_set_operation 'union') + button.ui.button.logical-icon-button{action (mut selected_project_scenarios_set_operation) 'intersection'} + img.logical-icon-image.logical-disjunction src="/panda/app/assets/icons/svg/logical-disjunction.svg" + else + button.ui.button.logical-icon-button{action (mut selected_project_scenarios_set_operation) 'union'} + img.logical-icon-image.logical-disjunction src="/panda/app/assets/icons/svg/logical-conjunction.svg" + if has_one_selected_project_scenario + button.ui.edit.icon.button{action 'edit_selected_project_scenario'} + i.edit.icon + = project/edit-active-project-scenario project=project active_project_scenario=active_project_scenario + i.sidebar.icon{action 'toggle_sidebar' 'sub-sidebar'} + .content + span.resource-descriptor= t 'models.project' + span.resource-name= model.name + span.resource-suffix= t 'project.dunlop.title' +.ui.divider +if model.workflow_groups.isSettled + .ui.pushable.component.context + ui-sidebar class='menu left very wide vertical' context='.component.context' id='sub-sidebar' + .header.item Unassigned + each model.unassigned_workflow_action_definitions as |workflow_action_definition| + .item data-workflow-action-definition=workflow_action_definition.id + .ui.right.floated.small.icon.buttons + = push-action type='random' action=(action 'positionWorkflowActionDefinitionInProject' workflow_action_definition) + = workflow_action_definition.title + .pusher + .workflow-instance-workflow-groups-container + each model.sorted_workflow_groups as |workflow_group| + if (contains workflow_group active_workflow_groups) + .workflow-instance-workflow-group-container + = workflow-group-header workflow_group=workflow_group + /= draggable-dropzone dropped='positionWorkflowActionDefinition' content=workflow_group class='project-workflow_group-dropzone' + .workflow-instance-workflow-group-body + /if workflow_group.constraints.length + .workflow-group-constraints-count + span.count= workflow_group.constraints.length + span= t 'models.plural.workflow_group_constraint' + each workflow_group.sorted_workflow_action_definitions as |workflow_action_definition| + /= draggable-item content=workflow_action_definition class='workflow_action_definition-item-container' + if (contains workflow_action_definition active_workflow_action_definitions) + = workflow-action-definition-item workflow_action_definition=workflow_action_definition + /.row: .small-12.columns= input value=newWorkflowStepName enter='newWorkflowStep' placeholder='New workflow_group name' + .row= collapsible-content 'project.diagram_title' modelPath='models.plural.workflow_action_offset' + = project/workflow-action-offsets-graph project=model batch_size_simulation_range=batch_size_simulation_range batch_size_simulation_value=batch_size_simulation_value chosen_absolute_date=chosen_absolute_date reference_date_day_of_week=reference_date_day_of_week active_workflow_action_definitions=active_workflow_action_definitions active_project_scenario=active_project_scenario + .row= collapsible-content 'Workflow group relative to migration date lead times diagram' + = project/workflow-group-offset-diagram project=model batch_size_simulation_range=batch_size_simulation_range batch_size_simulation_value=batch_size_simulation_value chosen_absolute_date=chosen_absolute_date reference_date_day_of_week=reference_date_day_of_week active_workflow_groups=active_workflow_groups active_project_scenario=active_project_scenario + .row= collapsible-content 'Action execution relative to migration date offsets diagram' + = project/action-execution-offset-diagram project=model batch_size_simulation_range=batch_size_simulation_range batch_size_simulation_value=batch_size_simulation_value chosen_absolute_date=chosen_absolute_date reference_date_day_of_week=reference_date_day_of_week active_workflow_action_definitions=active_workflow_action_definitions active_project_scenario=active_project_scenario + .row= collapsible-content 'project.diagram_title' modelPath='models.plural.workflow_action_constraint' + = project/workflow-action-constraints-graph project=model active_workflow_action_definitions=active_workflow_action_definitions active_project_scenario=active_project_scenario + /.row= collapsible-content 'project.diagram_title' modelPath='models.plural.workflow_action_offset' collapsed=false + = project/workflow-action-offsets-graph-old3 project=model batch_size_simulation_range=batch_size_simulation_range batch_size_simulation_value=batch_size_simulation_value chosen_absolute_date=chosen_absolute_date reference_date_day_of_week=reference_date_day_of_week active_workflow_action_definitions=active_workflow_action_definitions + /.row= collapsible-content 'Old 2 Action execution relative to migration date offsets diagram' + = project/action-execution-offset-diagram-old2 project=model batch_size_simulation_range=batch_size_simulation_range batch_size_simulation_value=batch_size_simulation_value chosen_absolute_date=chosen_absolute_date reference_date_day_of_week=reference_date_day_of_week + /.row= collapsible-content 'Old 2 action offset diagram' + = project/workflow-action-offsets-graph-old2 project=model batch_size_simulation_range=batch_size_simulation_range batch_size_simulation_value=batch_size_simulation_value chosen_absolute_date=chosen_absolute_date reference_date_day_of_week=reference_date_day_of_week + /.row= collapsible-content 'Old 1 action offset diagram' + = project/workflow-action-offsets-graph-old1 project=model + /.row: .small-12.columns= project-workflow-groups-graph project=model +else + .row: .small-12.columns: span.fa.fa-3x.fa-spinner.fa-spin diff --git a/app/templates/project/show/index.emblem b/app/templates/project/show/index.emblem new file mode 100644 index 0000000..5b92eb5 --- /dev/null +++ b/app/templates/project/show/index.emblem @@ -0,0 +1,50 @@ +d-breadcrumbs as |breadcrumb| + breadcrumb.item + = link-to 'project.index': span= t 'models.plural.project' + breadcrumb.item active=true + span= t 'models.project' + span : + = model.name +.ui.container + .ui.header + .ui.right.floated.icon.buttons + if (can 'manage' 'model' 'panda/project') + = push-action type='edit' action='editProject' model=model + /= push-action type='sort' action='sortWorkflowSteps' + /= push-action type='show' action='displayWorkflowActionDefinitions' + h2 + span.resource-descriptor= t 'models.project' + span.resource-name= model.name + .ui.segment + if (can 'manage' 'model' 'panda/project') + h2.ui.header + i.grid.layout.icon + .content= link-to 'project.show.dunlop' model: span= t 'project.dunlop.title' + h2.ui.header + i.bar.chart.icon + .content= link-to 'project.show.analysis' model: span= t 'project.analysis.title' + /ul.ui.list + li= link-to 'project.show.dunlop' model + span= t 'project.dunlop.title' + li= link-to 'project.show.dunlop-generator' model + span= t 'project.dunlop_generator.title' + li= link-to 'project.show.add-progress' model + span= t 'project.add_progress.title' + if (can 'manage' 'model' 'panda/project-instance') + .ui.segment + .ui.three.column.stackable.grid + .column= link-to 'project.show.project-instance' model class='ui fluid button' + i.list.icon + span= t 'models.plural.project_instance' + .column= link-to 'project.show.project-instance-group' model class='ui fluid button' + i.list.icon + span= t 'models.plural.project_instance_group' + .column= link-to 'project.show.project-instance-action' model class="ui fluid button" + i.list.icon + span= t 'models.plural.project_instance_action' + /li= link-to 'project.show.tracker-subjects' model + span= t 'models.plural.tracker_subject' + ul + each tracker_subjects as |tracker_subject| + li= link-to 'project.show.tracker-subject' model.id tracker_subject.id: span= tracker_subject.name + diff --git a/app/templates/project/show/project-instance-action/index.emblem b/app/templates/project/show/project-instance-action/index.emblem new file mode 100644 index 0000000..e1c0d47 --- /dev/null +++ b/app/templates/project/show/project-instance-action/index.emblem @@ -0,0 +1,87 @@ +d-breadcrumbs as |breadcrumb| + breadcrumb.item + = link-to 'project.index': span= t 'models.plural.project' + breadcrumb.item + span= t 'models.project' + span : + = link-to 'project.show' project: span= project.name + breadcrumb.item active=true + span= t 'models.plural.project_instance_action' +.ui.right.floated.pagination.menu= page-numbers content=filtered_project_instance_actions showFL=true += page-title 'models.plural.project_instance_action' + = pagination-progress content=filtered_project_instance_actions class='between-brackets' +table.ui.stiped.table + thead + tr + th= t 'models.project_instance_group' + th= t 'models.workflow_action_definition' + th= t 'models.project_instance' + th= t 'attributes.project_instance_action.state' + / ui-dropdown class='right basic labeled icon' action='nothing' + i.dropdown.icon + span.ui.tiny.header class=filter.state_in.length:active-filter + = t 'attributes.project_instance_action.state' + .menu + each globals.project_instance_action.states as |state| + ui-checkbox class='item' label=state onChange=(action 'toggle_filter' 'state_in' state) + th= t 'attributes.project_instance_action.target_date' + th= t 'attributes.project_instance_action.value' + table-filters content=model filters=filters as |filter| + = filter.dropdown-select key='project_instance_group_workflow_group_id' options=project.sorted_workflow_groups labelPath='name' + / ui-dropdown class='right basic labeled icon' action='nothing' + i.dropdown.icon + span.ui.tiny.header class=filter.project_instance_group_workflow_group_id_in.length:active-filter + = t 'models.project_instance_group' + .menu + each project.sorted_workflow_groups as |workflow_group| + ui-checkbox class='item' label=workflow_group.name onChange=(action 'toggle_filter' 'project_instance_group_workflow_group_id_in' workflow_group.id) + th class=filters.workflow_action_definition_id_in.length:active-filter + ui-dropdown class='right basic labeled icon' action='nothing' fullTextSearch=true + /i.dropdown.icon + /== workflow_action_definitions_filter_html + if filters.workflow_action_definition_id_in.length + ui-popup html=(join '

' (map-by 'title' filter_workflow_action_definitions)) position='bottom center' + span= filters.workflow_action_definition_id_in.length + ' + if (eq filters.workflow_action_definition_id_in.length 1) (t 'models.workflow_action_definition') (t 'models.plural.workflow_action_definition') + else + i.filter.icon + .menu + .ui.icon.search.input + i.search.icon + input type='text' name='search' placeholder='Search…' + .scrolling.menu + each project.workflow_action_definitions as |workflow_action_definition| + ui-checkbox class='item' checked=(contains workflow_action_definition.id filters.workflow_action_definition_id_in) label=workflow_action_definition.title onChange=(action 'toggle_filter' 'workflow_action_definition_id_in' workflow_action_definition.id) + if filters.workflow_action_definition_id_in.length + i.remove.icon{action 'clear_filter' 'workflow_action_definition_id_in'} + = filter.input key='project_instance_client_identifier_cont' + = filter.dropdown-select key='state' options=globals.project_instance_action.states translationPath='attributes.state' + filter.date-range key='target_date' + /th class=date_filter_active:active-filter + ui-dropdown class='right basic labeled top right pointing icon' action='nothing' + = date_filter_text + .menu + .item= ui-calendar-moment type='date' momentValue=filter.date_gteq onChange=(action (mut filter.date_gteq)) allowClear=true + .divider + .item= ui-calendar-moment type='date' momentValue=filter.date_lteq onChange=(action (mut filter.date_lteq)) allowClear=true + .divider + .item + button.ui.primary.button{action 'set_filter_date_today'}= t 'date.today' + button.ui.primary.button{action 'set_filter_date_this_week'}= t 'date.this_week' + if date_filter_active + i.remove.icon{action 'clear_filter' 'date_gteq' 'date_lteq'} + th + tbody + each filtered_project_instance_actions as |project_instance_action| + tr + td + if project_instance_action.project_instance_group.id + = link-to 'project.show.project-instance-group.show' project_instance_action.project_instance.project.id project_instance_action.project_instance_group.id + span= project_instance_action.project_instance_group.workflow_group.name + td= project_instance_action.workflow_action_definition.title + td= link-to 'project.show.project-instance.show' project_instance_action.project_instance.project.id project_instance_action.project_instance.id + span= project_instance_action.project_instance.client_identifier + td= state-badge project_instance_action.state + td= format-date project_instance_action.target_date + td= project/instance-action-value-display project_instance_action=project_instance_action diff --git a/app/templates/project/show/project-instance-group/index.emblem b/app/templates/project/show/project-instance-group/index.emblem new file mode 100644 index 0000000..cc98ef5 --- /dev/null +++ b/app/templates/project/show/project-instance-group/index.emblem @@ -0,0 +1,35 @@ +d-breadcrumbs as |breadcrumb| + breadcrumb.item + = link-to 'project.index': span= t 'models.plural.project' + breadcrumb.item + span= t 'models.project' + span : + = link-to 'project.show' model: span= model.name + breadcrumb.item active=true + span= t 'models.plural.project_instance_group' +table.ui.stiped.table + thead + tr + th + = t 'models.workflow_group' + /ui-dropdown class='right basic labeled icon button' action='nothing' + i.dropdown.icon + span.ui.tiny.header Filter + .menu + .ui.icon.search.input + i.search.icon + input type='text' name='search' placeholder='Search…' + .scrolling.menu + each model.project.workflow_groups as |workflow_group| + ui-checkbox class='item' label=workflow_group.title onChange=(action 'toggle_workflow_group_filter' workflow_action_definition) + + th= t 'models.project_instance' + th= t 'attributes.project_instance_group.state' + tbody + each model.project_instance_groups as |project_instance_group| + tr + td= link-to 'project.show.project-instance-group.show' project_instance_group.project_instance.project project_instance_group + span= project_instance_group.workflow_group.name + td= link-to 'project.show.project-instance.show' project_instance_group.project_instance.project.id project_instance_group.project_instance.id + span= project_instance_group.project_instance.client_identifier + td= state-badge project_instance_group.state diff --git a/app/templates/project/show/project-instance-group/show.emblem b/app/templates/project/show/project-instance-group/show.emblem new file mode 100644 index 0000000..6d64fd3 --- /dev/null +++ b/app/templates/project/show/project-instance-group/show.emblem @@ -0,0 +1,29 @@ +d-breadcrumbs as |breadcrumb| + breadcrumb.item + = link-to 'project.index': span= t 'models.plural.project' + breadcrumb.item + span= t 'models.project' + span : + = link-to 'project.show' model.project_instance.project: span= model.project_instance.project.name + breadcrumb.item + span= t 'models.project_instance' + span : + = link-to 'project.show.project-instance.show' model.project_instance.project model.project_instance: span= model.project_instance.client_identifier + breadcrumb.item active=true + span= t 'models.project_instance_group' + span : + span= model.workflow_group.name + +.ui.top.attached.header= t 'models.plural.project_instance_action' +table.ui.bottom.attached.striped.table + thead + tr + th= t 'models.workflow_action_definition' + th= t 'attributes.project_instance_action.state' + th= t 'attributes.project_instance_action.target_date' + tbody + each (sort-by (route-action 'dateSort' 'target_date') model.project_instance_actions) as |project_instance_action| + tr + td= project_instance_action.workflow_action_definition.title + td= state-badge project_instance_action.state + td= date project_instance_action.target_date diff --git a/app/templates/project/show/project-instance/-new-project-instance.emblem b/app/templates/project/show/project-instance/-new-project-instance.emblem new file mode 100644 index 0000000..d20d567 --- /dev/null +++ b/app/templates/project/show/project-instance/-new-project-instance.emblem @@ -0,0 +1,45 @@ +table.ui.compact.definition.table + tbody + tr + td= t 'attributes.project_instance.client_identifier' + td= validated-input attribute='client_identifier' changeset=model.changeset + tr + td= t 'models.project_scenario' + td + = ui-dropdown class='selection' selected=model.changeset.project_scenario onChange=(action 'set_project_scenario') as |execute mapper| + .default.text= t 'action.select_model' modelPath='models.project_scenario' + i.dropdown.icon + .menu + .item data-value='{{map-value mapper null}}' -- + each model.project.sorted_project_scenarios as |project_scenario| + .item data-value='{{map-value mapper project_scenario}}' + = project_scenario.name + tr + td= t 'attributes.project_instance.reference_date' + td= ui-calendar-moment type='date' momentValue=(readonly model.changeset.reference_date) onChange=(action 'set_reference_date') placeholder='X' + if model.project.instance_scope_batch + tr + td= t 'attributes.project_instance.batch_size' + td= number-input numericValue=model.changeset.batch_size +if dependencies_loading + .ui.active.loader +else + .ui.bulleted.list + table.ui.table + tbody + each (sort-by 'workflow_group.position' new_project_instance_groups) as |project_instance_group| + tr.active + td: strong= project_instance_group.workflow_group.name + td.actions + each (sort-by 'workflow_action_definition.position' project_instance_group.project_instance_actions) as |project_instance_action| + tr + td= project_instance_action.workflow_action_definition.title + td.actions= date project_instance_action.date +.modal-actions.sticky + button.ui.primary.button.modal-save.pull-right{action 'saveRecord'}= t 'general.save' + button.ui.negative.button.modal-destroy.pull-right{action 'destroyRecord'}= t 'general.destroy.button' + button.ui.basic.button.modal-rollback{action 'rollback_and_close'}= t 'general.rollback' + button.ui.basic.button.modal-rollback{action 'close'}= t 'general.close' + /.ui.buttons + button.ui.secondary.button{action 'calculate_workflow_based_instances'} Calculate workflow based instances + /button.ui.secondary.icon.button: i.close.icon diff --git a/app/templates/project/show/project-instance/index.emblem b/app/templates/project/show/project-instance/index.emblem new file mode 100644 index 0000000..35cf019 --- /dev/null +++ b/app/templates/project/show/project-instance/index.emblem @@ -0,0 +1,78 @@ +d-breadcrumbs as |breadcrumb| + breadcrumb.item + = link-to 'project.index': span= t 'models.plural.project' + breadcrumb.item + span= t 'models.project' + span : + = link-to 'project.show' project: span= project.name + breadcrumb.item active=true + span= t 'models.plural.project_instance' +.ui.right.floated.pagination.menu= page-numbers content=filtered_project_instances showFL=true += page-title 'models.plural.project_instance' + = pagination-progress content=filtered_project_instances class='between-brackets' +table.ui.table + thead + tr + th= t 'attributes.project_instance.client_identifier' + th= t 'attributes.project_instance.state' + th= t 'models.project_scenario' + th= t 'attributes.project_instance.reference_date' + if project.instance_scope_batch + th= t 'attributes.project_instance.batch_size' + th.actions + = push-action type='new' action=(route-action 'new_project_instance') + button.ui.small.icon.button{action 'openModal' 'project/load-project-instances' (hash model=project)} + i.upload.icon + table-filters content=model filters=filters as |filter| + = filter.input key='client_identifier_cont' + = filter.dropdown-select key='state' options=possible_states + = filter.dropdown-select key='project_scenario_id' options=project.sorted_project_scenarios labelPath='name' + = filter.date-range key='reference_date' + if project.instance_scope_batch + th + th= pagination-progress content=filtered_project_instances + + + /tr.filters + th + th + ui-dropdown class='right basic labeled icon' action='nothing' + /i.dropdown.icon + span.ui.tiny.header class=filter.project_scenario_id_in.length:active-filter + if filter.project_scenario_id_in.length + ui-popup html=(join '

' (map-by 'name' filter_project_scenarios)) position='bottom center' + span= filter.project_scenario_id_in.length + ' + if (eq filter.project_scenario_id_in.length 1) (t 'models.project_scenario') (t 'models.plural.project_scenario') + ' + i.remove.icon{action 'clear_filter' 'project_scenario_id_in'} + else + i.filter.icon + .menu + each project.sorted_project_scenarios as |project_scenario| + ui-checkbox class='item' checked=(contains project_scenario.id filter.project_scenario_id_in) label=project_scenario.name onChange=(action 'toggle_filter' 'project_scenario_id_in' project_scenario.id) + th + ui-dropdown class='right basic labeled top right pointing icon' action='nothing' + span.ui.tiny.header class=reference_date_filter_active:active-filter + = reference_date_filter_text + i.dropdown.icon + .menu + .item= ui-calendar-moment type='date' momentValue=filter.reference_date_gteq onChange=(action (mut filter.reference_date_gteq)) allowClear=true + .divider + .item= ui-calendar-moment type='date' momentValue=filter.reference_date_lteq onChange=(action (mut filter.reference_date_lteq)) allowClear=true + if project.instance_scope_batch + th + th.actions + + tbody + each filtered_project_instances as |project_instance| + tr + td + = link-to 'project.show.project-instance.show' project.id project_instance.id + = project_instance.client_identifier + td= state-badge project_instance.state + td= project_instance.project_scenario.name + td= date project_instance.reference_date + if project.instance_scope_batch + td= project_instance.batch_size + td.actions diff --git a/app/templates/project/show/project-instance/show.emblem b/app/templates/project/show/project-instance/show.emblem new file mode 100644 index 0000000..04ad567 --- /dev/null +++ b/app/templates/project/show/project-instance/show.emblem @@ -0,0 +1,37 @@ +d-breadcrumbs as |breadcrumb| + breadcrumb.item + = link-to 'project.index': span= t 'models.plural.project' + breadcrumb.item + span= t 'models.project' + span : + = link-to 'project.show' model.project: span= model.project.name + breadcrumb.item + = link-to 'project.show.project-instance.index' model.project.id + span= t 'models.plural.project_instance' + breadcrumb.item active=true + span= t 'models.project_instance' + span : + span= model.client_identifier + span  →  + = breadcrumb_detail +table.ui.definition.table + tbody + tr + td= t 'attributes.project_instance.client_identifier' + td= model.client_identifier + tr + td= t 'attributes.project_instance.state' + td= state-badge model.state + tr + td= t 'models.project_scenario' + td= model.project_scenario.name + tr + td= t 'attributes.project_instance.reference_date' + td= date model.reference_date +.ui.five.item.stackable.tabs.menu + = link-to 'project.show.project-instance.show.index' model.project model class='item': span= t 'project_instance.show.grid' + = link-to 'project.show.project-instance.show.list' model.project model class='item': span= t 'project_instance.show.list' + = link-to 'project.show.project-instance.show.groups' model.project model class='item': span= t 'models.plural.project_instance_group' + = link-to 'project.show.project-instance.show.actions' model.project model class='item': span= t 'models.plural.project_instance_action' + = link-to 'project.show.project-instance.show.graphs' model.project model class='item': span= t 'project_instance.show.graphs' += outlet diff --git a/app/templates/project/show/project-instance/show/actions.emblem b/app/templates/project/show/project-instance/show/actions.emblem new file mode 100644 index 0000000..730c531 --- /dev/null +++ b/app/templates/project/show/project-instance/show/actions.emblem @@ -0,0 +1,19 @@ +table.ui.bottom.attached.striped.table + thead + tr + th= t 'models.project_instance_group' + th= t 'models.workflow_action_definition' + th= t 'attributes.project_instance_action.state' + th= t 'attributes.project_instance_action.target_date' + th= t 'attributes.project_instance_action.value' + tbody + each (sort-by (route-action 'dateSort' 'target_date') model.project_instance_actions) as |project_instance_action| + tr + td + if project_instance_action.project_instance_group.id + = link-to 'project.show.project-instance-group.show' model.project project_instance_action.project_instance_group.id + span= project_instance_action.project_instance_group.workflow_group.name + td= project_instance_action.workflow_action_definition.title + td= state-badge project_instance_action.state + td= format-date project_instance_action.target_date format=globals.date_formatting + td= project/instance-action-value-display project_instance_action=project_instance_action diff --git a/app/templates/project/show/project-instance/show/graphs.emblem b/app/templates/project/show/project-instance/show/graphs.emblem new file mode 100644 index 0000000..e69de29 diff --git a/app/templates/project/show/project-instance/show/groups.emblem b/app/templates/project/show/project-instance/show/groups.emblem new file mode 100644 index 0000000..94f8fa6 --- /dev/null +++ b/app/templates/project/show/project-instance/show/groups.emblem @@ -0,0 +1,12 @@ +table.ui.bottom.attached.striped.table + thead + tr + th= t 'models.project_instance_group' + th= t 'attributes.project_instance_group.state' + tbody + each (sort-by 'workflow_group.position' model.project_instance_groups) as |project_instance_group| + tr + td + = link-to 'project.show.project-instance-group.show' model.project.id project_instance_group.id + span= project_instance_group.workflow_group.name + td= state-badge project_instance_group.state diff --git a/app/templates/project/show/project-instance/show/index.emblem b/app/templates/project/show/project-instance/show/index.emblem new file mode 100644 index 0000000..7a61432 --- /dev/null +++ b/app/templates/project/show/project-instance/show/index.emblem @@ -0,0 +1,15 @@ +.workflow-instance-workflow-groups-container + each (sort-by 'workflow_group.position' model.project_instance_groups) as |project_instance_group| + .workflow-instance-workflow-group-container class=project_instance_group.state + .workflow-instance-workflow-group-title= project_instance_group.workflow_group.name + .workflow-instance-workflow-group-body + each (sort-by 'workflow_action_definition.position' project_instance_group.project_instance_actions) as |project_instance_action| + .workflow-action-container class=project_instance_action.state + .ui.grid + .ten.wide.column= project_instance_action.workflow_action_definition.title + .six.wide.column + = project/instance-action-value-display project_instance_action=project_instance_action + /if (or (eq project_instance_action.workflow_action_definition.value_type 'boolean') (eq project_instance_action.workflow_action_definition.value_type 'number')) + / = project_instance_action.value_display + /else + / = ui-popup tagName="i" class="info circle icon" html=project_instance_action.value_display diff --git a/app/templates/project/show/project-instance/show/list.emblem b/app/templates/project/show/project-instance/show/list.emblem new file mode 100644 index 0000000..ce93352 --- /dev/null +++ b/app/templates/project/show/project-instance/show/list.emblem @@ -0,0 +1,12 @@ +.project-instance-list-tab-container + each (sort-by 'workflow_group.position' model.project_instance_groups) as |project_instance_group| + .project-instance-list-workflow-group-container + .project-instance-list-workflow-group-row + .project-instance-list-workflow-group= project_instance_group.workflow_group.name + .project-instance-list-workflow-step-context + each (sort-by 'workflow_action_definition.position' project_instance_group.project_instance_actions) as |project_instance_action| + .project-instance-list-workflow-action-definition-row + .project-instance-list-workflow-action-definition= project_instance_action.workflow_action_definition.title + .project-instance-list-workflow-action-context + = date project_instance_action.date + diff --git a/app/templates/project/show/tracker-subject.emblem b/app/templates/project/show/tracker-subject.emblem new file mode 100644 index 0000000..b6332d3 --- /dev/null +++ b/app/templates/project/show/tracker-subject.emblem @@ -0,0 +1,30 @@ +d-breadcrumbs as |breadcrumb| + breadcrumb.item + = link-to 'project.index': span= t 'models.plural.project' + breadcrumb.item + span= t 'models.project' + span : + = link-to 'project.show' model.project: span= model.project.name + breadcrumb.item active=true + span= t 'models.tracker_subject' + span : + = model.tracker_subject.name +.ui.container + .ui.header + h2 + span.resource-descriptor= t 'models.project' + span.resource-name= link-to 'project.show' model.project: span= model.project.name + .ui.grid + each grouped_tracker_groups as |tracker_group_group| + .sixteen.wide.column + h5= link-to 'tracker-subject.show.tracker-group' model.tracker_subject.id tracker_group_group.model.id: span= tracker_group_group.model.name + ul + each tracker_group_group.workflow_groups as |workflow_group| + li + set-workflow-group-tracker-group workflow_group=workflow_group tracker_subject=model.tracker_subject current_tracker_group=tracker_group_group.model + + h4 Uncategorized steps + ul + each uncategorized_workflow_groups as |workflow_group| + li + set-workflow-group-tracker-group workflow_group=workflow_group tracker_subject=model.tracker_subject diff --git a/app/templates/project/show/tracker-subjects.emblem b/app/templates/project/show/tracker-subjects.emblem new file mode 100644 index 0000000..82589af --- /dev/null +++ b/app/templates/project/show/tracker-subjects.emblem @@ -0,0 +1,19 @@ +/ TODO not relevant for now +d-breadcrumbs as |breadcrumb| + breadcrumb.item + = link-to 'project.index': span= t 'models.plural.project' + breadcrumb.item + span= t 'models.project' + span : + = link-to 'project.show' model: span= model.name + breadcrumb.item active=true + = t 'models.plural.tracker_subject' +.ui.container + .ui.header + h2 + span.resource-descriptor= t 'models.project' + span.resource-name= model.name + each tracker_subjects_for_project as |tracker_subject| + h4= link-to 'project.show.tracker-subject' model.id tracker_subject.id + span= tracker_subject.name + diff --git a/app/templates/tracker-subject/index.emblem b/app/templates/tracker-subject/index.emblem new file mode 100644 index 0000000..7ac02b1 --- /dev/null +++ b/app/templates/tracker-subject/index.emblem @@ -0,0 +1,20 @@ +d-breadcrumbs as |breadcrumb| + breadcrumb.item active=true + = t 'models.plural.tracker_subject' + +.ui.container + h3.ui.top.attached.header + .ui.right.floated.icon.buttons= push-action type='new' action='newTrackerSubject' + = t 'models.plural.tracker_subject' + /.ui.bottom.attached.segment + .ui.segments + each tracker_subjects_for_user as |tracker_subject| + .ui.segment + .ui.small.right.floated.icon.buttons= push-action type='edit' action='editTrackerSubject' model=tracker_subject + = link-to 'tracker-subject.show' tracker_subject: span= tracker_subject.name + table.ui.unstackable.bottom.attached.table + tbody + each model as |tracker_subject| + tr + td= link-to tracker_subject.name 'tracker-subject.show' tracker_subject + td.actions= push-action type='edit' action='editTrackerSubject' model=(changeset tracker_subject) diff --git a/app/templates/tracker-subject/show/index.emblem b/app/templates/tracker-subject/show/index.emblem new file mode 100644 index 0000000..73e7a81 --- /dev/null +++ b/app/templates/tracker-subject/show/index.emblem @@ -0,0 +1,36 @@ +d-breadcrumbs as |breadcrumb| + breadcrumb.item + = link-to 'tracker-subject.index': span= t 'models.plural.tracker_subject' + breadcrumb.item active=true + span= t 'models.tracker_subject' + span : + = model.name + +.ui.container + .ui.header + .ui.right.floated.icon.buttons + = push-action type='new' action='newTrackerGroup' + = push-action type='edit' action='editTrackerSubject' model=model + = push-action type='sort' action='sortTrackerGroups' + h2 + span.resource-descriptor= t 'models.tracker_subject' + span.resource-name= model.name + .ui.segment + ul.ui.list + li= link-to 'tracker-subject.show.tracking-connections' model.id: span Tracking + br +if model.tracker_groups.isSettled + .tracker-subject-tracker-groups-container + each model.sorted_tracker_groups as |tracker_group| + .tracker-subject-tracker-group-container + .tracker-subject-tracker-group-title + .ui.small.right.floated.icon.buttons + = push-action type='edit' action='editTrackerGroup' model=tracker_group class='right' + = push-action type='sort' action='sortTrackerItems' model=tracker_group class='right' + = push-action type='new' action='newTrackerItem' model=tracker_group class='right' + = tracker_group.name + .tracker-subject-tracker-group-body + each tracker_group.sorted_tracker_items as |tracker_item| + editable-attribute model=tracker_item +else + .row: .small-12.columns: span.fa.fa-3x.fa-spinner.fa-spin diff --git a/app/templates/tracker-subject/show/project.emblem b/app/templates/tracker-subject/show/project.emblem new file mode 100644 index 0000000..e8f6d7d --- /dev/null +++ b/app/templates/tracker-subject/show/project.emblem @@ -0,0 +1,29 @@ +d-breadcrumbs as |breadcrumb| + breadcrumb.item + = link-to 'tracker-subject.index': span= t 'models.plural.tracker_subject' + breadcrumb.item + span= t 'models.tracker_subject' + span : + = link-to 'tracker-subject.show' model.tracker_subject + = model.tracker_subject.name + breadcrumb.item active=true + span Project links: + span= model.project.name +.ui.container + .ui.header + /.ui.right.floated.icon.buttons + h2 + span.resource-descriptor= t 'models.project' + span.resource-name= model.project.name + each grouped_tracker_groups as |tracker_group_group| + .row: .small-12.columns + h5= link-to 'tracker-subject.show.tracker-group' model.tracker_subject.id tracker_group_group.model.id: span= tracker_group_group.model.name + ul + each tracker_group_group.workflow_groups as |workflow_group| + li + set-workflow-group-tracker-group workflow_group=workflow_group tracker_subject=model.tracker_subject current_tracker_group=tracker_group_group.model + h4 Uncategorized steps + ul + each uncategorized_workflow_groups as |workflow_group| + li + set-workflow-group-tracker-group workflow_group=workflow_group tracker_subject=model.tracker_subject diff --git a/app/templates/tracker-subject/show/tracker-group.emblem b/app/templates/tracker-subject/show/tracker-group.emblem new file mode 100644 index 0000000..f9c54e7 --- /dev/null +++ b/app/templates/tracker-subject/show/tracker-group.emblem @@ -0,0 +1,23 @@ +d-breadcrumbs as |breadcrumb| + breadcrumb.item + = link-to 'tracker-subject.index': span= t 'models.plural.tracker_subject' + breadcrumb.item + span= t 'models.tracker_subject' + span : + = link-to 'tracker-subject.show' model + = model.name + breadcrumb.item active=true + span= t 'models.tracker_group' +.row + .small-12.columns + h2 + span.resource-descriptor= t 'models.tracker_group' + span.resource-name= model.name + each model.projects as |project| + h3= project.name +each grouped_projects as |project_group| + .row: .span-12.columns + h3= link-to 'tracker-subject.show.project' model.tracker_subject.id project_group.model.id: span= project_group.model.name + ul + each project_group.workflow_groups as |workflow_group| + li= workflow_group.name diff --git a/app/templates/tracker-subject/show/tracking-connections.emblem b/app/templates/tracker-subject/show/tracking-connections.emblem new file mode 100644 index 0000000..055847b --- /dev/null +++ b/app/templates/tracker-subject/show/tracking-connections.emblem @@ -0,0 +1,36 @@ +d-breadcrumbs as |breadcrumb| + breadcrumb.item + = link-to 'tracker-subject.index': span= t 'models.plural.tracker_subject' + breadcrumb.item + span= t 'models.tracker_subject' + span : + = link-to 'tracker-subject.show' model + = model.name + breadcrumb.item active=true + span Tracking connections +.ui.container + .ui.header + .ui.right.floated.icon.buttons= push-action type='edit' action='editTrackerSubject' model=model + h2 + span.resource-descriptor= t 'models.tracker_subject' + span.resource-name= model.name + + each grouped_tracker_groups as |tracker_group_group| + .row: .span-12.columns + h3= link-to 'tracker-subject.show.tracker-group' model.id tracker_group_group.model.id: span= tracker_group_group.model.name + ul + each tracker_group_group.projects as |project| + li + /= link-to 'tracker-subject.project' model.id project.model.id: span= project.model.name + /= link-to 'project/tracker-subject' project.model.id model.id: span= project.model.name + = link-to 'tracker-subject.show.project' model.id project.model.id: span= project.model.name + ul + each project.workflow_groups as |workflow_group| + li= workflow_group.name + /each tracker_group_group.workflow_groups as |workflow_group| + .row: .span-12.columns: h4= tracker_group_group.model.name + .row + .small-6.columns= workflow_group.project.name + .small-6.columns= workflow_group.name +/= svg_content +/pre.hide= graph_content diff --git a/app/templates/user/-edit-user.emblem b/app/templates/user/-edit-user.emblem new file mode 100644 index 0000000..3208c36 --- /dev/null +++ b/app/templates/user/-edit-user.emblem @@ -0,0 +1,11 @@ +table.ui.compact.definition.table + tr + td= t 'attributes.user.nickname' + td= input value=model.nickname enter='saveRecord' + tr + td= t 'models.plural.organization' + td= ui-multi-select options=possible_organizations labelProperty='name' selected=selected_organizations +.modal-actions.sticky + button.ui.primary.button.modal-save.pull-right{action 'saveRecord'}= t 'general.save' + button.ui.negative.button.modal-destroy.pull-right{action 'destroyRecord'}= t 'general.destroy.button' + button.ui.basic.button.modal-rollback{action 'rollback_and_close'}= t 'general.rollback' diff --git a/app/templates/user/-new-user.emblem b/app/templates/user/-new-user.emblem new file mode 100644 index 0000000..0758675 --- /dev/null +++ b/app/templates/user/-new-user.emblem @@ -0,0 +1,14 @@ +table.ui.compact.definition.table + tr + td= t 'attributes.user.email' + td= validated-input attribute='email' enter=(action 'saveRecord' (hash trigger='email')) changeset=model.changeset + tr + td= t 'attributes.user.nickname' + td= validated-input attribute='nickname' enter=(action 'saveRecord' (hash trigger='nickname')) changeset=model.changeset + /tr + td= t 'models.plural.organization' + td= ui-multi-select options=possible_organizations labelProperty='name' selected=selected_organizations +.modal-actions.sticky + button.ui.primary.button.modal-save.pull-right{action 'saveRecord'}= t 'general.save' + button.ui.negative.button.modal-destroy.pull-right{action 'destroyRecord'}= t 'general.destroy.button' + button.ui.basic.button.modal-rollback{action 'rollback_and_close'}= t 'general.rollback' diff --git a/app/templates/user/index.emblem b/app/templates/user/index.emblem new file mode 100644 index 0000000..a7e8c6a --- /dev/null +++ b/app/templates/user/index.emblem @@ -0,0 +1,21 @@ +d-breadcrumbs as |breadcrumb| + breadcrumb.item active=true + = t 'models.plural.user' + +.ui.container + h3.ui.top.attached.header + .ui.right.floated.icon.buttons + /= push-action type='edit' action='editUsersContext' + = push-action type='new' action='newUser' description='Add user' + = t 'models.plural.user' + table.ui.unstackable.table + thead + tr + th= t 'attributes.user.nickname' + th.actions + tbody + /each model as |user| + each (sort-by 'nickname' model) as |user| + tr + td= link-to user.nickname 'user.show' user + td.actions= push-action type='edit' action='editUser' model=user diff --git a/app/templates/user/profile.emblem b/app/templates/user/profile.emblem new file mode 100644 index 0000000..6ba4d47 --- /dev/null +++ b/app/templates/user/profile.emblem @@ -0,0 +1,17 @@ +d-breadcrumbs as |breadcrumb| + breadcrumb.item + span= model.nickname + breadcrumb.item active=true + span= t 'user.profile' +.ui.container + .ui.header + h2 + span.resource-descriptor= t 'models.user' + span.resource-name= model.nickname + table.ui.definition.table + tr + td= t 'models.nickname' + td= model.nickname + tr + td= t 'models.email' + td= model.email diff --git a/app/templates/user/show.emblem b/app/templates/user/show.emblem new file mode 100644 index 0000000..e8f207f --- /dev/null +++ b/app/templates/user/show.emblem @@ -0,0 +1,84 @@ +d-breadcrumbs as |breadcrumb| + breadcrumb.item + = link-to 'user.index': span= t 'models.plural.user' + breadcrumb.item active=true + span= t 'models.user' + span : + = model.nickname +.ui.container + .ui.header + .ui.right.floated.icon.buttons + = push-action type='edit' action='editUser' model=model + h2 + span.resource-descriptor= t 'models.user' + span.resource-name= model.nickname + table.ui.definition.table + tr + td= t 'attributes.user.nickname' + td= model.nickname + tr + td= t 'attributes.user.email' + td= model.email + + if (can 'manage' 'model' 'user') + .ui.two.column.grid + .column + .ui.top.attached.header.block + .content PANDA + .ui.right.floated.buttons + .ui.icon.button class="{{if (contains 'ban-app-panda' active_authorizations) 'active'}}" click="'set_permission' (hash type='app' target='panda') 'ban'" + i.ban.icon + .or + .ui.button class="{{if (contains 'read-app-panda' active_authorizations) 'active'}}" click="'set_permission' (hash type='app' target='panda') 'read'" + | read + .or + .ui.button class="{{if (contains 'manage-app-panda' active_authorizations) 'active'}}" click="'set_permission' (hash type='app' target='panda') 'manage'" + | manage + unless (contains 'ban-app-panda' active_authorizations) + table.ui.bottom.attached.compact.definition.table + each panda_authorizations as |authorization| + tr + td + = authorization.type + ' + = authorization.target + td + .ui.buttons + .ui.icon.button class="{{if (contains (concat 'ban-' authorization.type '-' authorization.target) active_authorizations) 'active'}}" click="'set_permission' authorization 'ban'" + i.ban.icon + each authorization.permissions as |permission| + .or + .ui.button class="{{if (contains (concat permission '-' authorization.type '-' authorization.target) active_authorizations) 'active'}}" click="'set_permission' authorization permission" + = permission + each adapters as |adapter| + .column + .ui.top.attached.header.block + = adapter.title + .ui.right.floated.buttons + .ui.icon.button class="{{if (contains (concat 'ban-adapter-' adapter.id) active_authorizations) 'active'}}" click="'set_permission' (hash type='adapter' target=adapter.id) 'ban'" + i.ban.icon + .or + .ui.button class="{{if (contains (concat 'read-adapter-' adapter.id) active_authorizations) 'active'}}" click="'set_permission' (hash type='adapter' target=adapter.id) 'read'" + | read + .or + .ui.button class="{{if (contains (concat 'manage-adapter-' adapter.id) active_authorizations) 'active'}}" click="'set_permission' (hash type='adapter' target=adapter.id) 'manage'" + | manage + unless (contains (concat 'ban-adapter-' adapter.id) active_authorizations) + table.ui.bottom.attached.compact.definition.table + each adapter.authorizations as |authorization| + tr + td + = authorization.type + ' + = authorization.target + td + .ui.buttons + .ui.icon.button class="{{if (contains (concat 'ban-' authorization.type '-' authorization.target) active_authorizations) 'active'}}" click="'set_permission' authorization 'ban'" + i.ban.icon + each authorization.permissions as |permission| + .or + .ui.button class="{{if (contains (concat permission '-' authorization.type '-' authorization.target) active_authorizations) 'active'}}" click="'set_permission' authorization permission" + = permission + /= concat permission '-' authorization.type '-' authorization.target + .ui.segment + button.ui.button{action 'save_permissions'} Save permissions diff --git a/app/templates/workflow-action-definition/-authorize-users.emblem b/app/templates/workflow-action-definition/-authorize-users.emblem new file mode 100644 index 0000000..6467324 --- /dev/null +++ b/app/templates/workflow-action-definition/-authorize-users.emblem @@ -0,0 +1 @@ +workflow-action-definition/modal-header model=model diff --git a/app/templates/workflow-action-definition/-edit-workflow-action-definition.emblem b/app/templates/workflow-action-definition/-edit-workflow-action-definition.emblem new file mode 100644 index 0000000..6ef811a --- /dev/null +++ b/app/templates/workflow-action-definition/-edit-workflow-action-definition.emblem @@ -0,0 +1,244 @@ +workflow-action-definition/modal-header model=model +table.ui.compact.definition.table + tr + td= t 'attributes.identifier' + td= model.changeset.identifier + tr + td= t 'attributes.workflow_action_definition.title' + td= validated-input attribute='title' changeset=model.changeset enter=(action 'saveRecord' (hash trigger='title-action')) + tr + td= t 'attributes.workflow_action_definition.value_type' + td + = ui-dropdown class='selection' selected=model.changeset.value_type onChange=(action (mut model.changeset.value_type)) as |execute mapper| + .default.text= t 'action.select_model' modelPath='attributes.workflow_action_definition.value_type' + i.dropdown.icon + .menu + each value_type_options as |value_type| + .item data-value=value_type + = value-type-display type=value_type + = t value_type scope='workflow_action_definition.value_type' + if (eq model.changeset.value_type 'select') + tr + td= t 'attributes.workflow_action_definition.value_type_select_options' + td + = ui-dropdown class='multiple fluid search selection' selected=model.changeset.value_type_select_options onChange=(action (mut model.changeset.value_type_select_options)) allowAdditions=true + .default.text options + i.dropdown.icon + .menu + each model.changeset.value_type_select_options as |option| + .item= option + tr + td= t 'attributes.workflow_action_definition.extra_info' + td= textarea value=model.extra_info + tr + td= t 'models.organization' + td + = ui-dropdown class='search selection' selected=model.changeset.organization onChange=(action 'selectOrganization') fullTextSearch=true as |execute mapper| + .default.text= t 'action.select_model' modelPath='models.organization' + i.dropdown.icon + .menu + .item data-value='{{map-value mapper null}}' -- + each possible_organizations as |organization| + .item data-value='{{map-value mapper organization}}' + if organization.logo + img src=organization.logo_url.small + = organization.name + tr + td= t 'attributes.workflow_action_definition.completes_group' + td= ui-checkbox checked=model.changeset.completes_group onChange=(action (mut model.changeset.completes_group)) + tr + td= t 'attributes.workflow_action_definition.confirms_workflow_action_definition' + td= model.confirms_workflow_action_definition.title + tr + td= t 'attributes.workflow_action_definition.confirmed_by_workflow_action_definition' + td= model.confirmed_by_workflow_action_definition.title + if model.project_scenarios.length + tr + td= t 'models.plural.project_scenario' + td= join ', ' (map-by 'name' model.project_scenarios) + +.ui.dividing.header + .ui.right.floated.buttons + .ui.button class=offset_type_none:active click="set_offset_type_none" + span= t 'workflow_action_definition.edit_workflow_action_definition.no_offset' + .or + .ui.button class=offset_type_attached:active click='set_offset_type_attached' + = t 'workflow_action_definition.edit_workflow_action_definition.attached_offset' + .or + .ui.button class=offset_type_configured:active click='set_offset_type_configured' + = t 'workflow_action_definition.edit_workflow_action_definition.configured_offset' + .content + if offset_type_configured + = t 'models.plural.workflow_action_offset' + span   + = push-action type='new' action='add_workflow_action_offset' + else + = t 'models.workflow_action_offset' +if offset_type_attached + span create connection to other action + table.ui.compact.definition.table + tr + td= t 'models.workflow_action_definition' + td + = ui-dropdown class='search selection' selected=attached_to_workflow_action_workflow_group onChange=(action 'set_attached_to_workflow_action_workflow_group') fullTextSearch=true as |execute mapper| + .default.text= t 'action.select_model' modelPath='models.workflow_group' + i.dropdown.icon + .menu + each model.project.sorted_workflow_groups as |workflow_group| + .item data-value='{{map-value mapper workflow_group}}' + = workflow_group.name + = ui-dropdown class='search selection' selected=attached_to_workflow_action onChange=(action 'set_attached_to_workflow_action') fullTextSearch=true as |execute mapper| + .default.text= t 'action.select_model' modelPath='models.workflow_action_definition' + i.dropdown.icon + .menu + .item data-value='{{map-value mapper null}}' -- + each possible_attached_to_workflow_action_workflow_action_definitions as |workflow_action_definition| + .item data-value='{{map-value mapper workflow_action_definition}}' + = workflow_action_definition.title + if attached_to_connection_warning + .ui.negative.message= attached_to_connection_warning + tr + td= t 'attributes.workflow_action_connection.conditional_base_value' + td + if (eq attached_to_workflow_action.value_type 'boolean') + = ui-dropdown selected=attached_to_connection_conditional_base_value onChange=(action (mut attached_to_connection_conditional_base_value)) + span.default.text -- + i.dropdown.icon + .menu + .item data-value='' + i.square.outline.icon + .item data-value='true' + i.checkmark.box.icon + else if (eq attached_to_workflow_action.value_type 'select') + = ui-dropdown selected=attached_to_connection_conditional_base_value onChange=(action (mut attached_to_connection_conditional_base_value)) + span.default.text - + i.dropdown.icon + .menu + .item data-value='' + span - + each attached_to_workflow_action.value_type_select_options as |option| + .item= option + else + = input value=attached_to_connection_conditional_base_value + +if offset_type_configured + if new_workflow_action_offset_warning + .ui.visible.negative.message= new_workflow_action_offset_warning + each non_deleted_working_workflow_action_offsets as |offset| + table.ui.compact.definition.table + tr + td= t 'attributes.workflow_action_offset.base_workflow_action_definition' + td + unless offset.x_based + = push-action type='destroy' action=(action 'destroyWorkflowActionOffset' offset) class='pull-right' + = ui-dropdown class='search selection' selected=offset.selector_workflow_group onChange=(action 'set_selector_workflow_group' offset) fullTextSearch=true as |execute mapper| + .default.text= t 'action.select_model' modelPath='models.workflow_group' + i.dropdown.icon + .menu + each model.project.sorted_workflow_groups as |workflow_group| + .item data-value='{{map-value mapper workflow_group}}' + = workflow_group.name + = ui-dropdown class='search selection' selected=offset.base_workflow_action_definition onChange=(action 'set_base_workflow_action_definition' offset) fullTextSearch=true as |execute mapper| + .default.text= t 'action.select_model' modelPath='models.workflow_action_definition' + i.dropdown.icon + .menu + .item data-value='{{map-value mapper null}}' -- + each offset.filtered_workflow_action_definitions as |workflow_action_definition| + .item data-value='{{map-value mapper workflow_action_definition}}' + = workflow_action_definition.title + ui-checkbox class='toggle' label='X-based' checked=offset.x_based onChange=(action 'toggle_offset_x_based' offset) + if offset.base_workflow_action_definition_warning + .ui.negative.message= offset.base_workflow_action_definition_warning + tr + td= t 'attributes.workflow_action_definition.offset_value' + td= select-duration value=offset.offset_value unit=offset.offset_unit week_days=offset.offset_business_days + if model.project.instance_scope_batch + tr + td= t 'attributes.workflow_action_definition.batch_element_threshold' + td= select-duration value=offset.batch_element_threshold_value unit=offset.batch_element_threshold_unit week_days=offset.batch_element_threshold_business_days threshold=true + if model.project.project_scenarios.length + tr + td= t 'models.project_scenario' + td + = ui-dropdown class='search selection' selected=offset.project_scenario onChange=(action (mut offset.project_scenario)) as |execute mapper| + .default.text= t 'action.select_model' modelPath='models.project_scenario' + i.dropdown.icon + .menu + .item data-value='{{map-value mapper null}}' -- + each model.project.project_scenarios as |project_scenario| + .item data-value='{{map-value mapper project_scenario}}' + = project_scenario.name + else + .ui.visible.warning.message= t 'workflow_action_definition.edit_workflow_action_definition.no_workflow_action_offsets_present' + +unless offset_type_none + .ui.divider +h3.ui.dividing.header: .content + = t 'workflow_action_definition.pointing_connections_title' + ' + = push-action type='new' action='add_workflow_action_connection' +each non_deleted_working_pointing_workflow_action_connections as |pointing_connection| + unless (eq pointing_connection.connection_type 'attached') + .ui.segment + = pointing_connection.workflow_action_definition.title + = push-action type='destroy' action=(action 'destroyWorkflowActionConnection' pointing_connection) class='pull-right' + = ui-dropdown class='search selection' selected=pointing_connection.selector_workflow_group onChange=(action 'set_connection_selector_workflow_group' pointing_connection) fullTextSearch=true as |execute mapper| + .default.text= t 'action.select_model' modelPath='models.workflow_group' + i.dropdown.icon + .menu + each model.project.sorted_workflow_groups as |workflow_group| + .item data-value='{{map-value mapper workflow_group}}' + = workflow_group.name + = ui-dropdown class='search selection' selected=pointing_connection.workflow_action_definition onChange=(action 'set_connection_workflow_action_definition' pointing_connection) fullTextSearch=true as |execute mapper| + .default.text= t 'action.select_model' modelPath='models.workflow_action_definition' + i.dropdown.icon + .menu + .item data-value='{{map-value mapper null}}' -- + each pointing_connection.filtered_workflow_action_definitions as |workflow_action_definition| + .item data-value='{{map-value mapper workflow_action_definition}}' + = workflow_action_definition.title + ' + = ui-dropdown selected=pointing_connection.connection_type onChange=(action (mut pointing_connection.connection_type)) + .default.text connection type + i.dropdown.icon + .menu + each connection_types as |connection_type| + .item data-value=connection_type + = t connection_type scope='workflow_action_definition.connection_type' + if pointing_connection.workflow_action_definition_warning + .ui.negative.message= pointing_connection.workflow_action_definition_warning +/ CONSTRAINTS +/.ui.dividing.header: .content= t 'models.plural.workflow_action_constraint' +/if new_workflow_action_constraint_warning + .ui.visible.warning.message= new_workflow_action_constraint_warning +/.ui.grid + .three.wide.column + x-select value=selectedConstraintType on-change=(action (mut selectedConstraintType)) as |xs| + each constraint_types as |constraint_type| + xs.option value=constraint_type + = constraint_type + .six.wide.column + x-select value=lastSelectedWorkflowGroup on-change=(action 'workflow_group_change') as |xs| + each model.project.sorted_workflow_groups as |workflow_group| + xs.option value=workflow_group + = workflow_group.name + .seven.wide.column + x-select value=lastSelectedWorkflowActionDefinition on-change=(action 'newWorkflowActionConstraint') as |xs| + option -- + each lastSelectedWorkflowGroup.sorted_workflow_action_definitions as |workflow_action_definition| + unless (eq workflow_action_definition model) + xs.option value=workflow_action_definition + = workflow_action_definition.title +/.ui.grid + each (reject-by 'isDeleted' working_workflow_action_constraints) as |constraint| + .five.wide.column= constraint.constraint_workflow_action_definition.workflow_group.name + .three.wide.column: .ui.right.pointing.label class=constraint.restrict:red:grey + = constraint.type + .six.wide.column= constraint.constraint_workflow_action_definition.title + .two.wide.column= push-action type='destroy' action=(action 'destroyWorkflowActionConstraint' constraint) + +.modal-actions.sticky + button.ui.basic.button.modal-rollback{ action 'rollback_and_close'}= t 'general.rollback' + push-action type='position' action='positionInProject' + button.ui.primary.button.modal-save.pull-right{action 'saveRecord'}= t 'general.save' + button.ui.negative.button.modal-destroy.pull-right{ action 'destroyRecord'}= t 'general.destroy.button' diff --git a/bower.json b/bower.json new file mode 100644 index 0000000..5e425e0 --- /dev/null +++ b/bower.json @@ -0,0 +1,7 @@ +{ + "name": "ember-panda", + "dependencies": { + "semantic-ui": "^2.2.13", + "semantic-ui-calendar": "^0.0.8" + } +} diff --git a/config/ember-try.js b/config/ember-try.js new file mode 100644 index 0000000..21f6079 --- /dev/null +++ b/config/ember-try.js @@ -0,0 +1,83 @@ +/* eslint-env node */ +module.exports = { + scenarios: [ + { + name: 'ember-lts-2.8', + bower: { + dependencies: { + 'ember': 'components/ember#lts-2-8' + }, + resolutions: { + 'ember': 'lts-2-8' + } + }, + npm: { + devDependencies: { + 'ember-source': null + } + } + }, + { + name: 'ember-lts-2.12', + npm: { + devDependencies: { + 'ember-source': '~2.12.0' + } + } + }, + { + name: 'ember-release', + bower: { + dependencies: { + 'ember': 'components/ember#release' + }, + resolutions: { + 'ember': 'release' + } + }, + npm: { + devDependencies: { + 'ember-source': null + } + } + }, + { + name: 'ember-beta', + bower: { + dependencies: { + 'ember': 'components/ember#beta' + }, + resolutions: { + 'ember': 'beta' + } + }, + npm: { + devDependencies: { + 'ember-source': null + } + } + }, + { + name: 'ember-canary', + bower: { + dependencies: { + 'ember': 'components/ember#canary' + }, + resolutions: { + 'ember': 'canary' + } + }, + npm: { + devDependencies: { + 'ember-source': null + } + } + }, + { + name: 'ember-default', + npm: { + devDependencies: {} + } + } + ] +}; diff --git a/config/environment.js b/config/environment.js new file mode 100644 index 0000000..012a412 --- /dev/null +++ b/config/environment.js @@ -0,0 +1,6 @@ +/* eslint-env node */ +'use strict'; + +module.exports = function(/* environment, appConfig */) { + return { }; +}; diff --git a/ember-cli-build.js b/ember-cli-build.js new file mode 100644 index 0000000..156e23f --- /dev/null +++ b/ember-cli-build.js @@ -0,0 +1,19 @@ +/* eslint-env node */ +'use strict'; + +const EmberAddon = require('ember-cli/lib/broccoli/ember-addon'); + +module.exports = function(defaults) { + let app = new EmberAddon(defaults, { + // Add options here + }); + + /* + This build file specifies the options for the dummy test app of this + addon, located in `/tests/dummy` + This build file does *not* influence how the addon or the app using it + behave. You most likely want to be modifying `./index.js` or app's build file + */ + + return app.toTree(); +}; diff --git a/esa-coffee-workaround.sh b/esa-coffee-workaround.sh new file mode 100644 index 0000000..0a23b26 --- /dev/null +++ b/esa-coffee-workaround.sh @@ -0,0 +1,6 @@ +# ember-simple-auth's routes/application.js conflicts with the routes/application.js +# that results from compilation of our routes/application.coffee. +# ESA's application route doesn't seem to be necessary. +# https://github.com/simplabs/ember-simple-auth/issues/851 +echo "deleting ember-simple-auth's application route to workaround incompatibility with coffeescript..." +rm -f node_modules/ember-simple-auth/app/routes/application.js diff --git a/index.js b/index.js new file mode 100644 index 0000000..509588f --- /dev/null +++ b/index.js @@ -0,0 +1,6 @@ +/* eslint-env node */ +'use strict'; + +module.exports = { + name: 'ember-panda' +}; diff --git a/package.json b/package.json new file mode 100644 index 0000000..52fe6d5 --- /dev/null +++ b/package.json @@ -0,0 +1,81 @@ +{ + "name": "ember-panda", + "version": "0.0.0", + "description": "The default blueprint for ember-cli addons.", + "keywords": [ + "ember-addon" + ], + "license": "MIT", + "author": "", + "directories": { + "doc": "doc", + "test": "tests" + }, + "repository": "", + "scripts": { + "build": "ember build", + "start": "ember server", + "test": "ember try:each", + "postinstall": "sh esa-coffee-workaround.sh" + }, + "dependencies": { + "ember-cli-babel": "^6.3.0", + "ember-cli-dunlop": "^0.0.0", + "ember-cli-htmlbars": "^2.0.3", + "ember-cli-sass": "^7.0.0" + }, + "devDependencies": { + "active-model-adapter": "^2.1.1", + "broccoli-asset-rev": "^2.4.5", + "ember-ajax": "^3.0.0", + "ember-browserify": "^1.2.0", + "ember-changeset-validations": "^1.2.8", + "ember-cli": "^2.16.2", + "ember-cli-coffeescript": "^1.16.1", + "ember-cli-d3": "^1.1.7", + "ember-cli-dependency-checker": "^2.0.0", + "ember-cli-emblem": "^0.9.0", + "ember-cli-eslint": "^4.2.1", + "ember-cli-htmlbars-inline-precompile": "^1.0.0", + "ember-cli-inject-live-reload": "^1.4.1", + "ember-cli-moment-shim": "^3.4.0", + "ember-cli-nouislider": "^0.13.0", + "ember-cli-pagination": "^3.1.0", + "ember-cli-qunit": "^4.0.0", + "ember-cli-shims": "^1.1.0", + "ember-cli-showdown": "^4.1.0", + "ember-cli-sri": "^2.1.0", + "ember-cli-tooltipster": "^0.8.2", + "ember-cli-uglify": "^1.2.0", + "ember-composable-helpers": "^2.0.3", + "ember-concurrency": "^0.8.10", + "ember-cpm": "^3.0.0", + "ember-data": "~2.16.3", + "ember-export-application-global": "^2.0.0", + "ember-file-upload": "^2.1.1", + "ember-get-config": "^0.2.2", + "ember-group-by": "0.0.3", + "ember-load-initializers": "^1.0.0", + "ember-math-helpers": "^2.2.3", + "ember-one-way-controls": "^2.0.1", + "ember-resolver": "^4.0.0", + "ember-route-action-helper": "^2.0.6", + "ember-semantic-ui-calendar": "^0.2.0", + "ember-simple-auth": "^1.4.0", + "ember-sortable": "^1.9.4", + "ember-source": "~2.16.2", + "ember-truth-helpers": "^1.3.0", + "emberx-select": "^3.1.0", + "json-formatter-js": "^2.2.0", + "loader.js": "^4.2.3", + "semantic-ui-ember": "^2.0.1", + "tooltipster": "^4.2.5", + "viz.js": "^1.8.0" + }, + "engines": { + "node": "^4.5 || 6.* || >= 7.*" + }, + "ember-addon": { + "configPath": "tests/dummy/config" + } +} diff --git a/public/assets/icons/svg/logical-conjunction.svg b/public/assets/icons/svg/logical-conjunction.svg new file mode 100644 index 0000000..8c69f92 --- /dev/null +++ b/public/assets/icons/svg/logical-conjunction.svg @@ -0,0 +1,71 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + diff --git a/public/assets/icons/svg/logical-conjunction_backup.svg b/public/assets/icons/svg/logical-conjunction_backup.svg new file mode 100644 index 0000000..e50fd3e --- /dev/null +++ b/public/assets/icons/svg/logical-conjunction_backup.svg @@ -0,0 +1,87 @@ + + + + + + + + + image/svg+xml + + + + + + + + + + + diff --git a/public/assets/icons/svg/logical-disjunction.svg b/public/assets/icons/svg/logical-disjunction.svg new file mode 100644 index 0000000..433e0f9 --- /dev/null +++ b/public/assets/icons/svg/logical-disjunction.svg @@ -0,0 +1,66 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/public/assets/icons/svg/logical-disjunction_backup.svg b/public/assets/icons/svg/logical-disjunction_backup.svg new file mode 100644 index 0000000..73cea24 --- /dev/null +++ b/public/assets/icons/svg/logical-disjunction_backup.svg @@ -0,0 +1,87 @@ + + + + + + + + + image/svg+xml + + + + + + + + + + + diff --git a/public/assets/icons/svg/logical-drawing-board.svg b/public/assets/icons/svg/logical-drawing-board.svg new file mode 100644 index 0000000..28fe65f --- /dev/null +++ b/public/assets/icons/svg/logical-drawing-board.svg @@ -0,0 +1,83 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + diff --git a/public/assets/under-construction.svg b/public/assets/under-construction.svg new file mode 100644 index 0000000..c472e10 --- /dev/null +++ b/public/assets/under-construction.svg @@ -0,0 +1,83 @@ + + + + + + + + + + + + + image/svg+xml + + + + + Openclipart + + + + + + + + + + + + diff --git a/testem.js b/testem.js new file mode 100644 index 0000000..a40a530 --- /dev/null +++ b/testem.js @@ -0,0 +1,19 @@ +/* eslint-env node */ +module.exports = { + test_page: 'tests/index.html?hidepassed', + disable_watching: true, + launch_in_ci: [ + 'Chrome' + ], + launch_in_dev: [ + 'Chrome' + ], + browser_args: { + Chrome: [ + '--disable-gpu', + '--headless', + '--remote-debugging-port=9222', + '--window-size=1440,900' + ] + } +}; diff --git a/tests/.eslintrc.js b/tests/.eslintrc.js new file mode 100644 index 0000000..fbf2555 --- /dev/null +++ b/tests/.eslintrc.js @@ -0,0 +1,5 @@ +module.exports = { + env: { + embertest: true + } +}; diff --git a/tests/dummy/app/adapters/application.coffee b/tests/dummy/app/adapters/application.coffee new file mode 100644 index 0000000..9114c07 --- /dev/null +++ b/tests/dummy/app/adapters/application.coffee @@ -0,0 +1,9 @@ +import DunlopAdapter from 'ember-cli-dunlop/adapters/application' + +# Override the default adapter with the `DS.ActiveModelAdapter` which +Ember.$.ajaxPrefilter (options, oriOpt, jqXHR)-> + jqXHR.setRequestHeader 'client-id', window.client_id + +export default DunlopAdapter.extend + pathForType: (modelName) -> + @_super modelName.replace(/^panda[\/\.]/, '') diff --git a/tests/dummy/app/app.js b/tests/dummy/app/app.js new file mode 100644 index 0000000..7e6263d --- /dev/null +++ b/tests/dummy/app/app.js @@ -0,0 +1,17 @@ +import Ember from 'ember'; +import Resolver from './resolver'; +import loadInitializers from 'ember-load-initializers'; +import config from './config/environment'; +import './i18n/translations'; +import 'ember-panda/utils/moment-business-days'; + +const App = Ember.Application.extend({ + modulePrefix: config.modulePrefix, + podModulePrefix: config.podModulePrefix, + ready: function(){window.store = this.__container__.lookup('service:store')}, /* what we can do with testing */ + Resolver +}); + +loadInitializers(App, config.modulePrefix); + +export default App; diff --git a/tests/dummy/app/components/.gitkeep b/tests/dummy/app/components/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/tests/dummy/app/controllers/.gitkeep b/tests/dummy/app/controllers/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/tests/dummy/app/helpers/.gitkeep b/tests/dummy/app/helpers/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/tests/dummy/app/i18n/translations.coffee b/tests/dummy/app/i18n/translations.coffee new file mode 100644 index 0000000..0815492 --- /dev/null +++ b/tests/dummy/app/i18n/translations.coffee @@ -0,0 +1,95 @@ +window.$translations = + en: + application_name: 'Panda' + total: 'Total' + user: + sign_out: 'Uitloggen' + dunlop: + module: + wba_mailbox: 'WBA Mailbox' + kbt_endpoint: 'KBT endpoint' + general: + save: 'Save' + rollback: 'Cancel' + destroy: + are_you_sure: 'Weet je dit zeker?' + button: 'Delete' + close: 'Close' + confirm: + cancel: 'Cancel' + confirm: 'Yes' + modals: + edit_project: + title: "Edit ${models.project} %{name}" + edit_organization: + title: "Edit ${models.organization} %{name}" + edit_tracker_subject: + title: "Edit ${models.tracker_subject} %{name}" + edit_tracker_group: + title: "Edit ${models.tracker_group} %{name}" + + confirm: 'Ja' + action: + new_model: "New %{model}" + select_model: 'Select %{model}' + add_model: 'Add %{model}' + + date: + today: 'Today' + this_week: 'This week' + this_month: 'This month' + unit: + second: 'second' + minute: 'minute' + businessday: 'business day' + day: 'day' + week: 'week' + year: 'year' + plural: + second: 'seconds' + minute: 'minutes' + businessday: 'business days' + day: 'days' + week: 'weeks' + year: 'years' + week_day: + monday: 'Maandag' + tuesday: 'Dinsdag' + wednesday: 'Woensdag' + thursday: 'Donderdag' + friday: 'Vrijdag' + saturday: 'Zaterdag' + sunday: 'Zondag' + +import model_translations from './translations/models' +Ember.assign window.$translations.en, model_translations + +import attribute_translations from './translations/attributes' +Ember.assign window.$translations.en, attribute_translations + +import manual_translations from './translations/manual' +Ember.assign window.$translations.en, manual_translations + +import organization_translations from './translations/organization' +Ember.assign window.$translations.en, organization_translations + +import project_translations from './translations/project' +Ember.assign window.$translations.en, project_translations + +import project_instance_translations from './translations/project-instance' +Ember.assign window.$translations.en, project_instance_translations + +import project_instance_action_translations from './translations/project-instance-action' +Ember.assign window.$translations.en, project_instance_action_translations + +import user_translations from './translations/user' +Ember.assign window.$translations.en, user_translations + +import action_list_translations from './translations/action-list' +Ember.assign window.$translations.en, action_list_translations + +import workflow_action_definition_translations from './translations/workflow-action-definition' +Ember.assign window.$translations.en, workflow_action_definition_translations + +import workflow_group_translations from './translations/workflow-group' +Ember.assign window.$translations.en, workflow_group_translations diff --git a/tests/dummy/app/i18n/translations/action-list.coffee b/tests/dummy/app/i18n/translations/action-list.coffee new file mode 100644 index 0000000..65b8015 --- /dev/null +++ b/tests/dummy/app/i18n/translations/action-list.coffee @@ -0,0 +1,5 @@ +translations = action_list: + title: 'Action list' + start_inquiry: 'Start ${attributes.state.inquiry}' + add_note: 'Add note' +export default translations diff --git a/tests/dummy/app/i18n/translations/attributes.coffee b/tests/dummy/app/i18n/translations/attributes.coffee new file mode 100644 index 0000000..a69e6a6 --- /dev/null +++ b/tests/dummy/app/i18n/translations/attributes.coffee @@ -0,0 +1,74 @@ +translations = attributes: + identifier: 'Identity' + state: + pending: 'pending' + uncategorized: 'uncategorized' + unplanned: 'unplanned' + planned: 'planned' + overdue: 'overdue' + rejected: 'rejected' + completed: 'completed' + user: + email: 'E-mail' + nickname: 'Nickname' + organization: + name: 'Name' + logo: 'Logo' + project: + name: 'Name' + start_date: 'Start date' + end_date: 'End date' + percent_done: 'Progress' + expected_number_of_workflow_instances: "# instances" + batch_size_lower_value: 'Batch size lower' + batch_size_upper_value: 'Batch size upper' + reference_date_day_of_week: 'Migratie datum dag in de week' + reference_date: 'Migratie datum' + project_instance: + reference_date: 'Date (X)' + state: 'State' + client_identifier: 'Instance ID' + batch_size: '# Batch' + project_instance_group: + state: 'State' + project_instance_action: + state: 'State' + target_date: 'Action Date' + value: 'Value' + notes: 'Info' + completed_at: 'Completed' + overdue_at: 'Overdue' + inquiry_at: 'Inquiry' + project_scenario: + name: 'Name' + tracker_group: + name: 'Name' + tracker_subject: + name: 'Name' + edit_from_workflow_group: "Zet in ${models.workflow_group}?" + workflow_action_definition: + identifier: '${attributes.identifier}' + title: 'Label' + value_type: 'Type' + offset_value: 'Offset' + extra_info: 'Description' + #action_offset_base: 'Offset base' + batch_element_threshold: 'Batch element threshold' + completes_group: 'Completes group?' + confirms_workflow_action_definition: 'Confirms ${models.workflow_action_definition}' + confirmed_by_workflow_action_definition: 'Confirmed by ${models.workflow_action_definition}' + value_type_select_options: 'Select options' + workflow_group: + identifier: '${attributes.identifier}' + name: 'Name' + offset_value: 'Offset' + offset_base: 'Offset base' + duration_value: 'Duration' + batch_element_threshold_value: 'Batch elements period threshold' + offset_type: 'Planning' + #batch_element_threshold_unit: 'Batch element duration unit' + workflow_action_offset: + base_workflow_action_definition: 'Base ${models.workflow_action_definition}' + workflow_action_connection: + conditional_base_value: 'Visible if ${models.workflow_action_definition} is' +export default translations diff --git a/tests/dummy/app/i18n/translations/manual.coffee b/tests/dummy/app/i18n/translations/manual.coffee new file mode 100644 index 0000000..8a50fed --- /dev/null +++ b/tests/dummy/app/i18n/translations/manual.coffee @@ -0,0 +1,24 @@ +translations = manual: + title: 'Panda manual' + introduction1: " +

Focus points:

+
    +
  • Project scenarios
  • +
  • Action bound group lead times
  • +
  • Project progress prognosis
  • +
  • Instance specific action planning maintenance
  • +
  • Multi path action specifications
  • +
  • Manual addition of basic concept explanations
  • +
+

Planning steps

+
    +
  • Conceptual planning
  • +
  • Actualized instance set planning
  • +
  • Operational interpretation and possible operator insight modifiactions
  • +
  • Operational result of actions
  • +
  • Analysis of operation and comparison with actualized instance set planning
  • +
+" +import project_translations from './manual/project' +Ember.assign translations.manual, project_translations +export default translations diff --git a/tests/dummy/app/i18n/translations/manual/project.coffee b/tests/dummy/app/i18n/translations/manual/project.coffee new file mode 100644 index 0000000..0a43602 --- /dev/null +++ b/tests/dummy/app/i18n/translations/manual/project.coffee @@ -0,0 +1,13 @@ +translations = project: + dunlop: + workflow_group: + text: " +%{models} are groupings of ${models.plural.workflow_action_definition}. +" + duration_value: + header: 'Execution lead time, offset and order' + workflow_action_definition: + action_confirmation: + header: '${models.workflow_action_definition} confirmation' + text: '' +export default translations diff --git a/tests/dummy/app/i18n/translations/models.coffee b/tests/dummy/app/i18n/translations/models.coffee new file mode 100644 index 0000000..a123e82 --- /dev/null +++ b/tests/dummy/app/i18n/translations/models.coffee @@ -0,0 +1,39 @@ +translations = models: + event: 'Event' + organization: 'Organisatie' + project: 'Project' + project_scenario: 'Scenario' + project_instance: 'Project Instance' + project_instance_action: 'Instance Action' + project_instance_group: 'Instance Group' + workflow_action_definition: 'Action' + workflow_group: 'Workflow Group' + workflow_action_constraint: 'Constraint' + workflow_action_offset: 'Action offset' + workflow_group_constraint: 'Constraint' + tracker_subject: "Tracker" + tracker_group: "Tracker Group" + tracker_item: "Tracker Item" + user: "User" + workflow_instance_type: "Instance Type" + source_file: "Bronbestand" + plural: + event: 'Events' + organization: 'Organisaties' + project: 'Projecten' + project_scenario: 'Scenarios' + project_instance: 'Project Instances' + project_instance_action: 'Instance Actions' + project_instance_group: 'Instance Groups' + workflow_action_definition: 'Actions' + workflow_group: 'Workflow Groups' + workflow_action_constraint: 'Constraints' + workflow_action_offset: 'Action offsets' + workflow_group_constraint: 'Constraints' + tracker_subject: "Trackers" + tracker_group: "Tracker Groups" + tracker_item: "Tracker Items" + user: "Users" + workflow_instance_type: "Instance Types" + source_file: "Bronbestanden" +export default translations diff --git a/tests/dummy/app/i18n/translations/organization.coffee b/tests/dummy/app/i18n/translations/organization.coffee new file mode 100644 index 0000000..54aa4d8 --- /dev/null +++ b/tests/dummy/app/i18n/translations/organization.coffee @@ -0,0 +1,8 @@ +translations = organization: + project_actions: + title: 'Project actions' + select_users: + placeholder: 'Select ${models.plural.user} (%{count})' + edit_organization: + title: 'Edit ${models.organization} %{name}' +export default translations diff --git a/tests/dummy/app/i18n/translations/project-instance-action.coffee b/tests/dummy/app/i18n/translations/project-instance-action.coffee new file mode 100644 index 0000000..f4e6ed2 --- /dev/null +++ b/tests/dummy/app/i18n/translations/project-instance-action.coffee @@ -0,0 +1,5 @@ +translations = project_instance_action: + revoke: 'Revoke!' + done: 'Done!' + has_uncompleted_constraints_message: 'Not al required preceding actions are completed' +export default translations diff --git a/tests/dummy/app/i18n/translations/project-instance.coffee b/tests/dummy/app/i18n/translations/project-instance.coffee new file mode 100644 index 0000000..4942c30 --- /dev/null +++ b/tests/dummy/app/i18n/translations/project-instance.coffee @@ -0,0 +1,6 @@ +translations = project_instance: + show: + grid: 'Grid' + list: 'List' + graphs: 'Graphs' +export default translations diff --git a/tests/dummy/app/i18n/translations/project.coffee b/tests/dummy/app/i18n/translations/project.coffee new file mode 100644 index 0000000..e51abcd --- /dev/null +++ b/tests/dummy/app/i18n/translations/project.coffee @@ -0,0 +1,36 @@ +translations = project: + dunlop: + title: 'Organize ${models.project}' + analysis: + title: '${models.project} analysis' + dunlop_generator: + title: 'Gererate operational application' + add_progress: + title: 'Add progress files' + diagram_title: '%{model} diagram' + select_reference_date_day_of_week: 'Select ${attributes.project.reference_date_day_of_week}' + edit_projects_context: + title: 'Manage project types' + message: 'Manage the different project type options' + edit_workflow_group: + title: 'Edit ${models.workflow_group}: %{name}' + edit_project: + title: 'Edit ${models.project} %{name}' + position_workflow_action_definition_in_project: + title: "Position ${models.workflow_action_definition} in ${models.project}" + contract: + title: '${models.project} %{name} contract' + new_project_from_contract: + title: 'New ${models.project} from contract' + edit_project_scenario: + title: 'Edit ${models.project_scenario} %{name}' + project_scenarios_show_grid: + title: 'Show ${models.plural.project_scenario} activation grid' + load_project_instances: + title: 'Load ${models.project} instances' + edit_instance_action: + title: 'Edit action: %{title}' + show: + dunlop_project_info: + title: "${models.project} %{name} info" +export default translations diff --git a/tests/dummy/app/i18n/translations/user.coffee b/tests/dummy/app/i18n/translations/user.coffee new file mode 100644 index 0000000..85ccda3 --- /dev/null +++ b/tests/dummy/app/i18n/translations/user.coffee @@ -0,0 +1,8 @@ +translations = user: + profile: 'Profile' + sign_out: 'Uitloggen' + new_user: + title: 'New ${models.user}' + edit_user: + title: 'Edit ${models.user} %{nickname}' +export default translations diff --git a/tests/dummy/app/i18n/translations/workflow-action-definition.coffee b/tests/dummy/app/i18n/translations/workflow-action-definition.coffee new file mode 100644 index 0000000..7825697 --- /dev/null +++ b/tests/dummy/app/i18n/translations/workflow-action-definition.coffee @@ -0,0 +1,37 @@ +translations = workflow_action_definition: + authorize_users: + title: 'Authorize ${models.plural.user}' + value_type: + string: 'String' + select: 'Select' + text: 'Text' + timestamp: 'Timestamp' + boolean: 'Boolean' + date: 'Date' + number: 'Number' + connection_type: + info: 'INFO' + constraint: 'CONSTRAINT' + pointing_connections_title: 'Pointing connections' + edit_workflow_action_definition: + title: 'Edit ${models.workflow_action_definition}: %{title}' + #no_workflow_action_definition_for_offset_selected_alert: 'Please select ${models.workflow_action_definition} first' + #no_workflow_action_definition_for_constraint_selected_alert: 'Please select ${models.workflow_action_definition} first' + no_offset: 'No offset' + attached_offset: 'Attached' + configured_offset: 'Configured' + no_workflow_action_offsets_present: 'No ${models.plural.workflow_action_offset} added' + #no_action_offset_base_given_alert: 'No ${models.workflow_action_definition} selected' + #chosen_but_non_added_offset_workflow_action_definition: 'You selected a ${models.workflow_action_definition} but did not add it as an offset. Clear the selection or add the ${models.workflow_action_definition} to proceed' + offset_type_configured_but_no_offsets: 'You chose offset type ${workflow_action_definition.edit_workflow_action_definition.configured_offset} but did not add any ${models.plural.workflow_action_offset}' + offset_type_attached_but_no_connection: 'You chose offset type ${workflow_action_definition.edit_workflow_action_definition.attached_offset} but there is no attached ${models.workflow_action_definition}' + workflow_action_definition_already_used: '${models.workflow_action_definition} %{title} can be used only once' + base_workflow_action_definition_not_present: '${attributes.workflow_action_offset.base_workflow_action_definition} not selected' + workflow_action_definition_not_present: '${models.workflow_action_definition} not present' + cannot_set_offset_value_due_to_workflow_action_offset_presence: '${attributes.workflow_action_definition.offset_value} from ${attributes.project_instance.reference_date} cannot be set when there are ${models.plural.workflow_action_offset}' + cannot_add_workflow_action_offsets_due_to_offset_value_presence: 'Cannot add ${models.plural.workflow_action_offset} due to ${attributes.workflow_action_definition.offset_value} presence' + offset_graph: + show_attached: "Show attached" + show_pointing_infos: "Show info ${models.plural.workflow_action_definition}" + show_pointing_constraints: "Show constraint ${models.plural.workflow_action_definition}" +export default translations diff --git a/tests/dummy/app/i18n/translations/workflow-group.coffee b/tests/dummy/app/i18n/translations/workflow-group.coffee new file mode 100644 index 0000000..25488c0 --- /dev/null +++ b/tests/dummy/app/i18n/translations/workflow-group.coffee @@ -0,0 +1,21 @@ +translations = workflow_group: + offset_type: + none: + label: 'Geen' + reference_date: + label: '${attributes.project_instance.reference_date}' + offset_value_label: 'start offset' + connected_finish: + label: 'connected finish' + offset_value_label: '${models.workflow_group} finishes' + offset_base_label: 'before' + offset_base_prefix: 'start of -> ' + connected_start: + label: 'connected start' + offset_value_label: '${models.workflow_group} starts' + offset_base_label: 'after' + offset_base_prefix: 'finishing of -> ' + modals: + edit_workflow_group: + title: 'Edit ${models.workflow_group}: %{name}' +export default translations diff --git a/tests/dummy/app/index.html b/tests/dummy/app/index.html new file mode 100644 index 0000000..61400b2 --- /dev/null +++ b/tests/dummy/app/index.html @@ -0,0 +1,25 @@ + + + + + + Dummy + + + + {{content-for "head"}} + + + + + {{content-for "head-footer"}} + + + {{content-for "body"}} + + + + + {{content-for "body-footer"}} + + diff --git a/tests/dummy/app/initializers/globals.coffee b/tests/dummy/app/initializers/globals.coffee new file mode 100644 index 0000000..60916d0 --- /dev/null +++ b/tests/dummy/app/initializers/globals.coffee @@ -0,0 +1,50 @@ +window.client_id ||= "client#{Math.round(Math.random()*1000000)}" +#$translations.en.attributes.state ||= {} +$translations.en.attributes.state ||= {} +Ember.assign $translations.en.attributes.state, + inquiry: 'Issue' +default_globals = + flash_message: '' + preload_resources_fulfilled: true + current_user: null + workflow_action_definition: + value_type_options: ['boolean', 'date', 'number', 'select', 'string', 'text', 'timestamp'] + workflow_action_offset: + offset_unit_options: ['day'] + week_days: [ + {name: 'monday', index: 1} + {name: 'tuesday', index: 2} + {name: 'wednesday', index: 3} + {name: 'thursday', index: 4} + {name: 'friday', index: 5} + {name: 'saturday', index: 6} + {name: 'sunday', index: 0} + ] + project: + instance_scope_options: ['instance', 'batch'] + workflow_group: + batch_element_threshold_unit_options: ['businessday', 'day', 'week'] + project_instance_action: + states: ['pending', 'planned', 'overdue', 'inquiry', 'rejected', 'completed'] + final_states: ['inquiry', 'rejected', 'completed'] + + time_formatting: 'LLL' + date_formatting: 'LL' + +export globals = Ember.Object.create default_globals, window.app_globals + +initialize = (application) -> + application.register 'service:globals', globals, instantiate: false #, singleton: true + application.inject 'controller', 'globals', 'service:globals' + application.inject 'component', 'globals', 'service:globals' + application.inject 'component', 'router', 'router:main' + application.inject 'component', 'store', 'service:store' + application.inject 'view', 'globals', 'service:globals' + application.inject 'router', 'globals', 'service:globals' + +GlobalsInitializer = + name: 'globals' + initialize: initialize + +export {initialize} +export default GlobalsInitializer diff --git a/tests/dummy/app/models/.gitkeep b/tests/dummy/app/models/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/tests/dummy/app/models/user.coffee b/tests/dummy/app/models/user.coffee new file mode 100644 index 0000000..7e4da4f --- /dev/null +++ b/tests/dummy/app/models/user.coffee @@ -0,0 +1,8 @@ +import DS from 'ember-data' +import dunlop_user from 'ember-cli-dunlop/models/dunlop-user' + +export default dunlop_user.extend + organizations: DS.hasMany('panda/organization', serialize: 'ids', inverse: 'users') + knock_knock: -> 'I\'m here!' + + diff --git a/tests/dummy/app/resolver.js b/tests/dummy/app/resolver.js new file mode 100644 index 0000000..2fb563d --- /dev/null +++ b/tests/dummy/app/resolver.js @@ -0,0 +1,3 @@ +import Resolver from 'ember-resolver'; + +export default Resolver; diff --git a/tests/dummy/app/router.coffee b/tests/dummy/app/router.coffee new file mode 100644 index 0000000..7689053 --- /dev/null +++ b/tests/dummy/app/router.coffee @@ -0,0 +1,12 @@ +import Ember from 'ember' +import config from './config/environment' +import pandaRouteSetup from 'ember-panda/utils/panda-route-setup' + +Router = Ember.Router.extend + location: config.locationType + rootURL: config.rootURL + +Router.map -> + pandaRouteSetup(@) + +export default Router diff --git a/tests/dummy/app/routes/.gitkeep b/tests/dummy/app/routes/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/tests/dummy/app/routes/application.coffee b/tests/dummy/app/routes/application.coffee new file mode 100644 index 0000000..f7f2144 --- /dev/null +++ b/tests/dummy/app/routes/application.coffee @@ -0,0 +1,56 @@ +import Ember from 'ember' +import DunlopApplicationRoute from 'ember-cli-dunlop/mixins/application-route' + +export default Ember.Route.extend DunlopApplicationRoute, + # this method gets fired after a successful authentication or successful fetch of current user through + # HTTP session or ember-simple-auth session + current_user_loaded: (user) -> + @_super arguments... + @preloadResources() + + #setupController: (controller)-> + # host = @store.adapterFor('application').host || '' + # event_host = "#{host}/faye" + # faye = new Faye.Client(event_host) + # faye.subscribe '/activity', (data)=> + # return if data.client_id is window.client_id # prevent duplicate records with same id, still an issue in ember 2.9.0/1 + # console.log data + # switch data.event + # when 'payload' + # data.payload = JSON.parse(data.payload) if typeof(data.payload) is 'string' + # @store.pushPayload(data.payload) + # when 'destroy' + # if record = @store.peekRecord(data.model, data.id) + # record.rollbackAttributes() + # record.unloadRecord() + + preloadResources: -> + @store.findAll 'panda/workflow-instance-type' + @store.findAll 'panda/organization' + @store.findAll 'user' + @store.findAll 'panda/tracker-subject' + @set 'router.globals.adapters', @store.findAll 'panda/adapter' + #Ember.RSVP.all([ + # @store.findAll 'workflow-instance-type' + # @store.findAll 'organization' + # @store.findAll 'user' + # @store.findAll 'tracker-subject' + #]).then => + # @set 'router.globals.preload_resources_fulfilled', true + + actions: + debug: -> debugger + dateSort: (attribute, a, b) -> + date_a = Ember.get(a, attribute) + date_b = Ember.get(b, attribute) + return 0 if date_a is date_b + return 1 unless date_b + return -1 unless date_a + date_a_string = date_a.toISOString() + date_b_string = date_b.toISOString() + if date_a_string > date_b_string + return 1 + else if date_a_string < date_b_string + return -1 + 0 + diff --git a/tests/dummy/app/styles/app.scss b/tests/dummy/app/styles/app.scss new file mode 100644 index 0000000..e69de29 diff --git a/tests/dummy/app/templates/application.emblem b/tests/dummy/app/templates/application.emblem new file mode 100644 index 0000000..7c21e96 --- /dev/null +++ b/tests/dummy/app/templates/application.emblem @@ -0,0 +1,28 @@ += flash-message += partial 'global/top-menu' +.ui.main + /bread-crumbs linkable=false + /= bread-crumbs tagName='ul' outputStyle='foundation' linkable=false as |component rrr| + = bread-crumb route=rrr breadCrumbs=component + span= rrr.title + = outlet 'error-handling' + if globals.flash.alert + .ui.negative.floating.message + i.close.icon{action (mut globals.flash.alert) ''} + = globals.flash.alert + if globals.flash.notice + .ui.floating.info.message + i.close.icon{action (mut globals.flash.notice) ''} + = globals.flash.notice + = outlet + /if globals.preload_resources_fulfilled + / = outlet + /else + / .ui.icon.message + / i.notched.circle.loading.icon + / .content + / .header Preloading resources... + / p Loading resources globally required before continuing with the application. + /button.basic.button{ action 'debug'} debug += outlet 'modal' + diff --git a/tests/dummy/app/templates/components/.gitkeep b/tests/dummy/app/templates/components/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/tests/dummy/app/templates/global/-top-menu.emblem b/tests/dummy/app/templates/global/-top-menu.emblem new file mode 100644 index 0000000..f058471 --- /dev/null +++ b/tests/dummy/app/templates/global/-top-menu.emblem @@ -0,0 +1,95 @@ +.ui.grid: .computer.tablet.only.row: .ui.inverted.menu.fixed + = link-to 'index' class='header item' + img src='/panda/app/assets/favicons/source.svg' + if globals.current_user + if (can 'read' 'model' 'panda/project') + = link-to 'project' class='item' + span= t 'models.plural.project' + /= link-to 'tracker-subject.index' class='item' + span= t 'models.plural.tracker_subject' + if (can 'manage' 'model' 'panda/organization') + = link-to 'organization' class='item' + span= t 'models.plural.organization' + if (can 'manage' 'model' 'user') + = link-to 'user' class='item' + span= t 'models.plural.user' + = link-to 'event' class='item' + span= t 'models.plural.event' + = link-to 'action-list' class='item' + span= t 'action_list.title' + if (any-permission-starts-with 'source-file') + a.computer.only.item href="/source_files/": span= t 'models.plural.source_file' + if (has-permission-of-type 'adapter') + ui-dropdown class='item' + span Adapters + i.dropdown.icon + .menu + each globals.adapters as |adapter| + a.item href="{{concat '/adapters/' adapter.id}}" class='item' + span= adapter.title + .right.menu + if globals.current_user + ui-dropdown class='item' + span= globals.current_user.nickname + i.dropdown.icon + .menu + = link-to 'user.profile' class='item' + i.user.icon + span.text= t 'user.profile' + = link-to 'manual' class='item' + i.book.icon + span.text= t 'manual.title' + a.item{ action 'signOut'} + i.toggle.right.icon + span.text= t 'user.sign_out' + else + = link-to 'Login' 'login' class='item' +.ui.grid: .mobile.only.row + .ui.inverted.menu.fixed + = link-to 'index' class='header item' + img src='/panda/app/assets/favicons/source.svg' + if globals.current_user + ui-dropdown class='item' + span Menu + i.dropdown.icon + .menu + if (can 'read' 'model' 'panda/project') + = link-to 'project' class='item' + span= t 'models.plural.project' + /= link-to 'tracker-subject.index' class='item' + span= t 'models.plural.tracker_subject' + if (can 'manage' 'model' 'panda/organization') + = link-to 'organization' class='item' + span= t 'models.plural.organization' + if (can 'manage' 'model' 'user') + = link-to 'user' class='item' + span= t 'models.plural.user' + = link-to 'event' class='item' + span= t 'models.plural.event' + if (any-permission-starts-with 'source-file') + a.item href="/source_files/": span= t 'models.plural.source_file' + if (has-permission-of-type 'adapter') + .item + i.dropdown.icon + span.text Adapters + .right.menu + each globals.adapters as |adapter| + a.item href="{{concat '/adapters/' adapter.id}}" class='item' + span= adapter.title + .right.menu + if globals.current_user + ui-dropdown class='item' + span= globals.current_user.nickname + i.dropdown.icon + .menu + = link-to 'user.profile' class='item' + i.user.icon + span.text= t 'user.profile' + = link-to 'manual' class='item' + i.book.icon + span.text= t 'manual.title' + a.item{ action 'signOut'} + i.toggle.right.icon + span.text= t 'user.sign_out' + else + = link-to 'Login' 'login' class='item' diff --git a/tests/dummy/app/templates/index.emblem b/tests/dummy/app/templates/index.emblem new file mode 100644 index 0000000..696f773 --- /dev/null +++ b/tests/dummy/app/templates/index.emblem @@ -0,0 +1,14 @@ +.ui.container + = page-title "Panda home" + br + br + br + img.ui.centered.image src="/panda/app/assets/favicons/android-chrome-192x192.png" + + + .ui.header TODO + .ui.list + .item Fix markdown show (action extra info) + .item Add identifiers view for dunlop project info view + + .ui.header DONE diff --git a/tests/dummy/app/templates/loading.emblem b/tests/dummy/app/templates/loading.emblem new file mode 100644 index 0000000..4be4a75 --- /dev/null +++ b/tests/dummy/app/templates/loading.emblem @@ -0,0 +1,6 @@ +.ui.segment + .ui.active.dimmer + .ui.massive.text.loader Loading + p + p + p diff --git a/tests/dummy/app/templates/login.emblem b/tests/dummy/app/templates/login.emblem new file mode 100644 index 0000000..0597d4d --- /dev/null +++ b/tests/dummy/app/templates/login.emblem @@ -0,0 +1,6 @@ +br +br +.ui.container: .ui.middle.aligned.center.aligned.grid.login-form: .column + .ui.image.header: .content Login to Panda + .ui.large.form.segment= login-form + img.ui.centered.image src="/panda/app/assets/favicons/android-chrome-192x192.png" diff --git a/tests/dummy/config/environment.js b/tests/dummy/config/environment.js new file mode 100644 index 0000000..e7507bd --- /dev/null +++ b/tests/dummy/config/environment.js @@ -0,0 +1,60 @@ +/* eslint-env node */ +'use strict'; + +module.exports = function(environment) { + var ENV = { + modulePrefix: 'dummy', + environment: environment, + rootURL: '/panda/app/', + locationType: 'auto', + applicationName: "Panda", + apiHost: '', + apiNamespace: 'panda/api/v1', + exportApplicationGlobal: true, + moment: { + includeLocales: ['nl'] + }, + EmberENV: { + FEATURES: { + // Here you can enable experimental features on an ember canary build + // e.g. 'with-controller': true + }, + EXTEND_PROTOTYPES: { + // Prevent Ember Data from overriding Date.parse. + Date: false + } + }, + + APP: { + // Here you can pass flags/options to your application instance + // when it is created + } + }; + + if (environment === 'development') { + // ENV.APP.LOG_RESOLVER = true; + // ENV.APP.LOG_ACTIVE_GENERATION = true; + // ENV.APP.LOG_TRANSITIONS = true; + // ENV.APP.LOG_TRANSITIONS_INTERNAL = true; + // ENV.APP.LOG_VIEW_LOOKUPS = true; + ENV.apiHost = "http://localhost:3006"; // when using the stand alone ember server implementation with rails server explicitly as api host (foreman run web) + //ENV.apiHost = "https://kpn-staging.panda-suite.com"; + } + + if (environment === 'test') { + // Testem prefers this... + ENV.locationType = 'none'; + + // keep test console output quieter + ENV.APP.LOG_ACTIVE_GENERATION = false; + ENV.APP.LOG_VIEW_LOOKUPS = false; + + ENV.APP.rootElement = '#ember-testing'; + } + + if (environment === 'production') { + + } + + return ENV; +}; diff --git a/tests/dummy/config/targets.js b/tests/dummy/config/targets.js new file mode 100644 index 0000000..df7664c --- /dev/null +++ b/tests/dummy/config/targets.js @@ -0,0 +1,9 @@ +/* eslint-env node */ +module.exports = { + browsers: [ + 'ie 9', + 'last 1 Chrome versions', + 'last 1 Firefox versions', + 'last 1 Safari versions' + ] +}; diff --git a/tests/dummy/public/assets/favicons/android-chrome-192x192.png b/tests/dummy/public/assets/favicons/android-chrome-192x192.png new file mode 100644 index 0000000000000000000000000000000000000000..3227e810f294e17cfcf3035d7aa9548c432585d3 GIT binary patch literal 8641 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 R?B^i>P-ShU1_fBe{{#El2Uh?9 literal 0 HcmV?d00001 diff --git a/tests/dummy/public/assets/favicons/android-chrome-512x512.png b/tests/dummy/public/assets/favicons/android-chrome-512x512.png new file mode 100644 index 0000000000000000000000000000000000000000..1958fdda42122cec1c494a91c82ce031027644dc GIT binary patch literal 27402 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 X2NA-rWrfoJr2wcXXvo*fT7>)$8L~PW literal 0 HcmV?d00001 diff --git a/tests/dummy/public/assets/favicons/browserconfig.xml b/tests/dummy/public/assets/favicons/browserconfig.xml new file mode 100644 index 0000000..b3930d0 --- /dev/null +++ b/tests/dummy/public/assets/favicons/browserconfig.xml @@ -0,0 +1,9 @@ + + + + + + #da532c + + + diff --git a/tests/dummy/public/assets/favicons/favicon-16x16.png b/tests/dummy/public/assets/favicons/favicon-16x16.png new file mode 100644 index 0000000000000000000000000000000000000000..53167e343db4e7cf2d3463b19e1b1e288cd304cd GIT binary patch literal 772 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!60wlNoGJgf6SkfJR9T^xl_H+M9WCijSl0AZa z85pY67#JE_7#My5g&JNkFq9fFFuY1&V6d9Oz#v{QXIG#NP=YDR+ueoXe|!I#{Xiaj ziKnkC`$INaUMsoRjTt%&42&y1T^vI+&inSC_nutH&_4ft`TDb#5&L=-JAP_!E7D0* z*%I3{?SbyDZ;u6T$_p%Eu`c-Le+9pllz*E+Cj+uYbCzl$XGf!T(v0vcx5}w0TUNj}& z`BC?KF@v|_*Q^b!3Tktd(tTt%Is`Jg~ri`aar zcGoze_5+L4MX!eb?cb#}OHbgsQ))OLc<9k%qpE+NCR>sx&ygvfEY^Sh zO_D)~=BDq`Y|d)! zayz#=*){yer@2EF4*2epeDLbN{%P(rKUi+<+_Qgc z@vjhzC1u%*=P0-Rja|QXjmUDhYxM~WgwOlP^&h|M^CU}wYt`Q|Ei6yC4$wjF^iowXh&|KHROxMUV#L(Ev)WXWxK-<8`%D~|5 zkEq8e8glbfGSez?YnZe2Q9MvXcvVP5Nl;?BLP1e}T4qkFLP=#oszPQ#NiqXN#hk~V zcsL5fG&D~6pFZRHG>Cy&nOiTJTUc4xd$I_#u!2j2$>9`c<;@`qr*B+2apcSqnIr6{ e8$1?x=`p+#7cBYYWI7dS1%s!npUXO@geCyb8a0{# literal 0 HcmV?d00001 diff --git a/tests/dummy/public/assets/favicons/favicon-32x32.png b/tests/dummy/public/assets/favicons/favicon-32x32.png new file mode 100644 index 0000000000000000000000000000000000000000..0a8582c647cf4bd874ae734a43e0faa801d39f19 GIT binary patch literal 1406 zcmV-^1%djBP)kdg00004XF*Lt006O% z3;baP0000WV@Og>004R>004l5008;`004mK004C`008P>0026e000+ooVrmw00002 zVoOIv0RM-N%)bBt00(qQO+^Rc1|1DL9_xWB0fUsE9jIq$9iR&b}UY7I%T_o6G#(?|r|&@4WYZZy;saG}6UQ zPSA>vrEM9T>ESz`$5^;kPB$hA1YwrFOTr5{!VH2i#W9-E?r*K*43hSo!n*{)F5+o1 z6YRv5I{iWhTo~ukMeqP;7evkQ4$1`-QI#3%32fQD@k%^RqAT&|>LcqTnPCRabR|Vd z2AnPWYwdvyr=pw~KeK>>q=F2~VJmM?ot_~8f_TZIhkFbB3kbZt%q}W{#G)ir^A=hB zfR*9Z0h=l+(IJ;@yiNuGXMQS)Gt69a`42}qtpoq^J2zS5^KWM-Wjx51Q;85FYI2zO zI7D2f$R2yDt*33}s?2;YVTv&h-spaH{50|T91QS1dwjKd!H2>@2-4&$MaA1OAerpH z7;|O6Qevow!jl?G49~VP*Ef(N5DIDwH~G`fS4+U|Yw%OJsi0O;1RVtNwyZ2pRa8Tm zS_GdaKHnQIRu+3(f^?)5gi7*TiV#4dbDPiD!Dw15X9_{nz zrwZ4Kp3J{f&^{(mjgzZ1dtA;#FBS?nAF>-CO>9UzKdGUK8p`;DZ6r`w^a66;9#^31 zMuj{yO+AC)3OQ}Z?6y*d+ z0ApNdIZ=iH11P7!nGza@I0j$4zjc~Nt7fD@u8QdtE-Yc`kTS`ac4-lx)XHI*oZm6! zsx$~ATD3}cqKia`{a0itt@jkUL@+tPQKpG!{6{o~xFYONgj`927&?0Q-NBP-mr2-6 zClv^=o-P{FMx1bxyMubSPAqj7JlMDB?3ImN*f(U08G$bA9`U`3-t%Pd!H!ZVW{ z*A!XkL}+FsrK}|ni!j&hkvqc|dw)9rIoATtbWWQb?{bGVUQcP(I!~Q)h-gbN$ib@#!4-bW!ww z!x^kEHwZ9+0000bbVXQnWMOn=I%9HWVRU5xGB7bWEig7MGC5Q+GdeXmIx{dUFfuwY zFzxtS;{X5vC3HntbYx+4WjbwdWNBu305UK!H!UzWEiyS&F*76Cgx@G{a;ABePT>%h=S&#LUDT#0SfONT5nC0O}VJbn-$ql>h($ M07*qoM6N<$f)Ax&Bme*a literal 0 HcmV?d00001 diff --git a/tests/dummy/public/assets/favicons/favicon.ico b/tests/dummy/public/assets/favicons/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..42e16e2ebbb20b2494b6de88bb80dce94ae1bb75 GIT binary patch literal 15086 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 Z0Q--qe9oSUXZ%|MP*>4bu6}0w_CJOc1>OJv literal 0 HcmV?d00001 diff --git a/tests/dummy/public/assets/favicons/safari-pinned-tab.svg b/tests/dummy/public/assets/favicons/safari-pinned-tab.svg new file mode 100644 index 0000000..4b00477 --- /dev/null +++ b/tests/dummy/public/assets/favicons/safari-pinned-tab.svg @@ -0,0 +1,52 @@ + + + + +Created by potrace 1.11, written by Peter Selinger 2001-2013 + + + + + diff --git a/tests/dummy/public/assets/favicons/source.svg b/tests/dummy/public/assets/favicons/source.svg new file mode 100644 index 0000000..4bf258a --- /dev/null +++ b/tests/dummy/public/assets/favicons/source.svg @@ -0,0 +1,137 @@ + + + +image/svg+xml \ No newline at end of file diff --git a/tests/dummy/public/assets/icons/svg/logical-conjunction.svg b/tests/dummy/public/assets/icons/svg/logical-conjunction.svg new file mode 100644 index 0000000..8c69f92 --- /dev/null +++ b/tests/dummy/public/assets/icons/svg/logical-conjunction.svg @@ -0,0 +1,71 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + diff --git a/tests/dummy/public/assets/icons/svg/logical-conjunction_backup.svg b/tests/dummy/public/assets/icons/svg/logical-conjunction_backup.svg new file mode 100644 index 0000000..e50fd3e --- /dev/null +++ b/tests/dummy/public/assets/icons/svg/logical-conjunction_backup.svg @@ -0,0 +1,87 @@ + + + + + + + + + image/svg+xml + + + + + + + + + + + diff --git a/tests/dummy/public/assets/icons/svg/logical-disjunction.svg b/tests/dummy/public/assets/icons/svg/logical-disjunction.svg new file mode 100644 index 0000000..433e0f9 --- /dev/null +++ b/tests/dummy/public/assets/icons/svg/logical-disjunction.svg @@ -0,0 +1,66 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/tests/dummy/public/assets/icons/svg/logical-disjunction_backup.svg b/tests/dummy/public/assets/icons/svg/logical-disjunction_backup.svg new file mode 100644 index 0000000..73cea24 --- /dev/null +++ b/tests/dummy/public/assets/icons/svg/logical-disjunction_backup.svg @@ -0,0 +1,87 @@ + + + + + + + + + image/svg+xml + + + + + + + + + + + diff --git a/tests/dummy/public/assets/icons/svg/logical-drawing-board.svg b/tests/dummy/public/assets/icons/svg/logical-drawing-board.svg new file mode 100644 index 0000000..28fe65f --- /dev/null +++ b/tests/dummy/public/assets/icons/svg/logical-drawing-board.svg @@ -0,0 +1,83 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + diff --git a/tests/dummy/public/assets/manual/action-offsets/non-rooted.png b/tests/dummy/public/assets/manual/action-offsets/non-rooted.png new file mode 100644 index 0000000000000000000000000000000000000000..a8353fc23218728f0f3da60a831bf2e114d8ebb3 GIT binary patch literal 4543 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�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 literal 0 HcmV?d00001 diff --git a/tests/dummy/public/assets/manual/manual-index.png b/tests/dummy/public/assets/manual/manual-index.png new file mode 100644 index 0000000000000000000000000000000000000000..40d338ce53de342a1c1535ba16cf735c47269772 GIT binary patch literal 32768 zcmZU*cRZHu8$W(axU)wgdxh*RTXuFLdnBvd-jr2H5|ZqdkQK5edxos+y|S|R3cthi z`F>yDKYsV?d3v78eY>vfJkR5JuS2M&x*{Gn1vUbKz z@Sht_O8Tw{1P;m7zi5cWWODdJEVl=0@>p|7LR>z47tf0>1cDCnK<>V_*Z6wM3nSH& z%XXXdKPgx?)sF&=dtc5Z%cg3Nxv(we+;#&Gboq8 z@$id|a&wNrk4JdXcT3d-PtJG7X2!3eE7Q&*dDi0Roh zVP;`bW8k*^sF9OBp7QT}rt`lB;@_U2-^GiVCOXhgQ zF}ae4#`2Dryu5sPc(^uOOxI#hszg$&`AbX0HVW<2)6v=&o|>AP3JRFf9c9JE^ifK@ zyu6RTs6J3r!&pGf4qcPRYFOTjq><=cbRnP-SIW`~Z41UBD>VLbI9_gh6C2~}^#ENQ zgkx)pm?xo37#TOS8!5fT)2ICY7d{E9u~AX!l%!Z_z+VxK)3!9rXL_|as6p7K%{Lg+fy?F6LG$n%Y^T4VX+F9!T z`}fDj#$1cUU}p_^>OEJFaW7uC8wMTzjHGm6?%BtET4p0SZ4vHlyFp&W=z< zC6=S3vy;JtoQ8(x=1sS`CJfTBTeP(GPnt3DL*S|@*kTetd?3Dc>kety(fSzr2o53_ zL-dZD=s8?GrLcqQgR9qlByYkm=Y=+JZ|^5+Es|Cq9v)_9biDH2{r&x2UF-q^Zo7Xv z>~WV23=B3WDhar=v$L%&Eps>fT3TAVy4L=5M6V4O;Nam23JXj5o(d$WDj>%uCZd%x zS5{WWO07vkt|JJg5u&%L@Ryr{ak4eiOlrJ%+7yXm+wYF?~S;6fBr-g5JZNBJuQXn=q=34`>d5KCyw^?$&;6X z==PKTGj+b9izOv|LGN>Ob5U6TtaWs5$*^)#{rV0uQjE#&6jlT1}I9w+chNbZFR6J-UGyOJZSFS)pLQ-XdP~z;n8yXh&i1p_5 z^mLxoD3J^w>YxzB$`v9DhXdjFS@SI0!y52x9Jk4PpfD=RO; z-pk7?P15&U>f!1D78V{4+z}J;Tmj+}wOf&1fgXoRrNdNPIOQ$u;PJfVPfvt$R$FUp$D)h* z#$0nqwrav>(HdMtL%uKP&d!cF4FSCXYLHb!Lt{wC($Z3#CQ>=;@7fwkWWF|=JQ9jQ zili^a*&`7V5pkMcSg|k5o;MpC8};?|#c8u3`BVH*9VhtU zvc|@~fBVM6%>1RbeJPB)POsW6H*c-%#nI;I=xAS`Ci$b2h)ZmiK#FpSIDR@%h-`t7*Ln2PssT@_r-}@m(e+i>YdN;?N+4j3>Hrg!A8B zukNq%1i#X8rtaAQbEuq5x~7dl;$H(;T6n>x)nr|96Me(69qX2%#~Inq7^Sqixq0l~ z4rgql{^O{MqaKi zER;TNj|d97Ue24u5XCAcmUIjUjYiBvHCG$+`t^od=x|CK^@L<(iW&WIJhszPQ&WF< zY`hH**NHZui3|y0&ABi)uJPJgQ|L#(>V_Bc&;_16p$}|?gG5h}tDPrJH@kO|cSCM+ za&ntc9XDKolGnUt4YT8aU#=!2b4}(=quIx}IFj%$U%qJN?n52S)9SJ+)mcWOU43N5 zjbNFm`1qd|d_%9PSYtY&!e(V=DrNm1sSUt{mYHi$0#%1E!LBD+1hv6L+|l07!p6o% zPygiN-^tWGs(owiUEG#?V4^<9Fw{*~7nk{k1z%s^??#21tl~b$JSMNLDG|B8MJ=tZ z^g%T>HHk&Lhldew-@XkET@}(go57!mc{y<=@~5duh9*++m$K$r?W+rah4PdXYIQdi zj^6P{&8~Hm=MRNcA`P0|eCulodcC6-oygdg8 za~{{;P1N^fcTp~iGE}a_W5eL{u}D~}L8%oU?=HpvKA`yMXT*4nNAN78$lc_!AFU$8 zVxF7tA|ejAx3_n8?jxZoJ2bz><4eE|SqgmhN{*p_xWG`KBUX+f^vxT+3cIn(izBJ{ zAb1Mbrar<2{QRjjL`q4SRQuwnq9jz46+X(t)03rI47m>LwhN&CF(#dghzuLk)hBXd4*3B^}a#Qo13bdTCLyt^T&@LyTNZ%e?>}NARXJTzTAf0 zE3*I!Ltr3!KWmhdNuAF#JG*yNCKTbX^Eqb&n&(@?GCqHX3-Nr38IM@`VAd+`JRgKw zfD2$zPxiXJIA331*N{5D+T1N{ii(QH#>U?$5hk378nmUQB{(zA&dxZb9P#|t5nsMA zN4Be|s3eKHv-K>t%{k#yiv|&vR8|%h6-9=IMs-*`%2vl1xPo4l=wv_avy8}7ABKzB z*xKfMOjojtWG^LlYMngtR|;XiS?SpPDc?y#?9R3UE}TzTSH!2iJu9^9 zzP{&`H~7(Lca?2zM?5E|pT8HFQ@0U56_sc}(v=qwk&+Hi;gQxy=2$wxrp8+8d_FSh zxiWoo|E2MI6G|63PA!FjK+?fy{y*#uzyf}sf{1Sdkd9?F@I@^qCdPQl4f;}r{RAe! z^@etNXr#J{J2Uh5l>U2njjJ>YO3VNI<^N&t|NYkLOt_OGir|iBWj|}PWveAI$8~wGk3Mm9EUH%KAni>QR4I^(?hp_XIy^W4{8V)Z zI<@qPi^{u*DTU*mavP3DZxK$fqsi<_puF0*iu(syb`$i`qFgP#}t;$^g<7uEC z+?O$0v7XQQsk^oHa_*ySfDeGt$}HK%#Kc%wSQ1np>**b<6A-{k1{5}Asl12nATlzSR!s;+&k&LwGL+1_t=~`1pi`hKGi9%9G?69w;f%U2w3ot0*gfP+|g1 zpRSb)KvJ>s`Wewp>XgdKj@B}cZ%lo2W@rbyyG{OyiHR=XC*QrpjZ$i{;0tw-Nq5>9 zFVCaYV*MBp5OB0zL7Pcx!%njC-Axw#y}|5LS*|#iJ9mJ~c<(I{g$oJ_BGc3SPM<;D zm19imHmUcM*s%s|3;gQ=)9)Ael&&b6sBma#D0Z;ZIuakwHbtzHgFUmHOiH`KGUgaxZ*R#=Y33FRCRK zv9$+;FV^VGccol$Xf1d5FtQlF-P-nJ<>sd4w|Y1JiR)EEgEUQKTy*rp(vp?6b-Pum z;fx_b?`>QpJnD>3k1wqMZoJ5?-0IIcWeiD-ye-u7uA?twpQ^o_qP_e>Cu;NN&6z4) zP3FSuhG<(Kea{ZdONJ+|UX|B^hMLLVMbd2fXogT+jT3-cL@8O=+jpB6&oLJi?VsssdI?F-o?ZAx-u+A2 za1WtmuggqN|57IXIu_R1*;#vQE2lvjvhe&&dvsE!G3C|#KgBy{citb_7l;zT z_piNdXm4+CY63)lhlPb!#O1p6mn!I<^75^~9pHOSO-{mL9xgOaii;y7CccI}@wsW5 z?BgV-SG?gqk^vLZ7oPc8P~t~rrKg33g{mqpeR+ISQxge`x|w~7l>f!YyN`q%CXb8Ob0G_H0V%F_YL3s_W6?WyJ{G|UTGde-%J>8)wN9GeBhdOA9HVw4Tn zH8T1!@hDo%cqk|+{4URrK7aoF`gMH~U0|b)g#BSOi^iLa^RpvbaW9@2<(LkO#l=OL zbV4pg&nScP{dHC5fu5d1?;bO8M*{AF)%{+n%YW^({($L(goKdv#G#pq;$Ex2v!EQr zaT))8tR?31Cb;(C{Svkz9OR8N`S0?Ps73!Kk^^2dN5`$z)v+35wwSi|_8GXyEpBx_ z@6-L&r)}^0`;LDc#u#zcd+jhhbk*syPHhJp{#EG*&4J#eL!`zhe%Y|&_bFUO-HIyTFrBp z@c0A;v$S#}X{9zF6KubGun;;p`{^646i}LnL-#NP=hoM$2njWfjn7y58NZh1KQ5r) zF>7$FKaWjJJU!d0w|({uk4D@JN>!Tw#rLSFeqc9*GQtj%tXy1eV*LXHdj|);`zsHv zteA_J$HxtV`aa8t0e9oB|Mwv!g)^?J!(#Uh5u>=c_^ZIc`m;^3??%&|>2&~b!__zX z(q(6d{ETMz*OBQzfBsy07z`bTSR`|P=(qA_m%u;;0@_B_V6O-!n z%_A~y=t^IoA;7@E2$C}gRtpfu$k@1eP#ua+LjW4A$?3^*TBu}is>H(H32@}X!a|w! z?aiql@bsF_oo$*%Mk-56c7Y-1z1WAU^>k$w}nbV~EP}XyE+DaQDTnHEr%ZT!~gOkFKWg?g^XK0VxSaGdi05gRc~rIA{PNX zuWT$Wv(nR@fv}Rk0PssnMs~C{Q-88nPI{M{+tJ3x$-{$2#3d6jNM5ARpPy0i=7u|K zohF>>y_@UnlmC{bdTu92N6Y@f?dj+c0ZtFHm(0p&@zc9^@4~Uoi|z!%Q0cj4f*BZ< zmR9e*XGIad1g)}QGp1CxFsi(KHsNGell9}LPoIFUu220SBVb#ZslUwA$^UnLZs`48 zE?x7BMp^{n=|<%Yp-dI-$X5O3MfHV7nq)Z$4MIYGhwEeYG#`w(q&(I~f#Z>J8RclQ z(nvht-=33wDUDzxtPUQm_>f9X|7UrbjDq4h5Rj9-WyT;XYHGmk@DdOdwB$=DEiFY^ zs(t+qf}}MMxp{GXquKgI<(4s7#N}w!*(Y+E_{f)0jEVp!049Wz*Brn;v9_{OPrgSq zlUC&n=h^?_WQ4LSRpJG}W=_rm#YmcSAa+$4?brqnH8}e0#q)p%!^5bqt~Suu#{;!v zr7wLnKRhzBx4Szye>f(~15Ozf+Tr10&cYNCSLTpr`b{QaQ^r3$pw5COXJ}|xj&{R> z?_@Cd(dz0duo4c^uA!kAuQW`=)@`6gL4C80fjT%X^D8fNbMMlZV`5^qY#77aKrPnP zblO`|qY|;Eqin>X$dOy^PKH5p4BxA(>k11ow~OAFm)9h>C#%(Ae(_S-CLe|O=ax&oI8;5r z7zBB1NP#Y=gCRn^rm!~v4c#D;Cug+;w)LDPXvhFL7uxV1JsQx-e|#lDW|C?JqHhJ# zb3}WaY+fO>@k*?bPFw;4N0plg?%OB-paMox23Qs<*<;nc*a2)^CxxVQQ3r7EVr;hOwL7gnT7G+&8)3~1PE51;oq z(Qff1qkmar4U$QRGa4SQhC;FRul)HV0IRaM+*|pO3M#P2au2nk;lvFRW}xIc8^q9v zp&sUL*1M;QdpB8?vZ%&?5^_^jQOWn*nl4Hr#MPRpHRDSlpp~@Ck~Pe$cPH6rm5BQl zj}mc0^qYUmwsn-S_!zTTjME`}jDb4e2r0H!^XSjmzdqnjceFXRx4)k(WIsNTt+CVV zkAs6lOPe-6J}$iL3P+ybtN~$aYRbr%k*`|>ClzI5^G)yTvw<)AdU}7BmH@EUhW}7j zQ*(E6+TPz6;Nbyj%ja|-*u!wcOLPfw@#jaI)U>n&y6~*M&`D3!9uDd2NZHiIot2W6 z09L(mBURY`^8riX8_Ag7{76$(Ov*Y5m+$|6Mlk|81?vKc6hOwOz-P0xR$roHP1}@6 zgYf3%RaIYq391wDUBxnC0Rf}$E{nj49jo&}CCuH(%gtTgUF<@cxE^J;Q+7)NJJsr@ zM7S|DH8-!FCDwP-y#A*8*jI#_d*;`R_ldq+^5K(T{bwFLwa3C+2_a-73azAhdmov= zd%Ll;7H{7zba}&Hc+*Jr#|f`>!B8O~RpNG|LMeu^?}81pI$vMCzdvy(v_vTNioZxH zdCUT!{DLpAw`$1A2agcQH9W%CuQ7}Del72rR?=Ve<>j%N=M|0%7s7FZLo=-d3U*qr zV_3$OMr~?3W0@)lZ?T`Kc&Uq1M$#l->#~5q@tE@1;h&9Xl$9G|X_A!wb+1#uN#j?cLc4i1bv zLTsHGZh+wZ_+9u4GW~U18_qL*6ao3x$gcymG{&H>QcpxC44$Ug+t~fdvbu2LC#&bh z3fRQGddG?YdXQ#l&S&o>$NRZT4`-x@hj*Q*7{V$%R)Ea71xnJ-NlAs6F)6(*w_0iYlc!!d0;-pf&IlJvfdi6d} zrS;3-boX$sqk_`m6iRkSnLSt?Dyl} zzJ1$t_;&2edI9)G#&II7R?!34NeV^)0rJZCTEV6g^Z3=;Y6+OmxG^6C!M^v>cB$KD z+TnLElHTG|_QbrwtG)&6#@7u$=$M%m*kYLP+`+)Y;!jY0Z}7?W>8EU!KT*#jV!v30 z<<=u+Q(U0DMJe5+qM{-qa)vS=$&+#IzwfgT@*XfffJY@`b~KUM+H5)_Ad4oa8qpDE ze)+PryqsSM7_jPZj5eE^h6Z#g<|szI8iQGMC!S4fPPM8h0g?FNi+%XO=P-E>8rc8D zQ_$QxqFG3&!P;tFbXj!qR#wJ#m~*-ZVoOuZH;q(rz|FTwNh`ogGWr&xoaHc62XYp5 zCh0Lk-0u{N9PJqS-EH*)0F%Q*1*G@$=bPi@>_Gi>P((5y1ptSHW(E&YNnL#!it{TB zoSrl(4TdPE5IjmD(4eP#dyC4-+(3HW4I4JfEd9C>r$TRd2=6 zxB_1L8k&>fhh$L1X&O(UP2Vu$oDJs@wk$N=7D+D!B6WnJQ8-ye68-Uc&{6#Y*#~ap zA2Ei3~yx&w19z#PP zn46octM|aJ&{^IBNXe=9t)aVHjgGLTt*u1wD|g-TH8>E>gtO;&hxPC0+AI*T6i)xL ze`rl41ISX?pMCPUIdrl~QO_H_hNThr&B%S``>8X+%-r0)Jv|Pv!2r^Ty62^&7(;pW zQvq$W!eI)W)e_4t(qI|T{|>u_XL1fB-@OCB8|1PYm&MLx5m)fNRnj#Z@-ZY4&74>S zcdhju{%;n5qK|-H`=mznpZ32sxyQHUeVZ>zL-XslOR4S!IN0z+*v33RqtPo7IWwXu zFShQ}36l-McP}0wz{W6z2qC?F&uJD6>6s~!S5WD1SsU8c zIQ=tsF?5S{a*Sj)YSf`_cP?mnCw+7sq$Co#&cb$$ORs_V`I)B}_>8cnpw~CXOAG($ zzJ^Q^vd{bab$wHa-|5UYo#;wY?RxkK50^<)VPm?6ww5fSZ0VMiL;I390c~VSi5xQF zU<(JmKH%tAoT*=KQPE{}IatW1{^#!O)RF;%(C__%Lg`*s5Yo^@Ov|9|N?cM*o&S?p z6BCLX9=te^I5;(`L~KXIgIv#Uy8(jp`Z`?z!pEpgLK#8}&?VGy_a_!&Ka`hk%gAOB z+aaNPOLu+LRoZT%TkzW$oqX5PfMed_%ENq>a^QT#e)&l2{2IN$D|_{R`uZ`oFX?kU zKCIIZytA&qy8-WQu>I~^kEEA_RzoFq8ZP8UZ7C}(AdQ^5%j$r7_3WXv=XI+vH2X!6 zYC;cb%IcBwlmurPJP3!DsEq7*WiZ!S!4%Yc>SM96ccQz>| zOx6e23S+vnwws-F=@t%=4BS+0j=$yF`;nphQwsn6W$60+4SWQm45|7A^zD^@CIuk% zDKR~t8V@0$ty@DUL|*4Mt%KZ$_tYGHkl*>i$Wp%K$sZDsY|9gGQ@2*TuX%~nNcs*f zMkWAy7{J}X0wiXWz1x345hs@hXXk`2J1#8^dd5Drm`9B|D3G%JeHoM#N|`03epKiF zUn>gh>K6Hf+&;H;P!ZytehxMEhKVos=PnZ)=fOt?C*kS^xoKc>&!` zY;eCrnjdmva6~Bu$N0R2Ixhq4DM z)Y8)HO5#vCKtsojC8ebRyFfFTpkhnwy#&%{!FM}c0U8)1K00kddAU0*0rXi(YH(8E zO?E2D*P=V@vVO5BFh>JVDkv-asGcn*BEn0KYt9033=UEiwwN!OnRseoE+c=_)I+dkI~Vk z=B?PpdaBt2wkXt1YdAb|nW8G|$X{ zRSxdE0#_Vk5Ttz+ki6R$-b@#+bB!UZ7(+<Y~y5{JY0m7!rJW=KR)WyXmj8Ynl?0F$oLaC0jIyP~qP zQi~jY*gq?P-2Q&J65G*qw7=t#lA0F5H{xxRtl^oNdhdl1 zQ~z4GRV}`R^|?7BN=kOpuwtjo?CdTJzJJUc&_h3TSezYhEI8dm((@(M028~?C>0e+ zw4h-fZcc>-qQe!y3!WlGPXrnIv9)p`V^dyml&;ANhDIak_#n=;I!?$j5Yd-|ebxL* zJzW!IvW5Bi>(|gO5<7Z(O>4Z0!FiN<1H$rHDT|nxAzO^lj*dGrUE(6}9=coor0K`xqsgaoj{n`F$4H@Z zHzg-0=gzCRI9-U}09FFwj!*_9O<>qVL$~PNjy8>h2t(zx4#MEfJ<`&Gbtr=UBKbluZ?*U{A_iwvig zO0}Vn?f}{IfwD5{A{}xoZlbmSNiZ@)=gWI1EosMwSjV%I#6Q0{y)fh(axCReCZ9?5 z`Nf@!0X4I7uwwU&`YNUdz6D@CQceS=7-h&jUFkzZB#|BV{dWsN^VT|x-3nG>`d#1V zl%@3yLO@V1Jml}*RG#qvzOZ5s65-%07S2oN7$pWYqYYFzux zbYP=hcZ~#X%Mf*WOG>I9Ty2o1Cnt@vv>vqB-@S>AKPWUKH{|M*4S@tOk8Y8X0kNB! zo=&}zHNL`b4(lz8r! zJ5+b}(!%oBM9|$D!s&5s{Hptgp}v9Yt;E-ZPelH)TH5-!{s1optn$vFgR5{VgTw_uNv z4;~Z~38dF&etFVVQDbqjH!gZdMoDYHX@5k@ry1wni)e`|LJ0(v!>oZ-AoWnxjMUX5 z{toglhCOcUeA=a>e-Jbzrr2PaCxDYZye{2k;UYy-Xl~sK z4n#+$15ttm$%wqcAD0u5=|d$T{M16e6@LxgR{;R)| zdyX9Uwly_EZdRJ-M36F(qfNg+%tdQRJ}WkMCyz}_Gc_=v03HKAd4D6gh;($bEBzUO zYgcm~WGJ3)!t~RuMb6*g_)TxMwvA14sj@K zrYa)`09l~z)Ykg0B}I2!r755cY9#Qr_V+*Jip$Q*f`lLGBiZAV6Ts80ZEbKPBb}Yf z%+bJK`d9Z4)<*o9Jm0;n+tf=+M}?nO`-jtSOoR>l6tIETW4wBDV&|4t9E;h%I{zcm zmPrr~SJoa&C2%alcSr^svEAeN=0P-b`Mh>s#2-%=m)*TRs9>o3qeJ<+U@P8XW{!=6 zk`APn|HYvl!=?yiA1T7WjHh)iG6a3=Rmtdwb7cPXX^irnIkJ4}>9vUN_I4~4{!lrn zqgP@ah`OdaI+hT#`*-XNr4nMP^Bg`tK7&?wj*pLL8Sf(@qLD1+F9}&O4$`&1f9Elp zitkj<^^QY`2}Jp;0F;SIVQ%j2n>PhuOZY^g2mAQ~tO=#htm>1(=IIo6zd3upB; zrlU9co=}Ky@?rm#rla}qi(!H2!99+h)xt3jj`9R~ZYtrafdTpyeaS!6;rrOtDCdXL4XQajao!%V@{H{}_E_n>z9V@+J=H$-h0LLtci>0KlhN_CAlJiN*bIvO2jvaQ z0EV|;cYg4}d-tg`figzO>UnXa?3)`^&qbL$lT2i>R`88n)?gEUx^dp9jKDPca7|h) z*Kf?`>6a1l{+G^Rn;`AR{`1@UV%Kx2eKLI#XIsFD&H_=eroELu6TNf5pxt4 zhTKP9Whr0i)tfEBE!OWQUHj5-9I@$|uU8>hDCdsvaL}c2`Ln6*i|&Bksu=^K#sJ}E zX!VZwZN1JdmqI3n+{rdoU^pXvE&f`DBYpd_0pIm$sju8dNba6*zkl9RKwd4S@3y3{ zIMps#?yf9rtOotPk*2LB{`wV=48Yf%aAckjZ|SEfV?cXo+l~r25_6!tJ!AgZ}TOVfhdK(iaw5lcQ)& z%|i)KPjhCMldTQ^{hD<^15xQvxgh-|nG&ZAGJRY$3w;97OIivm=CoYo)AC{o|1bY# zTRGNH;bVcSCs)Z2=@8y3(_V%sDlB{VGJ$){)s@%zcyRjEi(-|tUWbGP4v8+tApIFv(%Q)SZnm*!Qd=Vr@N3|6wpti(z$nLX>v@zK%J$Fih)d8o1h+@bnM zXZ{GuHLNAS+X6HiPiU%}hJ#FK8Fv~`O zq&TSm^<9PaVwb?0t$f1YM(!(=r}1=vJxDV^rT$@Ht}soV89D03yOk;N?+jP#Bc1e< z<2jK_;q|ymefPi4C;Y)EloPB04FtF$6C6;vt)?VFDAVz88A%`d6>Zla{VM<6zQFt0 z^XK;u55)W!4W1uoRgUs=wzNIVq1VH`+8M%_WTEjKFf3Yt;w9Gbr|L;L6F`t!VqQ_RM3L|1a{$+EkXtsc-Z-83NC#SSPaH>zb-1tAuRR&32e;k&1Do<7s za3|WlBeNmpKaEtu924J(;+ehJpv+LX&YW1Zk&8OjmXTKS9{kK(6 z_OOKlL+nKNTG~{%O8b3hCI34m(*5)kh6K*kf;F_~w0jwEQ^mV$;{1>97k{opqZb&q zvNW%_|Qot14WD~;ap^S>~JrOQi^{{$dzuAUs^2712SJgMW!y|N+(Ql!j*o= z^yx<=s`eN3Z(Rz#7~PGje^^w{a-b_&)YJLGX~E?FNCa`2jMir9o6OxJ=EE5w*V(-2 zNLWBZnac^D^}RP(Ke#?(R2}lKWuOzImnouP`T^o*Hy*FOgA`dtB|FZi^nP?KY%Pan zAh{S>pB)^2$FJR@BAG(R;nb#tjdmmdp6@jWqZ-B&s}kNxFPMV}@bGRQH|VJlI$xtl(z+wKY!!Gohq+%Xv%2sq6YgxCkbVZxA@-IUu+`} zXSoxZw`sBYw-!OlTg_vvHn^`5qJ2>ld>sj3t1FEi*L`m8+LaEzY;G`|Wm~DBhwniy zc{B3!P+wm|V%YLsi(MRLx%thf?;i-&#?su9%MQ~~92piA;1oap_VA<=&~ue_Ko47a zLP7#$t^pku7e~pYL$>)B%x834)%QNtt8}1_>jF)G<8`G9u-qxER!`cS139OM!|OYf zPmbjiXtJ``a^?y%UWc*WA%5oRtwJi0ehIC}bd&pZ?rqMg<Qcq(9cFR$Y>apM1CI0M~Vl#r8g zQ8-L|!ZMgi)pL%<@1L`24#n;Dsyf|Rr7+W^@EC{dJ`e}+Q&?R5(~iOfiW)<`Ph zO%eSOzmuSm{i_7ei!0(z5d-sCGc(6A}|EoI?O$=W2t!N*p@q z1JfM&MMc>ItNvonTDeAC{>GR7^32gggM%xOnKvai#+CP#Jx!_E7xRWYZ{AKn9F}$Z zeH*l>t7y|#4%b6T>IE*h|E>T#00?eBItWECbHetUGm1-{4d6)bu_1Cw>e< zP*Y@VAnwtv`;he`WSz3KykSlxK~>0kz9pPeIM3s~5)&l4K6OsoC+Y= z6RoYmSDEadB;j7Lgduk`JnYf&;SR$?U785CypLeKRxqsh)eVsC(nvCk>qBTO^l*~% zzH4=ZNUZkI`9V?1=9XoQzwld=**?w{oW72lamY16!f(#Wz-Vo0slu`=7BYGMu!tO_ z?Ego8zx0BHc$Ctgm6bPyZzCgTzC8&{Ntpr@zr(@>GE3UpAGO)0>U_lxCFB|UVVH=T zn)(6Lhw}1rRpumN$5i0u@Q$o3O9h2CkajQ9IT(ndJGAYil(T~3-^zWy&2RNyDHB}q z5!b4UKl3;)4tE9jLSzCVt7;R&)tevh}0h}@FZsl#Z^8kUGQ0r8kBk?g~e4j z)Z{Avx6yJnunv);iUM}W-i!-r_fWYH%2`NyNZOQ@m6`gT1e4P@EbKsDJ|!i^h>PHV zV>Qn6*CWtJCngw!Kz3=(3&vnTEHZ?;K5b_Q!TI_^KU?=q#r?x?T5D&HF z8w1&)fWq-$fB#)*Xb0EAzj!SdEL|B6AaHXT^6}1Qc zgaU;`kHdg5S1q(CI$lkF+TpD)i3g|so25S=x-7dnmITWR$vKAoa;_GstEt92=7`vJvnLg zwQAx)nb?2Dd2gawd-)dVYM;;0ktzOb8C+?WXo#7@EYNx&7Qy^OG!&J@^N&u(L^5_l zNzHRiEG!eFqi$+ef#x!T0MSd;ZJvYg!9EH%Ko zzJfg{h2JhPzCwmhD+0`e*{;i0yUv^(l|SSpha4Ibei@2*)x$e#3q;*cNDT76kat0) zEzx(CrDe6TzIdreXLR;r+YQ)V8qN7RDdUaC_AcuPosv+bh8dF9v?ztU! zs)f@$qZjh0qS^b?-jBLeFw|_6lV;gNL0%PtDv+Lm_~36_ zPSQA!_wE(Jg@*>ow&deljdabr^TP>P_2aWMEKJPbHf8(!`w+*2X)M9^Xfw#zfTISK zA2P?Vq)?9`6_|&19ke~rJ77-l?D+U&Oiao>XBksdT17TO8OU6L`3cE^W5~dWP(^66 zwj^O;VKu|Gv-*E=qCl+(3=*oMxe?{IUMxT!Cz4tWvPWb@L@y~}rwH4P@&L~*e_#YQ zW_$ZG5SNcN_7g4Oe#l4pIJkb^{yxlsu0IGVsAN_tQ1UC5RAPFV(Xvcc{ry^(Q*zrXhX& z_z?2o5RI6i+MXbxp)r{m`6ugpn&{2HZCmV})GXP{Hb$PFry%CR#;T=?=cG?>SFD3o zrisEj4h!ZWDY|GSkh*s1mEe+NLmgx~$tvfczS~kQg#V3G1XNmv$AzNKF$B#tIj@KW z^?$H5sEQDNOR4b#e{$87^KOf45F0W&JOKs;9Rzv!?f39YH~3U5hKo9VC;}4a(QE8$_TaS zB2rj4fR}Y>w=3vnhWON_yfOLT9G1$@LU2#Y%MT#jF8Q;uZ*?E`$bA$S0#ThfSVx2% zECV^dFGp<=JdZm_S$gXFnT%nc$lCg-y4QDV(9j3L$vlC9_$2nJUr?P9fKY4e9SI5Q zxTf><(%x?kAobb7v<)1mAnCE;f>pyJP^TDlkYh< zzQO=Cn1l33RTcDj$|4tMXAwA!P!XISOhJ0KH%S;Q{v5v+km8-gr$b3t<`=-Nw^*)w zCLr+I5dY%eqh-X_1GMRA=5Xo`fosCFi0YTRVf-GBVqif+jdJ*t=%~z zQqwiPPKuz+WN;f7#9WT~(xm?SOxBB{5LehAW;#IJMXP>@Xl!W-dNT)+ah%{L=&gj# zCW?yffR6wIl3jkc9pPe&c@EGi`EFZJk2(h__#zHqxu8U0L=5E5%j!%ca6q6Px6CI_p-)JDzr6QXVOe@P6V!(6uTQ)Mq z_u*MdlDK#}S*Kr*_hI`SFOMJTiiwuklcwwDV?8~aje%I<;o)*B-rk_DHlbY)m17`8 zN?!hR9eQU3z70c^lAH=?{My<#qdUZ1{wT3~e;>80K-r8oHNlV*?O6e|KiF_Oc>O&I z{MmcT;6U2G?9JA*rlg>RH2pcfxBBnCZvvEN4xMtygN&n7Sl&QqDbMJT?1v}V^l~F3 z*Qn8|;|B9?@%Y0^fD#9*3B!O;Pj^EsiQ<0hP%3>ow3d^DD+BSD1R}!kChQ*ugG-f{ zm)CbmCcvn9Yf-(OHR2A9E_<#Y5m7W<-aj!W`}Sho-ITBC=}*KUL|js*{djsS4iPE^_)l36qhhXJG6<$kfw)U)M5h~WGukzjJ0ZmtiU=xK`&@K zxQU02rhZb#U0biK6JLMy7gqxdPmixb100`7eOG|6RaE;qOcvLeh2pH%BTq;ecY3~C&Xjw4#AvcALl1a7~ z9fxh5kXZfCv0(~&i{HIDNlKg%G&O)bA}TNeBp+mvUi#7UnqTj~iW8at1dGG0SP%A? zaj`iDQL+T!B9M9^VVqZ;2CD(iqMV!@IE%IY`=;2z^Xu!$v9Zvre_E9i$tY))Jc?uE z?aQVM`s41-S;$#*y!ImhOm*6L!Lp>S>$==^8yw@qFp!b4>0w6JK{Ab-5DD0;!fVHz zB3$8J{uE$oZMLf+JdlI|9>N$h&@WJdGlYHzs4~NNsd2jISHsG})`yh^-ynfc5iSIH z8s2cR-As_?d#Dd77;9m>jjq@bSEA+`+1rU*#GLdp-20ZayLjooxK<@M}+8w@QAqK&eVY4*j5!!>!y zt|4g|E%@|@ZrBrvrl=pMQ+Gi&8PMBj3XcZ;MzCHnJx$1F>mMwF#K2#HciC$^ys<)>Wf9a# zOGQT-)@}0sq$fGPN6z8xZ>Fz?JPv)8u6!#98$0jJ%Lfrgcl@JEN1vtc&+$2^UdP(D z!u0|}dt`)wfB-AMmI#Iv;N|tbs_Izww&o+lA9aj}va1i6m-xyuWc#DDjh8wN`0iRe zXur*08~XCrxP~!mP~aU^CV{Y2pL#AH4$j&pX4p)~{3WD=_A6>fUsza+Y@gzfN8V_* z>8`oGvB~4;I`Ta@wu(P8^Mdz@p~;&xeftz*dTpi;EQ^vjBD$bh3=F-+JiR83KtoKE zMucuA%+iCtv5Uvuz_~ta7xZ4AHbup@V7hf1{YtnyRWcoXMKpN)KZ|79QD;X5MHy4% zRwk%`|2u7Clni2sxftUeJsHJj*LC>9OlrP9s-D<4;9I4=F_C8~v{}#KQvP`?e)VMx z0@vpVfQU%qX?r0Kr^Phfna_(|XOkQMkT7VE5pY-{SeThvg8u@d1fAYzVS}RJHxgbc zXWwpJp`;B^-?;x6-Pl>!oeIJwF!lGeU=3 zT}$VB0{ruh(dQX>?sF&CF0QqK*efRDn(@2w_w2TATF#fG+xGWs%Z5Sq_7=(~2A=}1 zZbHj2+pgGX(@a5Q!NkJZT_PR{mpLsAX-uC*;VhLDqUc@Mp3684mFOxj1z?zKm$|8}jhKS!Brt<|#3H2yYACy!&Eg&9lSLjM8{LBRvze|%a95?4#m_{RnQ2~Xj`Yd3;FVdKFg}+&aTdV6 zyq4>WF620E^zdQ-l)cseZ!~h7Mp8-T(Llsr=tRz(mFny9Jjh+Gn4<;VwNLWJIUx)X zjvfccxko3Niq4B5*aK3nB4Y+(xXYSuKl3(VYOQpa#;Er@ciu<0JJFa``#4UkSe2w8 z+t2!bN>Ar)a`{$TaPa>AWzO|U5NIH&1A=jl`4icvPb*6vfZWcU8)DFXr7t7K(Zln$ z>}(|>Y%M>HG))}ZhiJsTX)&I-2Jpe0j}3LHeX-Fraurl1gPJ=8ie}({d<8MIj?yT41r_$eU@YB`1x~Xd&)x#qGymzOSUfnCoOLS zd!vuJ)GeIg;=;);nxAStZ{b}QtWklg)4IA!vm3TeW3fV((3D-mk=2lTq$r4 z4iG@KHC+7$VU36(7`&+R*civ3vA4D;T)ti&W%%JQS;7&v!zBBA?Rrq!;bf_ntOnm1 zf&)|giGn^eTUNAgv}C5~t6^(NKdCloXOEN%GBOqbdB2g0<2HpMj*GLcP*Tz`46+02 zCzcMc-47v|UlY=#>R0zkd$8MHM!T)tLJR+5SIk6caOh9`%Cy(9jeZ`z{#-YAc>Wm*eAmUw=BhR(|MPi1b0mGKq^LWd+KDB%2y^w7wPXpU zX_NF*sj@w#|1Pbhj~D!GfEzb%fUj9mQesg1TrgM$22s_Ol)x#66mVNx8vr`!n@qn= zpLX5CmIs&Az#vhM;Rz2ph>(uW@BVvfS)%211H5;oW!A@Gcxw}_TWc}1P8YQ z{qTZSM0k**0^^lAg2I?f?=!!L%=MqT2sX5upXJOMa7TYr(b!YA zg_W_bRg>w7`Io$h+fE`$Ifb>4vC^p(2I-M%r@3oM-LXncd1$JZRsV|abOA@lBA^R4R?AgXn zQpvtA@qWhdJ?H)Z&w1x`oYS$)%rp0M-`DloE<|c%PT?&y_4fKeeS_*ndmp#(E}lU2 z|J3gdb|sysK#7?CtFiF4DuYMf^#C?!zPjJ4mT~o8QBmB?@_=E>lbURr8!A5$Y-Hg8xs!5J+6jBwReFl4c zj9a%}e+){Q_8Lvzv+sJky6iRU;+`vjLlP6iVV8gYoQH>JYSF7eMaxRr0 zVrS@Xadn`@g`26ek|96A?+7vb<*y6-!k|r8IdkT|pPzt&8@t?BbLscr;nYlyg2dHI zMsGehV9V_0Hpg+}`oiAe9&3TNuRR`2(TV3;TR-Fon%N!yfeY0+ABZhWjLUWD6^~kqD zqQKi4VS$Ucx#^Xq;xzAiGjXQL4CL~@oCI}7 zj@?RpmeTSr>McvLPuHxMtAAgx+E#;cULTwK;N@IbrFun!{ITV{G#J(fcv%?kHJpcx z(dpmPEHl^bb2kdjDjs3`$m%e_!^r4K6rJt`ESia3d=sht0oSh#pHm9STP6L~UR84Q zu{13n&4@cS@tVHd)qggh^~j9nE9vN^*?`LvBQa~&gTCb|y7k?ew+M2yy!^Y4ZZpZ% zI&EwC_}ZoQ|B9=rtBW|b63Gz1uVA}_WE^^)W2D*Ni80hTo47cd z;)+1`@B{jlCQ{IDC%26>j*qxA2T1-z^M}el9ZsK|U~OzK*Rw>^fBaCtw3?ZmXSZ32 zJ^#f{fg-8WK3}>@MJ2wqs63m6$#9q7&mWeCtSY+-K45=+jLoal@pHsSpC3A1$+;FA zylIxgznjIqPfixu-wHy+Bv&S~mMIR14gzt2ftq&eBxqqJK;?PN5kUzE2{;*F@!e|E1IMF=bg` zOm(E#^|Z%bj}bEPncm~ZaA$Qlxxc^POR@OP8*^+tI1hk8!tVrcm?{%ux_0_0D=YIz z8o_sZ9Jnmxf7ttl{anU-y4`E<1P5QRx3?#es4-6*%E*Dh$$`Tgs`J%hrL~8=0i)gd zZL)7RFZ_wLn~zD>dp_G=HE_uLx8r8PBPH3%e5Jv~A4hx&=)!mj`q8<-D9eSSUc6{e zNtQCHCqxg5NJVJK+4WE*o1!7@R%AVZIO+-f(&k?fZ}iDPLeU03;`Xb7>`lL&w35D? zNCC$c5)yi!wIIb4c;?-n9t09iaME`LLDoM2p_yr>E?(f4uXY{NGW#87vVv z$u>Rfw0tZjlK9EBvs2R8F74^pW!p)VTawx1nr)ysz0Z$k``nB;F7TNL5;G8awUbFX!@Z%EzZDw3;!I#@^WD4AVWG6woN)HOrLr zb7AUNuyxTjEfKgXgDNF8TaHr%+B)hb2YGFMd>R`W#@B_H8H zGvgJD^r~gYH3lN>j7thtrMjN0g;0Q=xRAV;8gFqt#>Vb=B^U?mzog`a9qjBUH?!7$ z8Mb1IgI4*c>ieGd*K1>z(?0%M(c2F<_22vT$8J?-`ro$rRb?YVg?$vyAL5eoTq+Xp zm>W`3^}tPDxl%Da9i(y{PUus?79gI_o@JhVUfvJ=bx~XvlQ1PCjwwz^gA|-;Gt$|q zw8QUL`LY^)hs(%uEd3(lKep%%bOgqy6o>%>EB*@&h2;tfWR!4*HdGw2bbIf2C8<^w zRvoFKH2Sjtp(ERS83{^sOHVrYIL3$-#VoD{@ebSwR3xs(Sa7K^H*{`iLaC1|q#f+Y zegG+g_&0-h{)DR~SCX`mJ2=~BEF@=ZaP8XGOBtBisjdIh|LW)19^`wBa)Qb$`IVca@tT_zJ0`-H&IK2A%af6aP;9~e`5CS z)u_prQs1o0G71*|UjHzzn6~ILp19Vt{JYqJ^Gx9+t41JAVPi5b0G=!^E^Zem^KfS+ zHyd-P`VhkGl2cOPYrC)PKHu#MK2(w?a5Dp4YX`U~=ys(e6mG99AOr5*J2AZQ8u1sk z7MZ#QK$qbOY(}osAF(Y^vPVEkd+8EKlqQtOfP4`i37w~3UEPa=JLTlUmE#@@DHXkb zO)ICiE61?-_CFYtj?{!VT79P9&cZ-f*YHj&>9g6vXrA(xYS3;l z{*C_mLnP}V;szqkhi4CE>Ka_U$j-&p+}c`cmbori{R*hLf9q%z%XW>IB`}M=uCt*P$lJk<1&Wdd@!%u=B1PK^e z0o0yd8T(4D1(#AMgyB#NH*N`1@kFVP@#p}Qu{!>bHVS9iZ? zvXXtSLD!Wo>F}D0GrMRQGp?!C7aWbaE#ER5St!^Uf9<*|%j)7)ah6RD;_oRhU-HK{ zei@!-CXwtAUlNABK~*)rcccy!Bqh3Ha1x68#_=#)fZ3yS6vO*ZZ_aoI>4&M4k=154 zhy;zh>GypWJUu)p(YE>*KsYke)Bm1bf?+vIv#qHKDOAq#Q6#f2TZ7$*WU<}54Yp+> zv22WKruWKi8piuLMqg$ev@WYG8Q2h;qjn(rbTWc4($ccK({xgxO9`$jZyks|dCzA4 zKegiBIaQvcp99-HjWa?cHQv@8wnfexiFA;*GeJ)$#Tnr?5Vqv#fw2p#wLtvZ^_;)u z4eons!_?0d^L9IIF%tM!_9SdF*n=l6t}~ra!JhDI)5J`IPI}w91g#4f>gRhr>s>st zh3M%~u+-fZ1hZ%L^z}(>C0uCQMMKVthKzuecGpg?PkdkdzX7V75)2*_-`2>QSTQo(&Cok}MfC>%|4@agW0vaPU zEK;(wFL7fVV2ay)?m4^sx&5+kJG>N)6SO)6Z8CM?NxE0MW8WC;SvsV8Pd02IhG+Hu z(y&tjfcvORp|^_!YHw=V4@3oATw7015jGsNZf<2O#Bo4V@*pq}Ui59pJ)FF~3)Oc0 z`T3fKVW*6Yw#YMTPS>>$ej1{PAt7~rYBZNV|5Apz#!6fJJMK{^aa0qV9n4ds82f-U zxQS>cHmB;5zhZYuT{}!85?*sthX3HSKk~DX1cc=q9`s#h z4G6(?ijIC#(~HUy9|}lCTL}dA{>;wKzI*rX>(?SkAG=oyk<=Lx4&<*)x^VKdWZ{)CO9!Yek;3BM!2){>?i_RsX2LunNg@?k&~0Dm&y}(AM^<2<4s&_BAEiP zy}aLRqLh(IcwkRKad9!;f?jI2?tTH*Dk~ZEfCGg%Sdm8K5`A&w{SWXFg{eCaedWg8 zYmtTQgo&9@(me>Lh~!&G1pb;-WFcAcAG#iAXJ;b$rAj#m`IG* zR5iFU=ml86f}c3eh;tZ%jFFKMvK|PfiK(d%&fA^F{=lh6p`7SOP~MzFW{N=1R>hcL zcIev>!djB0d-`Gt@EJHxc=ieeX8=Wdt$D;H)r@0LNOjaHW+umWi51=~*kqAC3&w{t zu+pBWYPOXpLW7ls<@2*oewWL14w97ghJ(+>H|9Mek9M^z&%t*8lp^S_nb@1_xaZIz zuE4j|=JIY6vC45TApVy+DF~fH9vb{t%<<9)-~?H@KthXTZ9M`{t;0pS?9IKN}u>AH-m^$8LVrMeZXL-I{<=-Q_`m<$2{>qB zcl+P@(ue9&7Su|7;qv2aXiF1f4BzFtajP&;!xxllUL4r3WW6xChi@xY*8hL~1^nmw zdPIZLv#`uVMzKn&1^@ICg_hf%O_v+{yiwU$Q(vY=i=f1Y-E>iE*>-y#ZwqSprAmT@ z`B%&d8g1-g8AM;B@}8y(4DzLPbW~++O!d`XuTA#|gbT_=>pjkUb+}5~eReE2m7!jWU#2)Z?iQO2h)V_sM95Izq6gnptgoQ0 zB>krg=1*cfNAo7b<_ZS*S*S^C;s{tq+Bx$5P+Pm?f32y(ngclUeE+qk%i%b=(Wgkzn_k`YYL$~XP$hOlG&je|%%aEh48Z58;asCoQ<#U1=V(MKT9DWx>mG{r& z#PR+5A>&ZiYc$_X?U9ewN|G?lL%aXUAT1z3b=Y=B zkAa1+Pd0k!SVwKx@j6Y@8$ogsVrfDDM!wLO$@L*i!}~Omdt(LksHlXaG>aXFpH!QR zgjB=MH8VN67x=A&gre)1!1Hq<)iW@??)1YGoKDcVU7Qfq#9c%U42lx!H*6=8LKCK^ z0v0q}A}d`JE=4^If@1Wm&&@ySvWI1@0Uxs~Utg2wEV@eQE2{?&9+c!yTmut7H9sF4@U(?M5b~tX2vB80 z*RsRU$aC~?=iD}aex|5|i(=hQ2!#7GG*#D0PfJ6EHZ9D}zqho|GcXK{0tN*ou`Gah zw(pr5%wUivK`x19Wo9-&P2G5Pw7G`VIm>(rcm0E$154x77cY~1TXv1HNR80o$(#uB|;-wh>}JH4-$0th2^0;`Sb~` z)xaarthf14pK@=ZKAd`1(|!JJ65hhe`N4bH1?a0+Q-m3h?Mi!SH_u4ZnOankwptt`y z8USU>^P-paJJ%ThLY#~gTw_%g6^i`bQAqC4;^9bCiGQXNf0L{S?>A~T_Pkp|2wOqN z7Zo&sraj=P-dB^Uku|UT$Nk+$Yto0>R_VEn34JA6$IQPxt;^E#YvQ1UMn;N<*Bs#4 zq|D@L^RN&v5cUOt0iYv13VV=WsuB++4r;LQnpB+>gbX@J$=)LCjWP;(op?Wd+-<_d z$VkxP@u-%uVodM9jT_=FA{X$=<;z@1hFO}q>Bp|vVgEZ zxK4iLBP5Cp=R;(nUP#b_i5+O1Kbz1KcCZ*)Nui}C?JpC6v*G-~vC17-;p^+GsHoV! z$@l!FZ#z>XDy)S+n>n05jf^eCz%D2SK>MluZrVv8`=#gNix^;+Ku?N4^B)RZ3qJd* zxc`Cv``oO=W#ghA10V8l|F`r-+JpwzH(<4zY-9sZi~RMhFp1&DTs6mz11GEpw>TbB~w6*zyDk0mdO9g%a zP1vv5rMiQq!8CJx<`x#vrkLG+Tx;Q&YXC34#?rOaRw(d(3=L6PmM1b(#d?1v4%^mA z8WoR}ByQel#z7nOXQe5yxT4YhlY{@}?XXP1T96UYns;JmX7V*c;T7RspZ zAl?+w4yzj68}=0m_xi&nQ1C!vj>lWgDE0V8I4Iugq~J+X-N#P+DmoPUD(S$o#vj?} z!(rn~Q-Kn63@dYM`oG4xe~!5S6DOS-462d)4pg&$nc>d&IUbbe7sU$3S5{HxB3EH9 z0%C--F^=QdhBo?8lvA=}yhn7sVHgsGag$jm*aW$lepI)uJ=T1Ynl!jYbC zMCh8D$byO5mia{71 z0*C|TtT$S{PfCJF7mZbCCt{pAs+_LqxAAlVM!Rmmk$wPaLX}Xb`c& z>+FL|jYSL5`P8X$?~y(nG)qe!Ve0prk1D!&R3-cG~C_TwGYE z?Cqs4sWS@CfZc+I&ugJSAYLWNV>rr{Xl;!M44@*|34&BQ^Iav=An5I77x=_o0PkO?W;S*>Lv1trU zFSe&)Bmm^OK!3(EFl_*>4|T@{atcUOJ%O{Wi=3LejO0>djLpc%U>DxS*)e>)G>j)D zC56=72O=Cey?xcIA38VEZqquZD7R!hB|Z>La(p8gx8AB zX-|yTEodHa=0F3F&;p>a#|F5ruLdV9(|x(eP&&VnAaH@`P)aYtEv%W_oh#dhRv)eW zy4<~V?0#jX%8VzeJ2Z4lJ|)k^{WEIs2EcMxu5d`e1jWP8uP*W|Q)YvEb)QdBy6bZcj$frK3P+w``WX_`w5&r61V8pPF7m4O4lKgUIWineG{dlB zE6gTgkFv2`%;y{#v&LJ4V?a-D39He=%?)8W7#d@CRgL<1pc0QjsPxp-Lbpk3HA+qT z=?YkGEv?9hdH`z&9uEjl5P%BD1mv0O3G%QyDcr?GmgP`DA?y4JsXe3AWr#=li-{B< z8*Lq(0pt1Zi4uIAeT#Aiqt8x@fMf3)MU6xxd%$D1A4wHvxK7l}5jnYeH1_Sjon2j+ zjC0{*eA@`z8px@Xf&Bx=4#E#`m>S%7#qa5kVDL!!0cCVjet{hdJv9D8K6mZ5qixFv z8s$+)O>1>Q?g_4d_};xoJ!XE za5R!I8NB@MxeiNc-f@ba@@HurnS~xjN{apJD*$Txm=FPbL8d$^moRm10fEQof5R~I z=FJwgIPZU64#Z10e1G!Wwl z2O+dCSXj2*dH2}|Zso#+=koc^K>|UL?cEDcAG>m7Y2(_Wk8iM4{LYp3V#NMLg+c6^ z9{GY`d?O-;!4mD~sR7z!dIDfT(x^Mqd$cZH@@0nvST=IA><(zG$v(8oaZeFcxVSj>?A3Tu{z}VnU@+{n|`jeMH5G_4a&71E1t{T7Kut@k7FE1-J1iKwPO{LblZeq z6M7Ufe-``uGsj@EMd7r#0<+IABSPN^yn_E@V8orZN@jn8n;tMKntWJs}jAkZ@){PqUTG#`{g~=^)fX#+y)BV`adJvC)+gg~`$uD7Bf{*v5B9 zftRt!+G@M8Ven2m&#z55Q-JS>slU_^jncG~ju^{tBa;6($)~hXt_&_X#WykyvfR0{ zY3966svcU~rsig5AKIP|j>DP~;oG>m!7ETYI|2Eo>WDu)jfNc6;Z6n4ePQqioP*OW z2UiLCbTFQrWCELj@)ns)0|RDcJ$#IOEdDwvXPD5U;Lt>~hv+Q?^bI&kgy)0N5e#OW zr(b{V*w!FOUV?8G;Wy_Kw2-Tf-Wi~M`OvLQgXf{4$hg8J28*oSoM%s-^hN7~gS52+ z6ddqXq(JioqDjX&#>>s^j6@RAkcUAXSWPGmfPDba#LNaD$?F~D%*u5APQM=aVT_zW zva8Ayfes2K4;B`*&oG2FTFHDmjCIkch6ds1&`<{n#RyQ~^^t72DN(0`Gp1U!ziHrI zV4|CTTPg0Dj49DQdveHRurgv{>hMiRMUgUfp9m?{QMFqj49fez0aitX_;}$w($pGa zPl5p1^S&=HPyW8LuXh#H;PH)^w}GqIS6Ek92XT~q6zjV8N`M9O{lw-`S+jUfsuxUO zUtde>7FD`^QB#u@o$K6`o5K677l`Kp+<;PtHDG5*bys(HW}O+5(%WrZ5RxcNM%z1Y zxOV3lqRnt#;GiELAFnr;Ml!z#(eU&2Vf|Qnw)>V0cuK(tyb8v&6YT}=Sq2hqRx;4T zw>PVom}Saaf7DDo#K)qO(qb#A{Ghxau$0-O_mR6lwohjuwHInGqyq5-4vma#U)XGf+@G+gWTD}1^F=IbwFm(`Cb6EC(-Xp6D#5q_FVBtVo z`koL>T4)WYe$Iz`CMO-TYUdist~S>r6S?n|f*KLJyO|z*4{{(E}U|bTu-^(RAU#6o*|7SQJv#0JoP6ZkV}|G`cSo zNWkLqvQpR)P5m?Ww{HFUI@z|3X?ayE`0RmaALr{WVe$1SR8&&x?d;UYDmyKLDgo2~ z6gYugEL`$Ia=qWh6DJ686VA+pR-2;YOs~KZ17Pz(h0*`!I2k%u zc64kN{s^CkVvGV0$PU*K#IVQOlr&8$1Hyag_;!)!v$~gK4A%xp z8=w?qU~uvIhD6@cY-*QLS;=V!`6mn-|AmBr%)VEeVaVv;;(`Ec1M)8rYM&V}?BH#MS*h_d z=2Q*jLqh=M5L9O1p7)^1!2CU&jRXjdVPDX)-ZhYb*kV6VNe9X@GFE(C%Fz!CyA505;o2WVU$o<_VIoUWKW<59Spn$v@V zTwLcpv|Tutz{vryI<5U5KpZhKr2O-2ybrxFh*zrZ2XYQ%uTBIvL;fLDsrt8MG`ENeE`-qF!f`6$FfRhwl(+G=_Lkf5%m9Esw`Ha#~Njrj)X28@Bk z+N^=P$96g(l(Fz0lpz2~ME+x4@U!@%=eG#}Q0jYvormXnM1(t%oyw-|fzy>qHsTc+ z8gA#~TgOZp7#X>y_h^V7I&>(DOy+)*ZHWxBiFiRm(!VTB5_1!&z?eNM7^i@IJya?K zjJL6oD*r-^q^Kr_SfL(A<({C`|4jqN;?_$T`WYP&aZO);+mdjYEG6|6Dw$jZ<#=B~_=y=AXzZB;r1fkt-jq7^&vmQ| zZb*dXp@N3}YjgPLk#KnAR44no<{fg2_FV4bf1}2*6fm2w)+0= zQumD1&Yk7Rk3_{CB-b-BJ>B{7CO%Xy1rm7X)KhS(VlSbZzO=PJI{j{h=^G*^%`!CAo$D+F07h&M@_Bi?T4plgTY(`-o(I z;nS~HPw)aT+RV&cnq?1pHvFR?zp1qv>ju^T(XZHUhkpJvtC*pVtm9}+VWQu#p|2T*_oxwWROCz>*v?z~XAGt2r1ZKC1|fozqjS+qk@XZU zyRu3=b=Cr!<>HFCjco1%z(?S9%#-hn55{PpuO75QCndt`UmP41&fiRUS8qNyFzJTD zrLqwkuM7Y7%uh2IkNdNO? zQ=nM5#bJO&+G9w~5av1rJ$!i6&aSt>-SO)762}|hp%DEj6j_TS1X3e6B=)RF5vk53u$FKoXxS*Nb^5^N(r>N5vzdf|bx`wSmJ2{Q@lYM^~{c!Ou8zI>Uuc4-~4q2W|grYG=bpi)2r}uK>1rRmUa8~^Sg>@?iB4Lcl+JK zwv3Dbc5Jg+aHZb5B|XR2)mD`U6&mCKTUq?;N|6fUh`|O&M6w*)ZHMn@@(1C`4h|fM z(nL;b>80XsKl=GoIK=E*?~bvR$9;TfJ$ytl)^V5ZRaF;m&!BORq8oeyysF3`K8ZavwqFE{9e6p{Fj=klQW0gM_tCIpV__J~;oVg&lg+C&= z714zSnMFm4`}f;m#0K_eJTNZ#D2VSU_>{heE!^3A`K{MXqiVMS&VnaLrlMSXGHb0t z*FdUq#9`pHR~!zObmQV^F*|CiVSElVtDwTK;bF^8WfKz7BPn#>efXBcSjB{A$FLdQps%lM^8W`0UV|Y9@AOSzds!0V9qycI{Mz03OExzK0I) zR$mECfdRke;)gRSEk(1f`loXZf>c6adO@uWFDsxC=?KWkFdhp@AgC2lT>|YvwgZqT zBhgn0U+Uyre>-K1)1pwM4x~%tW>Z>`l9#8{-RlKl zh!>3_g*q|@W)jq~*fa8*I%;aHahWlWtu?OqIMyL1(IMXhWh!IYziY@x`s}3Wj;3*b zQwUWOfL4M_-kPj8xpefE_hv58PXZ@Io*^Bl!)EN2K|xwt9pXsS3?z_@k(zpj2}6Ip zCo6cfhxnk;y02Us?x~i+TjY3tJAshG z)ekgyqTX_2zIqkmvCUQ=@rO7h?$(8sjH>^Tm6N+Z(p1-uL4K&BBsld_bq1fij#7|J z0)&Jleu$riK|j7z;>dDQYl>02&zs$5CyH+nSZF3b^C{h4@uuTp`gfR*1<2)x^Cts$ zUbIPqVE*=B{Zt*)bqQLdADH^3&2HtsVp==H1R)*<=WY+*Tb_r&0dq4u=v`C0QEcNt zId5|?vKGlFS(3`>ZJkHK!9x`MwJ$F#ds?mi5jKp4zI$x%b;N_KP^e)WTyJd6pdWPe zevv9Zhcg}v5X>JQxI9-^^E{PgN(c!_5;A2bq(YK;${a$H zd7jR9)!yfv-}}D*ygu!{ZNu`c`?>Gy`cBv5ucmT#J2f*k2?@#ebBglnBqUoxNJvPZ zQ<32-cZwZ(@ITwE6m@J!NN5;{{~;xbe#V3^Qre!oaE5Y#ia;x}gOTM_JqZaL$vJsh z4TtZ)OWidtX%%gjoid8jxmz8rJSfyGmK}Y$;cH-IOA9ycT{R}PT}p)A7c*aTKYa1J zrC(szCo^8NfHK~;1FtonXdHQ>_TnP9Y0(Gjc3a=ar^X%%+kav|ad*FFy>iLW!fMrQ z?D|QD5VKs(iP;tJdt#k8<6IYdYuw^Wd3W%0*~WZ^wrq8?zdT20_6?f0 zEnX6;{RQ4^(l^~!yZS78isloxjvH*Qw6~^eU7KfDJv=--ti6qD=9%Z<4zK=dH|YjW zl7E-u_(t}>f9L<-#r&Tu_VU{OpSSkkSNh*qOt@|+=hkt9uXe>n>d)R3jfc7Zcux|f z+7Y>36P_|2Rf9Fd)}As`4zr6668RQA96pbjh2P}oiwX&8aPQg}QmqqIwzSNU=NOoF z=%B=(r1PmTY#6hZJ?txIWNiH7@uBl-B^+2eX$schkhH9znmI5@I)*y{J}vbo5W3l{<_48!i(3iIqS-{z!#H|9}8*cL9Uc7FR#r|kn_-?#tgknm%?g?IefUozdU`)6#YIoH=IDfd-n}N zre`XL?qm-Mhyce3p%1nyVYCC@)~d^eQ2tVtjtaK_W?g@9y2g4x!3i zVRa^h(+(FeUfj8JCxy4s*QdvfjEwX%@{^LzBsOaEG2X9CP~$efcFkLfH?sEHwQDIe z7I*GUjExPL*+$m3HZ~?Sntn7sCi&iVRYYP>9RoI$qrBnMr;^ShTIuw14idee16k@! zaw-Sn59>q-GtpY)JBQq@x_-QK!j3gKSu^!A&u;dQ7Www~?o|vsmAZX=u+zG*+fWeO z=^;PMaoS5-S|hW|@*Ee{)gM<4j{W-Ob=PR>*bg`Fp!RX$C}q~*`vi_%)Pp~M7=G%* zZQ^5;Q#^k@plhPdELW}M<41Y3oN>G2K!t;qf-X|hTwH#|c`5DVX9JlJg)7BGM;i(T z1_aR1&{PKb#2c<}Hk#$)!xaR>Gt=Hgu5tl)eRkAHmaGNq{6}|!sF=Z@0$nx5=P76Ya3$Fme%*Jb^S@dZ60=^s@Ot>^-Me=;Y9q#M zi*O3P8Q*F1IXXHDQY^c?fB29(N!@Yy%WkGU6bBAc?6ak%IE`yym*-<#U3bf9FTUOW zb_?YWZ2f1_?yTypC#}gpYjQu=P4{D;oEvMtapT6+l2n{!``fEIH|u)~?jBUm9&jSiBu<~(`bxC$ZCzd6Jc}N2!*dH0UF~gcc_wwy_%-*9YS96E1S~qU zhX;eDd-LwFR0{HHrcJG`-~9H>v*}MqR$_7SDQW4=1UGeFUS0vcf`*oscX*M!ygb*{ z1taOx#6$Ai?iBlwv#8>d%lncN3At)tnmu?QPU|c4+1bZaTfg*ho9F6o@hl&Sb9Y;w z8BW#8P)ch0@Th=o=ygVObduI^{+Zkul|r~1+EotdsWP|5VN z(e$le;f$K<-RU2n@QO^fE>Z>;FXF`CzH_J4WqF49y1!ad-oJm3JA?Y_wmPx@lpR41 zRdH+5kEZY6u?lZAcrvaQQ}{+nIxlqQ+zj4-N}*Y=$o7bo)JA~7dyi+^*x9Ofl5gc@ z_wl$Zo@OB=BvjDzNBDLdcev7$(2*lYjvh^a|DKhe53&ddh{rpqi24f8w;1-A^LN8-)w|_ z-CeX)OmEV4awz0a#MSrZ-HDOsaD1JN>T+IH^}cM?$ZS$l629$c@k>3m)w-}`^FdqO zn>ReH!THXM77o->abvO}1jivh2vz{P^$RK8sf+ zDVO*w&iW8MWy9Ab2&o%~%ubXbu=<8k(v ziHY7pr}S=rqnxAPY8JeDuzqBAjJ#^ghyAuF#y{&~t{EFM`DJxYPkSfiQhupz%$JGD+ZU4)~(QKKE+wDfxdV)8noo< zN{+A^G{M?oW~-5qt_i=p_l=%zNPj}-?T-7gak|EPY9haH;lNo|y8t;gC&BYcm&&Mp ze~|9~ElAt!K1-pzTk^*>j_Ud|nR)Nt5#-Qt&erRdxvgip+(F~m)T&=SO22*Yai2Fg zAGVM0=X77%>s1;zHzd6=9V{zkYiT*ze}m#sU>P$J9X!xYe{Vgkh!^)OTM&5TKtlo?d_t~hrGMIb@(EZ&PV$B`i6&x2QmvQafNLo z_xk$vE9J3m1S)bt0q=u5W=;$oiy?O??Zx@UE#fCD?|4^Q8XNO7K~u7zdU$4|cHHnP z^{LVVd3pJ%F4i1i$WYSZft5M6|i@f}@jBTS$KUHi(do zhBG&3>nq1jpkn>_Bx1({yW;(?!U}_HMrO}GV)88AO5CDLE>eqSR^qV$5hrebn{z4! z^19!tl&;B3pt76zex`2^dgQrt=Pq2RqjT8mvt64-#5}%CGd(>$FffoO{7o6hE-G@H zk#D)$dj$TySap{CA<)f**DovP&#$v%ahlyU*k3bj$kwO z6x&xWN*_F!n3so@&jnWX35gN4RCz8@`2M{VcU((L3x2$}w>K|OBwT6!(wTS#beM12 z+1Iz#W}gk(hkJxBPw)S6%=S3#L3;m%$v(Ohdw}!sX7q~f?#Ri>X=v=K&F&9$-rQIZ z7PBuOnYEwnjg5d+9YE2W_0L|TVYTm*?^nM~*xmQ>QH~Kw$E`*C^;I=+nf~PC<72S)V~d>hx6L!V-Y4;L4KlW(Z z+Dz>ti@l+|w}G{y4CQ_%?-n!R-=j{;!;yd@Cta4NwDcVh9XWCfi>;NRpY+_|3`gUW zBbSIMA-`<>0!QesJ$qDR#lwSku2aVDF>pD?&%Z2qD|@i@SzU*do*Q9l=E*;8~mUDL*UX}^Sn z)nsq!9)7)kaYjeI(Rrs>mHcZ}J1m|u44r*ZQnH3WrcFmRBQgCX_!M@MsnEBm6Zgj{ zc-h%y*$6Ic(^VZVR5OV|zsnqg;<`<8?f?z`@ygas2a^NPU+tASf-+A<@1w?@mX_9C zbdR^=>B{=XVy|=ku=U+!u=?dtD#xI>^nwC00Rg&4fh#L3CHff}Jd(Df1Pf0&uzuDE z;0jcHRxvBXwWVp}td5b{EWVh>kF!l1Inwr#qj*+c)jrq$QS{h)IW*UwmVDh;+a z2>4_y60?onB*(Go_8Cmk0aeukc2YQ?}lF~eeEtWWxX+No--*tg1wB|Z6h-)69W)YNNC3OwOlhYn3I zyLeZ~P#t5U1@M)Wl=KM^C|i4cqjPBJdRWcNRx|d>wzjsu&5cFWT^~YmkHzsQD)y|b zEYMx!EQeD4fKSKGT3#Df`ZXRG{15HDeq6`cm?OB#o8^pxf{3v2F}N zt;F^LAL6+Fku%cC16$`~%+Jq1eCSZk_2c&|8@s!eX#}e$^!T-)74%Jx^ak`^Oi(M& zc4i|Kfc)s`JyYY}H$@-q2r*_+Q!1Ymo!siNW;EU%_pWz=`c7j>KYyA4tK7MsrTt7L zS&{QN6D@O~UstpQYrR?StCufb*5}(ZN@kQmx7)`9A3jv)2^SU?*3S^-;!;S_pqE>7 zbH92uwrX&vzdT->BXrd544{BQSiT9`+2hBLz0wZSQh3*o*oeofZIG^R;>2o4T;|?|C0}O%AHTxK$QoQV zA-BHT>d|+wxaW^`A@Hv@pKR=}!<$>n_Y&p2A)OCnwr{T%+o4JLghWI{0M^N{0oA=y zjr(IO4zd6(|sCJswe13^JM)Yv;aJADXJ zadEM+J-xkdi+`?`^-az$qgTeNi~v`#S7N6GeBuaIRCy*gjUGioMk-g0Z`fE{J2^Qq z`PF>+;^jLwGcyBi%KwvbMmq$IU(*f+^}r4y<{fJ4>PU3uau+6{OR_IW!@XtlvBKVb$|LhaMz~C zX;+Iilbg4ad19TSPTa&9FSeh|=BpL_3ee;tbwJ3od93^0txGyOQFSKQbQVy_b#!!8 zlAA|nApn#v|BS)sz6f}3vCV)sDR_~&a7!j3iRHJ_Bsr66XA-!DYLbx=MSU+e|dTq78;vW z>=&#H;E?w@zAWvvf%^yKzL4gmzkaP<>U2**fr4G0lm2AvH)x3aq}?Q>r1aA@FV$9~ zu6`Mw0b*nz9s=B}rtWUq<}eyYK1Eek%8R!}Vp`2|Tg`yceI+U@D_3dw{EPq<7+6_V zsLm-WY8Trb9U|=9nFS>TMGiO|4GE%E<(xJIuD(eD0Re+|rj3v~x3Oi9pF@gH3N0^^P{~!AroEs^;leh0G|--wU!o*bj_}YhVsE_)1Tfk<d=71VYA{{4{HpqGLjp!%e?;-#Z+=t#x6myw-MYO?QhjU<3# zF8t{^Pc=3=8d5t<6ndu}5)}fe$;spr1f`^UE%Ko&)4q*{aJg@Mer3baN#Z%^yoG11 z3RFt0di+Te?4RWHlO|uE0>R<1Ru6per7_MDBT$uWtc;7q1fGhFij8$$Te_RBnW~w( zy2r5Zy(nyDjSqqeAS53fABZ9;2HUMN)L z+F|vSxt;0EYJXc^{ZVWgXPHzI)TLe2+t{%cmBN;mmd=H7rD(hv9W@Oxl?7#_eV$%t zlIRz%x4!3NU5w~q)#tQTWS%nVny2>dAfz2sc;qn)2AlNjY;yD1{0bC=p`jtRFcOlM zHz<#VdPLbxo6jf2vTVH#t0KYrHhyQor}#bTnzv0&UqiWl6Uj@8#a84rv$Q;L!bBOc z7z#I;D$c@a@BXsBzMh^tIh_i@tlMvbWhis8ymW)Qbrhd~CX%ys=-q&|cdQ{yZq{@{Rs)U-70 zZx;=XmblHe5y0#Xt;QR3q|1|=8h0eiHYNx(y%z0ud?|Rs%9|p`Kfr+q~MHxsPt7|CV0$ zlwifOw?JQ+ds&Z#B{X3Ri??sytPF*zQfQs$Lo0IBG9myin$X zaCJpRMMXzvuX4^j|e_CcZq0z&P=UEZ3sZTt59CA8g)$#sooVRK~FMcTsBw>8*3Yp2-I1lAp_QTmt4VP$GqWy)s0Ek!8}BgW9vh}j&WOa@RVT-Rn)z5 zW#GDvO^zD(kjj#ze)$g(PEO8)2d{zCCY}x6_z@t$%5%RtS@nQ~gCG~z&-DCW9^k>M zz4EUX7ZyHLv>Ui@rv$4pQ&kPF9rkjcot>=^6IQIAaVP-<*FDjY@!mdY)7k&gBOhPq zT;2Vwtoj!(PD7cpW+<8cc1j`f7>E`KC3F(V&x_+)_1*USZfd*hyDSadci&v?iz_tV z{3%)nO`_uGX~K|O_4VVCwc&Ab8R{t(y(K4&KT7%;^PgW z{~2E1HbPXVP3oBOb|IibCI9fFSdCla?ho>~`eX);*^zp5oUirukTt%Pl4 zIKbFxe6;t3cElZX^X@YDO*YjmDEJ>f$aN?gT)sRHlmRm+T@#8$XGuv(b#*m-sY08P zho64HfE*ba=_|B3%f|=}9ICLe>18d5HB3p)o2b9(*pqSfd(e3=U1DHi;oU{ez`&6D z<_#mi-fPX&?G%;$Hcfl??v3QX%EQf#iVJTCLbP0hRr9M0;<10Q>#$kV-@GBK>^zVX z4i7<@>q>#u0KmJmlamB1-Mq$>$LrQ#U%x8*8KDW=P5(Ia)YWA;TuXvg72qHw`Dm{0 zRY&}Ig|MUcXl43Ttg|uf3klc96kW?xL>lcfhBS+4|>_G%TMeerqijpD{HZ1#CnKVfr8? zSu)jQ-?x3K0!-mpQ|s(mN5|!yjbGLQj%9xPJHxTv3+`5E-5yk z9}7NGPa(IwFl6ACfyH%kSp!}}EfQArIJDAlTNFh2VJlv7Q3IE#9p9S~XqwwVNvBkE z@0dvcU3iNY7ItF$&_jd=c|jNlA(In7sa(GoJ>jL)*W9XNE}!{R6*# z_yF6t>&LMjuWiB{f{D7U&w8(Ux6i`Le1>~ZLj!@C83L4%Gn>y2IF7Jt4TJaX;t(KE zc?6zua#~?ypJ98TCnx8no>F4)KmsKJ)|S4|0<%vwTKJTM1jAvKA?9hBZ$zQ@q&Ha# zDPb)g9u*&-a%$^>=o8#Q3U5+APt$k_-N&#PK_e^9mFgd0VR7DAbBwE}q$5)un0Lad z_V)SUY;4~5{wjZDbDqOw>7pKgd)}Qcl{l#j`5$-e7uQHqzj5>C?D86&)KYwM^39&2 zdqVf2w7@`;_`)gvSG`aq1dCVxvK4O!;C5ZL^OuK^z%Kv1Z!+$r4VQLedwV+_6NiAb zbQ!ed;$r71f@A;jgQBAOxw-Rh>@%+}#Qd}sKghwsuV3;sJp9x4(2>Pes8BflGW#xp zPVMzG(urVU3!;f6!l@hW_I7gUxU+#3}?UZ{Q7M83WWh}^4UjOOEnE=D=hGAAlhDStv$Wl+?J96Y=YAfAnJj>tY$k1Uz8b>IV=GU>JLV1oTNaZijg#ibq zH=44!+#%5I=9qN5D_dQ5zj|A31bfBPHv}$mgZAO6ox5wclB8);A8a?>;<5qTs_i^c1q9@4_ z3U?&9s?zlrM3Scu|BQ_RpreT<9N4{Q&*8&|rSB{h5YN7sgyKx_pJsn;R)vf$HP(r4GU3M14GhKQ(N@Czd)6ek#XP3PA(x6Ml&bpP*SA6 z*LBcWkW3^50AsSUE+(mOr{|a?J*TXkmXZQC{S!3qj>r{z`$hP5h1*)p?m-MdW7pR1 z2(ZpXYr1?b|o-!u#_#R<9*Cnv#=MfQn}56++Qc zPNcFfrJOq~B4USY1d3V;B+9jfp<6rCTKI&k5Bv(+e+s=kxT@G{K<;mZCR+TSnV#N0 zF^3hT2#}`uXnYs{*S>H4{sFg+hKByx6c`C_?p&wy+oYxv^Zf;(UVQ4u1K%PS|B8gIhb#k;Z z5r(r!%+X`Vrep1UJPuxtxYAmJK$5C+sYA%c@!97=Is)~E$7tE zfnOkK($L&I+p_QolvecQ$-<(d71Bf&4uBa4v&kQ-%< zp4>8aVi)y<_w@a&0KLDdl2ae)^5%@0*igqId!?BKY>Be@BmjN^_|2 zI#{dhivzZsS1e0~4Oil4z0bQ4o0@FMfzJY&)c>}dF^*71d%(QMKjr#4uZU&u*ZJK! zAR^F=#rN%4Y9F5mQ)rs7L-ger?W!{bu%_{O)!#cG1`t2o%Y9KbS=JI1;Ba}5(FQ@< z61vr-+VzBzjun(~jm5I$?#9|OrDYoB#|gXOeJ39mje5Elw(6tOPrJrfO7Mm+kULCK zFQ>?adBp7@^CTgwk+S*pIq!5x+zIAD6Cl;OzK96MC}or5Q6DNRWz~Irea|H|9fL>6 zB0`%weoG;vee!!Q@5z&$!`A0GA6LZ`YVe?BLXRL!WkZwYk2EN@3*^ew=7R%tc=P=M zW=AfWij`R}mhGf}wPhedskr(HyYorv`CWB_U%%(x7PwmUBqoN2hf`cUItH>t4j&5@ z`B#$@zuSBldu7<-+s7qY=|r_f+2Q8lMwIm(*u8se@wIs;S99|;MNR-tVD2VUq5g_j zZ{PCs^9QXmPRuSNY{ujls+0sN7RZ?JV{vu-*DndyV0b!axwfKj8cpAIO>~DLgBy8w;YTtvsQM=x~7j6^11D#~A~B z8R^vBaF}Q-zeb%%PDqeVY#g0mK{y2=AWs=9pb$Alb#?kDedF``MYjGoIx{mfp-;uf zzHe-7#IIAgA-p%yUD!Ne1;Y_)?#2bW?dk2~IuS^CC9-70HfuE9q$1csediH+^%dN}A0*rmn5M{p|RC&DciM>pGg8^k5N{Z*P?9XK1DZL>JVXW@#f$ zaPwwz*Mufdc*7_%a;d2SRf9U|ZMeo14XD^%U0vIVlD^suj1MI98W7rGruDE@@0KQ&i<4SHTVb*k5^cZNu3u9V5fk72@}1jhzKkSdLlh1 z3rdB_j)KSju^-q0a`z5Z4S1Q_+T6RS?}&6w4^|CWuh@F1B-2p0tLy9Q>+15*`@2Z> z!NXy@9^7gE?dw-sax&UVtD7nAOeQ%{YNL#pfc;})ZVNI|vBSOYeeYVV$T{Ow3O=ls z1@#s_pG05~YkLg^M1a zSKl+}RwfNv`2M{{jN+GWp$6Yj8o|zNuts>*?w`P4&wo;$Dvot{Y zfng#K3;VNy(rH+;7-t|^H>&MvU&i}X(dm-uB+u32K zh1EQH`jne70C5c?k(jDSdz*WD_6yxnpxP^LJBl4sadrUbNH(LxJg==yb1F%3I|Cew zLc0mwDGjlI0w(0xK!xU}rWdVd*tdul;yxpF8WR&!IGu0QR69P;uak`cU)AT&A2=D6 zWmqMhfCJH10dY?E%lOKXHfrypK6C5V5vqg9&7CbRA4g`nYcYqLdSE&z~z`T_m#`}gkAs`V>lWJ=B=CMojz1ElAWgqCx`@5ZVVZ;eCDJn@x52jnuB8AOfMP~%#e=I3 z?xK!WIs6ybbA(o#i2yZnr0{WoK`62w4q2ak+QqZ$ zA(#=8EDUo0#BEr6e@W0!cpULzXf{CIpFe%71I2jpf=u%gS_66h;KCFfcmzkh;^y%Q z$bzgRIFaAKFgNRNq~~;XnW@Mvx(i4X`97Y-F;q&*E=yW{eKFB6iz#A$8@ou%otD=Z z1rL5g1k5_Uc?_;6-#*@OM3eejTX!Eg;QqVgn4IF3D_un5!I++ubYrY3sk>M)sR^sK zje!dg9iQwyVM37mZC9+G;!{lHE604?3Qm`m%=z2-U8^fAGLato&=_E`jm!q_Jwc%o z1PVgl+;3}VM@{Yp?WY3BpT_RTm2^!rS`oY79cOo?p}-JBM%T2@zC~$$@F2eJHL5%6 zoRxCrqRXTdxvyNs`X1#lqfcwlL=vhzWn{R*V2!Fo3mrLr+#30}_0xh6v3>M<8ylf_ z27*Y))$5eGNh&vgixy7Z>>05031L6&SDxxyXW}g*7w?gu0nN zPIs7#{9=)kkXRebA3gIoiZ8K6UO}O^=w9vh;}!4rnkGV!`5?PFhgI7zQMr2Nf^U=4w0D(8!yfY8=jPQ4Tj z{YSP#UEjaArRi+pP~{5SOX~;enJuwNcIC4N-6zStROIm;zy|=g@G#RgD_M3xI7t6G z++X31vJ6))R>EjD;vd`&bi5Tw0o#wG$OxM9nNMEmcXr z#2*QJSZxb!Lm-Svz3q6Fyn#W$|wBY*E-oAZh6VVr-o??FIj__ z2NyDpKRx`bbc;fEfAi)IE*W*1XjX%+-nw-Q_03SQ{B7=mZ6Qm`%gdnefLv%Se;nzY zPCzP|o8S-^U#Si~$kxy5li%w^!#0vX2xc?d>Qi*6L#KI3(GDI5AdE;&!2~9#~84cR$Dd`Hy<}ER?^w7E?QXj zM7)cXI|N013C9hoaLaio$y_7sUBN7+?G*5-wbayr(1BwwUv7=De2>8AiQ~u3%*-&N zvYYx0HV#_=iR!k$V^3S$G99LgHo)NIppC-+C3b)7If^urP5ENbDNJx6##$7_;nOj( zP_{l{Y3mm*?naicY50Qjo9n1<;oqE!U<9&4guVTi+yp9&0H~3Eyl1*KYR>-P87d>D z&!$3NwMTXHWY{1@Ao@I-oLlUBEQ0rYTLGRDk^~V0DNo&gvNf|tA*WHhXBigJ$ugD8z3y`=SLw1xVLR_ zy+UR$+))tyTy^1?I>vxA+`B%wiOmSq8?VQ;8w$SG;K|aKFuWG(Zr?84f2>jvQ$P9{ zkyi#}nF2pY8n{J?B=t>NnwpYG-;lK*=zRfM5(5=JspZa*GHeh0{Yi5OAEsq7yMfav zV_A34^oB?bB704J3m*EsVKo>6f{yz1*fmc28te;c;0PktU?Zp*Xdz>TEzT8IsK@{7 z!NV(o@4^t8g+pUu!ys4iEpJq7%f9@SRx_vPH#+fl;_8tmY-nxW+B25Cf1Gk}=f%ty zhQtO^ox`pj8W(|FKT+bo#oLN<;BEIo^2#F^Zz3Bus$Pay^UOQzFZ@^Rrn3DXVt31n zHA)gYRx~klk#XB1^knJW`Hj&-&p@w%U&?>{CG%>`Qtp5R8!XR3BAd$;Hhmmlh@=4^ z(pk(%uwi5d$HRvZrA*AMCKsB-ZO{`_TvRj7$NN9FK^gEBo_)HNmz;X_|CYEb8kjhe zakq%dQ%E3z?BWMtCNT5g7Kq)8Mk%)xy~cVkmTgKtHSUU^YNqSn`}uXR zcSS>Ir@z7BzsvZXHuzWD7sQNoa!jCS(EIy*cK0Ts#o!iyB&6`u)fq_X66C7sH=s)9Z^281ho92`I3 zT%l_9TN|URf|0qE#nq1pXlIgqyo=)vtb``09f90pl7-mosh&)u)40zeKh2F63MpwoKi0ACC`TI|I~9KKTA71I~|=)fC|I67x@_7VYK>$u(c`^kOFl_gZOu1}83dl1LoE|@WCUN7-F7FDG0a3=Iy5tP`{X85zJik!7!QKh6d=e5R zWmgRU24l8toZXm9mcD~XFE}hQM$(pH5clkvo|e|(UDWKoNX&Jz6M-iY!!+j<2l+joc5_5ftL75YZ|Y(!}T!z7;EKsek(P7Q_|4VzTbA^3$oI zQ{?12oiJ)K%47O+kWD)RGB!Uk;TF921oWIBqo+_4s4gS&1(xM1WuTy-0I3=Q8MnH> zvf8r$)>K7KGHI$77O($@A}_;6F(}fg+E> z)z;F2$aV6|m*i38B=MTAQgKy-DR170+yB|eK7<=Zn0v(XMHzs%06{S%@v^c)e-#qB zGA+B*732XC24bQmDwuj7vVViAydItW^Cz#^SUQv z2N;JAjo~N}5s~L&cVDEWjFDEhAfH9IqrkHfIusj$sXVV3d0$-FwmTm>(){nc`WTmRm@d5n#`erPBT?7fU#j@SwOkw-Fer%wkw zd^mN=kEoJXADl__G0E*T$6-V|4Zu*!=BKiOfx%|=tz97rhD_yx%J%l6&kjgB%^{Ob zS_M6akDHrm$~sB?+-dtvK4(yy4Oszhi0k+Rkn|wS#vHrmS7%aephR`!=j^g6cMd#n zWX*Ba%tz}Wq#ls&9iPYUhnRh2E14{QHKa}X+__^>%1CWuz{BSRweN?)>GDI+lu#aU z7Jt0-{JG#D@s8&?6IYnO(Nk#hpsPrk0DO8j{$f*G-##&FB{o_jtWEV-{PmLHkp8mo zxgP)GvI|yLBSpimSP}`Zyp8~Q4#c=&MzGxin*!K4tn#b_h;;bd|1=ShwvlFs?4hZt z>F489GS_rIgzwa;;;bx)L#hdCLLwq>G~UQ^Z9RDKAk4!PST%%?Iy%G(MwTHPqlAmc zs(8w%K_qiowgxz9HM?zQMoc7V@UXD3{L=wY;B9PdAV*{xRbszgQdjrl^2SW%gNw|1 z4zMe6QxM|k2-PcbxH~gc!x732p&r5%z78@9eyFEPrAkq%Uk1b2%%d2E5s?zc3 zX)qWuP?#{F@vq%qsnj`eV&zkUWEoKn}1PfO&Ss}xn1N80r z6B`9%0>_vG`6F**?y&#p`4kN|OhWdznW1JI4l!$-uq>T#1N$^(67`jvhR-Mrmb9?Y zG)e*|1L^-CH)`9WN-s(}0zZKgmZnF9f0-2+5P7alR*dWLDMt(4!ib0L+w(|m9=9G+jQ05a zWZ4MO@CO_!RC|%2U;pu4;>$rRZz?H1r&1|i-f>a}T-0P&{^Sw!Du`-0Y#SK(a)whpJ)c+NeI4c6Y;zt1Sa!IM1#Ans;jG#;3ge{2S-Yng*N$cV)$?XzV09`oc8{Y6O2^X@#)ZGc1R_EG*PXP?KqB zh}*)AkQARzHaRaTVrNf~Z~XjOfkzwrnmEemdd=pRsVN50jz|55>cj3+J#0Ny(B9aH z$$BDgqaw%9BwK_7+z(V9r&7D(o{`zTv=o0LbmSiVwKb5FKXhoT-78%_MtEDTt;1Gg zh$$kZ^l(j%3PK(H5@Q|gPIWpFOhQAY-RIC8_7?#nMHHp{xu@m!@hEc<7IUf{>h&f$ z%jkeYlt_WYd{1uPJ46BT6g6~t-OcGz#0{WHKqCM{)N>dXJ=5m-UF`AV8s|5Qk_tb8 z{S2QCh<^H%iq#?h!{$PMnNGT9mUddJ*@nxMSw+z)nMr7aAfUK#(+-q9pq%{DME)1;ha=FdR1Ylc<$VC#k_Lk_`F$eQ9^>} z>Z9&%IFJ3ZCHj_HTF5Ts=%yon^4C3qEtjr&KIVBBw2}ZdZu48Wa7z#)fQ*N{o;p&Z zkfI<7OR@%671@D>T_#&95q+{lPLKr!P|gg2ZM@8cmEiA#Pzk z-UgJep{u)6L3yBjd1l7?ekJAxk-gK`(mG28MGt0$UGY^uMqE)7i6^XzjzSv@M_7yP zLrk5vvf=A?;^ccHip@UTOR*}Hbo!SspIJm9N8;4h&j?HjTCHIya%PxJGJ>taSNnZp z0b0CvKXCSx6ejO*UGcxXe2(t|l^q%x@&5W9_!ejR3`c1DIMBIoyoA~SptDa1f*r*9 zCW1BIxC|T8P(VXnjg6$e4G2;JL886Ynb;^QBD8d+!~y9{patN5^nSitUA_pICTeSF zUU-BDz5h#y8=09_K9eX6+U3oI{mDW!Wza`>!%H_t;}7UPK14kh>{ye;9x<}m;a zWM0r5=A2w*Wh-#6FbkkX?!2jkS(e?j z7YouMTf$Y^9%}}x6d8fhO>@fgSfNL6Z#1+(0#e zIuQ#wd>9~=lR$-Dy))X6pbqmq3Q7c+l3jS#vtW~f*y;=+IvVe=xShc9TQbkf(Jtt%h~hQQ-F-2 zqMkaniaw749gOGIIl>f#Q2Y9o-g_5EidUsghgJ=TTo3lhiSOYFOb_ zFTJ_%1#`MV*>gfuZ?A<&%q=dQlw{R7eP|`9`J#8eli%I}V6<9mu<@T~=e)mdbTPGPc}Ht|2QypBT#07_gM!|sq3NazYEFbw5>L1p(vDb61}`bgmW1iSi2wAf%67xn4(CAk3gqZ{W6kXz28KRQ(7Y)0W4*SCiCR+uA~Nq4+;*xe+?5|VKsb=G@_)Hg7NZ59HJRP!(Czd-W$9z z{M*u>>XwY1>T`)Go)+NYrcuFlPp6;L4zd>EqYJMnZz3Q;_k0O=`JXwHl{JlXg=bB5 zdfYpO5p(YfK|EdIZI9ySD^G8f+Pn45TDvm06Q5=i6okN^-i;f>-l8ksi-hkS-Oe&;+Ni#&>A`D_@$7UlQ&;nswBHVuLc=_gShD(Z)t0h&8CY%?#*`$jNc$$eBLN*n3i0BB--&rBimu4WBhrzfm+oH^~(C%J?rfHnr|l$ zPt|>sa^5n>vTw&>F)^_dCoXZ)>*TE+LvRg#g#yb3G$r)0$>V5ZZ{NPfx(iWi=Vj{V zh8GnS;L#zk`VxTTrluU8Iw5{&@+jTU&ktTgU&c#VdrtE_cYKktf!uJ%(lSyh2}6lv z=`mrJXrHo;mD{Ovx0c_drZDc`zYZY;fm@RTKUg`*;uo%6y3|a0zy(7FkZ;S@7p}yr zv}0(l{_(ZXk5VwbDCL24P|OC3(NT6;B3Fqx0mWE!*~CT zjv7Ya+C2C(o&R!F4We~3>_d36C=@68v)!2xCE zuXwTuY$f<7tkw==089uJ3QI~}Wg`GIc`RoYS~IZ{(ud73{GLqzuW_KX3- z0Ie`SAw7gE;h9u$Tb`=zrl-dvD;|f2$~r$#2P5r#32?bQ<_KdMh|#I^dHo1x3HY@$ zJr*QOZ{C2(7#0!H*3@KdY3YuTHr|l-?s(@DOti$YbEvFRtG`dc$O8R2a6tN26Q|SQ zGI8j9?%R8L4wyZ7(!jrv5P0$9!lCYd2gfFrPJ7)%?BqJJ8ykh$@MLk;Kxb3MAF!WIW#s z+7%ikrW6+z79e;es5SH*yG05o$~X)63o1kZ@Njrc3@^Puqz}bP-sZ@Q@XdBTBtAOf zZ{oYjTDi= zlft{AuP<(a?B(X z;2fLwd+z=Ecr?oTa=m+Anh`|XjIxbi!^39`LU;6jT-(6h)GD4x5U8mPYgbDvMw#nX zCNauz@gnjq-dv?b6y)z}GGKZ85nJP((q=C#yS%xGeX*8vT zO3DzK5~3tYk`_fYNR%QZ%_S5XBo!qTX?{Py+|RS0d+&Yh_ucP)kK^sWk7cd=hu`r1 zeXr{}uk$=FI-Es6wl;|>N%q?z>mRpq=O#t(RxLl$Zgiy-PrVO8cr@CYOy9DkqkX!T z9>=f0+w_x`Y7flL3b)*fjI7+QZkO==8Ni=gdy{qe=#6T_;AiW3%I~QR-2LLki>gV$ z7^a9(NU{J&eC>Q>t>xt8ny(n`iB1ra78;j81UwKkWKLF?Ei<`!H!6yRjb}g4;Fi&< z3kCKeVWuOc(A|7`w5qzz*FTV;^GP#r$c>vfE519Cy*_YjyI)|o$?%tZTetS;(YHtO zzpk@%#3CdYf%HmG`Os3AH01bGlbgFhppZPdx^$SYpC~8`xQ7MQa-=K8N{=Krl3Y=H z6)y?$_b)6eYF;Z^Ca2*zen+{^s26;&j-KB>L+z%y;ROPCv@QPHTBJ5}=1ffYHosE5 zT2Lw5w}o|l)myvS2Fo@NsUbuB00%3V^+hh~YG?I%vh3K~B-nXf3dSGOon7g1d4DVq zyhN1{p%yGyAY(2f;^d3196vXoZR~n`RR5zAa@t!|q~C1$wd115MT76X4f%xMziH;h znke-@`f^h&)#;KYO3KRDM!$n9@VmL$#5e4{BED5|6frYr%s?%kQ#{B~?+&*wIeAw2 z52PFL%0`adSNxALi(E>IbIGu(CziCewN(enrxz;?3^dliz9Pz4kv>lI(+d(qT-lKz zb(3L*8HH#q*3{G_B1|(&-Usg`N7T8fG9)T$+4zC;=gk}H^$c!DvlU=u^*tNB9xO%M zw{F@r$^%^&j~8-mwy!4HUh+^epl=@(?d$Sz#|5nuTr;FcxwE4)t)>S48jV93WlTcC z#R;EkYXvnB&Qa8;ND~2Wrj|%7@Rn^uWTqWj9o$7kRyJ>IHIp=QjGBI<@rgzvWLad`Q64 z@EI;YSJ@AS7xk-4SS6HjF?pKDjtB0c1H*VxW5`9l&=;ARvCT#eK$AFL%u*Z-TWqpP z%n{$K1D?<{UpVFr$Wz|x=T!}x@%ed#pa!SLMydqW<^JSTv5@IvVx&8hy! zu<-IXjMi@bHpi;<2!*l6>$PjwzGD|&yKWs>DFwy+J~bstI)D8|&2u+4mMqD7Z|f#n z0GA6W5{YWLHfq4oKpp^L>gs~{nw;r<{0?gvlc{p<`rg61!Dgc46+EC(hIeVgVN^sm zfArm^Cf4N98kic1=Q0WkI@jdBRQ>7=O??lad`Q+l#rcPY?m{g8s9!h-br}Qrgj4lR zP^Hr*$0bMpBf7z3=*qpkel|8n!c#hSjOUt+gs2DBc`m)8y}jKx5={&4M93 z$vB@!uL|0u)g5DYZt7t$oS4KNWTy?E9uYLhReS7dX{@Vsh>wZeW?}IW znmpfNm3>L+oE}RBA{Z27XqRGQOUDmn(P2XKal1Oi`Ch$x0k7&2x6{+pv$C%BO(fH% z{@L4c;j-<@uzbj|K26$Yc6Qm0y@IA%O9ZBQN%fbO21ywrC6%+MXb_iZ-?$I`#z}np z{vBo;J^~s$K8Y{!;&dF(uQoKyF5Wx){NFj}tAB6=*d~tfI*?MPGQj`p&vGE$8~ddW&iV4Ffg9$gr*bUb~1rS*rNnxieajZB+|MqB&RP^t4$A8KotxZk}S z7srN20n?{`{`syMD(U*aFqH7kZ}x(=eR8svZQj+qyk)w;Q;VFO)I9}*6&`KAh~ig4 z;Tl=q$?S;NUuf^npI#SO-m=9cbo%1rNJDRyfm;J<;*L)lcrNuAX4FMQq;!Jwlfg!k zl9D%#h&$;Se|@N{V+WmKmMju35%JRf;y;-`2G2;Zf>fDleg|_!?^I+|Hk&td)d3ZH zAELyVvicN`7(ik8we8=(pVa9=$(yH+K}M5s640^^kG}ZRW3@z|K5LTP66c4^K&Uyj z`@!SK(7<1|Upq)__Seynk>2V0H8cZKGZ3X&jz#POoSq1aQ@Qg~em~$FDW; zW0QwKzBRC~ghW=hqtZJb&r9~4O7PiJ9w~UR>CilwI<3rr>*M+lVZt}!7qJ)MsOyasl ziQFO)*^-cwxUaRfEaJ?J#m~yNCnP4)!>O*3=)l9UkAwt|phVxkxI=8XQVGCPRja*L zIS$vJzy3Pct}ORcL57QNi`$|rCG!-+Q+}?JHqB(zrKXUtn9aV=Ij2tqtF$bo1edR zktdmhZm4CGqdi6PbxJAy2mMgk$^5jUu^5x4T!zY5>(QE{(*tL7N(6h+2 zTA$!_TKg|L1pI)lf;p_o`m3=zEQn@-sp4R};spQ0pD1pw)hT$U6e2Bx@o;?w4^l5N zF|+3WPR`Cq{k$Pv6c*5?oK3U+^yt?&*DP4@LQG8TH|!&Xcl4lj+}pvOTGG$m;kv*Q z!_HCrcL9jC+&>1qu8XDnV%TWn$n zEbh8L*PuW4<2ZdL(^|{|&_>7>TV(;v&;?+DS=^%TD7R|#(TNclRbnjyT`iD_m>{(6A|S4BaJfE ztZ6%F11gP%V1uyX8jkn=Qomt4)@-;&mt%VJbQxfQ=Q>!S*mmIHgU*CN@`6jC{oIT`Of&w|v{ z1=y+d!tnF3`w z8>vwR?02vE8)wbhM`V&G#~ecf2SGmb6NJ>}mKLPl_qE52dE#)zInZEtL)G87`<`C- z%v&xSXiOgc#MP^FZyMptNBw#KjMOsF_&0BC;`6pl^E-Q%r_}hBkJK`@{Z*@`+vJRl zF4TX`Mn^8eW}54nxLH^1bb1Bp8rutngl_OD=iRimK$dVWvNIikQxw!jZS16%SDrtA ze%|rHy$(I*U!ayC1IGOY``Io*a>5l6l?^>aHgNyJ158jZDa>#eemZDvh2$<2n*lU7 zYnF)oN0K)LjpIC4tXd_|SLn&XTmiGCHQevLAzKDj-`r76VA2>{4ERCHOw}=KB;I~$ z|BJ=m*n$A&1;cP?vu$eekAg*UW!U<^Jym{oqN^9A< zNCq(ktSR)NG|xv2_itI4m7vuCn$=sfWKi!z+#tjyjypcRC!Mr)Ni6;`UZBN*?{dwZtyo~UDsAOK~#W-LE!w8 zYH$x(g9F(oELj(*$b?I$CPA_VMp{ z!NXm%b0qGe17W4HVOW`j+$?^N+<^^>isS0CYD54h=9eR=!Ldh?h|8w`Wb|SAs0LmHmsOv`8a20UBVnc zOVz^g$cHZ2GEh|G%RViD)iGGnc>kQ^Y-mi?WXuu zSf~y?{`5+GQ@){2s7?d5SMJU8MR~XS@U&0q{Netw|Ijvwd@VVA+6mw9zka!$FW-U8 zQNk0!zwU0Zzj*ZX+4b`>X}?(qtWsmP3{%sG zY!rC7PHX9K_Qu@H==UWG7bs*;G%fU0S4KsQOR-S>X-Yb$wEHizNnNpk%)73;_j={p zC{CV?yLMeKdCpN#3VU6XRDr+ZuRmW5Ssvl36$fkEx(&Ju9UP)6Z2EI^MZ7=tTVu@Q zj*gzjS$#-?r>CdMO|%gqI)NPvz!8>DJlF8rnf+uTJG%$OUGYrI`4ZOv%4R}lHhc%N7aa`zM@dqLLS_Qjg7U~x@>Wmr!~=a2+gMY z(_>@BcxR=jL%_LTa_4hkwe8teG}2C<%=rGKr#L4^f|u=B(V5^SVyUh080o%HzW%z~ zL3!MxXHQ@P&%=k|qN(KWpq2Bd==*Ouo`u|>9M5jWf0yGSXacbtheS#*bj3srM)@HJ zT(7Txf@=Z1%C#U7*9spfB}L*Vka30$D+0|$QwOBqU)Z<+AFm1KW3)p?Bt9siqOwtQ z`}U|Ojk@dCk5+%4g)1w9L;CqpHA17K>uF688b*33uDh?Mt<6>-DXnr@QuGziuD4G6 z`CTIwJX{-c7YLH4M3ZLNBc-YPi%!>AKAk-q2U+5_SUlNzKW}LXWk{hmx4vJeEITT z@`{5u)1-{iBP4?uha|28gnJ0>9bfRHbFmI+g^Hd(-2nqkh&hmKC?%mnT zWi_30STj^pzxN5xalLp1zWu7Ic@AC$hRN{b=_K5wHuhaZxSle{Ulor7h#v)d|4I+T zGxTOIHQ73xGakB~^{-U2aN)w|2gApZ2JU`zBIreBy}yg zD~UHh4$qL23j%`xZxD)3!5Zrw{2*Pisj^Dz4Ey9;N_f_jx^Ur*R<7(JDvH^3=~Q78 z&Q`kpxQ_x^ip1s3M^7$IKT$R-^&jBpt4B*5)QqfFj~|G=kH6HFT$|?7d+T^ZPF?E| z2_d^8>3f{MaQwhan$G=GgKs@J5`1)F}N*OPsxekfTe3PrM? zlNbuUaUfU544`N#DlF`$t1)vXI*A-0yK7k%xge`HA~L1t(o~2sTs$cU5qoAkspfnR>^i=W-QgSxMqKECg)R*JX!f0p|EY}P7 zmNgbOI>)M|(39gG$Sc5jEqjyAXaFYFN5AoP90YG$50gdq*+W0sCEg-{fAC%KOxDd)9ncY{n7!Z2KGLL>di)tCk4EHIA7PP+oJX1v}aGx z9zAlat}PueDj{RcE<1RzmD(_8P597r&_6{+MkXb75JIRJ*tE%%Z;D6zaGK3z>E}cb zmQiYgZTZ^$axTO<5Zq~{iOJ^=A9P>CBeTf8G7m!{EWQBG&>?_RK-5VN8wm0<_H}V_ z`?KM8ngua>vSEk*Qc=O_V#*SGloz0X8PP14Et@yP%?J%;+a%Ze!-b|LF+i6BTjzJ_ zK|p|WdIjihM~ry?<_-Ny03W`H{9uWK&m%LHVO~V@t22X_xKMPlP0`{6$Vcg~b`-e| zSq@e-CN$hI4!~gk#tqVUP%WBISWlysgXiw36+PW+{?u*n)SFGKiZQw3$3~|+?uiIk zUcSU_9E&LOMCftIcdB~f?3n%hd5(=5)erSLCk~>6Av!WD60(WsUU1G(Nl;0RVP=e; z)i^o#*6+-JFc2y)A~i9QgAACq)#YoOB`(RZbD@YlY8eE5Z1qFe8QND^=G!41_uOr^ zx3%-a?M&X+1@MUI0%M*Q3&viw>5MFiOcN(ggm}c=0jsMmp@LzM?{t*Nc;G!-W3V5{9jXiawRo^X??YxcZ; zm6f#(1VnN0r19gQXJ@1ES`?NK4&7QG)NwUYSzI)QKH!`k=XoSC*VLO18XAB!e(CrV z_%CAOiQTq56{Q&!E=MjSfqaWwGY3Ha_7{-I=o(T04j3kXFL`=MlxF~KAY~Z_wD8at z50{g3hwDw6wcegbNI&|^j=$He*=%hcx4rB!6dO|W9qJd0Epaj?Po`Tub*i8#1eReh zhGO=EByfN(U1g{P{06xI;WkBP5m(shyWltqyzFwr_MGOY+eR>{SU(&li`X|a`~-Xn z*6E>7i*=7MYN`Hgc=)7s;E}w58cpCbUtdAe-MA;7kft}Lzm*-(Enzw=o$Wwhf`5EX zyvZADML~hlue&!@3@JJ_gBspsR!A;5%YE1E;yA!+Z-4 zlgfbq^yV(0q^X7EP~{R`C|H-A)0ScHl4?ob2!IAMd?L_EOG|h4pj1x`moV#DT15z+8Kx^mcmu#|? z6&1OnFMHZOzUq3Rh;G}Cu_}#Qg!HS=qL5q_oxM6lPg(bC;eVI~7M7Op>sRlebsL}* z3sOK04b@q7s@mGxa5CP<*!n;!;QT^DHg-}A#`H%xM{vmna30R`tVT}j&fS~=0QuC}%f z%|Rf#aoZI$$BnZF9a9e$nS5s45BHuTmxjU*Sh@0POgC*|eI_kVJ_yE5p2JiM6tbN( z=f;}+iSkd1i;I60Prf&fq%?i=Nm^uM2G5bp`TH3pvJo#vs;Zuukfw-8u6c5$bLKSx z<;_keb5)von-6Y&*=>@%1qL4--wo=+G96S_Iax7gZ0hYHye`2^vF_fV-Fkc%5lJ}V z58(#M^dw^&^4IOW7S~I=OvYwkJ~XI#pt4_pk59f_xYrK;HfJ}J2N1>fl~ySodUa*@ zSAG&xR|_AQJorF2Qy)(~6%6s`#He@5C`F=DWaS*yxGgD5> z#c|Q1w9$3~sbs)_`csFPU54f8;I&&h0+DRx+rmsZQ{aMn@-BuSOqC@3k;;_6jKGl- zDoX{0tHx?2&6OH~S!OCKl2G4i^aW(PB${sAKSAK3xFlA{+BpA-mFtD@TSdo{>&C}pp%y6dYAm$wiRU`$AJ%t_1?Uc7oyi(_#*(RE9RP-m zvjZcxzWgNGf=2r83IFFGn@yV>933GDlLUL50=v}?5~hk_L_7PS=n{Wb8k(zD&go=u z?)=ZJ_MC!`EuOQ;XB|RKBZ+WWED?KwlQRI1UPPGskufY5`(9rk?IIYe5OD=`{7Zr0 zf##1x(9fTJ#l$YhFAZLLFFt;R;QPZUxKFe+T>D^Afj z7e}w9AGwdcmG0N4LaIv&;eNX8%P8jQ%LC+)%>mB$k|$%O)ju1ms9q@h1fb5gEn7fa zIB)nWq#z1DX=W;@rRjI)1?KoB&~ZS=PZEtK^{yIY(giW>hxGLHH1;;fEFJ$YcJnGL zt7|J)B3(s;%B&${#bR88!9lBm5x zA^MKZu|-tyCe!A)Vb(*{B}_8e`tu5jIJ5@dLHY#n=x9lmfee-1RC(}m5;Um2b4Lp< z`(%gSsrj2?L2sFH<8g{|2tNu&R901;zj)D=OwoGRYU67w?^^*9fd{iQebnh_%H~VA z!Nk8+2f@!*cy2Bo=Aw>JF`Ko0`*un)q8@hkc-S#Br0%MKA(Jy=rp;0tF(OkFBNp6p zmQ1}87uU$CRX4*+X1F6Y7_=0x`6)4_{dFPiatrX`o%gmL5)1XP09orE;qnnGZ2-Fs%8ktXnZL z4NT&T*>LWud9I>C{$~US5GpnobU-|hy>a8%!|!wnIZf4m&Xg7Ol02k_8*6V^%9*fl9-=CUOaIP&dWhSP;c3o}4wz0&(;GuOF;8^4p_ce(}_*_6X zD>4yKL0X`b&lhKQO@VSlZOI)t0WB}ZBY zA#3fgdw!b;)<0${RPQ&99xx)QbEuwMN84_!((;^;B>6}Abs!scc!YQ0TJ9&2_8lSi{+r}0V>gm}wW?kqkqg5rBC=BpRkDZv`X&noS z*PWg;xOiAtm`>>+CA%sC%8o9~=%ZyeDcu@--A86l$x3U1@O%@k*knmu75hM_x{#qR zlO@c%lamuPN<)V>sVvC2amgy$-?>kt;#3%3=^lD}PHV5kK*0F!7JM$M3G0e$jGn3Zp>MAC%KVuF*&?DBiAP!|kHy)s?;=dRY@_Tk^pN1Uk=tVAjt=$PO?$lyVM zJQ_`e3LeJZ464Dq7pWTktY@jI>*F1s0vvR5l(Hyhi&x`f+_h_0DntQ`Bxw{9OE5{8 zoS<$y?d_dGDADWr`1q`txR;N{UslWF`6(c=qjRG+6-;yqK$a$SxkExm%j0y%uBYKP zI0c9cbeTxBWg6Jzb~d_(oupACWMstT_4nRe`<3l(wc;yb&X%B?q>@t`4P&vrt+VU3Y6WFeUG%q=?f<;_Waq*?vB#bRv?lTEnl{cj1^nd{QtZYBlw**^3PfviM3y*x;1; zXs$JrE|!jOZEJ&mgbDepsYDzlA>WE$E8D++-h;3o10r|v79xYaE?;FTY8YoY#yfRQ zwAB(YSxOy$ec+~r;d}iH-u1*~vRHptQgAQGKQ|2;=M=jNP z|KX4S*()xEv}8H{d@(7qA$5jknPtl@D|3^o+uievYj>xw>6S85t0xc~Mq;ZRZJb5De z=$F2)hYKu~gniVb@s8Db?d{3{R@*1qCziQ%bGj3}xbrCfox1XGtd@UqT>fXTNZ#S$ ze@mqFp;3f!S#>poK`aG!MkN!m9q-d>zI^#Iz#+MIc;B9VhkYzBAMKM?l>(=i4mjgN z(2s_nX_#<-lFatYA03=K=@IZQV_gIhA%GHue5oiT_Y{$s0O*P(N>He0m4DwZ(uI{$ zI#zHB7P})J!Qe7B*gv^`mo8m8vwM>ay=~jR5)hb+Atq0Q*LSFz%~A`CtCmxm&n_K5 z*R02UQ`1X&p&Iy3&7JE3u2->~o--^F(O0bK&1%Dj8CUOwIj-B?Xy1_juziwK%=13; zfr4Iv9+l=!66B8T$GEl-kJ%Byl$9D!`TOJQV?U;>3pEOEbCQx!{81P?)2#m{ziM~){^RaoFbpH3N=>5dm|=uP^lZatC&2Q3KKwy z<4(`w- znVFe^5ieTQOJc%1niHJ92lk@94}p&XJHm~0I^UNZTFs@w=E9OBLM;_zc){o2zkicu z>|yIgJ{FbmSzm!$6mVElPTQqHaH(YK-%fr50Z>` zeAu;X@ZG{fLuqrEs8ej=U`<$nEWD1K6rx1$K7H($bZxM_SKV7g_9JmL)b{72r-~hg zyB!@bcFvsgrp9}3K2}N$PMANm?9-?3Y<)XT#`HETDWhyVAyZ7A+%G>r`o;~tuzW4u zN-C*lVa7J}WKGaj+?;SR(HmRqxg=>Xm1e%k*i9?J#-I(NfuO*n&cdQM z;zh=Vzm6Xk$7NMY3{q!_b7f%h?&PHa^#J^2^Fq`}NWL7m##ZeD%(0xmL=W!oybatd zo!{C?#V&E@^CY@KN*?RM37TjFz`B9;U+gOEQ&oOYfU8R}II@6oBA{$|ZZnc;^Ep|T z^4NjB1M|6Yf>~&ClV69{h%OW@TQ3SYZm{O$UyY-#B?`^9>Jz*{^13Ol&@(T!f zz}}QvsFXiNBqgES%Ju6bb8TEo-n?dF!hiug=wXZ-WXvWqR~Y+ryY(2j$;ss+Ier_? ziirH~hIktwoGBrK^~(7Nlv`xujTguEG1aA7^pJ9SaBeJ6Rq#^d<;&;nxvc%LwA4)Z zd3#%n|6jO)>l7P_buL;vHYMOa8f_%)%vVZ%SAeOV{lfiHxc8I6(k?yynfF`?^ywj)?l_Rc43fhhDv^*?tQW*!G2GRPPEt z)ovm4v4)H?j^Kv+q&3Vn-G=ENidtk5m!@qkIx}{$>)yRIzU>E#G)L(`^(`GQl5mmc zWaIgDf43aKpB8Mee9H%ymXNB5n=r1J)u^BVCG8?D=-0z%m3Rc)640L~6%N!oC%er` z+V=+s$_WcTM7TBG1_XJhRB-I||5b?l-wTswjyrd*!)u;l5b6U(_R%!|woF>n>GoP> z_uoG#EZjc6Howb&(r@gLvKu7HrdVK%z$oT+b6@p^5nt=+#N^M^22%pBzGu*xqg*aA z1{g25+f$m{W&f;iVtej6eZRsO7$gRW^zmr>CSGiZbr7b?_QhR0Br>2!B*mZc`4A9Xl^9e@aVmI)vr` zRZBhI0^SV|Q7MiI-YRWNf+h$Qtk)Q~%WAu-!^1DkQN0Nt;5%I})OaR0h1q<*KbfS5 z>z?p-mM$GD{1rIF{JnWY?z7_fNX2yA;Q)#lgaQW(ZF)Iiyhh+QzPDkg{UPWZx!pv* zy=4Ah-1?XK0G8LT9ad)0112)eV9FuHN4_31aA5bIJ*l>#my#Rf^;rI_ukLf&XfTu* zk*>*ckq%HKgD|gu=;~is#@{iO{#j)`mhQ}{AI5|#Utg=(T7FLxw#n5Ec!j9MV*_a) z%$EQ%Yvex+?}FOQ)H)OoNqBJz(Cm^(_s)pvT_nIB}Z)o2i>}{DQ|B8pJ-AbvEwK zouTU6FZ0ou7_1!fhFX)|C|kSbgp}a(1xJXjA2QKu-x-vhd=8yo#za9_5bTwgUAJi> zM9Z;2G&SEndQ^swth`4Ch#uh+Pu1+ow{PBPn)DPE*_Y~>+}mB4+VI=;;mw;{*Z(67 ze8iFnhVM&n39~(W9sp=>lugEt9whJM$0t_h%UeCB+&Llpwz0&$gL;B;9mGpqLIN32 z+{$fxk}GzzhESfXJpH;lDlsuIEmCb@p7iI^(&Px&=NH!F0H0~aY{3%M_p0e+hQxJx zqn1WSzOzO?&&(8ba-4}`dVczucy(A+w|a_H^~v!Z)P9>=zGi2@o`rmTOKW|0(87hJ zHNjgAD3E?NHrB20nNw2Ikt=aIloq)?kGF4MyKiSPUs4`F{-1{r+gSzF<6QdeZoTuf zu0dRBcnQ6Prh4<)W!Dob-*iPSzaeXMo9ttYcRI(m{cdY4dCeePW|@VB3E)*WIj%s% zxP6<>ctvq{oq(nk^T^iz$KO-R>3vGNB4;{)~J9dorxLsJo$l1J2(kocY_;kdteQ1%^zi8nM@L%pc6kI%> ztwIa#l5fph~ z_X4J3j4Q53N&uIZ&b$4_?w^%YTx@D$a_mbNQj6dqkZux1%4{IwhNO}8WNfP#cSN4$ z*7|+HeSwz%Cp5euFISmkU(l()*@Jlj7#;9i!6yGqCAqw|y25DByDK1Jue-M$cN0m{#yqU`bwk!={2O!?CbSgT-e^ zR4b4@8Fdulbbrsiqn$K~-G0g>l1QeAZCVLfg1+Z1Cj&4W-!pWXGi~ipp^bGV>-p3dd+j((TUX`R9^@D*T_yg zjV+9Eng~RSP&?Kp3T6ib zdp{LOglEpEnm;+h@HX8eNLkY?9OHb`^jxUfQA28t$;Z$k( zW~>UZ?8O^DmI2j0dwO;b2PNP#v7s$vJXF%j(vKZEa{IoyTrp2G9xfwx z?sxmAGY?FP!-__-U8lj%hgJsK*A84e+rEH86z{29-F}YP-skJggF45gmMvY%kdoz0 zv%$}RLjwHaLB3tM#bxpi7@f2(IU@icH1D%!B?B7cuA$1l&5z6SsflqHOg+e33&RQ) zV>8Y>)o09rkPBRvadAdHkyg!^-*7LX=j4n)-o2$T>gls*RxAt7+d8@sHN%Eq5N*KJ z+1wvlMYpxKe#I9jKYvaqOS{e`f>1&vVEbQo{UyJD_Do^=bia7_gv*yp;U;?spv=)*3Ai-|WtZID zx`w#p;-c|>_|5YWzAW)qwrwMNRIk(4@6lYh?k$9HpPM&p*-5GgjXkG_wD#tU?%liR zL>In$S5sR0joI#H8{gE)XgVz&DTSOI{~e~Kt1}#Rw?8q8d=q)Pzr^iXIxi_+GkOXm z2&BitZ_1Qa@BU4F|2e70A8)f5x%vb%Y6wPobfglq=8Tj&>*tr>F(0T#-v(bz@V}65 z74IhB7A5@l?3uss2c+&hg*C()qM}0hl>O(l(@(TV+m&eRih0fUIK_3&mtFyVd$hxX z1wGf9P0W6osY+iDqW&&wRk}eZPKaZxY*I!9%)6iaI*OQ z`*%VYPHePoVjmx%3xb#xp|j^#glS}wwb>FwL+cAB{%51%HQ%3Jv0FUpAdX~W%i9Aj`y)`Ppmg-h>v<=Znjsi#qMCN1@6KZf0Qgyz8e=nV8^Rprh{ zoVw`kT8cf)<`eTM!A;k%_cpW%DkvMbtLS|-6tsO-A|gfM<<1%ZX7;NGkgvp9v)g#CDH z;qp_vJnuPF=s|T2>S(pT)(SHu!sW$72=rhwyY6UV>56@G?O_=CSzDf#f3MObKsY~% zDBj}EOj-mI=d&&on^(0<)*i~RiniU|hmin`Ht5&yZ&Oo)pbPYHvs&?2&T_K6JHdr1@WB*6n*(M2va*Me@E~nufbr#J7(; zF!)HY^0>d;WjfD*4>~`J73W++EphE(&4^hy&A)Ska+>pPK~(UDKv|uY^^p3Ne7tX)QF||H&hZcpQ`rtreY?Lk*DC=NQ1AT>gU&gfNK;H zMW%mVU0hU90{vv6vR~c1_^I;p`>h6Q*bLc85X29v?OTc@vOOexla=K9_y2*Sg*1eV zjodVv!@hEbu4~tzhYz>F2qB#!sGU4|GZRgTXCfHg zz{P&bdx>y+Eq+^fYSTWJEHgL1;^no3eVuxa5q;-_$JhCZvEQE0NPTxyza<{SFO|2d zKJks8Dl5s0p6IvcgVUpm=@}CzB#mTTxJhJYZY@r5W?66(+-%P~ALRf)ZE$mIXICU* z{zVj|ll1B4-u9--<=6Cw{ds<}PDimG%zcC}a zh{6_lO-xK3C)@l_Pu^{RUxIZj-QjA&CRa#s-^aTO!2pcY0k61}DKy2V&Cm{Vugn?& z1D8iCa(xnt^WV$@rP0kwRPVZZz0u!)-{H&Apy$nq0oI%?)QI8VWADH zv9@7N6?Z!D;zi(=!!fu6fuTY1W|F+DBbXaTadZHX`t^Q z9iyuu%m``gHx-tT!A~Bah6&5BDU!Ql+cI#VO{_`L;Y|||fWPtmK9p&6p zX7?B{!J}fF?!0R*HDfj@zIM3rpz71v zOJ|Nm-}?K2n8WGp4+l2JAD;d2l*mTaXB%C8Q!Ny|<3+RX4c@4^e~N7S4wb_*X83MC zJz&V+b076ob{sixy3ujfGd(Sr9@iW{@2^c)SG1KoKL}Z+ezJDv%=YhXt+5XxcFAcK z%`4ga%h_n&Jl2n1=oxK0vOd~!E`IYzovz{G-IIQO#pmHAzeP7mm+p@{aPpw&J!f)n zB9YT4e{1+w3O@Y;VxQ13Wjr(52!0#@f!4Qv7{VSg#-p`jeR~D03AJGi?4qW_U4GPF z(V2ZCm@t*plxas(Q&4GR2SJm)!{7Q6?lAZ@=GnQ`2QZu~4kuG2cADAH&2-xGbzs(I zYt{n|Zf0SBFWMXl;N6y4(#rT8oay(^DjlcOwCE3%5!L77x^|t8dL8$2uj%L{G-b1s z+TJE%ltF67JO$O3KsTHmWKuDW+wA!3df27rG()!^4@%k~lp&A~YOAqsc(4PX9b7g^ z-v#>$!Q~(Rcupf8oU(grrbNDOG3;nC1c0MrHPrfvA%h@7t2-%m#?P;DocoV)tZvsI zW7DJf47e1s-s7ajL2cRARK2yu-KTzG}4G7-;^N{7&7Z>ygP3?!O_Rixs>MRJFgJI0OPyQG+meUa+mWNB zM|o@gI&X5by-d>mNPc~>8D{cOr+EREaP)k)hw;# zz?sSIzr8!0I3%(mH_Be1qW$H4J252R`tB3Ed_4eC+bYZ*!Tg5(i9s&Mdv|e4zAQOO z2)JA;l~T4!~0@-3&5AicFDB#^gXP%7?au^*;)(4no18!E*&QT zo+2WRg4&d8AoEk?Tj~11E4;o``_RAMNuTbI+~i+>biP9nP79#kzyH<$n^)|nUS9-# zIq>A%Q8X*SbuUMMcXGu?SPz&CD1071c(7*FX@So`rQ++gK{}} z#WdSxNCENFV?0Jk?wa?%U*}!i5k4;y1=i!TZ(gs{uE)(>H|E}0xSNuq@Gdn24fZ4! z6Q@y^sj1Bs!^RnjtcTQt_b7By%C;j1&98oGqxQ#;)8iBipR}G4!(Q`YCRa^^4-jZz z4SyG=OLn$*YB_Q^Tv$9#k)bn~FMs1TOu#GE)3SxJx#*d(^aD(kQdLu1v25AnDDv)- z1m_*@?m9|_OO|*Ugq5qG+@>QVLBS5L0I^SBA*nI3?rFI_4D460eldUIj^dYH52`nJ zvf0#n+r7CxTi^m+0M5*_`UKfJy>=I*Cg9YDo+A79%o8oWmYJPBK$qn5$yjnr52=HX zMk=pfyVgP)a|#%th|%kZ%gM-$@$k?Me$er7TkJnGkQc1Fq~~!8!ovfjt9w$(PWG-p zyFBtW0}9K^Hki}`P=J(>me3Uew(v=#CRsXOOyqzl!YVOH68MP=n5nn8M@Ps~xeQod_o(2R^%!RJol7L*zv z9wD)@b$)_CEF`5&T1Mt07b$aFoS(06UXrq+;%&v5lo*f>pmb*c6>AchW+5&*hlz(y z%E-H$n($<#6E)R8BQLLis{Q)(2~Ejzb|TxubH<8n>yllMAtdT3>LkabspXKOw1X!5 z>oetCES@q*BT#wbL}3EEd)sUG;Jng@Fgam7OCQEU%;OXQFY+~kOBIrv({0V%77;03 zA&nY7l10m~kG=O%X5|Mkd_&hY7(2$N<^baim%tvc|52Vv7fo4TOXR4D3k=gNqKb=) zc{o|9_7~qwG@QOzY;~ebY)M^MzWetf?z5TNFi%}Z5Taz)J8pJMT7P{-UJ`1F*-}_b zkeMI`CC2XF9rx5+-UB*1E5N1DUths?ch}icReP8X8N9Ug4NIcS{qD&(Iecog4-u*5 zTw_{B9cQI@a^XUb6w}PsY5M5namq$b14X<+SWi(=Aleb)qS4c(ai#^WrBxjrVyw6e z%>uyF_iZgto_MIUR2jJNW{|v$#N-lPn4WcY!pz29`>TIIgdjDoJnNwhGdeSVh@4n= z_7{Ai9llnx>;+wszNGo}Cj}0~KG%m8Jr;-M<8+nYM`ZUSU`5So(?0!lvE9AwoZcWD zB?7-ky56LXtD~y%$bRjw%m%Jllwx*$??WFbrg1gHo#xBW`|rPAbh|>$ZV*-vm??Qk z7rN>P2m2PnmOqrNe4Mi0xn=EBxxQl9@uyG9C(3%)Yi8FcJTj}Qt=$>FmLxQI_1!yn z>>)yK-~NpRk5aT>cX=o&67xjvt-5@A=r-1iyaz!URmJ!3_J=nN9x?>;2t;A)=z&3- zw=Q|fgz^I;X-6N@mG?{UB~Lo-Sb@*X;SD(XaTIeFRkba^N5}pm=!}h;cnS zow_a~p42j?Afj-~;5z?hVBxC%`lwSJH6WJdS68(a@+4lW#H++8+*10PRdnu^7ZncI zSKKfP4_!7P)t=eUjx%{k71No!$k(6Ko4o#^zMo(KU2$M!PVhw5#=zdEBTV}iMt|(M z-#sHUlV-k&yk@pw?bxytf0)o^y$)c?X}fsZ{qFB9q2Wt)x4#<^aiU!P`+y-2y5kpi zXF#98Z8kP&$4JtU?rUb_bh~2WSif}auvGmss;d6E7YL7wi|xGzBZ$)rtHLHnnLxC~ z?MF#bQQB+%{k;rY^+}`UfRUowuqA8O3|BJ5XAhA7_wRdiN2N`8`|;zL?0U|Jle>O> z(e)L_DkDdV#aMS$@}3{!9}pmX)i5uht{Bfbj7NS`)|0kgsVPx9&dT2Y^vPlllx~9B z3Ov1XvUYK#is^Z`b`}CQ=_Ylxm0-@4+RS59w%OVqJ^57PkS>>6G5y$={38qrv^^FY z@AGM4b7FvAsQSr}dHvD5(FU24(EGrw-D3nZ@;sW$SAz}S9JRPbcV`Ws=}+B@P4!s z%=v*2mh)8?>pxSiL}wy=pAvx_F4vr-+}IT$0l zJahfAs=v)}m9CSL=ZNh!E)%-%h~egrnlf$L z{Gi`+?EEBQk@oEillJ25iD%VU%p2&P`n1?-%;xTkG-XL7W{m#3N_rq(t|cXM;-VYx zyrKWZRPm8g7&eL?l(!CpEDmdDQ>;)8Lsk)}-rV2X`f(ETVEGvdT3HW&<#8A zmf+yBRY}b&f6XueUCQ1TXWjT^$(a6}HERA!Vea$k)BRqKNlxjtf6%tovu8hINGfSk z?WPIW-tad;*$e7wO2iLucw#TlW$BfoGQ)5q?|0Ncnr6XpUJ}%p`hQGr%1TRbk6)V} zv^`5D1Hmmrf)Y$8@6F2rd3gGA{4Ly5cOEPm`$2tM(~>pKAJc1sSD@Z}x|4=&dc*Ld zFF!vOSC7P;yyoZAA}47XFv7vd6TdyiapjO{*9zC(07@Y%hfvxoD7bw)D0jlk%Tg!* z?BRCe_~aJFubTlNuy3}`dJN^IX41tyaw#fFtre3;NGzZlpuwoGu;yVFEYukq8hU%~ zFBjSag9bSkRX7|qgsBD@EAp~Y_|U^xTcpd*+NvVW_weJ}^WkM`r zm2OEejhr7M@4+3y#vIt4Kyl#=vPY6wAoHwejWvaWVl!$B*Aki&fl6HlOqi{(WblpV?(4RbMqO$W_-`81)@JyR#d- zPI=X?8PKb%9zLn1_^@x@kQ^0}Ya{NNf-Lv`qfHF5fI|E5p{cUpa4*{CizlrQ%SW`q zqDd<0_>n$Y$LS{$7T=DE@TAuj>pFYWAR6YSAnR@25w+@W%|91HkbT$g!X8wtV}BP} z11E_|?+g|nHI2lQJEl$n1`3kNr}FjF1=TLd1mDJ!H_VHf-S_EnB-^Ob#iG6EAcKNh zSxlE&x9tp_JnbN1qP=4nnXF5qThr@!gD_^NE2dM&Y*(-O?zC3fZ|*3jY_r=m`*zop6r}gp-hx4ehXkV$UcR(Yk?uYll*}`wY`mf((=R1N4}$5NA11aC zn5e=|FZ_5vIGU}IE$dS|W2_l^8vY#;MoX@@+X{#}?z$8DAN{7;aZ2WIY6ubq{HuU0 zpbt0n{F|p(uS4%a*PdPXif-`N1xAy1n zPVL}0Pt8;S%#WOISX0vMJ9m^b-H=}L2S&O=WzcD>HY5ityx1RZVf5sLZ;$QVTi(JF zvruh1zP?o;=Bqsi>v>hXi-la}c%PQ%ZN*80{|fsEtgvMoW2YF{!vya9p!NoI=MdAt zAg-o^cc-ijHv&wqI=w(P*zOxh=6Aq_&-G2R^lXKWJ+Ml{2$(V)-8aq16%z$xTEO7k zVqXGs%po-U?LXId{Dj}G(_XHFzd`um;}u88U6MNP*a5@>N|o@4X&ey@0`=aU&H!*ARuqQk`1 z+otOOu;qQ-hlYgdEW@qu@5eG@)BKJw{7`_ozCRsOUH_hzscGbplqxJeG-boatgtj@evms$yIs8%@NoC8UHGPc`OrJf!hvvr zfzH~0mX7Z}8*}?@n>Izgt!39?jDV}U3^o>tUrT5FE*jha=nzw<;HW4EdSf0^INfbX zIn1fn^gDHhuJPfNGPFkhbkUzM?}ma-4vfoXgQ97F#M}Z3_$*VXv4S4gP8IoG1PFCC zznWVRl=TNLChR~2`+i4I(a{30Ydo$q(JI!Y$80Cuz_ny0Yo~6#V_dak%+k=tYm^vX z^XH(4#Pp$X%Uzi3c@MQ_aGifwx3syD*|t~tjTD=)pPIA%o2Sm&BsVRWJ|dcLavt{< z*)3&~P--A&#vB}AcmZp|Q22kuno>dNXwmxXTM4AqnX%oH+a!b>GE4XPkaxM0uFzG+ zsYw-A!mC$59+cFcWg>LiL`3d+eyph2+XxUCp41#(d1=o{sUJ*Wxoq%<-2GJ^Wi%ad z_0L^OA0=tbgNK&9x3}$}f9IS1>%ab;4*qXE@c(D8DBW;Y06DggIz4Nph(Kdb-do4> z;jQ`OIli^g`NeW=xpIj=UQF^h)!EN+`u6LtjD%;k)n4rqP|~kOGmp&=asOrQeiw2L zkH!1k5jR&yE67NcOLY<<_su+6*~A<>`(Ky~$fV|01CG*ZsQ)!>CRK~7s;(I3wj@Zq zYipUOaY%Ox46mh2_el;BkzFW$LuA_`(&4A(;wjADyr^?1Wy5_aN4@3)q$q2k~WPam=wfBi>h zrw*^WR0)H3ptvX;llL}in^dIDJJTVOBG^Yfhl+!RAWXxWpsVUXuzMLYP=GS}Rl~eU ze(j24szGGwPZ@;eqZc8ph^MSDGTK%cbx0}-QY%>&=Ko@JMBplBDZhmi5@WKJDkVJ% zzuVU~Y96Ohm63ZJD_%1Smy}UYwtvU<9(ov-RhhFknLjKCni3KBC+&QT+eJ4 z>@6HMY8^EQ+U=aw7zNL{#dOjcIyA3RUJOq?}XmDgC0uB7qEb!^hv|1dx%ppD@G!(k4 z;FeVGynXvA%R*n@!Kf!RH5Gl*C_Lr_FSWD`O|ziKH^*98{dw16d29u&pRrntsxJ>@PT>|}(LjT)DN;uI$X zU&g^;)@Aj&+sWx7_gVx5sAsA@gU7N>9hw_#?`WNKYwMVOL6>$_wU&|Yk%CQo5oG?| z^sVRLCMJ4&dcdrHNZ-0La=?HA;weHttKcKd(=i`#Z^gv-qy>azGGI1uI!5HrYn6cn zW151>)n`jRsbz*d%`9W4aj%#dkU4+N%3ks?-0?#OYyhBSw3KySOyXSx#bNnG|DO=w z3O=)|K*NSk$q=DuviiDJL>AyX)5BW_S|v_&y7g+WhCnGtqzkF5&5d>*lXY0 z4XXYZ4aOyITsR}pfQQiTMwB8d#Z6O^pxg!r%bzSqTzl=>HFn{VVgO01{)*D#LXQOS zR6GU3KCM`smWX$oV@(ckU{^v|m0FH=4xr4=(%#!GsUxVPDToc94Y~RUC5eRRU&Fkz zD<3dzkr9@td~dCBbFrHqXdpB$AK1;ot5I6oY&BPB{GRoEWk+*l$Hc)kqL$BlX{A}5 zx#qD22n}PzK!eG3_V-*Duo(^W;vqvtXsmb_StVudExDs}@yvRzqEDyo@e`#9%F09{ zU^$2q>;UadC5-N9X42r1cpsAfWW8~u9ONQo+Hjfr-paHY(oaJ$Sj=C$P^{l{r}xF9 zKP(a&aEQY5Q2mUtkwd^tba+t-h3+8s~be}^#jY^mDP9-aU z032)nq}<-Vt;VI(A+;qL_#0+7e_cI9dTV#0S;JfVO`AS5JFeFu0Z)Ui!|m#Qh>?`} zzYB1crg^*bi_sz7vhPk}@*yE1N#qj98bA-yqgbB~@sPb**{hZPCi&~bYuro!9@Y#W zMW2jJ07nxDnA{L8wAi4?_<~PYQ+rGyn$F3+*j>P%|5mR}wuTHV{8QFks^M%(_qFQNdf+6^`EX z&#aM4jg6nHFmIAYZr9Bp;tr+{=?n&&(a9Al0N+FZI*KrE*0u0 zk!z0jK&44CWV(B@L%gPpM7~rLH=8pKn6O4g8u^3TzG32`C(F6(k#0XHxrp;}_to3! zHJB@~e97U{Zx!A;Ug;cOpl9egYvlHd7lXhLy;E(qD1M5f`iN|AL}K)goinw*m6-E% z``y-S6Wdvm+<-XCI~Rg$7sCr7;Nk4$ixAV z@VsAOW!FjA5F?<0aMCP(|9**>GrrX+W#53=A^K)yoR~YRFn040`HwX6WW7CoH@LW5 zuq>>xmS-~iEAu;4kvVnkTY$WJA2LU)%4ctj%THb+#+z2D4I8xkiTyhUVvjXq7}|SY z43U|=c=6&sUazTmm}Er_;NXzADc1MgIW0|RbLRsIebM<^MXzhjA=w5*Nl0jgzXnJV zx?t6`ljT^=F$TS^&JNAK%N#YBG|eM?*REun+OX)Bajv0wVTBhIy?$L; z-vm?qaT#2BOi(#dHWqsC5|`?Gi>x2+SbpfOwBxxn3(yECPNdDD%QUkIw)8VD&R#_~ zMKN7jNh$X=|DY`cSo@qQ0UGuIh|!L*D<=M>t!*F{91g`nW1zyG93)Z^|5stx9t~w0 zhX?D@Mn|=k)ri`%D+b#U;~rbO+0oI(gh5g^m5emw5@RP0R{N>M$hC5rRfh?MQen{? zCf&4^Tq?;W*U(HgF8h2=|8!1g_xsnF^L;b#yuaste$Vs#M4~0$)+Fupy{@isp9g*h z1_9+*+{Ekp6~Gq^2BW~$Ad3N(Hr00VCA?X3BPJD~(6TLcR>3fZ+mNbV!57-vy4$Mn zQh9mmye2QNi1G3980;KB7^8m?@?epejUQ-N@N=n+g$1UD(20ctGNN)zaZwR?$F4CH za>w|V{+rb*wMH}=XjM>n?1Tp23u}2wULrS})q!>Jvq|1uR7K!WMpjmSu1f!M@OOPz zuA{Ca-6o^i+{Wha_;JvCv5p}+;oXpD;p}tG9TmCHgX!ss2gMw(S+Xusd=jvoY1Gvl zSkcyyYZl>74M7e(g3A%xT0ftG;*2A*esUo>XyefW$>xpn1m%qE?1F=sFbKtYSr6`o zez>BoUkZk}{t@U2q*z&h*k27da0VlU!HsJhA62|uxw2Q1#_&rbbaiw{n8IcB|9W@nH@m;RwxX+Z8vvgw&ayfdfCCROmv?K6v79i?ib9gnk%DKy?ei$DwhQ~tm_!5| zV$cgt16=wYi6dkeTLAuWVdbC$=D+>wz#h)41F=TljDaDQ+%Pzq8~vILNTzY5N(m%9 zs56{ckNNr42V6tslfFC(gK3+FST;rD}*?* z7)?~6k>}f?ct)nJum!pyKQ}KGk?A{}$ zPUz5t-SR52Q~JFIgE6p^42zY>p|LPjfNsjq-#}ftK4-pzstryxpBT_Nff=|C?DHbl zRR_TtkPv|c#4-ms;ue|zCZAIJsQVi1)9>qQd`lqj)R&V%H{Nqx@V(-{%W?2VIgV`I z`M3Tt^Ro>hL2dmCEX+sNtIXCjQqOMw8+nCKyeT99O05S5Pe$_a4~MsjLC2wlIdmlqL0sszu>|~VisOM+p7j_p5DW=9=beZ zzveDY!=1l0*Z%F|g!RK?y`)oZ^ug{b4z1ZEM)R6G%frLa&VL6QEDBHgdN|u@+d;=| zi`Xh&Suf}(c!D*iidWNUi(0VTPK4TEHKX+dX%POnpiPg9ET=!6PSpA!2}w(RV)1p? z!p!V!yWnoMsW3jywx8(2!BSezqNy=KbzXJqX7bqAQLFKe+6uzK(cCjT9`H#mJJ-0la_Y=zCymdx%0H1 z8mjnJUjVTT+mo!!{3$!wfmTePLNJ2$NDDOx5?PR85Of2(3%tS;?B?^OF<7Hj3?YJj zY6@kPXJ!6Lv)m-;L9{n4X6D|ZP_#mnxeri)`%QNK0aZ6p+2gx)ulK94Fu4Zgv$Ov8 z-Mb6XHhjiu@+~nM`Nf7#4;8AiYIQRuZ@R*wI2`koEe{W})Nd$K)Nk-3_|EzeXd0TB zkZKFKr(p3xs3#ou0X=q+|Z>YQ_`K=2fY!JcmT0Rho4eTW?kPQZkhN^9rN9`+&8 zMN3(|4r?4@gZNu*3Y6RA&g@`>crF4!4j*}vcsq*~iFB6(27uhG_1nfIAorv zbJmg|HGCSSA#?7JOE&gGW-!2!Mn^n>iU$LV$^09CDC=BYl0NuoP@+W>n|iipo6Vt& z%E7WhYgCDQt*NQ8pRBV#4wcxXj5i}g0K;ST_Uo@TNEJ}+c|%HN8OR;XK%bFU&&Uz# zcYaC%wv%nNpg2q!&3zajK5H7RRECwV6GGoO-Kaj9JSVg(0j$t@4fSC%9t3$+@QOya zy+Z_Bn{Uaam^UdVjRq*wJ1}JdcJdlqZ4bCt0P&+QK~Utu7s6%TK@|Ok5P{GB{4b38 zpFM#8Vcq|qSA?1##x&Rz3LPFU*#smq4m48ARAW1r_7zRj7jmbss-C`l#Zy_eTFXP} T#>JegM*f=h5G0RkihcRPOn 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 literal 0 HcmV?d00001 diff --git a/tests/dummy/public/assets/panda/android-icon-192x192.png b/tests/dummy/public/assets/panda/android-icon-192x192.png new file mode 100644 index 0000000000000000000000000000000000000000..6a42f4ce81664cb4f7debed199f5be01ced83d56 GIT binary patch literal 8164 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 KMNUMnLSTXrF>zD? literal 0 HcmV?d00001 diff --git a/tests/dummy/public/assets/panda/android-icon-36x36.png b/tests/dummy/public/assets/panda/android-icon-36x36.png new file mode 100644 index 0000000000000000000000000000000000000000..ab25d936720aaf3bfed5c7d14b95fc46e557f0b3 GIT binary patch literal 2039 zcmZ{k2T;?=7sodODun9jP*1|0wBrvcaD+sp36daDj5LK4(g=}+8V&&qcywth#ezVj zsc^s{NK<<6y>~$YK@b%I5$?y$&HVk}%ztKg-@e_?`@Y@T5^izER76Nd2ml~LBp8z+ zdu*>B3<3Bv2EI)ZfP9!2+0+Q+cE~M24uVD?lK}8j0w5p=fGub! zU>*Q(6ab5k0O0QdAnBe|c1{<7eMAd$OOxH*-ObI-wzf7FizOo?V{2>M($ezn+cyS- zfyH8@qoXG#CaS8cE?l@ED=WKi-#$q2=+UErfq`GXd|6sr^6>DGmzRgZVEgy)S5;M| z(P#|~4I3L9JRVO{QnIV7tF*LKNJyx-xHu{*>h9gU{{H^=@8AFW_3O^gj-#XF^z`(b zH*em)eY>!*@b2Ba<>lqo)z!7NwNIZu&CSgr5QxdiNhA^(8XEfe@#Bn)jP&&M%*@QJ ztgMuj6as-DCnq;PK8{ACBO@d8^YdqCXJ=++rlzJwM@NT;hlhrSy1Tort*wQHg>g7s zczF1$SFhf`e=jX9&Bw=g>eQ*~>gvVC#hRL$t5>h8sHnKQx|*1n#K*^1R8-8*&o?$U z-nw<`(xpo)D=V*GzqYZlA(2RliHXh4&3%1+rlzKBHv8qvm!Cg>e(>NyM@L6mT3T&w zZDC!Gi|_0s`oCx~QnAprBxKa`O4}=TlQtx3{-He*CDWrZzG%Qdd`J zXlPhaP!JOn6BHEW?d`3osMyofbK=B_kdP2i@*%=oXmz0#GrKRQP=NB6rtE{Yy!C;<0e?B%gMkErMOy+?D2Yh{f zA3b{H`UG&Q*+mZkJ{h91p`}hMCr;3Yd``SkqPVXmYoboQKI2%t zk7^hHANe5OJXhs~)KG6*i}m9WH8nrkE4U-b2F50hb)MJ9stGs$+~Aiy zW=^Md6WImy4&|`JlH4YVTlz^6zjjN6p54gGV1^M@aDL`8v%)Qy6RA0`hdoP;6cXRg zChJJgg(Z{APWonbs#sknWnwB*EwXj&>Vj*>Fu}=c15Za<`-NjkNw3Hudrtp~vV#y-|w6cr(f&LNakj2P_MQhDcM?bM{m%t6-&1*ikFg#u% zprQN~f46+wSQdHzYV_Fo8TWYmW`kv%mAKP{eBGL!6qRT1SGGm57dRXpNqxi1I|54= zNqcK~v3@71FmAn}LBSyAXsBZt@?k1i{FJ7wqe_8iEw#N5GC;88nyOtwt+(3fAVwPe<~>Vu4IalGUf2+It9KVbMA?*zI#v`>wR5MP(OX`or8u> zy|G~Lw2WN5rFfpR$koMgGr^yt6Zi=~8g86~6~v~yRSmkCNAhpp>R%E?fW!o~hZi4C zCP>3ZGuOubC9*d{W9+NLCe_60*A^w4sKcNLQ{ECKklUaCN9~8to!ph*9kkfqvs;l~ zR@!A&Z1Id6^?aKN=AN!g7Xw`u{FHP8Q>V-IJbHOSyp7ON|ID$9hyQZn-@&_Va{{X;;bC=u3&IoralT3P-jMkIumt#Fv+r5b@T7 zydl@_^fg`5VFixneRZetLz6G45-(Qn+_*B)ZmL^^(ZUqNO;6O^fZ2(S_nh_4=zyM! zBG<&0OQ&!dc$y~zG5~`@YpbKt>S(Mb8jFVt3af@f;ZdlD(b((%6Toq!vz&bYe?W1F zN(~fnYA=JAn-kZE;>iF+4u#1eQJ5^63qo51tD&QgLqI9D)KR~vqcMncXl;Zum+Ov4 zBBB34gSAIP6{0oVJegx5%a94<&U;92j>16*k^T-Lu{aE_uR8<$y(*-;D^tI~ z`pmy{NR|)<&{|lW7D^L^!6x*;)*(dfJDu|^KL$V<8O1%;TY{#nzniim6M++p&49L# z zI27^)0Q^CfSr-7%WdeXCHMP!09{{)mPM@;FZf|dIZEYPEK#% zzFl8m|MBBTS67#tn;Q~|ymjl=$B!S^*4F0c=3--G_4V~dMMb%|xIlewZf<#bc>;mZ z+1a_dx!Kava`ECt1qB5N1j5J1r>LlCZ*N~-UjFUdw>NLzV6j+UUS1<3qpx4T4i68j zsHg}D3E}bhl#~=G6#Dx0>#VFS6bjYc-0bV?8y_FPv9Td1CzqX_{qyI~@87>;Fc?Ea z!^OqL$;nAoRn?a-Uz(VhR99DXIGnb&w)Xb+_wV2L_xF#DjeY+7d1`8Ec6L@oM1-H8 zzqYnEE-o%9DJdf(BRxGmEiEl3CPqL&puWDIhlj`7+BzX2Au}`c)2B}p6BFa(f4W?sB_v3KuYH8r(gzkaQ(tON!I1_T65Pft@Q6b6IQ+S>Zz z!-wVN<=oudSFc_*G&H29rdCu`6c!d%RaJfY@`c4>J%9e3N~Hz`1?lMMbaZrRYHEgs zg+)h4lSrhZqN4NX&qqc^Dk&-D<>ggYR*H#5_(q2Axh14i0|s;DLjK!>LoJo<4p0`0?YVrKRHH;$f`CBzSeWE?b(L z0{3|3aG)bd#hvv7?M@#Dn7z7U0N(YR7N*8_xYn?QjT2{xQmxgJ{K68lm%r7_NXW8I zAaafzd}e$iU~+;+p9=Y;!0JFEFuse`22)FH+g+Btt(TR(cZKooBptq^=;s6-ZsGvR!AwR(jx`;C+3)@7QVKXs#?BtlXXfo5|`m9=y_=(bK;)-Ns*=H z$?x6*?9*36eH|RGrY>T&Bs=Z=T(gp$jDE&eRZV$ZU|3ypLnOBk&|#gOQjoS?$&qK1 zQyvra*w%k?S@mUcZM;$6la|-p@9y!9>L1`AI9+4S=5QZxv&!E%Okhf*1TTkOI}QN1|SK+f$t$S0d7(nvx@R zeMV_*%tT6}rO(40>Spx0;(lZ6z3lspUx}H&(v zE!YZMkw0{FltSm^$wjWMUywz+4xEU(qm~=TA2QY7xU54dVty*cA;i5$Wtzzn6vw$QlBBGL@I#Am%Z-aN?O|rXN;iLmg5~<6@SuG2 zW>@QyLBv*obKl?%yv3TV1asZA)X7Xyn4qWcLtm-6nw;AJYhnpJxS(AwEV$X>JH(@KFxW1&lFP ziM|?N$Idw~gW12BpmyfemnJr(>0qk|Z$jzf(U!vJPt%rH z!z8B(N8T>}`mioK2*)p9FQBiQ@$PmV(lRoaBS)mCf}q>hRVs}A#8* zYL$<_5bJ-3k~zy;HIS33pJVG&0=ItjNV8yy zU+28b8RlG^mgmHCXRylL+y=b~&pJ7a&B^y=b^Imy9vjC&2gG*ogM4toUJSMY%#oGS z_KroOuGL;6VziQ(_F&@@!I#Zi zT}#uMP`Q~hS_G5ae6p@0*_fHh!bd#}EAQF{KCZ|gpQ8V91$>Z{Xjmtj8-YgDb@e5J z20$T@+UiK8IudP%MC*bDfj)*n=pqm;gVDkN6F~NH^K`%d{{yPSRXJdQ*-i#OA9q?H z!IubFkO?FroQ5$8Xu7(mLDUaG=5N=-|Hb^1Q&-o6=t=UR0Z0r= z1n><58Oh&t{=+zWy3ss#ke-r*0w8kWHw5oVCep4`iNHUrf>S9Z!>0~b|I)$RffRt$ zL}N4&8VD4c(ap6AA|k)(objX+0fdQ3e3rpHIOXu$l)bG5;O^;71h)?-Q)u2kB)B^v zz|+--;s^il!N3s&ZL}Nm@?~w5raMB%)lGxwuAxmpxT8>)F*>dUR~Q%p1J|c_PSpp@ O09atHOiN8%ZvPFphdgHh literal 0 HcmV?d00001 diff --git a/tests/dummy/public/assets/panda/android-icon-72x72.png b/tests/dummy/public/assets/panda/android-icon-72x72.png new file mode 100644 index 0000000000000000000000000000000000000000..c492205cf4a8b0fa9b6deb34c7930675639c2063 GIT binary patch literal 3306 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 D(A(2I literal 0 HcmV?d00001 diff --git a/tests/dummy/public/assets/panda/android-icon-96x96.png b/tests/dummy/public/assets/panda/android-icon-96x96.png new file mode 100644 index 0000000000000000000000000000000000000000..cbc06d4dd7b903f9adf0fde3c50f677e65d94944 GIT binary patch literal 4095 zcmZ{nbySq!*2kZLp&KNnK|+Zc7&-(5DJelfQDJ~#KsqG^2Bo{CMI@xVySp0%K~OqI zI)yvl^{(~)?p^Dy^_*v)efDR6_c`mFKh6o!)>I}TWFQ0pfJ9YA@hQfd{B8KynE&+S zpuYhyQeHzI0Lr6?uHiTsALRU0Spg^+VcfzvtY#`tH2}bq3jk350C0&JLTv(oD+B@sv(i(K60RU@X`-z^??d|Qw#l_UrR76As6bgmGV5zC8KY#wbxw-lM`*&?^ zt)ru3Oiaw=kK0Zd4h=@p9S~?{qWo>Qk`uh6MpFcZ0J0&G0y1Kg5 z)YMp5SOf$F_wL=Zva?OSMQsEmvZ2?+@~ zIk}XSl$V!RZ*T9>(UGU8=Yt0iaB*?LV6dN`-}3VE;NYOUyE`v0FAfe4EiLWSr%w|T z6R)nW8XFrWB_)G{gSWT0<>chV#KfALo6%@=U|`_gyLS~875De|Sy@@r($db(&c?>Z zUc7j5=gysujt(CmA75YJy}dmM1oHm<`-6jnCr_T3nwtLj@ndsy)5F7ql9KZ9@Ni;c zf{BSqR#tXkU;rB%n~{-mbaXT+DT$w--`d(bIy(B~gscIb8KvE zwzjrBJUqw8$ET;K=jZ2_mzRZwh4%LL%*@RB`T3tefBy32OIB7^c6N4NUS3gAQCV48 zMMXt*b#+5SLsL^zYiny)SJ&|H@c8)n^z`)n{JgWXGbbl!LPA1pY;0Uy+{cd}{r&x2 zTwHQ;axybBGcqzhefpG~oE#n=uBN8u=H|AwwY9OavA({(y1Kfuva+r=+CB$H(XG?X97qQD0wQUS6)Ruiw(r z5*ZmuNJxl6p%4g!iHQjn71f(JZ~FTB($mvPNlBfYoD>ukf`WqR>FH}~YIJmT>gwvu z%*^!k^xE3mI5;?5U0vDP*M8Rn^neQ(RoEsi~== zqJoEqCoV3|z`#&hS$Y5d{ivuY78Vv_Vq$J?ZZa~mM~@yMkw|lMb4yE0ZEbA|3W|b) zg6GelOGrq_%gYN12?+}ei;9X03JSh_`7$gl?A^O}At52g#>NjHKK$_E!`ruSo3&M= zF=rQKs;;aE1miHNU>pJmmFLbF`?s$GJnpxa1pq>CRYiF{%cYzYua{H?G`$?ve(mxI z_rTB&fKVUI0yDHx$dTN>Lw-W@k%!09<|DPS$-O3OJ;UVi!-`md5KF!4zS1~hXax^{ zN#@w4+@RfF$&B|~IiWZY&c2KDAhwO07H4$*S(+DGk<)ZRJhir`mA|H%>bZUN zAMhMJ?OBQ2SeI%X6Gd15eE0OQr0d&|UMXGaT+O43L_?dE^@z*!OEz5RG}+GO?-fl( zZ9{L7B-Qw7On0P}$0SVln=XR;71o=%tEX1RaL_@?FV6X|<<>`c_{eU@$lxg(>_n*G zsG%3EY+;GN70HYmh~e{2FX_mc`|OS!&F>^yABX1K7;{?W{KhX{wfq1s!Q~s}iPEZj zNzrO3-vIsV)Wrf@<6<%PZcRwka-}VLDQ{}Z(aNbl zwegcx3fh?xcxQ*CHn}z0DhMAc`n7w^7`51heTsKPq|Z#KDc!Ud{0Kf?igO%8pbv_q zK~g{#@CT;e9L}wh`?NV)o0d)Ai(}Q>>c5wqgD5yuDOV48s{bUGr$E1u7uRz&EWZgA zrw=TR@NfisFMVVvo|&#Gc=5t{pmo!wSVO_(d-COvz@FbKBG-3YaQusEi5)Z}Ij1yg z=QDob=Osck94E@O(f;7g5q=wl-V_^?b*FI znKB?lf-%1oMyyv;aGLGZCX`We@|(=6u{YkKCJP~%i9S+cTorIH9vQUTMHe0SQBO_| zIEETR=pCA28IwO<>#YoXhn_>j6JU7@*0^-3ZES3^M3KkLM8I4^#~Q^o3ZNXR7HB`9JS1R`H!xnp z$@*fp1>o?+w`=6T18}HK7s^2{DK2%Hp=8=xS9u&Oni8}?RVV$kEbtFTV3yCYXa$Q=r}OkB z1H(_Lkg|ql8Vu{t6tnu}_#1hJ> z>=~)JSiV4>o-o_sDd4L;48+o8+4&(|DCnxsHHY{DEWJ?qQ| z-j%2%id^GDl^iy5r*4WFyDQ^)^l)M&f5+M@+US#T?+^~0T%%+F>^&HgPav3&C~ z_VhDw!oT`_-l-!7Lp8x4Bw)ZglxW`W1>rt-Ogr(nCp}39U>RF+THxWeSHmpJp(UXS zZMC{sfW#`n>b%+&!*VyUJK=aYN%4$uSBMvtc`yYP=`*Az#bxh{s(OlQ&8S_Ri*Uv+ zv%BP94Wyw0)L61_4*OM-){yyH(2FX(^AJ+;!k@zZELL{V2rkfWAe*WZDYoWT?uQNH zZ?F~xYxhD_Eu+aRDAz+{-E7p4S8E7>vP5JW?ZaL(= zXcUO&{LLoUX2B2m@=1p_s`;BkoNREaEgj|0fV}IbmGd*wEECh3(YLw;W?y8uz-@YZ zGfuni&s5Jk2#<4z1MoD6<`*1xq=x;VOrY1dl~fg)=QUVee}az}eAVgnD^;|a%v>wVNtGzpZRX6M@+oY0JX%eb-q}oG_e7(q@bSYuO14m->C8EK+u!QbBLpe{Hm9APxg{7NM&_e zd+Ys<3Y9rIk+a(nHAbw7O@}L1d5fbftGMXi{89#dT2oVyHlpmL^P;Hf_!+hlS?xMe=m{N4=v<@~1vdGw39X-yFJCpt)E@$$!5V;}gY zwu0AJ>9xwmj8^+{_+?b7$I<3lf%jYH^=aC9UKTC%uSVi_(g;R;0d&Et(P6|61?a76sZgwr{{+v3hv!mnvM{B-? z!}senjkqJd^UA^G5@`Wp>bqCERZsBqB0h=E{d9I)&zZiJ^4Z%T&7o`~7u>ELjs4Pk z{CtYGKUHAMxQn-Pchr574bU12bITcE0K@Avg$~d4Y+w`owCeGJvmW>RgygEYV5Q+x zURkR}Bii->jtxJOE>x-TD5l3($k=t7rJjpAe4S`mHqC~E+OavJ4K7^pz4cWKC>v7s zvTw%6PuG92^9|p3<>+dKDkD&N-COFBQ)6sE;xP@57+F|i>wR&O$M-|4yh4UvD z>1t~#DXD00=4y+ubCHzP#-PpsH21bq@V}UU*OZjBL|7p$T>z+r5EQ%xhLtVC#lrys{Cie{4tB_g75bY0rK6#Tp#UgMOacZGh6srz zOn^=?2-!b$bgeuQ07O9{K2vrV6Vm@j=-E?Mz{1J~fmyzwt(}XFJyOsD?q+3XZ|5xd z{~IF+fs2ZnLrqOZgq zxcmOjJ?Fmno^#KcnP;Av&+~m|&dmHVF;BJC$VnJU0002FI$T)~YtjEyB0Ox{hUcM! zHTd>0Ef@gMm_&MGO@Q@r-1XEH0d?cd+gOL)7OtlS0Qm9(0Kwq^z!i2VcnbjV00RI! zRsaAr3jm;V%WKz_2LNyno@yGX+}+*X-rjC(Y*bZMJ$?F=ot-@>C}?tW^7{Jv*RNmY z<>jHFq3_?n-`m@}yu6&5nTd*ufhW< z3J(vDfq_9uNhviob#`|4>gwv}&!5%R)%N!GTwGk7oSZLTzWngv!`a!{`T2QYU!S|RAOUe4GawK-Mfd2iz_81rKhJi zKR^HH&!0DM-UtZ^X=!O4A0M~2w%)&gUq(izy}kY0w{O0_z90~2czF2c=Elp*i-Uus zsHmvFzh6;NF*-VWcXyY9g5u=lBqJl^=;&yCe4K@ag_xMw*4B1uY3aw0AEczDs;a85 zU%$@J&#$Yi%gV|!Ha5PvxTvhGoSvR8C@9Fw%R`}1?(XiNKY#A&=@}Rp7#$s5TwGjT zU0q*a-`w0hI5?1$l=SfMkd>7!D=SM$Nl8mfOHWVF%*@Qr&d$ln$<56zE-tR9sAz0# zY-ngGEG!HQ3)9rp3Td{w_{>rSXo)oXfzlM78Dfh=;(-xi<6L$NJ>g-YiqN!v!kJ*Sy@>T6B8R89E3ul z&z?O?PEIy4F?sy>v4ewyqod>0)Re!!|JScyOG--W>+9dWd-w6<$HT)zQ&UrYeSKbD zUV3_Zetv#wX=w!oh34jFCnu-G#6)#Fv|sHUc-t*y<(#N_Ge`QX6=U0q!p8yf)u0R#f^{Q2`oj~>a(%ZrGJJi!0^ z7kg`QY@Vno1EL97;8=&)4Q}d=wg2?t0M#MKN7%qxllz6N#0$lEO#ZZkrRea^Az|FD*%!M0&FC?1%Q$IatKUY6DJ+UgN z9^MQ}a{#($>B}r4-d;Ob`$5Y5t{680Yy%C?>o0cZs*USOGc-){W^)Gak+X^$%&|>D zDVXZq8zzY876#OIQgn+4AV3eaJDc3N?G64nSzRBtu!WK#R-y;Ku54=piGcDe@`9fe zp65hatXpLcJrZ`~jB-rdpuc(VPeb#lwLNc-f`pE?c01grvc6q51ti3&MX}49o+wvv zu1_|Uj_h5DSiZ?HdO4W(J-Vv~KX!Juj%q9JPkmzY7Kq`Tnv;J@Oe48bhtzXk;jJpB z|E1%dfTnW)zyo$(`3}93YxfvfkLt5`&qb#Rj@Jsio76$Q?OYQStNfGJ#R34zR}KcO z*`4a0LBl@{I)0;OW_Y6EkHh7sA1d1b{$?Tquj4cHl)3$@g~YbhhbWWF#eJsupR_-0 za_(TBMmCM!|M51TSy6jDg#)^on96{AtnNm{kjfVpzs#Yg>u4~qGoVDKS7tnvFrmC8lRk6ww=WiX!HMsJ869P2hfKMaDJM0Y&j5aszlnn`? zhg0FmgW3Y#IRAN^jp1{t76?vzHrc%3!=Ux2-Em1#gX{}}UTjHQT?|iU?dzR>b?@|k zjbHAp8jcn6dOM_?8eoN<|3Hn}coLh!acx@-ur9C%n>Cv$nVO;U&4)r}*C%}^^1_D5 z6CwgHqx^Ftjq#|0eDb)_4buS(AKuH7Qc>`2*C=|B&Oe~OcNgy%5(znpr9P=p+^9}7 zTfwPt+T(~N&Y*SV9Cf5qS8k-s;Gp|3uR&F~LtmN(sv27$(;Tp*^C|+xCjWRo9NtSt zXUfji|3~m~fkl7@nU5JUdXdS#P)${3aM!_5vYa-#H+MuixsD{+{v|p4W1lKTPDI%O zPWW~$tY?t?WDt;9A)u=nKHAP03hp{4GzOg9?EKolH#?CEJ3!Y(ylGb2<)<7Lqpyj+fL?M%X1yQOSdGto z^$Vt!wZK-SO^0I~?(ZyV-+T1Tn}Lqucoo5P8s#)(*R#W68x-sY6Llc!@jv_fp(43y z2a&&0$x8k5B)hPZL{i3AUapiv(vE(E z@tPEoWoGI1IM6%$y?BWw$E=`kHe;vlQ~2;47=&|zzG{i@O2sr%iEOoKZ5tP*mgxW8 zSE*CG=x)f_x`aPgo!Xt;97=+RX*|EN0>Fj(S!pVZUDB28zNq@F-=G0`qVhS09oaAa ztP9o!k~F128;xoUDafi0z$%BiC_K6kX1o}DpJRqDIG$U!gd+(+fM;z)>r*wv<-x~2^JvEm!|u6nVQK0We$FE z9!&XWIeeAhXIJMdNq23CK2_B=@kL&FdX5qx#ZW#Op09#;)Wy{slOhEwP7@IXGkDWmg3R4q=5AYc*m$ks-m3EtyYhmed!+`@q17^u*qa+Y3S^7+xh z2B(uTNhy-7VlK9sKdiCqS#J~9IG|yG{Y!#&HA%Xv*?eHCs1tjYlD}ga?3XFuY(ft( zC_>nPrtcbTw>we$5c15?IZh6t(%Ti3W#dFtBhx3gFl#3me{&_4w$C9oPbo0HxA1q z%MYAGCl<+Rn7x#r?N>48o~Yo;cpgcA+#h5FrfPJwpDY2jAGW$oRj_2|eG3(7H6Pk8 zGPc+uxV_2jLzmm%;1jP6sXO55Y=3;P?#*>Y$#&UQzjYZw<9o^|nmvGu-|P%ypV@Kt z8nnP)922&gnmEZ=egmT0V_F@kCLmx&Xzo{ot&A2;TJie_wO2d+y$59-xq+y56!8Ri zMBaQq+qd+yDd&K}I_j;Y&ux!9UzcwXre?q1MAqfhyB)-th?}QucP`m4jA&k;qgpfj zc#nnVM6zQh8{HveuSXhH`Z0~U77CRYvrdqanlR!0*IGSPclwj|l1Lr>fMZ#u^LDRfV@)~_ zzWB@Esf$FF@3fw^0N=kI@DzexvtFN0bR9BbKAJH3^0|B29g}v2bV<-=z;-4pA|4h< zZ!H+qY1K8^F?+o|tf*=Ex?u?NvJe#b{q8`*rUjN*QLNEE(Xi-Fp`RzLz2f~ke@I9A zoc_hfxzD1fwQA+J!@l!ya^bi=;K6N&1X*YQ)>z7(Q5TZwFm&-N%gKAbJNz>KHA1&S z=MxR_sC7YnpSHFyE}mx}>B}b%<2Oq17ff_yH<2H09Ab9&%gqtSsVr9(zcxLGMO+I- zENr}9;9azJ-U2(c4oEi{&T6e+#vZ6<#l3=A;Y>F7*Ky%+~#QpQc1HLf%n^YcWuCigPvE|bG%AMcRb zA^XU0?Y1nX?Ov-F=irQ=LFoFW9J5nsIC4AD)ep}L`o#)#rZ5-l85`o$aw{$zuL&Xl zF)DtXMlW|$?EKs&cSMJkv#RVjg*VlNehd-l)n}bp7f`$D-jlb1mzs^WPv|~`Zrur+ zv3j5*nKsp9!og*GQVkuy8h{H~%BGzeYIK zU7vZG6=hN1sV`c|dW=aSem?5%@&aTlNVTAH)ZDXSMq6O8ilPq3R^=FJm`C!$fz(Z9|sPz16YG}=# zzu&fHn#kd`%Se#dU)LZm(Hmtc;gc!w$j)VM zQ=>pHgW_Ma>XmF`_XgKeUTrbmP07;cW6j8zKHP5wUl?D%X#7Otq(oBuRU_8L6X~Hd zmq(xLlvQ=a;av~fUdFz)>{_M!8RK50t_~5Wo)rRNBja3)t&m=pnqm<1=W~8JYOROM zdXB9h`0;5r9aBZ=+e}nD+ONJf$m6cFPoz(w{{X%!ScrPc`F=Y5QH!4_Atj zY|4@y8dqMYK%F@YIYbq|I*?L zMDfX8@j_lGRG9nORS$1qnNnx;*u$VZzu9!5t z0|w&;6&1z))+F6zYIP**jnWVzS~C0CWVFq5GH4e>27?1jgYXWTu9L2#YZM3(-P3BQZX1 zNWgzqCFSQm&PXqcL)*!1PJakgA5Kcc(5?I1Ro^$0Kwe?!7Vr;SP1U!?ivEYEw~0q z@SXRot=f0DYO8MFb5Hm0JWqG^?LRu=jjB8W?h9N106?IqAOlB5kAE8WQ`DQVxql56 z(5;{8NGasD$2J0j>f7c(DKg0U-du4XP_(7XWYr0s#9a z0Dwp;06^)K*{m)G0HEE!QPz}we0;pPxajNa^Yin2_3D+fvT|*0?cw3!&CSi^?bzzJ2?qrKQEdz<`1AAM+CbSB_&&1TQC?59UcA8pFfn8luAlU z!^6Y(_xI=L=WJ|jA3uJqudkn(nbFbF!NtW@Q&USwNcj8rZ*_IGiHXVl{QUm@zOSz@ z85tQrKmY3LDh>{gy1M$p!otbPiJ_rkV`HPOtt}cFT2D{U^768ai_7ip?dIktFE1}6 zBjeYvUzwPg1Ox=Asi~1jq^GB+jEqcEQj*gD7v9ViPTJrPrnwpxHmX?*3 zm6MZ`o10ryRMghi77`ND+1aV5r&m{3mzS4UT3T9ISorz#=jiC@?(S}LbMy4{^rfYx zA3uIfO-)TqOpJ_-3=IvvefyS_l(e_E*U{1O`1p8kZjPRwo{*5x)z!7QxY*m$x#mzS4U zP*8aK^r^0{u9ugWoSdAEjZI}`FDSJ0|NyG1-ZGosi>$TBP0F&{S_4zwY9Ya0s<^8 zEki>?nVFeYRaLF6tu-|@MMOk6I5^zg+`wS4tgI{)3N#!# zl9FO!VKFi?N>M!i=Y%{lQ<9efe88YpKqWX%3VJT6_|LZjKyJuZ0(J736=k5B)+==x zIEK%xC^|P~KdT3Up{0~0A@ua*a@E+FlyzZb29p?j3K3Xfc>d%@)mLn23_dYTicmdn z+NoYhv)JSFr4*-jSzVd^cYXKo4mwBmEp>|y?kyenEbD{Hzi_KhcGUbru2&wtPefO4 zS0E(VsTQ-lAoGC*9E=_^?{F)bP1gCPQT0Q9=L9|`=d?o+u8Xhi6RKH)zWvIOVwEw= zChyU!kWsL2^{3IPIV0~0XHdh{+2v`*NJG}_W0U8TxO_UUz(TC<-}YUYL;jTb1d+dk zoEdWQJZm*Vu2sX?Hd=T8tou}b{KVx}$WP?qmzJ1Nl53YnKDTv>oo%vhf%DcoCyisy z^4XHnhY)MTFQlKzAIiN&`F1k$v88tytpKyQWawR|zmw%OkTEQtre!$1n4D~vL+PLc>a5i~(2y*eofW+gIr z&Elout^XsD&yg+JqNDGy2e zeusIhh+? z6aWV=evc3k3}j>-2-!73pTrI<>$|&Np&d6gnb0@r9KXFfftuu(udr`QDZbT`9(<{; zE8Eodrts6qZEgr2@pZ-~sc`2EwLO5ct!@N+H0qO0!Fp(@nO3vi!_DWy$&g>I^C@#n z^;hh9)V40w?njgi$gLyy`7R0CyDn!eb9`(rHJ;uO%dXKNqKG|r(;`Zb!?l*ur@n)+ zkon)ISK#M!R^fO}=y*r4wdy}H`IP}J%hy4{v6g{D8indE$R~Q_M1?n13-P0CYwxp7 zK5a@-!a=el!FYok>0p^-=M7>$p-4IpXqu$1T)&`^F03|hgrv!baFgu7W)s7x*d1+z ztv3)tsbI^TYQ{PMR<6p{@aa7HA^gOf`rOH&(*uT+YG$rFqgLgP^bBy%d~)!VU9sD4 zJt)xVgrIO}h%2~tHOCXR03q79{vmwCgt3wHM47kFivYeaXl0SEdfe3S|v zjPuC2H2#J2pA>|Zzx%LB=2`A%!h0~l%=m$qi~R{$`e@gN`I$YM27@(~Cva_y-I@%K zR6~Z8AD@FSy&K{NWz&qFGlweJ-U5LRyAh2#ukcMWmW+gyxuB|Ooa%mu=W%n#&R<~Z zt$m<2aE*6>H=^jJvU0Rn<5l|Lo=za4g>eUbBKFnu*eKYEZK2z$%e)q0{g8@Jy_Fyd zxNCj*|eH2zz9 zu0tDN{Zd7JX~4Ay^KdAJG&k{V&^C4TZSj!+^|B!r1tKe*8b2o84I7>e1RF{bqTRA? zj2HRM;G8!QW^|*8dis%nak-Dmu%)YzhRww$b%h+l){s;Xp=IoYB{^cwUXc;gxAy#$ z?zxy36)sz11Ty*+(ydNBr%Uh9i+N}i%ZY8Vpx_j|%=~bYF@+c)s7m$&PuixTV}~); zM?0eX!!H=W-H7CcwP-L84&^BDs)1OQmkl*Y+C^w~y|+ve1w$JaF_Sq4wMP?YYCw;Q zNGirmz*_b&rUOT?n=s=zxfu?P(t=+LpKtgVrpX0QaYe$m#yjH(+_oj z@Pk~VzhG@9V{h;lXcQZYC9+-nAIDZZX&GOTum*O%7I^zuDOV}LJQe)AbM$wfO~|)y zBM2RKO12rx#|~xw9C(mp|Fl@BK!iz{0w_@8vU4(>AUHYHfI}`gmLQwpc1~+5s`h;m zVR3%+F#V-esRj{Z`m9P?o_b zrY|3^l3lDUpid=5#XT`Ejt*Eo!i zi?wV+ZH~N75)#iEGWTfI2|XuhWh9M@h|?gdJkS9=WpTF8-;sxR!E)b$oBE`u=h^v23+e|KgM%|o{l*Z}W+V1EvBOLR=fXDu z3NN-z~33l&qGK4O?0vi5qvq3|rTN6y3 zd>E|NJn>tIg5N}7+?%Y7DI^D8Ec}XTH?t>Cclay1yMJvYVaOoyz>ltCTrj224nO8E ztE4U%=229E$~mp-eGrpa)~(uK>snb`(qYOF=XFsfd$w z`!~P~9UJtRR0iE*oKrCeaa-t0%R)CoLdg;X3AN^JN@9CW8AqwK%rXNPP@Y8$t_1Xy zSxHFXU*5^8 ztDBNid86&g9JN?Ee^4rUa}kGh-2H{p z%Io$!$@W_gULWK)y(W`SZsdTZ)knsR{Rw(>3EFX|pmBe=s<|U%)FpUO?Atu@!wHF$ zXuPI3pE=g=fv(cMur^0845Xu7_j9i|))u>MlihAc8>gd{xAx3XTD_N3Qd8qan@{-r1Xt8K;LD*TE%k2XQ zU#BD;qSZ6l;LzF>G$AH2#aLOzw8E=s=Ub46e<^gom}pP1P{w^+C(w8HU9Lhi)2z6% zp=RoP2@Q11YIV!!X)la4dx`;g)^B^AwzWjhL8H-Mf5ibsMg)7xwjPyqD!g%iXP}$~ zwbIVDtqbH-IB+fDJaA+u;*7Ad#U0>Ur|+S8T^3$#hd)pNwRd2aYwTDhrxI2a^n1!i zz@3V$E2p6f5dB`8Pl2mPYCQ?syP5xq9lx}MUM^(vsU&^G5hJk=WG@TV)GlaEn#`XW zivHeLhM}K{NytV1t$#IwcrW5pPN4TT!TYAYn!i4mVd~nOiqU#TokxW_X_pM;>3vX( z2J=Qi@rjA?k$}|fslAeF!O$=D{(X_-M;)8A3&_dfQx^A@Z(uV8 z;?ta|Vi27-hY~w!n?CKLvp}zy4RL-chtUk!m^PbEYe_&TF^VKTrF*-FN>jDL_8GD{ z>OK)`O2>-`W-6VPA-!H&Bo8!bZ*6Fr8k(0}7;FFG%`94~6P53uwpP<^P@e-Dj!+i} z@o-0HKYVHkO|Me?dbSTQ{me?jHviM@v_C zQzQbQXm4tTP%*W#F}I}`M*f=h5G0RkihcRPOn zx~lu#RbAD*>FL*>{&r8jnVP8=tF5VojX{9{006d%vb^rIP5x(~Aw8d`-V?+<8<4$> zh717IC1KuMBR~5HUb;%Mz^4hS{bz>`qO7X{0RGGX5E=mhx6f0ddjR0g0{{nB03iAv z07yM@+H}ML0141m*H?IYdU||(+}YXr`t_@=tt}N5m4k!B$jHd?@$tjM!{+8@O-)UC zdHMYO{Kdt^{r&yy?CiUD@6^@RDJdxt5fKp(5Rj0NUcP*3V`J0Y+&nio7abj~prC+{ zkB^OwEg&G^?Ckvc^XG$ugOih!!NEaKPfu=cZd6p%=f=#;%sM+e&(F`Nr>Fh>{kgcf zkdcu|NJwO4WD*h*78e)K&dzRbZcuG=@0&MoczAeFP*5;2F_n~*_V@RH{`{Ghm8GJhf`fy@$;tWp z_3OsQ#=n364h#%POG_Ue9ew})T~t(*jEv0J*LQe$`0nnmsHljAg@u}$dT40K#KeS= zkufJH=koG$b#;}9h=`V!mYA40G&HoMqXQ0yqobo^U|=jQEnQt*_4oH%T3Vu^p|P^E zHZ?U#N=nMg%KrKD=hv@aVPRo`fq~1*%Z7%A%F4>7rl$G%`4$!y-QC?nLPB4@e0lNW z#l*zK?d@$@S=omVAKt%zFD53Yr>8eMIw~kANI^j%EG+!(+qbc?vB}BFg@uLn_4OY= ze(dh$sARs_OLgMi7kdTltI5;>wJX}gjDlRUrxVShsH#adcQBza1 zwY7C?Yina;V{L71W@cu5e7vQlrJ1jwv2nPp; zqM~ACWMpM!rG|!vmzS5Ss_M$hN>ES`A0MBZnwo)u0UjQny}dmyF0Qw?w~mgEuC8u; zd^|HVGXVjClarH=kB^_9A3r~TR8*9;wY7(b2QM#gQc_ZVef`wbR9jn{ot<4oLbFbAv*mZ{NOkb#*m1HWn8b2ZO;eF){l3`Y;%bjg76k zx;i#CR$g8{c^>Nc{465as42+befpV+hX*m9rFTjjBD2w!P7{uq zBHPoD27PLMho8t%G`*-ej%fnFw&8^fbNSh&4)YdkTH09}_=JyJ+!{9I9H~kE`CECn z5Tcpub)Y|Iz)})PM})zBWPOEorL+7*lZ=P)O6n=J8~@=bmM*3srC2dZ=j~?Q&%q=* z6h!pq3+z^(-hP#RmrXh8BPRzvt8J_qb&D9_Zt z?MT|7q>C#>5cydcGzO=nE!NeB?dV0yLmP;R7$RY=l;)=D zCr~yeD!ZVa4y=H=ti(_rXdBp{1_&A~bc z$EXg{WUmt3sD9b^qCM1H>sZ+yl# zdi>NtWBB3Y_g4(qn)KVAL!|YQk_7USG6ch}6SIqy;W*#M{k343r!g+kTK3LN6u-JY zXQpSJ^Wz>@-*aaKCaLtLDp0%i9C@u)jV_6=AW2)A@kF8ZzQV#^Mvx(@$q*7OcR zKsb&=!Nrs55JmDzX1XyA?jmRKLAD{-&Q6K10H@xEH*a7?PMW0{?l!as-p(XT;3LC{ zgEPQUY)yHFP7F%b@A5l&zb&ZHfdj}sp{H!G2Ul_IUwl5GU{rvsGdC>R%pHE?N*rA@ zGVZKw{>0S09uguqa$SUElBh9W!Tv$e+Bso$_!1ZI{rO^)E@Q9F0db{Lf`qP~9?}z^ zE^0pRAo4lN0=M+$6m4HUy~rTNP#U$gdQkr=JM-vPZWcIpTI< zD06nMS)cT;7=_V?PC5G~x*2oFFb|DPi2KeDgbx+xRCo#D^B}%{h=|)%gOjU)!B=|b z*7~Lzi3U0b58I*hQ$k6oH6=l_Kdtb3>xpe&1=fhIU}q-^=9J@C?jD3j&SFCNyHb;? zBm4<^eu6>zWA5u%8d)6i@an_d24OGPuyMU8;6}gu^);ZH)qQXhF(DsQ{cC-I=kP3r z1`%<)cM-+mtNyB+x^PU_h-SIXXymwGeQ2}#85SPhl^a&gTVGyHkL%ELbAwD|3f*yY zldMi?gi>he0jy78*F~CeQlqKJBjd-MmiV{E zY@cqfvHZWO=0uYND6^eZQQUh4@uzo*s0=f;(rM$TWv~k9w0%PzfZz$Q`yRnzRrLn& zO$N=&Hksi(8S>vFof6okO8p9n(;kEj;E<-u`ee|(v(vzn0RdzrOh7s(hfYeq{U4>R zB7O}CZE9}1X?lkdiF-q#Vk4Bf52$iF;$^olkjZXty z3%c|XXPec6J)-zBr*RMb64!2Eyzh3rRAMKQS{utVE%Yr#+kN(V@d-6)7@5 zh>2yv zKg!(jPNd}ntm5Q*#@-O<8eaK9vTN^j`DF0MDq4ohrDx4v78gE{LGDPom?GWITDUR3 z<{>b1FP9ClVE56oP}bgi_Y4;&)24+C6f8EdD_Q;=cX;gbs^BMY!P8=yH;SZxrBzCX zLW#?J2%?T-L~yzfLI@*!I|Eiz9ozgVW0S;aRAw_bp8LZH<0403%7dPj(TuK07Kc_# zn=c1*`awfzI}&|33PB7+i)ZA_PuUQKm6SIc&20ULV{kwYRuGC0hJ~KP*q|RT$vqHi zE~7yLSa4+exckcB`Y?fKQN6bE2#Im{}E(#P_i+{`1QMT|NvZfx&XQU%RQH=Bo;$ z^fm%7Czq41JJy&q2z-#(O8UnIuUrBGQyeBdKfGh-(}n{PA#5#HkPJ!^(F2m64a>w7 zhS>FR!FJh_tXl9l^wL? z5y5}I*RXCdId3;T?zH79lZ%O7UeA>vDphigF}(1n@4JU?!P- zfus{+-XAFf=uHyz1UwZU36#3*qGiK0(G8_I%KrGfBoy?nx@P1h%wF}1I?sgZmnJjW zi7{C#wF}d^QkhR>ozV8NVYhY0_RcuY600P#Hnc0SwN;)o63eDyc_$ZfObc*qA>~d@ ztJBbFl3!jO4ZQOhb5G}YZVM`N0E=F2G52`vGw)GWDKNU&a`d;QJSE2kglNaQpaI9J zFx_D`TrCoEL}0VR;cI)r)gK<6;(kr{{;Zz4q>PqZYdE);vW`i^w8nKo)+60AS}5&J zqvs82MS#>9smN=+N4>-)-@3R3fveR1*zByz)oc398xiwr@|as)9xSf8+oUE`WKl*U z`x$Oy`Hpj`0U?jSty_Y1RwJZwO}dd!(}N5A7CN119F{%yF~(BjzcAIz5ke-aXi1Pk z!A3ih?_W#B9VS*WKfTa(@Gm?YR)U4ucKzmPg*$E58nsqlDG&?(EK)}-5_g@rv!zJc`1PKvyi^#Hs#iWCp;`+h_6yPqbrAEsT{O-j3Yu_8cy}M?@z>Y6@pQu z|E91ZoLOcMpqaMoaLGY3%zR>b(qZ~QjG85=OC>POh+S19+GIjzi>RDPBpuYvXS$S- zG8e*`lTTMMFz7t&)Zt!K>vmBghKFH+BZ|RM-ag|Rpn!IOldNTJ0`2seBn=En!Bwse z>mug|^V8rOzbiU{GHWTGw6IY?{KQy1w8J!iva$jB_!f>l+n@oj(p z%|s;Y7fu@)`*dM7zVS-Au#ak>Q6%P3KMaV*!7}HUnJohd2Pag`mp$Z@ zXI*zpTiGd4Cus4xVI|-E%}AA-$m)i~dJwA(Jmm-j;H+q{uCXvNtJu^Y+p(i*t@e-W zyL@|)Z7B_^3yCd;>I=F9u)D6XJ53Vl375XN;VjEu+1pLI4Pm5*a6(N~j6wd($^bk*DjBm;HfQdVcoR4`k@dyaCd9>38@o9w%+( zZwblK%AR^F;8h(z?A4Qu4&OD$VrM`I)|)qkmsfXHUME5YSLG|tUDdM6xl6NxcX(>g zEbqNCI0>mk^M_b_Dzq#T_kFJ^DqOJEN-4Z2b4-9Tie>XGHuv#j8?ejqDTp2exuF^tsA;@4*MM8wqaXlwTKp3-smQSfvgiUh}wVP80Cc>vZqBN zG`mERNsl3^MaYed-(MU3M{yuI`tb;!uUO1mIKFi3fCvdcJL6ibyd>gM}zx5A;dmqF-B zWbXO5t?4NCzBO}wB%c(zaA_L$k3~ONEC?UKSG>Z8O9E&%PTN}5^PoGAu`HEeH+StO z8^bS=UOn|H>xlG`s#LDa!D7#_=-q=;l`aGEavBugWaS!2zTI8qY`N2;Zqe~s8L>tG zRf*HKs)$ti!YFCyi}@$y&AgwUeq7GdM(|AAFP*pgfvBbATZ&75AHVjoFOP54=n+0~ z7W){&LwDaBPkCfCk30CD36o2k?>fvp6kg^q@^UmZW5$N&7;}1!u(537!)ObnCt~&n zg;p)z0c)r`Gem8N{Q4J&HL{B5+=ySMPO=05=n}Kg6@8ke)lO7Nt1wb>yW6)sIYwP8 zMdHA&d%GmwKf_k9-vOg*(3wiR2rZvbo6Fo4}U1`%Ur}{xAjP0@$h51#t zI8jUe8}gfBIeIdpI#@r^`r*N^2k`OUg_+bB^+#`98k)$khiui05!M{NGLG+$x<%}H zWr&{>QWv=$5d6i12xH~9 zPFm&yggOGcKa%giUVuPz)UPVI-#z2y#XNuO@A3L+uCu4snDT@`sqM!F)6o6${+d|V z>c_eRqEVq%uR1LQhS*y8d0$Vvfk>FR>JJ^6-1I#+*x%E!#$P8W7AGpV()*RaU zIXT_P#FFn9?ayAsmUZp`H()=Yp1#m)%5NDyU8OYEX{zPH4TO}!p1)%#hLta*!OMJ4 zGK4NO_gNdXk8ANq8t*^*9Uam(ud(4{+EI-gKsB|l1^VRo|Arv^q~=6)d_-+iwC3M8 z)yz~N6V_25X|Cei=+bR#>Ws4%uIbsFBY7>sDQg^9=-6D}cej9r36=NBr#aoMzOjg{ z7K`E{eb0hc*|>dui2;g>jw^+a87_Y;6xsHj%CJ%#uF~k4G_L(rpKc&JqFiXUm;PEm zD6q?NIz5E0AiYwilTLg0%kqgN{Ko(5?Vpxp3vhkV`)SJg3;})nt1z%>aqW%oITs=6 zNz=_C4X%&c8VohhG5uS%<3L4=6H@BRE`?|2#Jjvf0?fH{buKn4CDQ5*Fj;Ph zKKRBHuNer5C>g(MY?sw&M~3EWuEZZ;9%4^hCFnZ_>AmfV@(Eq;#2ZMdam0=!DVLlT zs;@|RMVVz-nW88Uw}v1R-t&#U;judp5H=eIk1>qZQ3w)ojE;F?;VlpSl3f1v!0rP#FPYXU>~< zt9gQ63aXennrMRZ*okf-ChXxsLB+H`C~IFW!#J0nsta=t8a^$P%_9W8v}f_16BAsC zu=KUIAJp}&VU?E(0Nl0u@WoCBT;%y|Dx>PV%6gPds%i!72L|C(ib!RQ1{CcA+Gf>c z7!oQx#3oJW-K^2Kc%WjWgxD}i2C?Qn`2@Sq9E(WxhJ(M6Iw1jpWy6oB*v_NZhc{L6 z(Z*T?p~;dlju|@&FOPflch2BdgeA_65 zGOrCk@2|apDHy|Stzl46h$r;f0K7b4K~6B36D*_;77~3vc!bz_ctm-423L}U|5t*m zyRD<0-~XS`62;Q{oS^tm1uu6yn2)t56i{)swufq1+dD#>=>)lixP&=H=$=dRbMgpq zf_dq5z=CuRFqnrZH}~`YBbVbp^z(S8x!gVNSECM}1p%^u6!jgvU2Q}~<=r9Pu246a zsHpZc>IHzyp4z$p5A&azqM{B^M|%et02blJ1w1348Pb30{5NCdXbW@r2l-x2kM@j^ z{|nJ@bcMqFJfOgTy5jb5vzMwj)cij>8v4%^0Ol7G;pgGw;T6i9LAZQIaQ~&F=jaaw zcw}YMizNR%hYbG}GSF24>>OR7&$rL*>IQRhx97IAe&Yynck|-@pM~M(u@)4v1>4vN x^77m92t#c7pmuzM);xB+yfz}j5Nin6a|YLQe`01V?#~qf6$MTCPqJ2F{{;vXsJj3F literal 0 HcmV?d00001 diff --git a/tests/dummy/public/assets/panda/apple-icon-152x152.png b/tests/dummy/public/assets/panda/apple-icon-152x152.png new file mode 100644 index 0000000000000000000000000000000000000000..726741006bf38620aa10cae48e48632264c7a6fc GIT binary patch literal 6839 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 NK}K1+O7hG1{{{BFiHHCI literal 0 HcmV?d00001 diff --git a/tests/dummy/public/assets/panda/apple-icon-57x57.png b/tests/dummy/public/assets/panda/apple-icon-57x57.png new file mode 100644 index 0000000000000000000000000000000000000000..ce2a6be212104e29caa68571cf70137112bd46a6 GIT binary patch literal 2803 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 RqAQpI(A6^3tW>uN`8QTk_7eaA literal 0 HcmV?d00001 diff --git a/tests/dummy/public/assets/panda/apple-icon-60x60.png b/tests/dummy/public/assets/panda/apple-icon-60x60.png new file mode 100644 index 0000000000000000000000000000000000000000..48f9ed77619c0a433031ff8823a6943e7b334e10 GIT binary patch literal 2865 zcmZ{m2{hDS8^?bX28n-UDO;GVv)N65WFHypACZ#5j1glSOC;H4OR{URWnZVPp)$r+ zwyaqrg|TJ%3ypZE|9Q`Od*5^3bM8IQeeV4{-{-mK-gD25H8U~bU^~YK004&}O4ovJ z|NPZKCw|R{NnfU$lP+4uS^z*wVn4(%(R~J@g@HCu)h|e;JEBe~3u6EXx(EQFkpS?6 zJ{7tQ0Df=)SaARVWF`Rcc|Cb$PFH25n4vB8j*pLzj*iyW)=ElBtgWr3rKSD-{YOVf z4-XGNefo6&{(W0p+qk&6iHV8*{e2pZ_Tj^agoFf5P0iD%Pt(OuoH!vRB^3}5(9_fN zgDk>_zeEIV8=g*CejgXKKAt50K1_nMpzO=No?d@$J zA0HMLmeA19&CSi;-d-~^Gc`4}<>lo!Z{BcobAv#jn>TMxPEPLa?G+RhfWhF%$jGj) zuAQBo+qZ9XadG+i`R(rRT3A@f$jDSxRZUGzoj-qGNl9sGX=!9+b9t?ljY9UUERZf;IaPB(7c zc=F^)et!P*=g)I38jP&&M)YR0JloSI40~`*=%F25E`t_!! zriF!t#l^+1U%$@J&(F-vjE|2G4Gj$r4)*u=KYaMm#>R$|lM|1}ySuxuudg>WH1PBD z-@0{cWo3mzp#%m7%F4=md3mj_u7-t$si>$B2n0bv!K|#TXV0Du4-d!3$Fs4qd3bmT z3k%=9d$+i_I5{~vDk@4`T%3`SQB+ja#KZ&wft);f5(FJi179x?z%gfu=);2aarlX_7!NE~hR_5sFC?+Nbhr`v?)!EtEqoboQU%p&d zS0^VY=i=h>;K2huJv}56X=i7rsHk}H;>EtcJ`oWSRaMoPn3$lTAair`D_5>;ZEaau zS>3sFCpI?L*x2~&*|QD~4)5N*)7RIxw6si2ObiYVc6N3~p->0}!rR;X_3PIP3JMYu z63WWTMn*=4hK5(KUQJ3$@^V?H-*13h+ElxCZ$sj!H!h$`=-M$PNx!}V{5FjRL5|_PZBm&7+1T$06iHY&1b1Q?C@f6@f zkE6(1Ew$%)P-kk^ne@0Ec{k=>cx0=RANGTEXoMP-U+@CHXwMd}^7>IGyK*h>klLQ^> zW_0+YmMpy;o;c}f0LU@YtaH|u$X!p&@uF}>mcO+kWw;SO<35(BTN+7ABQiwtNUELys(O z+3HL@>lGff7oE-@%CiiJD$J%vw2zt2SnW?_+sp8-?s6!um9(Ow-DqH++iTT%4V`g4 zv*X^nXbPkKU|Mvt`)2NZ!ks@VU!#@cWS?b3kOxXf%S}FiDaz%Najw5%rOY^MWa}5) zcKiIupdM7RPa=K3yKYq?csu&(a^SLY(1QL&xkhcM!G?G`X;n=nmMBRn$`ONtVCMP- z2@VT?x1DxuH8QO*S_~yMdDt{^qs2^L#!WV+Sz&7Y*wi|1&A=;~qiEjk1<7+p`Nwak zz9e!I%ebE!1`{b<2D)Ew%S`(h;&B%=2(q9gy_-$N^G}%!bc?Zn{Z=NCB<&bR^HG|V%fe+w}~$y4RVcE zlYoZTddsMuGic$_B%P=&Hv4YVzE_~}BN=Pi8WzOxw>e+qap;vtCWBV8dEa7CJ^aKC z<759nnKcszn_3aff6hO!IDWY+qJO4;@G032-kr5QoYFdkuv3H^-}WI$llyeB zbOkdZOgrn-IC^cF-$PEsg6c;oG!C2yIi`vUMQUXYRe7&e#_cesAcE)eCmp03XQvc$ z2G~*iSw)B2DKZh?E$jFL@_Y5#8Ba(Xb}OnwURoz*Cgb1O9kIj;uD%-hd3}KX+Jk-d zYgH70k=S zMe~KV$^YmWThb{2p{Sy!2v>m1tE3Mxe5WIvzv=wt8iWPl+S;l48f)|=>))2FEDQmh zt2>syeHg*R*WJ?vhQs)~I(d2!VgET87#yRl;*4-~RF+r7!Bw4{6|gu3WeglAFYl

V$EE(j%bs`Yzc?dD1fghI%HtRoV{W{{jOtA&LM1 literal 0 HcmV?d00001 diff --git a/tests/dummy/public/assets/panda/apple-icon-72x72.png b/tests/dummy/public/assets/panda/apple-icon-72x72.png new file mode 100644 index 0000000000000000000000000000000000000000..c492205cf4a8b0fa9b6deb34c7930675639c2063 GIT binary patch literal 3306 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 D(A(2I literal 0 HcmV?d00001 diff --git a/tests/dummy/public/assets/panda/apple-icon-76x76.png b/tests/dummy/public/assets/panda/apple-icon-76x76.png new file mode 100644 index 0000000000000000000000000000000000000000..409f9919cd20ec593585275cf5b1827d143638f5 GIT binary patch literal 3449 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 literal 0 HcmV?d00001 diff --git a/tests/dummy/public/assets/panda/apple-icon-precomposed.png b/tests/dummy/public/assets/panda/apple-icon-precomposed.png new file mode 100644 index 0000000000000000000000000000000000000000..573fc0e7271f91ae38230ec5221a44fe65b4fb83 GIT binary patch literal 8648 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 DbPfM< literal 0 HcmV?d00001 diff --git a/tests/dummy/public/assets/panda/apple-icon.png b/tests/dummy/public/assets/panda/apple-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..573fc0e7271f91ae38230ec5221a44fe65b4fb83 GIT binary patch literal 8648 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 DbPfM< literal 0 HcmV?d00001 diff --git a/tests/dummy/public/assets/panda/browserconfig.xml b/tests/dummy/public/assets/panda/browserconfig.xml new file mode 100644 index 0000000..c554148 --- /dev/null +++ b/tests/dummy/public/assets/panda/browserconfig.xml @@ -0,0 +1,2 @@ + +#ffffff \ No newline at end of file diff --git a/tests/dummy/public/assets/panda/favicon-16x16.png b/tests/dummy/public/assets/panda/favicon-16x16.png new file mode 100644 index 0000000000000000000000000000000000000000..9b58a70ed908b37eeac0b8ac3a1b7f9ef72b81b5 GIT binary patch literal 1252 zcmZ{h2~68n9LFC4Hk{*#-0I|*b{r}Fx6~H?;E-BItCVohWeKb;y<|N`3xy^e0YpSi ziDe*T!3d5?qJ+UB#~ccTaTbJf$qj*&`##I%>tZ4ZL!^9B&U9jfiFc|Ut8U6q~QNDi6G={l>G%Sz;Dtq2ugoz`Y7RCTT zfdL>r3jiHlN?!mV0Rdp?4glzP0N6?%HiY^@U3f@PM8MhE+4AyoT3VWeg9C*^85|t! z?d_#fsqXIX+1c5vtE=1F+tJa{hKBkMGBPrbkB@aa-OO@_HrCeGV`F1WOG}H3i$g<0B9X|<%*@u-c6WD| zKp^Dg3(P%O=Gdnvw2L=ZA_xC$GIvN`r$z*b2Vd2KcMsabmtE+2$eSJ$yOHok~27^(l zRJpmiRaI3bB_&Z&Q7I`YG#V{3GP16&Zed|zYHF&vxw);at-HH>c6L^wP#hi}*3{IL zm6eIbVk;{vdwYASRJyaXQ&CaT-`^h?7+76hJw85OUS2*rI(m9~y0^Ed)oR<@+q=5D zu6>Ww!pkyXeG*6oImTCLFnLWvyCsEDKc|5k9dXy;h3+wEn|(y8^61f%|U_rB^E}>${^bj{J0MSG`i86+V_H_RUn)#<+!i#0(Pr>A$jJ z@}RBU^8N=s2OSl+eSN<8DoGvpm+ny-=VJ7U?!xr1>d=N~X-WAO#SQ#qg;`#LGtL>a z|KM`Wg;eb?qp5Pg&QveM>Fs5ob)Dbu)}G^s7gg`Lu&)grk>N$WCkyyg7R!`zP&0MiCE-)ES}&PiYGbpWiknh!^!1x4}qSB1JWMic;0yS zDl`OkddCQUf{=xxR53e2$Pvj$pCL*2)fn3mU zum=(Ib3Wk$1qUD$N`C3}6}T03ZYwg34!DB59C&=3P$au6=Ha-^L;+hYlH&e-7#zYR ykz?^J7KuRQB3|rRPY&0U#6-9R0?XTr&18E(g$L}<=eCO&Rsi&XU}`1hPR8GeVhykW literal 0 HcmV?d00001 diff --git a/tests/dummy/public/assets/panda/favicon-32x32.png b/tests/dummy/public/assets/panda/favicon-32x32.png new file mode 100644 index 0000000000000000000000000000000000000000..ee680c595a51ef62804a3f4cb872170e6f8c8409 GIT binary patch literal 1875 zcmZ{k2{hZ;9>*^xwbjxx)p}zr(-hTgXc8e>X#`VA?aLS{i6ml+AvkhsiicvVo{n+o z(^`iywpwa^U8I)U)zUW4+Ey%8qV!P|?{ChW^SpP?JLlf}yUXYI{oQ-+@7@##nvIfz zrUC#!$2$iDo*s=x zYierR+S+PpXmEFTx3I8?j*cE08rs>}nV6Vhu~_Qr>X2q$Uf$l`UQ$xhv17-irKPpC zwRt?=+qZ8YJa}MYVnQa9*VotQ=jV|~Cnqm1F0QPs%+1ZQ*=%!j^YrxeckkXUFE77;|GulMD=I2V zM@MI4W5dG|{Lsi~boylZ+ zd3jAvPG)3e1O^7Gs;bu2)j2pgR99CU8X97;SRWrB4-XGDHMM|%fbH#VXJ_Z4qN3H+ zRe?ZITwJWEs2Cd?8yOjS?b@~T=g(8AR0@TnrKL49Goz`gnU$4QQBiUC?%k4-5|K!h zkdP1)69a?63=9m6jEv&rc8Kc195+-MW4Wj=P(`c~w!n_eb84di$R1PuCUE{|7JWR<_Jbre;K%FF`J_8x=H z&WSbf-Y%jl;Zy5xS8P9=bqE{nVY>c$i)nYx)9+>MTNVN>NLi|+=}m21vt{C@w< z!rQt~OmvPpF*d5OHXv+nTGu!_do@nN5P1LCEI2nMfvsI|l<31AQR<&++>qIZ&jETz7U$zBizMN~N!Z#7UnW5^JVEsFye%nc!^0~#wNHnu zsZi5T9~XT8*CX3xn5uqWeulAqyP+vnzb~Jg0VLN6_1`cuR)es%r5}D6R8r4gLYyk; zy*zc|Xkl{AX(@c#abnechr0P(?@f2TIu+s1(KN}@7VJr1Gj5D>#J0}lwz1#?La=G6 z&#@DA?7l7vh*&B$iyY4{&(ivB^}d`}7wpTS>l;SI!n8fTtLJ(wYCpN|d~IRK7N4mh zZhD1Z&-ShqoBLnPM=uz`!7B5NZe-c8P1N_;&mSxH%D-xI>wF`tK@wK)Z3qhL+g^xD zO!z-MAHJb^#C@ATF#YJl2PPM$$cXhSuM#VF{bQOfME<5`FJ2WO42q6c` zL4;Rh^#Db2G3J&21 z@i-_pJ(A1hg@mL2b1^719gFkBFc?^4Q#KmU^fO_xO|Wz{+t`?4hG){5NXP>T?a$oH QfCohYcGfg<70EaL-!WY+h5!Hn literal 0 HcmV?d00001 diff --git a/tests/dummy/public/assets/panda/favicon-96x96.png b/tests/dummy/public/assets/panda/favicon-96x96.png new file mode 100644 index 0000000000000000000000000000000000000000..cbc06d4dd7b903f9adf0fde3c50f677e65d94944 GIT binary patch literal 4095 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 literal 0 HcmV?d00001 diff --git a/tests/dummy/public/assets/panda/manifest.json b/tests/dummy/public/assets/panda/manifest.json new file mode 100644 index 0000000..013d4a6 --- /dev/null +++ b/tests/dummy/public/assets/panda/manifest.json @@ -0,0 +1,41 @@ +{ + "name": "App", + "icons": [ + { + "src": "\/android-icon-36x36.png", + "sizes": "36x36", + "type": "image\/png", + "density": "0.75" + }, + { + "src": "\/android-icon-48x48.png", + "sizes": "48x48", + "type": "image\/png", + "density": "1.0" + }, + { + "src": "\/android-icon-72x72.png", + "sizes": "72x72", + "type": "image\/png", + "density": "1.5" + }, + { + "src": "\/android-icon-96x96.png", + "sizes": "96x96", + "type": "image\/png", + "density": "2.0" + }, + { + "src": "\/android-icon-144x144.png", + "sizes": "144x144", + "type": "image\/png", + "density": "3.0" + }, + { + "src": "\/android-icon-192x192.png", + "sizes": "192x192", + "type": "image\/png", + "density": "4.0" + } + ] +} \ No newline at end of file diff --git a/tests/dummy/public/assets/panda/ms-icon-144x144.png b/tests/dummy/public/assets/panda/ms-icon-144x144.png new file mode 100644 index 0000000000000000000000000000000000000000..9ac1227edfb23fa5aa63cacb579c39ac0851d567 GIT binary patch literal 6367 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 literal 0 HcmV?d00001 diff --git a/tests/dummy/public/assets/panda/ms-icon-150x150.png b/tests/dummy/public/assets/panda/ms-icon-150x150.png new file mode 100644 index 0000000000000000000000000000000000000000..4f1ef4a19a63622799b56bf9230d615f5cb82af1 GIT binary patch literal 6737 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 F`Y#VIu#x}( literal 0 HcmV?d00001 diff --git a/tests/dummy/public/assets/panda/ms-icon-310x310.png b/tests/dummy/public/assets/panda/ms-icon-310x310.png new file mode 100644 index 0000000000000000000000000000000000000000..4b1de3ad52f31ff3c0b0be7464a044fd56829bd6 GIT binary patch literal 20425 zcmV)GK)%0;P)Px#AY({UO#lFTCIA3{ga82g0001h=l}q9FaQARU;qF* zm;eA5aGbhPJOBU#08mU+MgRZ*{r&y>`}_R-{Qmy_?(XitzrS^LbuBF|A|fJ8OiZ7j zpWWTv)z#J9+}!&5`tnty+Phlhu#sHnZYz0uLp=H}-3`1tel^T)@>ot>R- zZf-?IMI&nW?adB}pG&B+t5&!@I0RaIcBO^IEIapX& zjg5`N!^7|I@9F93yu7@Hg@syLT01*CCnqNr6%`B&3>g_2C@3g6I5<;NQ+IcFrlzLx z@$ssvs(E>N92^`93JN+pI@#IT!NI|toSbNAXfQA^2L}gHQBkF(rNY9(*x1;6larIv)6==RxkpDw5D*Xu2nc$5df(sQ&(F`exVWI8pcfYx z7#J8mJw0n{Ymt$WuCA_tfPht1RWC0u5fKqCE-p||P{hQ<*VosCgoIyTUq3%T4h{|} zDJfD?Qi6hlv9Ym`kdR?vVLUuM9UUD?N=loXo9gQ7l9G~OU|CbvLP9}7 zL1biPl$4ak#l>l9X+S_gPEJmIe0d8X6jGY;5G@b z@bK``($ZsNV-ph-Nl8g%Wo6>x;$B`}K0ZENTwIHbi>$1y_4W0Efq@_(AXZjZjEs!H zz`%Zfepy*rq@<*nn3(0|<+{4M*4EZHH#eT1o`Zvf^78UxVq$7)YL}Onp`oEAB_(up zbdQgZczAfPudg#RGnST?=jZ3!+uI)>A2Bg8+S=O9&CQ94iMzYIOG``M-rlmZvUYZM zh=_^N zLql(GZ#@){k^lez0b)x>L;#2d9Y_EG010qNS#tmY07w7;07w8v$!k6U07^AUL_t(| z+U}HAPh>jVZqp;v^=P1CT^IwG-jfb2$(>FB*64LDoe^s zto*VmRHKU+p^YrMFii`PaD}#X;146r^1_1d|K~YpTm^TwouT#me_r1&7>skC^FE*Z z=kt6X2E!p@y1267cVyJ)F=KCWa|i!qxwz3lZ zlrnbx?Wt+$A}>;aM{bFLk67UUp&=U{UomN8MkbI>5-1)Cq3Kl5om z!Ds>ji5d?6C4k?yZ2hmYFdt*}*^QCKj94TuJ!kVX&px-!vM@={S3q;0e_^(A<@P)= z`EjQ&5w6IKEh`Fm@ueNhGNE7!dm!r}Vr{~myLP`^yatVNy)m%4#vty&s#jj!!|(rA z$>j+JP!I3i^}pFAX?be4tFvJv^#D`7U9)%fv@QEGaj+hVWDshknv)1}Cr)4e-_msP zh`i1IkP$bFSyOh;Ycsa&j3gE#8N57zqab(go?WlMp)HpZi*uenAylj=J3#s7TgwD} zoIeCf2vNm_W8Z$KY`N5{U%@ zS99rst?!pb>)0_au1;_^p^9QxD?WH~(qRc{1|-)*%_6G6RhsePM|b4ZxH3jYPFcK) z%6t1GlfYwm<~pC3EU^`co&feXe$i$9v40d4~rpTdGH%OVWdz6Vw8 zk${cKWK8*DPDCyr>Q)KGMM7slBFN0zwCBq@8GKOs-Xge^%ar>0)2?F$uJb-|;ga_o zm<;E;p=bZO~bxZ~gz(JN3g>hckD8Ox%&tBMs__X(Yoe!HU!w~#+3T}r$SU66A zr_6gZ);rX3IRz$}DL(Pd;v_3>niX!Fduss8*+v1trf$A95~pH8hXGC~jNEkBDR&nK z1r^!?hF|L5(xB(Wa{B|+lZf+x2iXVR6Smf`BA_lz5jFC+Z zzi%0M4|ek!xtgWlHO4sCC;-&FvUhJ1Q1_08S}AnncBp(sE_MqlOj$9!OCOrCEz(*< zU~Mr%JoochDIKbIcmk0TcIK@8658{IC6b6;dsiA5cD`g7XoO~)-rfS=frEsXtMt^5YmArn2H{P{AWJudf<3&D4ts$|;3_S9hub=$F%25ticba}+; z@7Ffi=D|S|$~!T8MntAV2DCqJLVWtkDNL60@@{~WMYk{GJ7h=utydt-+VX6apN%XXa7$^WCjr(*DE}#!KB9>k{ zbM#~`lZpob%$=-mhJ+T2)PD^U$%hBI&Vmg*n9KN?xwPqG%ew}l{gVG(kaN zD8y>P(n+vE+*TR$yqu!lfI&b{KA++Q*a#19)3F8@YwLCzY(jj~V+mqsXg3%O=Do5_ zNT1g>VBz?ti&a)#PA9^abgPfS8L%;#4Efrh`1G@yg*N?MbK_tW;F0r2R}SCR%esba zfUUA_Ce7YQf7m{&ykW56us3SOl+eKytUi>OgNa2ENpMAvpD)pqtQm^79c#F8utD$O z*6l#KxzJSi?#0rT$=LqpIVZvvAL4Q&h4kD<<$fVGHI*SPEnFEU5}^o!NTpJpPA8We z3Z#TAf+`-WaJi+Sd!x%7K&#InIfw{dP5Y_OT- z^6kf`oTw{Xx$THk3^tM*dj=U;} zFN7D7>f{Cn55ZJw9u9Nk93)^cNDlL#b&rN8`m+@m0zqf`LWnoXwW z=6avNz>?(fCb!L-Pes+GrY4pJK}ZYF6c=9@AJ7FbZ4cfs>Ui3M?d6>n%UlW zj_Ch(4s06r4S)?+f;R0qIb~|C@@Me8J4G+dy{KCFlw{Fk@fzhbZ_mr1 z-V10OY)iD*4S-D`lx$k$)*4pEQE7Ww9 z%q_Pxd-?_`IV6|#0;A9jBFj(6Zv#GLl?gQ(bF%hTl9S9s**dzje=FwLsw*)71lu)e!rcj2D@|5a8 zP=%r*kqE;uwOS_QwuCe^RQmbVRC#x}yL+~_tx0e6$gZ9W;q{Fh2hj!KZb)bc&_ZOd zoGFY2xRMN8daA?>{Tc$e@81}cC8U$zAZ!x?bt4IzM2HI}y`OV_)Mk}ZL9&Ipdx1M9 zB?%$O@(m?OBF?c5m zllkZ0w=NOV$ZzmUtCHoeBbVKUwb5O(i#>vyjVjU_sG7Sg{17SDk3=L9pdmd(hW9!m z#P1~oN${WK?;!95&@Jf+sb)fTD$PEe_g=r8Wna7aaIozOH(0HK&;}c9TwV7?d32&y zrO^@+s?-F{YLJS0pc;~{`^$j@nUi2qL{wRks5Qb@11zfM~3ox61FDZNWlhQ zch>47+h-Lhjw!(dX|+m~sdiigVhuLR)DJtUv??q}YXpJ{)Ifdnkv}Fy(9)M@f$g%} z$iXHMWQ@6^BJYAltpQ$Z?mi!apf<1*Rl+b73F+)MD~W(XqxIqZF=i*99uE#)Pl{)A&KlI2yhiey6shVA@*$I;s%F#UfBbnk#nDU>gl&3MjMHI*iK4@w#+IKb zArlwS8q}qEW2jRxBMC)Aq611)(76V}s{Q$%pDyzqENBt1)rDH^40I4Sn93dddv|_8 zILByFYv#J`9oF16diYI*NY&8=;Tj^+0{Tt!-3!yR9FtQ5wp3@s210|(%eO696I}nZ zq9>W1y1>u5YOOzTQc+u1ladrvg}LP9Z(|)x!NI>Bo9gde*kCnu<9qGra{^&52YR0z z^U8`EU_(cSfFpva-P2MP9D?5z&5DzMJfG#@u1=@J1_U%?*KLK3lLJ2^6Ga+L^U85G zI>brt+K|8@(!BDZW(}E2G6gQ&duR#YeouseEzzm4NraN5=N>8FzEW$nltz@=EBV}! zVznOJL`ci5D`urdNtld3J~@$ZcM1-+@6K~7Y+z-Z79HC#u25@K_T;~dr-UHLh_PA^ zzab%01HzRAa2ivy=8^lRN7^vF#o03X$QXlx zz{%A`UEvB1bQ_hLdh?Or<}J0eK^4L_&55wVZpn2Qr=)c&i1ZrdwZNQ4^+>pDLlBC2 zrfpV|j)=mnSopzn`|L9eFefoB$XT$#s~@=QWEZUKDG8;b)VOsOsddgs6j9+fDcW&7 zsF@H{rNZR%{Tqw6*wvE+AMCWVU;`$UzioYB_F5m6CI78dBqw-^q$6iS2jPZ+h{5+@ zv&vGLHJQJ?Yl}T8IPe$38Qm^UfeqTJ%b#}7YA;o($ZDuk6P4x(mVSd!BMn1C_V{EN z*+a*-S#|IF#hJDXDuiwEN~gdEFTG?%;?(^m93ps!jz`oeZ*(LAs_WsV!?GvC$e2ha z`P3h|f5uX~V0(C_s}o=Y5-OSdLUMFhy@qW0gEi!^nj6S!ebm+!ZnP8|70nlaeLlsu zXqU&YUSZDh^rK(<(tg6@rdzY%m;fsI(?7$V8^Fl!|7} zceh^7wW*^VV4OLrkVQ9akmsk}dm=x!Sw*JTG^+Z->Jan>`D@n@iiAulhG7d)(=sWK z%wL>jLyZB>YUKY}Yn|IzHo->N=Z^|}bIhM9ENv{NGv~QFXQy&06DE~%^J4=oO(NRn zz?bLmT1qw2f>YgaJmr`1EUVkqnU?*elr9W-Epx(q#TB*jJ^61{^U5jeo5C9O;3kr` zL|+ZmD9Ii$t?B27CM5}}@taU65N2l0`?Ql`9X1w|TRIQ^h1CngA+lL-ExZui-5gKq zHI>qB^3(?D%_5-$Y`}ZurmrnC840_fF==0X^RCN$tNqcIk(Eof-V+!sV^}2so4V{_ zIMBh`8v%|7zVpG0v%+eP97`rw5q_dkBo*DL*kBmoP^q-0JV(yKdIa&33u)&F=>wL=Bo-$f$ElSSjV0Y07<-ctrRMZmJ zt>uvFS7A24{KvWLr7k7Jm|Q$%-8Vr~xME7fOIR$HSDX2bzh>;~k;CEM2QJyUWjCqTf@IB@9m_%PJf#*;GimDLGu|M_c+u$N;8gNE~0{ZYGNJXu{M0;-jrc{eJ( zMuC7~Xj@RHBD<2nm(PCu@aVNgRPr?;qffAsC6gNj;~>G5N?{^}F$ z!XRgYQ)e}DE+x|?Mp{$w)LY9yG%|RGzCb8k@?`h8*m_GqrBN!o&s%GhywL)8bxFsN1I3Vn!NISlO;FMoPyTTdGt318N^*WWD(!D_?1CZo#ID5-3|vIe`!twBHF zL@46DwvOBZ1_c#__J2mCByE~Ax;5sU7lBrT^$v3=`K{;^`=g^F&a zrG~7~MzyD_Z+QOOM%;9jvkC)yYBSnEH_v+8(@ z6OZBo3X|7(iqpGauhV?Ou!1r(-Yz!8+JTM;a&DJP<3gc>! zeGn${YeccK)F!E_HWL2lEkw1u-o{}^{|C_+&zI-o>pnNe`1 z!OK}kae=FP&5BYGO+~7}1vR=G)=@+cZon`EBm;vARCRo65zk4mfvdVA8{j@+5F2O{ zJ$3fwWfPb+wI(vjreLOudp!Sazvh z=ryLmY18iySCDm!rg7YVNLkc6VVJx!p$Tr~)M_||9b&p+^9o7O5Xl0g>0qPuMn5f` zuyHFAIRtF+spoaH!Um#veRVDIP86K*VDB5SOVnww>9FW4N|M`33R>ue4Mgz?;p8AT zRrl1Eo3UroUP45EqkJs!d}=zK@jN`~-n(04*@RP2^AFFo4I&iDx5V?R=1vifJ_vvf z-JeV*!!>nbqJNKS6G0zD-kK9g#Pdew)i!_n4rl~+w4=aGz-Ba$_qdt6ChdStq-%&f zWhC}hsG=(Lbb7ol`KbybidQ9<*Zecui>SkfU~RPuBA(YYccC<*=BnG!OsY0>cO&+% zaoT!F8WP+@mSR+8YuGdyQy9hj6~jubR%1-=s`{tb5UwFiCz@5OAxbgvC24f|AmA#7 z=peXh!>r+yrnSib@im0Oupy{>f{B35XpU{8NkRc^*r+Blb!@DiqW18k>MR58BjOVqZ=qbgpfI;vrQIcC-O5<+goi270 z2u$+|k}dLdHIJ5342a@8x->?z2zxbM{x7H@3;|n6SQG)9hEulRK!;IaF~q)!O0ss{ zR9^G1giR!sHO8vQeuI(%j1I-dV4^ubWXGnmyYb&rdKpBh$gjQLVid|c9FOM#yNcbr zEv1*h0zc$mi5kpMYJ0gxY@C$wrQ=m+_6`JB2yBHQjA9F<7@rY zoa^x#Q#3Bn2oHTTDiY%y+46#^b>f&cb;p{j^mOYtnJCmRcKw+3nkn)aDWodyCT%gH&zj)iIH-D$R|AO|>##?1;x38si(Jj~q=T zzy(`wi}H}6c8*#C0e4s`S&HE_oky%rRvb>VF}S0W{a#V}Ow!Fe^ZXXFl? z?vgR-i5HKD=neKbzX(zpmaHVVvq#OcH|xgopBfHFKY*CjEzX{zG6oOJXpOyXT8cnu znV}~U;3=2BEt1m!8XO~o*0?R&*LT7-HS7%qOg`$nbS%7f#KrrHJPlN^c z^Oc)aL=akz|H=!aw@%3IHkhlh7c zt{tij{&UQC)q2O+O)V2+1;6fz9OBqCXdr}3R=-lx;J2)qoAVqBvH%wHxY?J8Im|;2n8fjA zx0udz;lK?WaKlh!f2x+;)?Sit{mkVdU<*kLd4@*ATp}M@a$(B;g!KwZ>d%P@tX9;oUkw$ZB7MJq$CtyJlUdNSh{x1TK zw8V!az;6%FE@#`F8$j9l3lCX2+y$sOVZ@ad9rS#%jO97L%M++eT`HKG$R zKuJ0<^;bNp1_&4zlZ#v0Z@+7LhUB{Q^}(-_E-7jZa=U^cwOsCPJ{w_eU_6XUmQH{D znF_>Thf$VPq-Js;xtT~6TV-425ANqXpBQg(VA2{4l+-G)1Uem-9rVq*Ox)VZ?(qDE z`GpiOJorACyef4!g#(xf%OE`T)Z`9SPPIgX$<>{yD#BfjKeZpRbzFn2poH}MRE5!M zN=_T57Rf`AxObLsgfnNYJ`ygxytmVy1DWA@!E0Ac6i6r>vjL;6+%5O1JPdl2nk$0o z(8jBlP9dMJ8XI9_dUtCft7#<5y~O~RkqU1P94V+;@f zI{s6Z1gCO339*umTki?-(21#K%n`J5fsfLjDHp)|5Jxwu$W*|o3JQbmFd}MCk9}|T zHlc*VSqsDyMb1gAvIjOyB%kv8bRi|!NJ-7yc%RZ&MllVC_D#7krHO242LawkOPYxs z!eFkQszk!$1k<-&!c_m#;J)UPVyc(})$b6koyQkG53>a!KVA_>uFhQ{PMZE5-O&k2Q)l)adV5kXTD;nzF@@`8xB7wWFT_! z6UGV(RZA4D%&FIqu6~fW7iAG;=%z$*$RP}+Yq?fx2;kLw28@0%!>X$`2(~TH=7{Z$ z64as?k7H+TJ6H~`Zs!lFa-DVchN!of0y}C_z4(l2Dvx4K0er}>Adq7j!jNrk2!mRu z3wm~J2HytI2-wc5ib5%9=%HVgNG@BjB3EG35sd;NzI1uLTyFKAm=wt_)^Zfd-@ozc zqX{x?q92;p1Y~W#Y$Ass}r4AJnQ2B;i z4%>3@Ie=}`Pjz|BhKa`27a2uvmdvq!O`(Q4<@3q?#)YiGp zmEqc^I+r=-KR&=|Gj;h{)od85t9<7`rcJQn_#$IosAH8YwK|4d_?y+U`1;?LkI((> zU<@T8MMQq(=4a<+;<+h=u`TI~OU+Zl(6oIh0-?PmI#}jxThkvW(QP1xWM{23~_`9oY zf^Fk%0n}hqqx!1EIYh9y< zU7Sio4+wa)j?%N#{Y3!V)2|dzgbj&V)BHRUkmb(!F^z3Sv$-<7n^%KR3lG1(u<1ev zg?s@fmA1YgLERCM;KGa#+huZll1kUSOZhlsBF{GIDR(vZ4)3b{`G3!Yd;I<(}-(=VV+tRNm zqYj$U9@q>>`RtvP&)4ST6W&V;)lu==5Tx?rQ6ixy!1)Kh%)zb$8$3fTM6-(Q6+QQ6 z0hLBT2%E=aNmTtOaOITKiaaSjqb%#h@DH;{*yi4SP)s3c#4w#`$_FV_VFTiS z_WPnxx;n+xSZJ$aIo0Fbh4|%n#!-XKzhmJqmcR`-AUxTMf5?_QX2Bq-x6q z_|6YEOYQ2H8RqtCxvunwebitR;A6k97E7(drq=O_jMMwB310Tr&pcVf!I zU@waI_LotCZNcZ%P=f{XqqoPgski`CNha5i`{Ep>Vhn(7UVOCPDr}g{z)tw@lD>R3 zV5G4hEYJ^AieWNXY42Xf$p&SCL@@o0sWDdh0>jwqwa-#SO(IG9%sNV2}XpcY#de49uui{WcNu#qZUf$jeCrGLZ# zh9SDr)jKH}(}z!a^Q?tFYLU1m=&b=RG*~)+CvoZ&QIyAjB8Hv)>=3(TtKLL;SbjIUN^T5mSi6pQ$xgVUvmcr`|qkK)?n-$vkjR z1M6DYSPZ5iU{OZDEoB74?m8>sJb*3a%7-|GqP#>P+)-P@MrqY-#1hb$){?VUBP{r6 z&v#cEtigtf#5wDq9>BZ+i;UOnhnjBBWVo~%_w?^h17vwEfMA zgA|11?#V4y;ztaViqciD@3hu$1YTIuUnWP(Rl}`;SM$XfD~$of&cCz8Tr0yuK#VOd zdS>q6&2?E1p3x0q5dizeH1&7!NM$fn(Pq6X2$jVJ?me_gaQ%%40?C#i%jMP#vwg71 z*wQnfUblt?mnMlA^G%q@%G^Forbi22$sNGC0K|cUp_O=09A+=7ohI39hyn2IsC9{?-dbF#?Y5`LwZc)B9 z&<&JGa__$?8X7kJ)V=!#Ry%<$*|#;;K;dizolfmh{Ntje;UyLPc-KAoG0q!wY-s;& z3qJHt1-Lf>$0N4>SXdoGArZzddh;m1A8b&)OyY2dDwV?d*X1iR2S#?_-SN*U{jB?^ zm<;2EO3Q!#;auc!ZQ{Vyz3*U%gZ&G`lUnW7_V_Ty6fQ}SF=IWa6A9H@jTJ>wbnTN# zgF=J}AB-BZmH<^-^$eAZ=#MJgGaWS63Gx6Q!Pe%#|`*1LeKYBZr zbhO z6F_?7r&FjH1ra&)18s>u`0?dTeBf46!qF_vvl`Yo23w5p7v}~#P=qPi_t)d)d1{2> z20u(E_nEo0AGd*}?|grOc=%n+ELQe=iw6dmFi_Z0Afq}DTa6(xF(GGWf3ih_4~#bi zOFpl#7N$8yYJ^u)HZxPuZ{7&_mvHW(mwe9i{Pk4#FQdAa7cckQCj$PFvCd6)-LjRs za7D^s@seEfY130?6CemuU1Rh5ExG)DP8^W+t;hWA+9zZS$$h_ky4R>l;KrAfWv~70 zE25&)EX&7qsK=fC8(P3aOnWSjIeZf;_>F(fv2_`PGbXF z(6>7hTyWd_X(D>Y8eQks`Woe1{GQsT02ayadgNd&$+y6XDdCe4MGK|=ERp>QfSR|jF^q2+(iwalLa zpl$r+mF5bO*g$=|H_$Tvt>1suFL@@xQ%*nN?ZvuY%QN_0$ib~b$v{^xys^$qBc$X#ju#Tk1zLwO@J?X z>4KPb-K~)TQ~$;{4)pIAgMq??BMr8Vg#tdDPKycp_gjRYRmU0vNzO##?uXgyihhJi2{sP?st0VNv{f`2oWLUn!+ za55wY+5X-y8 zTNCa&9%F5*8Zf3YzE76^u57JWanKfA#lD}PkjlkveYwT|5~`J)-96>Sdg6QcawJj22F_SSpqcy>*+SMw?# z*wveJgk4vcv>26xH{sZn?RyXN`;Xtlvz8Z2?KsN>j_on9{WhpxEO)y1(evC=UG%8n zA_H4)$B;8vR-XFl&fbzIc=8D!Y;dJC`3_hOOtEMwJ|IRps`ZrH)itW-iVW-;kB|{O zW&xGQuo1OAIRmwbU0M8kKZ791`lQr*$b`{QxcQH}cUAxJN`O!N`?#IT34=y`WDl5x zce5+42mF`n$H(*<1PLBF<3Fwb6z41gYbz?=+OJ_A?)m+}P0F@Yr^A~0h{9&~nG9;w zf5S_wdXt49ws~SD}kR8hCaqr+>;pi5Q|kxMNdaj#aSiV!A)& z*(vaR$h!Ob=T30pzK^qY_WP^<4A_9DsSBT!+uNsxFMaT;-iATXEQZ+Q$h5xvG7OAw z9p7O0h`N6UVVD$gUwELWXb8S6{yUms(*#+TfBW7&10u8MevTf2Sp0^gTfU$E^Q9q`dAw;g>$Hh{G6`TDrOGhqWktQwHaN}quf`5jY-hBV*Wo36uicfD9+ z+hj^u{mXuLdSX^2Bb_si7+j84vTc^je zSWGnPg{8guWe|=3-)6a!d{)3{Abb^j`Nzcghw@PMP&57DXJjI&T!&&ZuFS>(#MD*+ z{N&EJHwi483nYSdfgLsv{%0|~q8}fdX{n;Xq}!@H^5{Rl4};lise#Qli2OW8T?hzk zqK!7 zV#Va#eYjuZDff3L6aye^sE)@&sw!4q{P^x$?yOH(BQo%G=wR5Sa`2&=^PRKWS`Y(| zXMi~&dSR34^l`WM)Cpe)->H$7(C83|@M2Cq0Vjcrjl7Z1tM7m8#KS{&xRk4=BnC#;pm_>HWR@ z#SR{gHvl%d_{#i=By2#)CO#D%YRA-n0Gnv?{D_`*X(7&E_fB3Yt=tAgPZZ)l>qPPc zAAfQ#vd1%D^2_~CmE=_WiBRGY83e&XJo7iKG~V;<>Z3`5l!=SC?*8>DPU+ROS?N_R zbnXg~Bbu3HUK@@CK2&P^IB^m-m&*9w^0WXKY@C0dO(SS9$s=#Pa>v)NpWe7BBNC3$ zCMqZVq+?Nyy^ff;vW9Qr@s}_It!`Dw~i)YDb zQnzr|z59PQl9fJS0gwK=ooV0Ehb%_u@q6a=WbANB%JMTUR%cY&02;;Will_E~x$4M7Sd9B4K-`so4%;}p3Y!ZN!d-&+{J61g2NYg>k7{+cn@kAuKTUjDZ zda|t2&J!bn_2m}d-7}~HTyXlaYzo_zsIF5jVrR$Ckr2IS!oDtZfF3anLHJ2stJr_- zf5%EP`9%LH@gd^FeB#sagPA)DWc0jAHRkWGT|{&h!pz0#@7%rYK4_EW)%wL*ONCk@ z#Gihwozhe#N~p;Uyf)38r9|O=U?(Ao4ZiID0xx~(R~h6F`aUG-A%G+Pr#YR<25Ty? zxkP_=iA;V-@O|?S8tgxvl*KSy{WPK%wtcs{**7?S2-JYH=3f6{3b^c0!%rjvz9ePr z$yxj1mLSM}*EAZ!V_7V~-pA69It`A&2DIC_-&bW4*8_5Y=*4s|8emgnF-0%W6k3}H zZEe~DuqKjC_oSoN=LuPbO{SNYt;oRP*fBiml2K&WX=?q6Df78|C!yT{Y^UGNccF#b z*m9z~ueqQ)x#5_gZe#hrD<;g@kR{OF8!2gI+Q9_kHFxpBVKcz?VI- zmX`e(7>ZVzUSGnuW#sG-sPT7hRZOL&9~jkT@7+kuJCtPpQsVA_J?Mdrr8_Zy6=7f` zc<#DX%%MZ8`*1_nB>uL=mT{cdNGL8j{732<5w&jY>yjE}`xWkpgkCnzKm9(Olj10B z-~!(9i-=GGP65AIM9WMk5CV$*lAkybY17!;K@xgkcXx>1z5)1yU_)ZgADKb$8XmDe zmBO@G>$ou)(hV;WW5(c?jBkQZ91q895<$`n0hK&jX8vG^;mq!h z#KsTdxs!G!jIL!jhF4|$b4kQ`g?Q43$s#>19ar!?){e5>69u*nhwkAu9G|$psnY&K zy$7rWLA{UtnMJ_1@s2zB*4H!li@pF@r4au}#EbgMzbZRbJV?ts- z7@KR`VG(c{^S&uS*gASnZx*3Q-00^Cu1I;f<%ksBY{E;3;BtOD}CY62XxmH+uESP$zcMe2>JdUf5oSF9W>%7SMNO(0Y(4v#|zkYRuG29 zjmgXl-$5S0nDy!879M@=1hCuk_`8cG0!o3FfRAT9_eC}4c=X6u`Oh1`j&EyT_>RpyKu2Ac=n`RD*hXAf-)~X(`jJSrY1m$kbQ9B z?zwOe;;KjT#q`+&i&u~XTKGJ3-P(Prqbi-4!|vR!FRARRU+8)L~0R6XEQE_>eOrBgmdex<=23O-kZji z)7El?sdZTXRmC3<=K|QKJu;43FTG8$v04g$dm1Xv!#jRH-ht8CeFP`KO@42Tm08UK zTsU{@k?kn#tD)`Ih+puji$BffzdXIPh3b+WJs15yH(rsCx7)&^?he?pb4hY4kd{F{JH}Ez6Wh_!ud*M85aVj2ymB1prMt%c(Z-XzV-3P$maCcGUUZJ?th z+K+U8X>}^jUZ8nW-cq(T6E_*l5vC`;uiHVi&-Uo|24;VL(>aaSj&{o_;Qfc(h zp9ly0Z*UyOYsno_nzaHVt?UdmnMd!4#3Q!WqpmhL>QblB^DmCc!tuqwUo0u}rPb#p zot4SGsIFP2eavil=28ZwM+tFH_%*mSrx~RlGw+9}BYM&)g^MUL zItWsBnI8|{SlPRRu_*|hktkk>Nh?JT?zR~sU_7>qUgfL|{@eR%U@6Q@M*oc>Sr^2U zf&}#u!*mcmlla%Z-8|GONW-$bz{<)Kh{J~+4OgT>z2 z;P0c1(cQ++7fIeh{b|+W60!KS=G7b}4r^pj zOch1itUj|ry&#?JUe$oeQl+J3Ju6u)ID2q~~G7m!ugkiDYIpA)py=T{C+xNiNH zv0C>IKxr~LjF#51hPb;&ZM}jo)_gmM80-g^dx__uWyB%*$(i`-MAnM}u zifq#YP?V85QtzzI@3}b4ln;^p<3LeXoe&{tsIx63aB5cSF2?VL=W^TIgZ3^DE=#WM z8Ih7YMM{Z|pO9e4ca?}T^_-kwYfvz~(b+ELxS)-lCEKT8-ApITg)!<(!=g0CM%;4V~tV1E1AlhyTjf%-3MM_b1siyy@H70fG4Z#TjVW$JtyF zW`4;LYnO#YB@$*$|`p6d(FI!^Dnayf;>a)vfEHObUW zy{!yUX@asCePxkf3=b1Ymkm}?Bz=&IC{U4Wg@$J2cVdZ8-Mmw9gzv){PARwYwi=C4 zEmst^*!F=4F8W$Oc0kzz&YbYTg@_T9n%=(Ue;y&eFaNB`6Ki%k>{A~@`!j3R;0Wnd zpZ*IHS5+EemKfd773(Pl`mz|`?I&Szhn^+&j>>{jE6fwr?=R{%KG99r6fce_F}G^Z z5bkq{r{&ir001*`)~T<9f=$nh=v}r`YBzc&16P+KTQ%>-=3GBqYK-muWy$FBA=!fq z4yV<2Xl(RdzCW#)W-_a2*v@FwdFvRK-|6FUCzMAZ`j(|Fb)CC@#6&N+-pkH^75L!tI)*TySIYCq=~b#J4R>U}fmle{&dGpYG`ze#>L6co|X) zB-L~kE_V5<^(qKA2JFmw61=8PhCa?{XcrcMLo<2w=UCS`pV?AIwjTDGo2OqzM%a>2+Q3hwuuMw;wYE6>=N!iD*?dagL;Soe?~g6^$mm-El66r{@#>i z?K)W;Q_Gvq_`F7zy_bY^sXfoI={HNQEbmy&{sow`SR-eIwOPDg5n~gLu4^nLzp?wn zGsKz1zWG;Ve(HVhdzR?cqFF+alvM4t+3K5Ys=;UPYW1+6Woi$%5`n^})8>?vyIS5V zmDJS~T9%7l!iv3u|Cw-MUjm`=zpdk(zpL|hh}T>`Go2bviF^KIku*uJ^`3ZjV=Tgt zQpgmIbqD~iRG0yOTr;hEWu9+l=-vMXmPrM2~ z{=Mxc`;|#s+gUCN$Cx){ghQe42iqfw#Ki~NIj6v^`JlMJ(!_y&$9pRRT0b|y_kMS% zEXt!@Gl*y%=)XZeF&! zpAal*X0kCV^u{h-R}jd}7hi1*A7Xu|@0P!yV>8(baJ0NefPXy zMh~@P!tH{PHMXl{w=EF%VhfqSYfyjF@)(pL-spZD>Au0K-_PzB5sQJUlF% zA|5es@*8o5I>F&2ChbrY*LmWb(rhtCUb8mSS)=iw^qZUN2R(S=P1mX~3YV{B!B6PE z!91S6mwlu+KmtdpT~#A2wP%PfVTTz$%i_n*p=@9|jxP5_22z^F}@UaW1qGzw~wPjr;y{PD@Q< ztwP2p&=+DunoxXeZ?8z)`PX9?%r9$m3+T%)s?TMf83L`WO>Vm#kH9J^xzRI7`o_U3 z*>pbNoZ$x=C2tad-091aFUA(UBUfg5vIE5eI~tBvOxeJ3J8#LaPnm}}=znB?{_{bV zz5?&Y$XGQ7s`jex`R@Ljm1&01`YL*PxTztFvoc~Xf&pxEg6;~kxg9i3jJjJayzTVM zb8e1XR)1Hrs1mw#WA~BWUr_v`a7%IDOhB0o9hZqXlKMO%f&Vfez>6osue-M-ut39` z6+ejcZ$9#(SQqmOmRl;h1>bZ>iY8SX?;iSna*lm$P~^h?6sATPX;`Zh;O9Etr?+Tf z1O!#V=vjW#e#Otxx3gOObx|35a+Q5RB9S$OA=7oA6T6Z~Xl&C* znN}McFy3kBEp9`_nv#EeDC*<4jlB}O_x0R0>kaaN+dITB9wRhJS&oJG~09At<+te zL*h!iW53&9N-NL!;{|d^?;f5AiMM2AJdsP;Q>NZe5%>lDUgEjWVesmhFGC=T@x#Oo{Tp1_a6WS+)^4xqIG&&-)p@RPR1)dg z-n>0_i6=lxE-0URX4E@(Lh=@17-Ry;=rIspni>@!>kR}^Pr`TKjc%u&4s&jpJ*B0e zx2j`u?=9Ne()jF@9-Zf?;g5z+ZGTZ0&^jW7_!s10>GRN*txD*`vbCCW;(YpBPtBIu z44EtF^EoE9(2E}6x+hB;p5*qBhq-7AeTEMzF|l=c$AC+C+5>#bw@J|>S382^Hg)@t zhTr+ya%SGyMYV)Mw-{2hB9E+@i-jqfRVVasUP|t^2VSTJR0-H`$#~zn$}Y@n6Me8x z?G$V|64bxUcs0G8wlHeW1Gyv3*GrvNIyzFH<)0$mJ4g*Xmu|=L8Y>-A!*`8EE}%3j ziiWSLU!6y|5@mponFizQgG1Bj720DLx@bz@U2iW^y0B{d`XUtuDXDr%H6r!N2pe7F$rMXm=2dc<} zz@o+=h^Px5?*&s-q}4c6!2V6s8jV))^l_e!S*HmC`2JQzy7+lGz+hUQXg?1O4iAGF z(NJFisD#?2_+QMweZpWa7_74k9sq)ZIRHMeoWUCm+3t?zzOS)p$%Wr1BZ9_bXIh-_s60=alVTGKQW3xdx)wd$iV>uR(1la rp&gYlPD&7apc5GE098ZVqZMct3bgptt))F_9sqD{11<6`yU2e5BeYv| literal 0 HcmV?d00001 diff --git a/tests/dummy/public/assets/panda/ms-icon-70x70.png b/tests/dummy/public/assets/panda/ms-icon-70x70.png new file mode 100644 index 0000000000000000000000000000000000000000..80d11b1de6566278eabb4bae661104a8c6b1ec39 GIT binary patch literal 3213 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 KOs8DSA?#nA2g4x% literal 0 HcmV?d00001 diff --git a/tests/dummy/public/assets/panda/panda-base.png b/tests/dummy/public/assets/panda/panda-base.png new file mode 100644 index 0000000000000000000000000000000000000000..d00219b535dc0977840a741766cdce3418938497 GIT binary patch literal 78176 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�ts}}T$oJhs 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 Zaa`mv5)%HsS(Ji5^t5-;b2aTl{tp3Dq2B-i literal 0 HcmV?d00001 diff --git a/tests/dummy/public/assets/panda/panda-base.svg b/tests/dummy/public/assets/panda/panda-base.svg new file mode 100644 index 0000000..2f6bfa3 --- /dev/null +++ b/tests/dummy/public/assets/panda/panda-base.svg @@ -0,0 +1,93 @@ + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + diff --git a/tests/dummy/public/assets/panda/panda-base_backup.svg b/tests/dummy/public/assets/panda/panda-base_backup.svg new file mode 100644 index 0000000..2887e61 --- /dev/null +++ b/tests/dummy/public/assets/panda/panda-base_backup.svg @@ -0,0 +1,93 @@ + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + diff --git a/tests/dummy/public/assets/team-panda/Panda svg.svg b/tests/dummy/public/assets/team-panda/Panda svg.svg new file mode 100644 index 0000000..892c8c2 --- /dev/null +++ b/tests/dummy/public/assets/team-panda/Panda svg.svg @@ -0,0 +1,118 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/dummy/public/assets/team-panda/team-panda.png b/tests/dummy/public/assets/team-panda/team-panda.png new file mode 100644 index 0000000000000000000000000000000000000000..97a4ef24c428f40080989f6d5ecf0d25d81ee77b GIT binary patch literal 130421 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

W=;!?Q^{J`A;6 zY6}9!mZ!iY`1N1J>i;644>s*1gwOQkE^=H#&Y;zu+QMiG0WcVj3cLfHt!-@6rQJ2= zP07XhCtN3=f4iXe_96{5Yq2I>b`I~IC+j#FFGSrJnFyAl7;+GsTQClj*at)_BoVSz z5lha*7!fp&?@xSyqJL67Wmmmo)E|8vX_*XvCAn?NO{e5?Iz7Vrpb>H?rZTDSK=*( zJ$tt0Zm@{aKkFm0(OeDFX#86@}0SmoqQ`m zd_*(kh~UgpyHRyO{kdN;#k*3>1+qu`H_Pun$4pfd%a$y8a^LD@!Pk;NP;{~Kn);r) z&zQgZIM7Khuf?w2Vz%EsYG0fx(o$fZjSmYdK3Z;U%od8?`;&m;tuMm(w1?*+1&X{l zE8~q*IXTcg`0P|QdOY@&%s!B-Dw&&m1yZ8e*|y^_P=I=yU14U94x6~G90WPm=#8(6nkWU_F{qB;$n$ClFqCA^KW0*u&7q`6Ig1Z@g z*9#`jnFk-r6i@mz+=)#%rg{8$R2RsFOD-Bw4!pZM9PbDN{>JbW6pv+~DUh^aVffUh z7)tBdi|J$E=CI5(uAJ&Wk4;Qz8NZef7oYH0vt8lDa@(f@F}aoU2uk1B;Q+S8r7c=3 z=3t}>8N~aYk$THU5k@|Y(m~6Y_<-Lf$3;pVyr%UO&n-KOG>>LNk#a1d3pQ9?-xg46lYQ3 z#l^1`?K5*JOOGgw3)SJGWH<0#9baLNuLz0KO?|=M-Kgrtx|lV(=h4Sa>t2mj$;(G; z-J`~AI2-E9n)hdg4ALH9X7nATLA#OLH3&f#*e*Zn)1Tico6@tN4340PwEI~jjL+SV z&z&ecmb3I^Swm}$AkuY*58tWZJY$c(Xbg=8Znda|PD@E%513p}N&fZLH4G0>ew z2xa`(<%+WKfw3r0uP`Rq#KGE2u7wO|zomo0>)dC*_QKS2ZfGNmrB`_8+o2aM7W=*C!`@=l~ja1b|I z0Y{p0VNQ9HOrn}d21(Apic%d$OH3yiw8mI-i`K0>ExyN-M4?1>L=}MwVYiP-PQprY zn*hLMJRM~+b(Y&H4=#fK=LcW;;`kcSkq~^p_Sq@4H{VGf?5K{xj1rq~+KUGd)=Krg z-xy)a`e~rOd@yI~!nCntv&UwJuk6{x5&C2Rz-v;SQ1Fr%TSN2xJ)igEQx99c>fEkH zUD%yXc%GI}<1b*B{9aZIq}~D{=tpCAwm{Kn=X8c!7>a4qQ@^{r=9}hemxl-~Sg9Cs z%zMl_)p_rgck>4iP26^ot2&WGNY)~I)QLLIrD?#=$qEG-%JzhznwkeGhv=3;!ltYC zgTT2kN#IlGxy;{PuZ+%izT`D>Xk7-aCJ1CJ@d8<212t94ygsNEBS_`_uEUV;MWRVi z${${Lai~iRkulh1+`&I#m-*47C*eIr%@p3-rN;*8Hlv@rozJ(1?A^Op^lY0_g7}e9 zeKKD8^L5<>^iW*rCU{xEUs7Lctp0V4uQI@IC*$uc)?(}af*rQI*l3( zTFgyAmm(8|P4S~4^A)IoU(0IId3}ebSqnoZ)K3Rl+)UuornC7i=To#BK}goV{{9ZH z@I61MV8KgQmfbzODGc2T!<9qArsXETc5D>b45hX{jXuG_@zAl!g7jUyWEW78!~^|RrmA1<%BDQBAl=WtT~3rlaz`K zAVyF%FKlLBE5rZ6&NF+UD;anyv>e85=l=ZZcG0o4j4iU@Q4J<`J4GP+$Or%#$MKG- zJME*FN8K*8CjE!E4gm}fgay}!+dCas@Z{0MO;feCO$LkMwD@&JLPPMy$19_?1QXzF z8<{oMrzm>5p?+!sqfx}Z=dk}AVR;}DXj`ITE_XvCy%DVa3Sdr54JgaPNAPe(R!M@Q zm!FTXz;>Sh&xTBW%A?PY;kObGo}AbAgbbnt#$Y9yBd74ORQYjz%L{u0V#x}%OuZLv|w9s zWQ*F+FtW5lc??H4t4S;3M~9FDH;7u+ ze6+n|0)ti6gDCZtgG35iQ}F*wiIS7geo@KK++jUiQzjg0Z0k;?8um-0qpN@(`m;w= z3(bXz4AQ>RdgBaA*9_hlSVB>_Hfg80@8qh9b_a5}Z+*2yZHDnrEsD6AUQ-(KK!n0h zNB;Gz`{f8XPI7Ze?fiCP!-fr`SnT8pR<5oFt}wY0H?5+E1jH<21i(bXdn!`rXAY(Gb5v-8A~T$zJWLK&qq5F zszFs&mQCmOGj9hnRSh7A@>}013474^JIXMW+0aDzcz87Z%ACLojDgl& z{K7y~-Ow+elawYjrUtDl`FSOSc;R1t9!0_jn+MY*d^tUADkqs$@Ysp}DV`FHEb?WrF%gIYr zgl%fIYX{9<8TBTug}zi5&zUnvN%qWrQsa5^sA>ZnE3&{=k0ZNQz$4gTKJyS~v8SG@ zJQQbl(S{KORpX!)T^Q5t60xe^IoN)-a?OP;wD_{b$^e{lNf5*}UT<-xuSK5hj+v~3>4JZQ0yRqHAl$x^orIS3 z1MozagJUc#R%MZ@>tOLyR@0w)^^@K2Rv9RWHAvcYfVA4tU!$zMLBNk*0QPXt`?ecdZKq% z3copi9jr0h<;Sl#4}TB6Ct}^S3y*i74>RM`3vS=8tr?-@`Pm9&bW_r`;$c&W25NZb zM4Y)UG?o5HY+-;b7IIB9aMrbU`#?w-geVy?4wFxh{v6o^S)hV*QX&D)k ze_DlH)Dg2t9P3|gEG_zr?soh3s;RRt#V9H$ltJ~#b-(!c$;+sNRPoj$Ow%lpk6uoU z|JG7dJ1jmY{>^XcItu8+z8DkyV56dYWQ8(%F1N2toAdWkoQH0>1OCCnY@u&k zzA-&yPS&R-Znvuc-Zu&t>*4_6y-1}mkZ+_s77gE{e1atwVLBF#_uIjY5TS?0$G*uB zSvA76X~m)cQT(_-2|-;PlfbHQ3*$E`GRe6Bz^s4uI*D^ic(-LhYPqu7FF|=kw zw|3+OOFC#EPgTKP1F=RINu0F0TCs;vgY4SYkQ6Je^Lzc@4LR$tE&@?rlr4Opp|q$r zjI>QMA8*AH1+N)q?nSmR-E&yKfko@uoMi*ZHpBZiaLapodXAzvJT879aV;(dPy`iB zXl9+^zl>+U>z2HV3KJb#zWk`T!mYXthv!JX(I~?CFs5%EB-JHlKb~OwH^&s_*(cB( z3=bKK&4|!61T^;EPvztvTY+Z9N_@tDJ~tY^DwqM7>H4+va&`4RP5j%k3nnaFxUd>t zM9G+_5;tk;?8URd^9LZKj4*TGy*UX_4js%}Wa9gX|Ko!RE`jcs6erIX@JU10v==p! z2wQFdNRlxCYgbF!TPLdAMcX6?YH~HnIF>AFzI3(c7`mwp)$GrtfY3&0SI*~sfhLJ) z@)4%HCU^Nf2M8C*V-iBpf|8PI)F=m2tqd_@sBIm@vImENSV)y{8(heFRptWbYM_2Y zj4TR&N>UFh;Y0BXzN3#Xhck_m>}>nVPrmc)ljQ{j;mLZ|q=GoSPxjA672V!a;4PSt zfMFTM(%DP7d6^sMix3J60-MW)>ziNCTz3%FdHc$|7ogKf!FevibeiPkntpc`-4`ZL z(aQR#q5-tS-FKioiU^%1(D>&kZT6-e zR4Y?OK?9{w`)j1Ns5W-Vgo)F^SmtsAST5o?Yzj27S00bwtf9oK);&_|2&yHMyId7P)bf zqw=FiIj!jAv>_xe60!Sn5WNz%V1~NAeH!K|p#Wrp+?&KX5l2`eX)o(3v8!76ryqDnj;Dm~Cx?%=t)V>b2=j4D8M znv07T1h)VfmMA{`p|q18o;U%WScSZkt1v?)-<=KNUjQ9DE=CK*VH~atrMok#e4nC@ zuL%BRrV39DH82#hT9QA0RKu)WV*Q-NBFOs=lrOF~0oZl{HttIKv;O^D4McOkch0+! zQr@|<#b9D$@%r-gyxLGyI^=XGV)Ay@{(bw>#5O41x^oAJbHAhWa7GMVvW|;it|L=1 zL=6$Pjfej}2|cnTgZP8j%K0^k)&LdJpbR~+okJ$v6LKz1O@RiLbb#z~H@#Tbtck%l zJ;l98g!-i6UIN^kwtp)L(SZkS+5z1?lkvhng-~Vel?>lLelpP|`hN4Ti zJbc73&HA-1YxZO>lXL7Kcq>3C24Y^38{nVy1Mv%?rh}%xU}(Tqe_vw5@$(}JtpHO)fdE`szrpP* zhBey0{8|(isgte782q@nmpXj7wy?e4=seadJJ?xQZ96a995HTr&;r_wj65olu#+MW zT~mFO4nG?zw*g8%uDKWs{Q-)Say0m#1Ljqu`Z`D{uPb}PnLItnwy}P&lBf z4)A$ePCYVe!E}Y}BWSO1@$kf8$?7GU-D3H&_QT9|j(F$!xC;`jX%B&rD4y=r$hiTZ#A!5&^- zPw)+d(Q09sgCuPt?o~uxB~mxjJ^)}NFvCa}+aS3CbW3V8AcP|Tg~`qsxR0_Y6y-bdnzylq zza>owGm#ActrIU%QeM5~uqzS~7QX6Jjc_;e$-=cSzj%*q>B&yT$mNe62G?oAY?7O$ zqzviYoA>Y4Xb8D4~wkP|G7h1p|uaxqnx1%#9n1Nk@#?^2j3Nhk3Y8$!+(XIE2U7qFQ-qtK<6C|)1f!1c3tfhSQw1xy2OS^%eT0mS8Xf!Zs{V6%6HYIn z-MC(Ug`fec=;`PYY>~4OUx$6Dpbz&q(m;H1%alcP%&io_gy(@NSSii)7APW9354aR z1l$37(80gLm!6^%uv7nEzX3p`z-Z!B&9mL4()Z$0q;_Co_>dt?L7vQ*w-P$nRPqkP zA`clw$5IKCKD<sVf33dV3l5$JL9;(35lR(PE#6+MJ z>Xw#2OWKaKwzd60fg^6;^9Yb}6cCYH(aouOd3o)aWNIEf=+n;I0C<(|cw8UDm+|qT zY>TFxOXALBY3)0u^S)S9F+!?NG*h=TOAvNqpqWE+k$bn@HAfhlR;fP&s)?Er$mF6ViUHNq2eL8B-Ewk!S&9qWXYEJ*Rn89(+XR zQqJ$u{regT6)MGA6-DWFbRdneL$ZYS1w4y2$$BvaO>P`a$G+y@(}C~Gnx!n`78sQo zOc8>1h8{~AC!#IYR{-L0Ku|}Yem~1+EQEvEaJ1=eFho%2tzt(FMo0{ff!V!#HtTCz zULXJ<1~1vhL?WB`Us!&6GG*$F?_f)BVaxjl7NiB1IfmHU$B!RR92$X%Hgl;#oKNj6 zUC2g2!|)N*kdwQK*K|FQxiHn%0@f%;*&#CNwRqqCt0qxWNLfVa3wEkjeg8_#xmIsG zdWS$xvfWP-C@Pkp(s&-=1SwCE7}rkT-tc@fU4~D?qd04i$AoXIQg#{$ zbI~x5VIK2>8t*e6^Fp%dM|f^uDSd5n?YQM62~br-21&>o%wFEpv5+QrkoQq{Xbc=9 zap*)vlqdBDRN>?w_+)M6}loI{qCsK{4u)=k=n0;e3CH$AK6GN^EWzASW6 zgV1CdJG;?OCj?p%40ADzmXkQ&Wox=8C$*;YTql?j=&7J)*WYp+qdo>hlI99D8H08n zJyKj++D=^38#wR{_-BvphXfB#S}Czn@Wk^^Oo~3tnkjZOkzUrG`4mVHgs~`BO=e=o z4t(E(2$hG?1mZFURr$vZ=vWqzFe>r1ENZ`@yk9MGjwyHl0c^!eVdn64VD{wnn)GSs z%<#sYldKtlA{vJdMZsxv6EHROLIgS~%l6HtL=4?Lv8xQrr6W}ymOH(>UI_{PSLeH{s5xb0>wM?qI>dCGnk9W{5+YT5L*5OL2ONjI9wRaD_MJw>7$G8J zm%=^oV4_K$MJoe+B)b(|iuNucgwt9V^=k(EAN`sSDqkja074 z(Xk!J_NlUc5P)=AEWyy^7T%qCHU3Muw#5?Pv zU{`q(tim!FUPPe+}8R7OpC?1y)LBIGsI(dgG|TOf>^qmM96}A#`gpbg+EJ06l@#j8@U#g9hpR zA>gZJ7&_XH4WCJssXqFgkDL1dT5B-))YENR55BO}_2tt%fXA%^`UL$e+`rTQeCo2n zchVRHA*urIo1o0Q3GCc?2so<_GmJ(|7OD*AI;;!Z;T~p&!~mnPxVRGaJqZb5W6+Mh z@^XwT42(1k#Y(DX&YZc4o4f6p3!Yx!aJc(%UFyfi zu9-rq_?Pm<8OtYE2P5yWuAlh%u2`bg!ogPk=+TIFxtxh%Ir$`FmVsGr=l}Xa##DZt_*h}ncgGRgi7L&+c$SP+Fj1=>g}+Z_rL99(4H|H=D8>jH{w6Y- z^f3O{mCc0>ByBSk6jf+pBGBZhVQ3d&`g$R(K=B%u{Jm472;3&kaHz&th6Nhd{WiAQ z(gLVqZKownLiY?;f6QrUW<}n;GzvjhZLL^n;K%Hj|MNVG0Q=HN9CbZl>-#entd%Rh z#GxoJFUc;=Bq)F%aPfa6rQ?q3&KqJWX1&^orG${{N$8#_%JcFP~8) z>SZ~mlWR9BH$XXo?$d?5gM-7HtP9;ie@N*GJ*dv&EF@KmQimwxo!-*nc-CK;a2q$prwKXbVr0VcR)aPtqh3KOwn zEOMp$pfNT`PIUaCx%dLf!(H|1FQL6fP-)ju#c6NM4Yeo-37*hcI@Oa7JR8J#J-~h1 zjwk?>_>|S(g$$ZTiDwV2-W=yD0(Pwq5H_VN$ON2EoHS-Hw>thcV0)I#MZcB*=5Mf^ z&}9ehI8Dd85t#rX1721fG>t^9Da-e65CkOrvEICZO*{;H$(Vec(X#o5gq#*%5>Ix8 zSTq?*UpOW+2&0J|C|xB=l|c-qPM{tl45VFd_!bO*+ks=jA-`b~+eS&+h#rg9F&>gK z#Q{X4cDX5?FY2ty!6`8(Z2CVxY+r>!g|3m{ZE#(_ZuaAfMNvQpR&i|Hs4Uyd{db2v zYGGw%{SGGfM>u+lobNIq@4JYNBPj%NgT<*JCx^g--L0Ciu^BQEHLs~ zz@aNfgvNdyX8HFa-=}NF60+_2jP$K2^=UzC<2`s1dhZGw)_=;7an5*htx%(tUd|<6w17q$wX1o zM0Ae`U8V@iHURMfFn^-JS(;axz5VYCSpSTM8JHP=!~-Hw5Q|?Kw^oixQR^u!Il!1y ze4(I<0h))Q+BUWV<9}a=Tp0>@%A@Bx%PKp;d$!zfXTDy{XXGb) zT>MiGh)K-<`rm*0@6(`#@V{#vjJW*j+9n=tv=`&jF4tLQQy*`&C=dv{9m}F|NCbD`(~L>;Q#E%{Qtr-ZvOxn zX&+y>9dls8U?{7*wZ8`zPl|%&S6)A|W1U>e$_^*k81BTxTLn`joY=9ugLm?8g1*WL zv!ywXB%x`R$6T6|aAX*q(I3~gof;pC^WMEH1S^XlyhvY^%hn{EqrbEc3(!|oR`vxk zvH(C)7=~_TAkW0X-;Vz5oUS0eV>@sju)UWp_5=uPK|*Hy$#GC~OMeP-y^#lWa2-u9}v`(Y6gZa3*zsxYWE zYp=5~f7yDgkBJo=YW4#sSr1Osy>)AV`Cq>^`N*f zFxQ{{{qPmx0k^lUzT1Z>R}qdw3%1VyM5>WwCVyAs?-SXfAiQD+hHMZrebl@+go$_W zk;z{+xVrmK{{x9MAE2EI01Ux_AGwteKSw@wb#wPN%Zzf8ZUh z0TUy6d3lE)7E`87v1u>k5?(oPHRt3{S+9N|-LB%Ju@%>OY`F;s2WGqQuV!AV{Q}%X zKXB9nFok1Oea@5plcrOB2pIytkvMgLVv3B+r3o;p_m7O zX+6lth1RQOWMsfq@j)jhwgqR{H`CGrRs??a&^+wyVIwmBkeV*00|MIBTuP-)Ebt z<|g(Sd<8jM+kvx@Un}I9U&ScGe+~p~Dp2a;3;A-JfA=RuRS!OZQM4Hof{R_Y5N>#Z z=P0rM?*;yT%zBpNK&U#7__HcYUP~%l{5dS9Ak32lwn(~t?-Tj*aU1}hc=q2b+t1gw z4@ky|GTZU-aSd>5CCK#zf2@u1ESiDiFIl|!NLe!EbWQP0qBVn|jpwk0PK$7;1@coP zJe&CouD`eOhWk`)80bPLuMvAl80;y5G8cSJYt?%rnKqhaa&mJwXhhKHyFY7zJhYyqRm$ z{C4)gQJxK}=oxUr`=U+|eZM`trH1eB*K0R!6kVOY)C`6;930JGzHEST`EnN*m%HAR z%S8&m^a$vlJl>CPe_GK~Z-nYT1f?Pjo%Zn!4)-E*7U0o8IWoBl9?2SLez-(5p<9p} zSOkN*f$uS0dH28w=0f3k$4S_GJaZB$7tvHtiS!c;#(>+#ytOo%IjU?NCbyUmsLR8$ znnp~x!a$3&>1lc$nWg=B&vaeD!TJwZ&td+O!Aw~`U6&gU{d5{V;{ zB!dM3S~cTqVc6k~#U+pTf@8PT0L#5mug147f5Lip(OK1ptcxA63pzdCI#Me033ZIX5QgHF;2#L=clsVQKuAQ;%SDdzrhoHe3Q`Jc{6hL zS2=lVe3$|xHXOm&P%*9ESw-$r3q+=yrkIWd1(Sc0ZX`wkok5>>^z;I=hOsbq>C?MfdpjdB-4VmMs?)E8=D^jH@ zNZ4NgC*L$Oi|Nx)q;0DRmVL|z5VW`9!Q&h zNlBSw!EMDTzaT^=`!0^uU^F=xWU(Sf+SI;Uz54C}IvMd(Ke3*F9TD)st0$Es^a=%G z?fC81`=_(MEbn13QeR*Xw?hPgdQ(@#1?$orQwq?lmaXBzMY=x4hXODTpyP2nR+c~s zxnUu@)t(o zB=^N59k!cPRaHUT_QcYZ0|l4T*6PR^^&uJx2#B0eun4!022HfJxAT!$%N*?2=GNA} z)47b)q3@*|5p6x7;FR05=Q1ZZw*t_5EY+I{2?=IcVm2r>rKP34L@l%n{DngB)K9Xl zVI1c0ciHi)zeGmj6yMQ;!M6&20`06qVIX*v;UvsYn5Gu=XJDSa6)rot`1%q6?5&8u zHqiH_V>oV*X1yEBixYR`<>z;_D#;O94ZB(+%h7?)I4gtrt1MHehGRHB);qsu7bxHZ zsET8&-?{Xmp28GCqVxCiT_skHS({NA(kNTZX*Aj3rLBCtQ6@iFXI>CJKlcOoR*+Bq z$yFa-^*t=8>g{`*H878b^N81;YjZxeV_OM)>2u-2Nc=q-5&AwoS%*Tv`X7SxSH<%j z6_{ z8^S9AX-^&jK3qNYKpAv96bOo|=6>Ier;L+Qs-9GzTkHwOM zB}+IkQMJL?oeHjEKeiPsctB0@0>x2kFa`aQ0-_uyjPlqXtzgmmVG>nq`I*7!U+Ke@ zKQ7^kT!yfqV+JM(Zt1dy0%mGFL*0!#EV5{;g3@a&5q=>HkEtEBKzPNxYjKdoDuzci zctR&5_qle^&Y(nw5fJlsuvy|Zn!m5B)5kbW2rZZ&hG@M50~O*+nhsLxfG3a*=o7cw z)`5RVRxxYk=O5<;_XCnIKB#s4i49nKyh2sfRO=XzNV_N851xrb`wDWxDFw%R9 zVHk1mFx!l`dytP2n;`n59T&4u=SPW35 zVF{@;UVO5<=?kpR0!Y2g-+OHEl5ug4`^pG{wW%+*^Lq%h1cI_@Rhs0@>Fw!xc4jN1 z#M{e@gAx%YTXh2!a8UR(9k}di;z5FQ*oz{l%LzrQ;{PR%10Y`+9$Tiu$zXlUq zGc3!a#oilb7u>>uF)hXYU|Ves8@At2to%z7ryY$^i*bN1Z1&ySBoQ%_3=p``L?AP&cF#hbJFGipa&7P>(N zM7y_ndGatS;Dq%9)iU6>d%3zY_zp%bl8xGZ9wmV}E^jONXXy_85mjd_-yGf|hw_Ok zHF^!bzkXGTFJYuD84}FfH~vyuCChQ71%x=2{x+d4Thze#J5?nKPXJG(@k*6jTVH@x zN;tu%8{P>ZMWSmrjXdaj_BJjEJcvS2H~5g!O2yp`?a{(w64{DrxDPSP*k)R3!?s>=LYgj^vBK=u z-stJ;^P-Fho?W`Hr0tQKXGfACFueV(5y~&ypEadV99%|PS9}5i4V{i;|37wB^ee<@ z0ii|!5B|yeD|IyxE@yx(V0HHG3%(??0uBsu{ony_-6_m?Zx9h4pl{IfgTYY_k~#Ui!W! z)V68XtG8i@y}bC_dkp~;NC>7IDOX=?0DGEJZKuZ^YiU3+eGoq?;QgU06ID6$`&=9r zbpiB3JP5&lP}5)oZ%=iZa9#zrqgK(D!Rw+5e4kP*{BeK;b%k6I8K3)}m*eg2O=W~9 z`f<8U-uxyFv;bz{@by8SSpa%Og5BC;I%&ELbk7qF8R=u_b7-OXH!lybX*_RAdqi{W zF&Jmrx*{Q=3W+$z`ve{pwYxtZ>Ja~cfIQpFpOY?W-?Iz4olQs-epvVlcobEh58mM) z!%+QgHG97Z{a&T`g2PJ@YkcXv)o7hrzrRT6~V`0HZDH>5d3DkV$qI2Osj!1h; zSXnu#c!LDFIEfLl^K9Flig-hP5)u1nls}h(3P}M8kqd9Y8S4xW##Pb!wni*s(7lOF zgtX4fW9t-UPN6N0z3a^+*~2$?GfEoai$;KFRPJA`Q&d&;C%6M_npUt4JaLd@+(t33 za32AXhj3Bm0G{wrV$r;CkoF3JMla<7dO-8x6zF+$L5uX+tgnsMkP8g1#FK_Ekmy^H zxF}^RBHUG-agBtPVG+_ICoiuYY^)#Kt&MyErU32Wfv(SWai_%@j2BJk!fU_*!Z9(z zLFSZpKvM{mY9x=otgNhaasy$|Nf)Bp-yC!vtU?2`HCCNKUVQP*1&F&X;N9uvpP5~q zVC;{SGy>@wC-iL{J6-on4>wJ{&r0MTryq%UmVv^i*QZUJ_8zf8KwP2DGu|Mb1BNEv z2nfg7rrQgEQwWxgAUNXjoIE@hZ24Tg;bw?BR8dhcoX*b6Fw5DjkeIC+!aCAXv1_wX z+5{~U+tvG+xv7mrKi-fyR6KF0OR?a=PE0<-kt2p9O5cOuwH{xT{GC@JQ&XXUx)>@s ziu{ET6Sov0g_`5;w1s%kj^YbP@+a=$S+?=9ua)yZ{!Q9V@#29wg)H12OiD8V5G8@&yoQJ6K`t_DI07JL40qckQdxw9#glZpQ93S^!JY2( z`vq=5)<;8IXcKU>c{8rO1&|FEaX6~Otu`%k^b_;F#Dg^yz_Q&hJfoO5yanY_h{)*% zgC|d(B)fm@6Ew=;MHS$SJ3lKT$r!t$?}Jc>!#B&mx&XL4G(P-wp|Z`P3!Y9F{Y;?I z_oMC8EHf;2YDpI_;s|wz+M^!cr0au%%7Q{KKcwQ77r1z{zObq&r{2rSKj- zRejsNe{dHT7p6%M@IkjQ4(;=MP}^*ss*hb9##RqUb3vOB_vflG&Zq3> zabZQFBxB%cYj{Q!A!2~x!na?PdO7!(b41Gnc;Q3Rf(~9Q909Kk3NIhRU`zZ% zYdxyk(CgP)I%H3bzoJaVE@^-Gxw-fiDjUlq?-4hjZMyiG&RWr@V02LdgOAyS#fX={ zL&~24y$_70dG$%GOBd^=ob=6=(bZ7Ub=>h36>RO!yK*S(sZXGa;C36;%fQ}hb}oDL zu%_k^wNL<>ccXJmasT=YoJ{V7tkGy77x+&5H~4gF{`B&#R}~Z#l)~hR!UnXt;4={F}!b2E+XsqV`kp-~^1b3_5sUQelow8GiMu<|;5Ki;OyOY#8Dr z7=3#?R$80N{2A~TIazVD<$s=_aT~#uv=?LMjI6g#>Q6l zX^-t#cac+D`+Nt3Pms-~Wv%>4lZrB&YwLcXLJo17vUFdqMS`wl!7_%m7M=j8z}-)8 zSDwaEYL8`k(2Biob~xn_MwVITINoDBRPzu}%p4rKt z06RReGt`y#XafK0gVNkuN0R z7=q^HCT^NQHro&30H?o@ah3QS+mncdRKUlXrX_yfDE!6b0MACvSswew;+wkw-MWAQH z&h^>%SeK4;T>g!I&7076r$2t|e153+@5j2J#n4`6!r_v+`|+YH zF&YX5h%9E1AUt4ieG5!W5aw1M2naP}+#7ctz$5K^aV;P+G7?TY2ce?gOn;4t9VBA> zc+WKq?Xdq;CE3@k*>vQ{ktFD^sF18j&vh#SkmY+6$L4rhFC17tKE!`4L~)hken|<7 zd=(B7q7oY4PLcoIS8Z803|AB}W%o<2kQ$bN533z>iWhwdlf^EDHXB)7oln1CZg<#%B_GK zxbQvCPp)#+#&=R<$!a3VjlV~Ncz+`P*(+3IFSRykXI`-T1j;jQ4jA7aN!_E=lF$(h zC|Vnb&uq>fAB_=#!wtHh6R&Y-(9ah}X^{w^sQaX@1>DxfF=)13^?}tE46A}T1q`vU zd!_n&p;fA}%S7#wYHwtr5~8G}Wnx=lFVmmCm%2v{o~ zuWEXUE-)M*zjg@Qlv9?UUuwNP*IB5%n32Yox+xFZmaz}{J1lF~BdDg-U5Mk$ixtzb zBuhpt`P<)CEXLUP4p7j-Xmf^M+WGEa%F0;MVRo#Cj-a_fAHW)CnxpIlynB0k;N2=v zayg+n@y3>Rdwuw$2m!1u0D}FHT&;N&0xav(&M*+aIkAciW*Z!xPX?5;3)d_t_Nh@79$Rv*qr$F|g2=}t$ zY6p}dx4(&|vg(7dOf7Kp;Ga_nZv^?p_)p!bfY^Z!SDo~oco+pV?!iRNadcjcArcDt z``FF9-BJ=_3`9Qs@axNZvcmG{@#9{eSX5sP`+-*MW4(OWhmH^xBw2pi8fVII)k1A! zfhPL;>3^_=EuEFAHX1=Mu{l@pclyN7}ZA^J7POia6cc{)EVGc}+Fp<)VEgdCtG^7r>a_PXuw?_Z@JM@tn;#U1&rnX+e6bp|jS zZ{&1EN5>2lIk$m)sgfrVZUvvHSm?KT)1^z7h;P8Bn-s>%fAD>)vFWK(kjp25zmHq6 zlI7zFugK8Q!!FG20LV7=<-02!Q;ldgyhS)alSO#Yg!ARyZ~q1p2(; zEq8Z6%rxGkUgJdR3dce|%pI2iwxmHDm<9##;9EPal>D`$W7Ca=>->=KPO?q6UV|v+ z3wV%13L2O#IO4|#-C#BQZKHp4?{D>dj4Qp4@cH@paAL;ljm27LW>}TgzJ1X9Cs6Ua zutl3R!fxJKD^^BPBnh@EhO8#2f_;Dp6UJKs;}%p@+{h?fFN$G8*xcPMgfV*=LR0>| zO_Lxr@)exoY~afp>69Ccu9wmoa7H<_Whw4cS3F0K@+VkC+{6+i;v_&|Kn^{CNS&xp z>;1ZMKgLJlcKe$6XtsbzDQ>r+4fhJlk2fNRJdMHu^Bye@5i*v>7@$$}o-CdUw7Ud< zQRM}&&l_q_{k(H};5L-Ox&tO9JAPlB-Si0pk{`+4<((Tqh9~)L!OPFT^{pe8l=8;B+Z)|~>IYgAxNCIP~RzM=l6TY2BgY64(@S)7L zIg8OZ5|HD)_e4q?MqJnf{=ki-1q(6%H0fJxTCx&}~CyZ{(|>){;6DhS7=v_0EBO|0}0T4~4N-Fbhh)=xQU?tQxl{k{f4mAzxo>p6BA zu1B`r6yn%-E3ac%A65la;2JEPBKQS>Dv)=Hn^8?Kpn`PKSCCdM0H4YY zaMMh*A4k5_hqcT7FV!Uv#5Mx;cwXSS6<#UNqI0IM<$^XGo-@4Itd-(!<(mxBp21OV z&*dvuX!Kno8J>F({UdWeoWIV+vU2`D%K}VqsgJ}q)04fl6r>|s<7CK5?qp0fh%<5s z7B{pqX?CIURD+8-w~9TajtT4|JCU?YsQd^lDu1vY@mhs4Kn9%9mM?pQriq5+1pm>^ zxUZAxO4`wF&3WbZdL(gQEEWnbx)}7Q)3}$=RgC7XoOTrsG2nREHdwMu7LZW|mZW8d@08I}6tMZfy+ymf8SX}0^|={^Zt>Gu(LiIR zhG2^-JzNUEM%eqaE@w&tx8+9Zv2>@ zyJh9PXuM9cJo$l<%kn=0=&zz05Mq20Vv0jbpqaPK2!H+QA!baZKP0z;WvFWVPpPSH zM4J~4x(A(nc@iOD`NV=t(vC0z3p)LL#+BVG| zfOU6+6clfR_Qs^ks`C7K6w1rO!&`V4tE2mT_APoq-;FQ40yx~CdgEF=SuM=|mM4&z zr*`IXjLnAwi&)ncNbB23=MAu{;l{QXQUN!KWqK+&cmLVSNfQ6ki&kQLQq%8%*j9vbZ1Y-C1Smfp|gr6PT~ zj7*L?hCFc%AuCJP!Bj^F^Sv#VBa$gE6F8*S z?{wwJXwSgFL80uiB+|OgS-4OKytR(v*{jiRsNoq_T1tGlzaxQ`^QU!VU9~BSWUSc# z=l$m!xX1nGT#t@n5aDbhqxZrx5O2o>5%yk)DO%2-PKPsFs7WFO`=^!BUE3{GVhZu`8ny z+=^{nV5fhsX0&M*dPH@wc@Bc$r#^iS>qp0Rhij~;@*MnLx)<+H;dACioet&s3%0_C zp!h{PZ8&^ipzjb&9dr=k${k%w%X!fYL2LM|eEF<2e*pcyh^{=p=(y(-Y(;Gtu!n)N z)BCpjtVB^VYybP7x4ST2eGe3MyuKZTvO6n<_xMcT@%7HFTZx>trf7^+rM83r0Ng+2 zWV*{mC_#x##$Ckn(;V~Mt;haZ8)y2x5Wmh(;~uPeOnApk`W)dcLnAK?2kx?!n^U(2 zo&pm6kzgvTsGA{<6UTlxImAnInmA!7L@;Yr@8P{zpq>Di<*?bqP;-OvD?mveh1m(V z`R9@VzXi(3^e=4e+<+2CZe*D_1>pfUT0_EEeMa4-#MK%@-EgKD?;Ci?HZtB zsl*Ssf=@pO!Z1{~`MhvvCbE0Xy)?5!JAf8nfIMBx%q*-xCY6>bGSvyMRLmmJ)pH!d zRyhq>rd-)56#*(5`P{_j9{~HJphy6O*j_v| zU1}ki*=RjFQ+KmiJ#m>BSM8Kx=14zf(!{Gu%D_2s*|KHk-IYIn4-{kvUU>Kf?4YOk z6mS1E@E;nSyB{T6$RVIKQm1}#keFWIWz&@V7a=kib;*4<%a*E;lLF(N=oLox zU!FQ0!&cLieIEGz`}fFTQ`Avw6Hh7#hX-eP)b+muN~CPmg;)^h>}lT~?$0jNjdA7n z-&Y`O-)op*U&`L7l5>=3f9~98-jv4bZnGQwq2=x3j`HlRy)Y7nXjgPhB2cR+2s~rp zMj_F1tgE{_0h;lsss!T%;2i!(;vw7QJ$?Fg@z6${Rd^@|Q7WpFyA3+1^Y2T_^Ydj% zvI?)yK&SGB$2Y<5_Dez?eqe`#1WE!TQp18)vES)-Z_7$JkUUIP$+7GF{AwuhcQL8r z?L_XkmxktRmUQFXd7HPD|WuKcE9k^=2;6CI5%JP_wflMml2vSLCtG6#KMUY2pQnUa}60I|{$gsz;}_r{nncM4$%8 zq_YuucA5G=N9krH9*>BblResjWo&|e0TK~Eu`fT2qSqNN(5i6$9F zW9+7F-hi@a6P+@=Ca%UzItw&{?u zWAC-x7mC<$D3VQVUVw?)b+Utn4C&gwt2G%TJn|oU*0gPCm0}lbwHzM8gGZb#& z+%Bp@*n(NZ&2v$+NxWja>kn=S1rYCh^*8=}PCqLP6E-o+`w#H;3#jo@n9{Y!6qWtC zdW|X7{Ro&x)3Vn}+N*WgYojWx*cMWUy)k)5z(conh_Ax#x9{2tQ1G<(iW^i@L`3qW z6&>-jHU=N0a!h$uZpPw8iw>9ZSpp}G1{GpKQ7axg8oN%{IBjF9jcGhp(^-)Y(MS~F zZHbm=BN%T-Q`4NaFi@Nh$btEMEIEQ9@3wnbI7)RNPrQSP`BQ9payz56`l^M947IP$ z3$6Qg!V);^g1VV#8jjh-qlMOY;<3MOD(lIy|HI3~RZU$0MLoZa+rU~r?yIgjYOE4R zp+GEH+2GFIe7}fnyS2mrSN{T?v7ycTH?v{ECw`i%jP>pC_>!iCLm%hq!-lpL8?o7? zhK{X;wIr^-<(UBx*_4UvF-tLw2d_OPsp#%fa^)Zc8=CRmkV!z=C9j|xjf>uRsl$_( zr>o2llMfJ6aU@diPYuaGQXj z1u7~kYLYe2 z`*X$!(PW3I5l@W7pzcUq9%#4e=`$XUkAB<``iB$0tC#;=0d>bVM=PXNTQgRLM3-$t z->-f6#}2jNi#8$tiM7B(GUTsnN?;_`_Iq~j=ofSuNLPRAL(T)j%ypjw6mxr|j_zU5 z*r*5C{{PW*-SJ$v?^i`xl})nu&dSIh*~!i*D~hBat7OpDI+<3yuF?l5KVJo$`T3LbE;eF~grjDIdj5IHxgIYt1wtlsu#Uf+@8UYcx=EX58nG8j8wf0F$-4L-9GA=0}{#}k0HtZZHK$NyA8Z8F3tQ_ zK6NMP%73BuvWCyc+Z=w*D7#su9Ti(ci&33B8rF27Q&o6$p#?-cH+Q_%m-m21iN}`f z;Eu0QLTUs!JPnfGV0~xtgBgA;S)g!WdaIcIN2EiU6B@kdKLh08>VIT?90VpwXL|EQ zuYt7ErQ=lNQysX?4h=W)^?1l8p3DHlQLZISZultMD!FzuQjm}TPWf=*U z+*t4$X-naq7U2BSK>b8QSZ~u5<>kFXzg4AL(4^|H-0$6*P{qvvQrs&odbgFETLW*cYKg6GzjQ~2)eHc7yY~&(@gRS#8LT})j3q`xeg6jJuUW!{ zb|>oe^v1)d>oAn|LPU6W!2p571}=vI3uqSG_)ocesswS+Z#0iMe+;Qd?Zsc9DBBzj z6|rUEiF~LzCu?Yidq3b$?_bxO0VJM1qDp*6r+0{)OF``QnY&ceiz31n2j;&8b;;{D z0W?~Fs^H`Y;yCs3pCyPP&R7q^)L;^A<=mG`!TbB(taZNsJsMO}ZCLEV(9yVK+-F+? z$BKQmC$C=JHj(pH9v;eF_J;JvER2G>`qhbF#92$8H;%6S`#WBArSz-JmHv-!&z{xd zO?e%4?03M^*??Ui1GIksRs9%BIrCDa3#K_RXK_ zK-^U`SY%xc3nf!?j{pU`Aq(WB=rCy!t40cqJieC@4$>XJpB4ZOliV8Lx+V3^mdtfNncW`uv08~G zvHixaTZurR%cU9D%fYyL`a^VE1s4~Wg+yl7o2^{j+*Ch^FgW5&-LgOGUh|-I{LH7Z zv2e^b+^GDMcUdzu0V9Gs*4Eb3A9~x{cOQF?hM*O|l7wx_uHK0)babsyk^0N4-2lLe z8Bc#R6BGAPN6rX~t~~J z8XDZlq>i70adybJ`vJJZ|4Q`58a*)GGIw`0oc(Qnu}K$r^OMa3-jYy255WsNb7Y{c zjS>Y=QvVgL!=WZe^b8C(ej9zX9oT65$%5MscMXp9V;ha2Irz| zbps89dsWU<2PeK$BBn^iL!SLR!+GuM-M*!?2Ok5`ZECBjJ=A@Sf^IM_*OyXxz}%A6 zaM!L~r%`LkuSB8P@axEy<>ud7>?v0#iQ7CT5Kc6jfUlhaXFRyH#>b_;bp(g}8u zDp%NUSj5gnu-}X}Eyze9bwH2fY74{m?M~kU>(Dlq`}QQ=yLYPoUM`QeMIiq>D48&It40rW7pbCoej#q!_l~N<$L34bSHaoJjGA7MuCE%lMgeRxempZ zUg4-Y!%;%f?>{(>lvRE^D^Y!W8PM->Ty}e>lENJI`}Os>Th4b6ZH53m?enHi6aWs& z2N>Z;=12G!nZrZ1KI}e;(c$0&Z!{Dq7T4djDxvLYZ_gaon(Wppys0n1sCnk6QIm5& zFEm8=;p`1}!J8(&gn~*MnQ*cOE7J+kci{{-OT7LkU;vf(;GJ9mNIusSrPk$GyYX!7 zN`N9!%hvg-udiq>`wv}qTUS(=(?Bif6Jd%%G9fUXf8yhIj#H;jDJm+0nML1Zpm{Wz}=FPl9|J=pFLyb)e7rfK6&~B0-%3rv!yMf zqvx{UsZ;Ee0rAD75*1e%qD!k`2v*$&k|eHv$|*pqh9lYYRy zC4?L5s;gUoF0p*eGP|(}!4g{YaQM>~{5r8=!v?^qmc#o_o;+FQQ9oSMtE8;V4lpAb z$-?eIk}wmEd9M6!$$)Feu$|*#e^hl4Z%TZ8QLMKR<&yd!E~@AlH6QiA)NYR<*f!U9 zgg<|8?Joo+Ab=4?cl(!Q)jFInP0P%T0RYp42rv2WDgOQY70%5k0HI-R+GNJ%A6tvwF<%89R4jeS=2 z{muRTDYfq^aKf&N&37IWwo~=T@{kj_L0-p>$z=DF9x=nPd_BV}PCSG3N2*XHsqK_)!@+mDXT>?xldC4ikRS&|Wp5ThcuBIjmW@cvFdBOB< z5?T=Cf8c;{T`E~ly?i+tZ#!!xtduNXVgQHOx@==JjnX}Ge^lp%ZPqtq(UU*xP%jp! zuBs~XT#L4ogW{}y85(cRLx&IFt#i774zXJ!Y-Mq_bNJgi@5Jiu8ianJldeGiZYU95)j$zSo7FFE2bL!DQaNE@COp}5lA{&t?Z~hD> z@e<@|vA=AztD&78+tXZy*!03Cz=x_ou+1nIvr7dTB~LL9JVs!^;lO9j&FijcoqM`$ zlW_>(S7gY>Rj8_k2VE>IERIQRv1)$_l!c0CGiCEutQN@hiD9L1MQU~!ktXS6%KX$- zAmPc|7jad#y;Oa682lfV&!$|n^LXHGNKcZJ&w6|51qB2s7mROgCGOc(l(}sNC86DH z6Q;TP0+zS-YXgS*1V;X~jJ_}_9{~Z(AvKimjTxf`;99cM$_`vHW6|472*t9Es>&nW z{G|M{ZJ6-<&A!vt)-hQOIcoaK9-+mTK&YsA7^6iPd3pPXBi}+Xp#LrGG0x3kP$E{2 zretMh`LCP`2sr3l53PPFJ&tMQmzHZ6;4t32^5^69^aR3x75Z)3l^3sHcS7?ps=+i9 z9~--AK~C4o1#2Pd+NKzEeR~UKXnymmpALuLs^zjeV5(%OVmv}QDYvSJt#Uku0b=HI zz0&uzE@ebS@w)MF0jxOM!lUXRz3AQ5hm=oQ|4Rv*+uD|`t*mT%z8gG0hFHeDu&_YI z!x}9z_xXkB+q;Li`fT?nl@xH0ix1ylQ4db5ozktodzZ8?{%4P4DE7hhh?Ihuh|FyE zaHNey3}!tA;E#G9m>l~v$R@@RyZnAk?8a2i`dqV);WKi8swm;PvElfXsIYJ|px;6e zf@5F4(4(W-_vth@H@C`}pJLcLpC}p|{opQCKZ9)4J4Mvhxqw|bepy5jPh+))p+^cv zs>mZ>SNN~>3+b+;NV+e>`zKC04eZkv$S{v3|+@a70#UmQp z_5@(i=L8b@&$fp3skyoF0Lu2=e2k?w7ivRq^CA7ed;4|(tFp=-N2R7RLPzWD+xZm< zmGa@Y?3#jU^gU?uo~$Y$X<<95MAS2#8*0CP{kjjEg+ZmB-6HAYHS;%7ah6N7%fnza zreg+6m+YNn==z*Bb8&po8j)h>yLi}l8PLY37o5{$#mAyv*`H4(?mCXP8gjj>=Dp^zl7X8s@ zF6QSa5(E4D?{B==Q>#Qa7QcKyo(T0t3=-O*P|o%oS+_{n*Q@yQ?6+Lr6z(WjC4nAvA*&6mMPC8G(QrPBdB9>KkP^FRcI4H2u)y)(hor4P?6py zlcB$v7-M;Ayuzd6M*)bd`_15M6beUbf91Lw0#?XLVNN^L_4X|n1|AcBUctl}wbdy! zT~s48yU=8OUe!nY$@%CSck^cWZ>m6z7_*#QROA+4ze4?rnx4K5(2Cg3%gDmGMf-f2 ze$ds`UU_j-*) zeF61T-?)56ri%XQRG~Z>2j`d!q(l!_ZJOOVFtd7t766641u7B93%gMhm}_9yh#T6y zPL0R`{mTf?Ar~$xnxgsHy?pW3ty`{vD}Vm{*u~=^zYQHdv7#6GWw1c29V^EtK-Im|lo$5#wYep)0`mN9_{bvGMwDa+hHW7f`1#gRJj8V(S3 zAwDA}&|;SBBO=xv^6-#d3;}2m^MUF}7yA7sVPBC+fA@MOwM%R$uz z&ETgmUwTJm@^ET9(45T;nNsf|YoK~NX^iiBxVnZTeMo;HojY6kH_SQBE3sjOrxJfk zdsbdfj`X_j&dxl?N2&X!a0pR|NS``qwctt5*$+NoV06^EY&OM6s0DXH;nGee&=@)u zzft#zmFEYQiHJ+c*gatwZidW{cBLKNnEi-2v#6+O%yRG*&Cs0X(D_Lkpn$vi`|%`R zWW@T-FtY&a>;P<9yOZ9vg@LBXgIo!n}sDBl(fxIN>r3yDh0nzMjQFW7&E)0iP@Zv;X8w* zj6kHb2aa8GM-OfBX*|^CP&CyGznW?k4>X2sW7ImHA$;djVy{~~$ zFgZGcB^<~`DkN}M=F}*FbFw7VuUZ-wK!TUetTV}&@2~I&AjEv1~J}?rn zO3%V8>u9S;tE5Q)(^v@0rWRFMqjszd*jMa}05Avjj|!@&<_$^cjHNr;+U8#QQg5C@ zx+e!LfG}J@JDNeLQ9D+sEBH-8jIx!$K`}FI@%kz!CfeNdH+MyuiL@d^;7GHQ$cK@Q zcnAqvz{=DbPwbg{LL9metpU*6=99>ph9Nt&>_t|uuVIp!T4}>M2s9#HK9cerdh@BH z>7rKc%=@mqiJtZcpDGZldmu`Fe0-eNWwyM$Jm>Ttd>-IS;_5sP182{p$T3rwG|ttL zyHp)kMvqpZ5~+3QL7*gX!V?EVV!&uoL2-cUb}Pm^_{GGikyHe za?vP8PjYLibfX}MP#PK<(uPX8aB+EMMGuz;5n23z0XifIM48BdoMy-HFejLj4dArW zX8+5%v-%b?&U>|0)v8%j;^w<+HjImo_8B2XA z)65h6*?*sCVZn4Y6s0C*kUj;)(8x$Dus&CO`ATR+(LVU|t^nuw^tN6Bg%>4$xwc{K zg40QOZRk@|Q;(H=sXo2@Qylx9w=-DIJ)rqIMml|Ih6_+$h|en|es7(xM0XKW5eRKP z_ugYB1Og-l0rIi+r~9Z}n($hKR|?$M?<=~8_b>roTYN9YR-&3{G4;Pch-sr6-p^ch zwTX-zjf_!`QKob;K_4DGctHC~8G8Tv01fa`MQD^99Uc9H8qsNWV5j03!0M{PLha81 zqC*OxuC3Odf|B;|t!QCpV>^#HvF}I$$adCi*RBQsfaFU<pP{xm*`x$0Z zY$SHm(IpD}9e?~V=hgKOXvIXq9@-G+veKRuGgwugp>f-Bi%mZunB%c{G_c+Z{~)Sb`o3o51| zinb!BvF;K<@d2BG^<*4lB?rm68H4XsNQ+aA%KZH6aDJ&^OcsSe64M|aiBJiOj16hA z&54`x9Ha^KU%q_N7D;CfKH%cQiz8PEjQBzUqf6J5CsBB^EvPS9E~(V=dKG|>%hGDtO`Lc>1vJ^)Qwxi90|I2zX>2#aDmOA)*wD@uKF z$v^YbHf@2jsQvr*t8Ax>J~TcvbMx0*P2@Yqt7b2v(v6(Y)*n zKkAOn^z=C{0med}i|_k7;}Z1H`6n!H!q*6r%Q&WS?bqU|H@lOTbwxV9I3Wy%_wp;J@s*!q z+@Oeykw8?amb2ButgNhhdN&If1DS(h$i65yfYJ$3(9+QGy3u37m-+aZZg4VT;kg3b zz<<*lQ$ZD#oIio*^{sXbq*4QRROL*HF1>j1qQ!*95O^&ICrJ+wohFOksH5rw`flo? z9yfXa#^&#;g1Z)xvwQBCbpu?>OPQ#mkX`EE3_N+2Sxe#DA4Ekt*)-!LK{AYx!wDXHVo=N7MV;o z?+J4n7XzUL#0#k4zSAL4Ja7jD1Z3VkMX;8N9YSe0ufC~dO~K@2<`DbFtvtQMoC0Cy zjul@@jUbJkc;|vT1hbkJpXHR#{R>PCU%)@abniZN>{v4DN$EvX z)Epi702#w>G5S`@WFOeO_6EF%v)jR>({Ri zPI~%nxZ@JC6c zN_l?XfQ6U%Mq{U+i!7^9#uX@fr|EV=KlmiGToWBd$@M_po|cyNkR1PMh-Tor2-pR^ zyP~O?9<22Bl$GxZJaL_}#LZ6i z`^4UVc?}R~Dp1wW(UB93|AEg{zpyRQ)x?DM_&>_?k?#91W@ViR=F*NHp|Gk-Zec5> ztq@z{3Cv$E&AgHv>@)FFK_Mi%@Xt)(1V8Y7dU|?&fD<)!by+JaRrVK-9XsZ)u{$v? zZs7h@2LP>4D7BkjMO(xGK1Tn{O49Z4n>XT$nv08z&tAN+zB7;L)UtX;SKveuW^w1$ zIcWj~H0kv2Op`^vxe>homiTgy~+_ihy0?F|UBBsRO^-VHMq2{(lNNvoxbbkzlrgc%Ua#ss8(sUom+yhj>jVYWg&+IJs~-&sbflSx^~`BKJv|PvYMpl|@Zp+9 zM|l8NTtB%D@iziD-S3mhUqE&VIL*x%p&Nt1H4^AQH62|PU?UyCkUBz4atpRVigPs1 zDn-Y{=;-THO6|YT(A(FS`-i`VnKprul@S3JB7%)5;WAc6LBK^^yC&%2;Xyri!3Am5 z6~CJ}=12y5 zQgPG=hyu2uKyUaYbPaFB4R;9GfMmGZxJt;9?9iJx5rAxbBq@t(v3iah(;@`pEWC9~ z^0geiQ3kMM74k!w`CS4n&@`U*GiEqiSZLo~)KEzLh_ucBk=25k8w{U5eacn;$IQZl zfy|JU{QC6%u@>5aSiOCD8>re(lhI;X$dm7f{oS>SjoiD#I$uf2`}p|eDEV?0s0TBV zT6<(S}r8$0L{!k(hCCLV+_-JM==qD;MJLEr7CY7h?y&fHk z1?Wz5b#DO{7G`PyB{`}ABE5M#>99}p?{AkI&}rE_Ikf_lPi#;|5d+1LwnXOorjHGt zgjw0l6FC+MY!8t)@)ShLTLJsI0#A9p^8WF;t>9v!L5BSJVip>gm>2~Xr4@YxH4hID z>o0LZow;~i7I3}vkO^&uLxXjFcL^gSnwc@w4YIH*jM&cnT@~b*&C6U9lc-M+_Yv?q`fH6-+g9#mn`B>vsJHcMvdRlan_5hbH#aWksvfAJRNWPYnP*lM{!M!)NftJeuC}Ecz;c; zMp3tg#f1So#IteSINH!u{kE1Jw^(xl9FemHlZc2~R(v27{~K|WWHGT;Ke|_KH}kLo z7#euQRMzRtUD!p{4jk7)%J9DY1zs)Zk`FEor`I7mt%q+k?Ds7*new8; zG03U7_gXVxnqFgyPmv8VbT|%j4Xn1;MZL$m>lj*AKj>U8@AqbdI+*;M_K(9f{);m? zxw*~An{F5mF7O@IXUL z7~C)U3#ds8aAq5s!;oWGV4y!HCB56D65<;KA}jA$V+smT^K-v`ZQQhJQ)bUIB{4A( zS#T3nkP@|A@um?jD$A%bmC*5$Ky?s&-7juqJ78`+iFHG}`ieC4-rfzFlT7Ytd17->3m@%AU zJ%qm65A$i{W8tVownUy%hOK^r!_6kFTNh-wdQ7@+?4z zvavv>VJf_~J%(sp8yXr2ieWM}34a?(IGOMic=KYo3QH~z0|)l-jp3_d!?gr)nfI1v ziAFaa6ZVIFBgRs(`mL+4FLU-$NQq#=g$vOrX`<4riI>RlvrvzrD$zzEuJXo6TOhRw zO{FWEB8lvh+qgjMTsE~cFe3j^-sWX=14R-!Xzazy!Xl`ci~39l)4b1V_Cmzr3PUfh zhtH2e)Z5(M9j_$BN6iB*`#RhXzY{0i@Yzmdjd(2Lw;_x1iYOvD*( zMOQR+hCqfe#bk%>w9EJ%Q#>V_PiiQ5t?-0U|I$OHBb<|;&;Bn2s(qbVt-zTK*OAvu z!!!cAkWcw+rz~QrtE=-vCd>?aM{FAQJH;^XMkyWz#ZxoUp*ZnV-$qZ?gm>Be?%gH4 zxUtPL>o-O(3mn&!3-6aKy#Ms;14wL;+^37nNwIbLvUC0Sg~i1fSXyc$kBcrQ=nIhg z0ZZ-9r_a}HqfL;*W7CEo8|^vJ{a0PB!|_a|oa*RN2~(A&444D{(2>ZjuG3+leF3W| zQiNeX$KbO(CJ0;+5x5@QwC5ARSl3p&JNvtbX?47I;xe2EUcfBGM1$batg2{j_R!Mf z<4i+?I+E#F`8V8wX5c8Q;wxK=#|!k&lH7}L3I5A#u(ANJkr6nV z^y)bTHc^_NlLWrw(F)t|QOclKm;VDfSQBbFRY~iSqCyVA#x1Y{M}=c`@eFVT3JNhXgB#fTzRrh^l3y7`-rINY z+@SD7#dXWd0Y#4ApLx&Z0!B!9BY}b4#j|wm1Ki3Fc*Kr|!I7#j{N*v0%?Yq#^SAnr zH$9F+RgmH70rwFX6{f*)hAl;;N|G2focw=KTGd;r_@ty#fh2y(c*3^*e zr=nwM_}>w??`468G8#W=JLTUf3#k{@zl!}@MrJF5oVe2ag~-KjkAhc+Jv=A@N6Y$+ zX+P~L7sHF@S3_6D0JUvX&rMe33HQRSbxr48aK($daD=8)x4lGd{ zJ;ig+kR)(G_mPs6p6aINvT>Su?4${2EIyWKU$c^w_O^VIdx@=8i< ztE;QD=SmAXif9$BFIb$yF~@6QAb6WYZU)C?7o1`myeM~aY&cQOA|P>wVDhf~Hbb<@ zfSKG^11URov!b5rPSEzPU_&xA|2-yk6EJOur3L&!A=>+~@$u$Alj{`}6tH`CJs`IL z$78!A5e2Y`;&AF$hrfUS2IoiIMZ6RUF7)~%R7AVqd{LPC4h)t=2>$#cpAf18QgZ8X zgro4}|Nq$lR^n4p;UI1_xM^ZZgest;Ou>T{3+YA7zh!d(S2+@{xMG1vjggdDpE5n^ z>x;qc@-}GDTmF2Jr3lBv)k7>e&~HluH{zW5^9V>W$fSC~Yw#$p(Ch7XRsay#z;kt? z@JO|PAvHy@T~X<6O3&Fm@Q>qrTk%SQYq^k*P0rQVh?Wk1`ZH z`<8NWEn`vua)m?Ss*7!pEuC2%=;vsGprLW)(MJ8|hE&#I7E%RZoZP&34|aa^+^2}+ zol~)Zdj^YRf9Fozn{lw&yPa3goG15wkyn?ysZen1re`#{fLzup8`!!Ic45H1}H&ctw%zCZ+! zcd?MSuos#P`woSwD_|Ge*OqL81{cDiQ70g8Lb1$58hRLQKR>_J0?+6pxNvef5EAkl zkUO)%XSv7W577iMeL1J|;?OdxVBXB4K3V?;M111(qOx z!9RY!yQe1-uaPtrq?mh!8Nen28iNETr~a*lw)RSy-R-=V&Mvhl>Oi5;RJ8t?b^}@! z4n*gKm=j*<@Ua*(iXlQ6S^!VJlaUcAZ7z`u(Sv3NS_c(Nj?d*XU zk?xo(4hNUycUd&fVoSXK`swNEiy0Yh!?c#oRW&uym;=y3Gzk`8!@FT%W^OfoqyGoo z+B%F3QUlnG1zU5sPES@>B)u8^;y6D4+;rde(+ks^VMw<@U_C`RK&GML;YgIQux_Eo zoiS3#W7QX8Y6cV5G%&z`vb=M6i%td|uR<%$qHb>C5DaMe@b&O6Y^GOG6Kp^bw;psX zo7<^IPvHvZ8L;@f7~=ICURhcKpx~56k0+ob2es2so$q#0Q!XO8Wpa64QwR?L6`1i^ zTbLzbUY-+GP$^8;wv(VoB)hLpU`A-PM`ZE?nmO4cFU3?smlchKmUGp%F2$N6jh2G<3w{&k~PSf z7n78<8LhI|Nl00@z>%7)fHev2NA`0?8Gmz_`F|J!3}I4hS#QS-YL%^nE!#Q;g}W_R zz~g6(TK)2=uCC@26T8v&-rLFoW|p+*<@tB*%By9akX*9AX}~aVg-x8)w6u|Eis$W! z{{)w=augdn2pQ0SHlt5wN<{QC4p{Ax*k}N6t)f@-Z?26_A2h&JvnDXFX zzZ%Zb4_(w|zzb2R7ubSt*KBi6AZnbhr%z>S&G!(1i-H1FEQcC@(a)#%Ad>e}dv44UXO&#?NuB$AyZP>(#Zk(GyI!S$7DNY`oC9gu)rVBPtSr6&E1=NUkqc}c z9?q+!y1L#S85iQ{2$Tp5&g{~cY_!NBMWgBI8#Q(jC>2S6n5(9Q$nErPNpzd|4Vy4l zONmElfA1J~$vUwp0G7@8V{uu78{9n`O{x2I-{iVu)*RjlkK8L@!MvUr8QzrES;C?Z zk`xd(+J{YI(4!t9jbP#z0VwqDJWo!HqXvpl@(DBT-#z zW2SqIM=^qwZm4*@JH2&p7oHtRh^VcteV$*s`uprpkai?c?jM?9t+J;_qYvTX6@O(q zzWY&{<1Z<}PXJ$$n$MFk6$8r6uc_Ji@_oRgzf#a)SGPE<6W$XgJoF6e??JXW+Gu;J z^T=n|@{rHv3uH`yogH0{yeBOnO?acjf4s&Ss{fuk1GfP8Acj*5Lgc6;HJlae{Js@ zVC7goX)XBgVe~v=ok>No%<-(;Qp$FyQnmo50&gd5QQ@p1tE_z3H|ho-!6Xb)c%?wOi~dVu%|6Ui?K`M2ii;Y-3>{x&)|o%{WbJG)NhIzq7@T$w(N+}q(J$;!0s zVSN#r=F@+dckb|nqU~{)VmIWHD*lskR-T#n4(8#bH$L5=yZ6{DI>^EBeo%tnpeV<| z!Z5S2eJFk;7UUkC9d8jA@DsDFcZ@xZ%*-1gw;KEWxqH~89^&#QJWYZ$&%M~I)eJu> zdnYS85%U6Sz)Xr!5v~acau|tALXhDjvt}<(xLDEPpQ3~Qw7W|38-lbfuyoAQ$YX&+ zb)B^vE?Fc=Y&g)t;v)$Ez`YY1h=p*>6F7E7!S*%#56>Q&glFv5i=_Y>z?wTEr3Db} zUF6{B*;!Ll(VgJ{xH&Lh(fh%rD>$5uG7z<_R>>{)cV7O)R8@go@|Z&|Cd2EaYewB`5@xa5T=}QE5?Dq+1ZR zWE03kRA(mZ7{}pN4IpDv(&!Y3*cKAo;E{k+vB4g#=J1a8@pQmz&+cSUD&Wvo$;!)H zorx*|#B$@SfiHmQ7|>u5>FLZqk`k#`u53l8QHka71ax`Z5S(5sTmsC7>E%sZI@11{ zz99f0(kM&to@Z~|(E$MxV~%u(-S8aL3xi5=@<6(nIks)9s?+h}2N!O}q;_s~iIFUe z@VXRz1g+Ornf>>hhByA;CGtlIhboD`TiyZKEl`n*j(YUdi|boq?8Jq?U_ZiCK;i^4 zyO^v2^%kmj2_Ad|58nudlYxP5C^qrB=~1vB_BC`POg?ZEprK5a_wH>%jZTOR*h{x! zB)SmKv0PeW6BDfhz#~cuxatV)nCj1w+pw(j}66dwXTIw5|l+6Q!KSDIog9hPASrNVpT|GVGPFzeT6} z0ViV)1$7Jj4c#!+W+BAHdO79J9T|Z0tOwoQ?VU1X4Og5d7ze)!X~*tgO-uWqwuMoYX6+S$l5^gAf4-GavvF;1<{%x_Wta z6^9|Ri!3tT{DFFFY4NoS4%pbj`>P~OO!cr1+EJ3}A96apUfh<5+Ya;M1 zThM*r2PCNRxiR^H@>H zlKVC`F|i)eE(+AldVxqpZ6HytKmeLR`Kc*ULv;E=AJZ#V9Q^N4lmqrSdB@XdRd{jIFh?9}7S zZ!jgcp9WfR=x?kxico&Edg;L~7MsAw!SSE&+fda8t&pXr<-M(HAr8+ECACpOt}n<=wMPLR@s`{y|s-p8xbV8$Y4^RsG#749^g;={N1| zF}2cHS)iDHx~dmj{04SOBA75w^VS;0Hot`+zOquttz1#|ya=!on`|6?0)%UUw-E>n37227uJ#>L04+pr;u>HQj}dJzup z?Dr()6%pda@745rUQ7e1f-ShOuWvKK&bk;a*$^Q?PScPN1zb_tmy{hKPwl1nHd44AJ=)u}nSU#8 zdkg@<7Tj=vpNiBd@-}VZa^=B_OBKXrsF3X%34hHcGIcS?idhmJe}Dg~HRPi8+^pZ zLawG5G@aqeFP#TM&Ytgn`pb}<`D|BjuT<90JLP4qcyn22cacqUs*63s2%OT-7EmE$ z8nOrQPR^f`o@s}?ad^ug`tsvtc#iT2eG_7epF^o!{xH)HF-s7Z^0W(&YwJvats{*7 zz+KFN5_Y-&wk$9Jl%@NozZyN?0vX+Y0vuhL&_)|<21|=^*0k~I4+gFCaxL$(m(0(}qe0wu;m1)Qt z66%yp{;YS_e)#eIU~;K|rDu~l_3lrjkJR8!0mfQak1)6Y-wskNHLU(doN;CW+*NW;kj^PvT3g)^z%8&?b+9f+S4R#@ z0maw=F$7;Z>>EM0;fp2%z9c0y(22WsR&HIuT#zOS z<5fsNRVY=PvtHi7u$L;PrjqAZ7$q!0N;&Q_W+6rcb12_@LU;^{Zs5I}A+@7Yq9vdx zu80oOzWs<5nvGH+R5}~BZ_jtHI6hLh86(+G95nE%b||fowJGShIt41;H3TykTa~?? zy6I!_1%F!d?+&i&`dw~Vv_Xlf%JACFF);roG6Ga*H{?*3lTU|=y8Jea|5(#HJod|^ z@S{>!6#_9#>RmkVL%t;~{t#`FUWt^!#H4EA5igyTv@`)&$qGb7o+2{7pPv@W%te-1 zwPNv&n5-SGot>R5OcDs30K?Pp>(@n3!c^||x8_$=vgUB5fC_5U)PTM8OANAWE5bplkCq;)ZNaz=nkiWRy!ZIgpvM{4lG_*w@wYL=0X=_C>k@W7D z$3C~Bcrsb^fE`HIY)8ET@Cgv96o5$9fuC57GmxE7@@R{}!%lB6oOP0{AJJXwApa#a1mT~??i4C8_Y$^;PM%a`A5<9! z-{d*1BI#|t!FK}Bj!<6O`UGkLTU}LG%ml*n-|E6%hQ!ZI*3WWbgETz70sDR30l>u} zMn{0S@!8I42(1l)vmq9II!wzi3OnZk*zd#y39)m+4gvE=>j9K-Ub}UR4thP@EzS-M zOH%#^V~E9MX0qZiwZNosJpqN`$ZBM28imTd8C|&>WViqdn}?+SNXtDnySaz@4-K5kSGB zs|1h1ReJ-}*))(lw+_=bQ*YX|X>M-L)%Th{cujF5t7eQU$Kr9^tt+xbsmqKp>lU1O zKHxd)@pr*?T953o1n7X=>qO85&Nh0P9-mCgnA zXcXRfGof?*f-zPQi4n<6^%FWU(X4XtTO$)00|3?|xM&RVbI=OWp~JCUa`f^NgPVG4 zk>t0fr5}O>^G1`2x{0#m>C-J3l%s)Yvix>NTy{1)w(}*|Zq^|%9VjkPh%wNkKtZE3 ze#{Sf4`ZBLAvwI5lS7Sp(hKkAN$?qiTpHm+a0orbe}eZUd1#=+@#cs@CNziZ!MJJf z-MbaEFN#Sr8b$DW_k#xk96R1-ezx!*N(Mwi;{Cb#-4KRi3BtDjBz62R@u2DAKsx@1 z>g#$@!^z@2p}n&!4M5uy6`7Qj6j3qG!4r5c>@Ry_I{}qZfW$&=t^)?C2qvIFDs9DP zUYPx{7Gmm?S-fYqFX#vj;UrJ(k=>^ni2B^7{3sN3C4=IaMa*X2ds7~d&i@f6RT66V zI4^y#M}D|wCUVd>(HeG5-64B4jqP*u@@|IuL*ck0e_XWD)6Fdc;id%*=LC3eH0Unh zenG0x3G@~5RF({GselnJ2kO5NU}cNe?PJwG@2v>1r1WamDyahe1ec5tudC75}+LnoAbkJ&i%C+dK^-?E}NGGZj^ z%$YMWf3KtQUjkbr@EsZ??q8o@#4a>~>#(QavZVth&_N8XTw~*#jAr_}&=kDd`))hhe%TLSkKSRnj}X~> zdG}CxAzvy@7X5IH+J~Ex=vTy2jdJd>x*P$V4x?GKBEFmkGPOWP@KMEKVEpOo{>*21 z4ywo5eRUDZ!DKHyW~L~Fq;$)6CHhAV4YDZy76SH|n(9FqfFm`)BmKR{T9`r?*<=zOnP`aU$uV}(>1htd5^o%wHl5LlB0ann3(=>Wt1sR!92-W3Gu z0lN~oPe?HuO4o7(T(>pct|l<={wYYzTzR>S^;ENwPlt$s&3rk5ZocX?#9mnKy**z!xJcUmuLW@>f++DYTLB zeD|uy6O}{nNP@yvVYEk!;1)({qh(}X6IY$Kjl@n8R9U(j4t;ctMTdo zQu1#>8#K>?2&)H2XS@6Q+7WA5KWhgX`2EaWRf5y~71io8dI zt`ssO4rb z1OAo}-6tZPC8WmS!a0xFL3%T0gA^v8z@rCFeZWeKKYk2}Qo8`$t5| z%PGWyDxDU0Pxawm%in#a60&$<2?$Vp}>9C({rIbQpE+wH0JUn{b_8A7f?Uk>+#d{8zpUW<8 zd=7I2Xh15z9f_t5p^0;FbewPO?gylk@RJusq&e@gnAq5C)d(GD;?Sa7{_mMKB#JC3 zwP;0eX!wnVKVRh549Q8~DqOU18nL@R`z6x0vBmNI%-gpo0>SpYd=TZL{fI{-czF{c zrfFqQp{ZAhS-sueZtpTDMMOn6kC7;TomwqFa7xw zjijo@yvFE=6RahOHO8~h`-O!R5asnWe3y`fMrm-*4Tk?_JCp=}2%=pfp!%$g!40AY zn)&pJk75i((dpYy-h)g!wK3Hw8kKIrYkbMp?)k6isZM-LvVc^LrW*h;3#TGLQm{{f zB{OidOZTC14`$kx`WMCF)GxA9`-ZX%QRD7c%lCTzT;UG(G;Lqlu8`(WMxY_Iy!%a; zmJ!rS!@G=3;6dN_y2S`U(hCQc+)V5noe2Z?Dto~o zU15`tP@nC#Gx(9aUsFzw@%HW8i9exD70oayX?PXC^oLUm{s)=MXK({+r`<}k0P6C? z9jAR`g*u{Lrj=R+OzDiKwu+li3G7(RsXLC(C;2lf9@%4VxDhBuClHju>)muDf?)~! z^lR$fuTV=3?zt|DdZk7ug^ifXK!U+L_%7Ze0tFd*DCyOFNFGBloW3Iv&i}0WMueD0 zMw=>WOhX_{G})T0bZk}Y*Ecn(1Ru8zOAmjJQc_wnv~OYi;wn5yO?mLB{T%1PimGh; zt8xa{sB?1!gM8v=rI z-)RK&aE?~XGgTSLHA=%M<&IVX{L1X6cYMTHk>!#e4shZ zX}3edlHpkKK4c-uq@PzL2?w4T(AUgsT89J-cbO>JI72!P*A2@GExSocK%J8)n`+)0 zTv(562!!YMlSg;370pC#^zp~zipt8OfPhWZoNduSEqH!YbVvlmZfs^Ysi9ykJ}B04 z50y&y-{!wCiMxo;QkG)t=xBmIfaPB$ijxc`pUHHnyt-_5g!BiN-T;qlw^Yfn57Y7{ zYSm+8qt$1~g(qth~&;94`s&1wV!)t*$tsZuC5|`cjB^lgSo|NVbTb`@6{g^=Sr}t*asGOSCDOyg!xHzJ;`7{- z+!eXtBwe&M6(y5NXgP3miPa+b-irzkc3iJ-^Y~m+WU>&Cb(>6xz_~N|DVKw9O3BD1 z6aIfWTh;EL6R;rD72hHTTgT5YfdiLGs?uz`U6hemXOe+MiB>aJc-cZ=B!7MWA+vL5 zroFwr#p56&C#YS$_vOCDoT&enAT68`Jy_$QL!&Zbd_2Jd^HscfJhJTq??i;o~$h7l5gp z(J*nc@eNdwfmuWvq!G!#cYVBeuf_tEi@4`8tf}8s_D-U#nh1{;ePU~N@7uSp<>Dd%xjn7f+0_R@REirzAa)#Z$DOHknE$RBpW;Xz8w3<`00kev(mS5V$%@-yH`&! z^6;3eJt$=!5i1q`rehEW0e=pLFW>fyM!TZA!i3MoHeYEesY%VJhan6Chh%qJD-%_1 zD{imH8FTcC8EG++_+Nxt30wXw%Ai{ub{Se%;+5a#G1Ch90tyRVs5pT|8>=OEppY7B z3{^XN^r)Air4g{Xuhdp2{QR0QPvYHKXkcnehu#?y1n$!s-=MS&*5KI#p9lj7hi%ta zP6h>a0J$cnJ)gsDyTM9WLY6fwS8TO^{{_rz4a-dqLZ!b7OTL4+iJA3C1RKuERa87~vpUmm<^1{c^Gi!@OlEty^EdUp#m2L2!z)X{(a%6&I_} z*u*|LcKER3(a{ZmVE}gj(le^cB1WyMB55C=O9(gKw-gnhrfTo+qhu`-kyE_YLRsvM z3Ef2gN9{xdC$G1lxVn9g_CcGbv@kleHhLUz@2uKB10v1TTJPP1O_}&}#<%9?y5GO~ zz@yfOgW9b-zFQl!v?@9X?}|0>K6gvL{?gQ z#?>owq@kOUo{{`CX_y$#6~(G3*`_^a&=vwf`6s1WkNRce>B6n3==|>_!5OUI(sOwZ zPD&kMf?uDF=ZEwiW9ytZ+=C%6x&U4BRo#@k(0&&1WVHQa(>|q)0yL^^4jLHO$q~2z&__)^%=>T8$fMV8lJcIH9spxI1>qG z*D#*Y_g5b`85hT5Qu_j=0!kZPdE2i^;rmc&NKpnd9^~v7%BHA29B^p9omM za(?uf5x#ib>ds^eo1bZ~4%EoffmY!KSVabx+thKUfi<7S4d_LVm z?7uk^72Qu9c!kSV8pGTBr*h^6h)jGO*0}+$mVdCbv!E#|LP0XlJ7&I>KoK)Drazr} z412WW>c3oVYU9TrD0BTn5{cP+O%a*vURYu8^+Rn{Qc_YP5O=KK(2mCGe>ErcT!T<9 zwrMMMW^C+w=1R?aVLrV-GQ(RVu38qI9(A2%Z=XDSVYYi*iMIFGT6W(uj5$V5PA7i# zH`%BGGn>fTY`s@}81;j7hMjpqSy{A?UiT-QORs-TiM){9e$9m-?Ar9RRBLxZ$~;a) zE$uaU%AsF+#*{x%# zb$x&TuzAx+_=C6OoZ!iuz6?bIxRdP3;_H2AQADwuF!yzxAtnYKeSu_XFq(Q0{dc00 z`SN#f-wKQ3=e02HSByOu&=Im6XkOrO1OoeqIyXTR4!W`i$k&~j;J zQJyJI&o;}0*7Y#m7bv}ptG6Wl7I#DsAY-XF?Fm;%1uNjV6&eaSTvr~h*8pj~77}^| z$M$hNwOwql7%PHew=)64J52{ak^-mJ>t%n(2}oRwTfVHMY_vRJucut`NS^@$2a0`p zfr3vNEI}1*XY8joa7n3Sjr6Oji!zODiTUVhdSPTPIQu@~)UiFtb2#NUdB6v*$7RrS z`L}NE{H5gRphfYN)KR)_HI#Vy2M1ec!{G(gp{gKg7TcB3h?%w9$B1m1*osJ+z@{>p zYn&7=t5?tNxT2C0J8|7EUhR9@>cl0dQ31Drm&FeP1b&j2fho&N?_Z%?Z*H!}=uo))3D8uE3H%I7xOrrthd<9?uGj_sB1qpiXe54p{Nfz)I;R~ilhuI#gr_hcA&i} zU;d!t3TI!g-4|*@xXqJ;4j(D{_W zgYt@_(0%2~vT&76++&9*=Arm^Bc7{8hNqz8(xrV#qO%A|N6KUV{2_4w!m0RU6I~tt zexID}3<6R_`jYhJhQHwZxp{ZemR2^ketL1wPQ^qk`{>7UHi!sPYUE|-g1DMFIv%*+ znB+d^GRKB?+(mUUQe@qR4X@Mx;O4zc8l&qL1E<_rA!OCLZ-c3n|5SUms zdT*(aFKp=u*5-L9EH>7vM-G->Rkgq)!{1Cz1qDl@-;r3U9zE(4SBnY^F^Kz#@ZwD* zx3#PcNsK?M?fQ4CvA*Ms*ki}c5%NDHB#0G!xCllqEpmO2@XBtY0QU*iLj73FC89fh4Z?5l znvEOQu6@;U))Ov82!PP#UEBOEAli@6npc!@$>7(++grtNwIX-bB1v zY3rOk?!Af69?{Hpb#--}-I~;$9#seys&w+V{QWbvNzx*daev^PW#Kc0OA9}Y4maD! zn%p5!N`jq4AP@%d7!S^yt*YDU9D{W6J2<4S$~m-jI}RMm;y=wb>?8b&r>j~&eni{8 zjaT@q$IvHHOJKIia%BFq47OSpnfSQVa=PTw`w!Qu(QN*6zZ(k-)zaNP(-yhA7 z_ktC4j;~2%y>_fyUIc0wZ@l(m?y9c$YqL_GL3~=8WyhNR{|&sYsz|ptSmJf-&Ygh$ zF)<})vu)P>`admzN(`);A!ScY>k$pzj8oEj47{$L?15o$>xp8K9#Nj5Q)dBz=sq{H z6ryKI+~=fJTf!R5v6U4exDX-Cw9q{%>P{{02))SxCIixx^3s(TVEf}2dY;9#SHB55 zWAebCv@BX1|MD{~rh#Oz9re4|Z0a>fr{!}eWijGWxEd!eRyk$~nqKti(O&+QzlvKz zM-W^0-WQ9>D&EuQF5oW^@lgGsk-4E}x1FJiMtCj90WaphL}CNbp~PAf=|C!xgsA~Y9G8nH-A5Mqfb1GR771W*vi zf%##)wW&TnKD1ewQ8~nIS5;H4_@Dh-P*9*S$y+TrBqaCuLM~kqL6Os@nFh-u)IF^1 zO`tu3t6*g*lOZH<5B;+w3vow@Q)|lhKi6*8(_|Ug+uP^VDD+fHw+W303$LTarm#Mx zl(dTlg2(=UoTIXrm_@D5>7><`$#|Jyu(+ZQUk_gFVD7k)Pgfn(roV&olORNO;ls8E zl8hH0%qW*2bbbfg+t^9sWAQMNArztx-3^QdI8jfbxxdsAgJe%Co>NMODU!-Xckjk2 zC0KYVg!mGvAO?J!cO)(8_r6@iM-=0$=lyR6ue`GHXskdw^mompQa`CQGsQ7uNf^lh zBvCvLSJC6`m#59`!_95VvCa%6zw!*HA;=K+y*JUzGiyfda=72N4>ZvRg%}%aa94e6 z$i-DbC~rS^k9E^S>}+gg#LYnSM+n0!=x zlN7nNuDxLjoepRF=BdMKxF)qaWL4zQ#>6SjR9750uzv55ZPq`&oz}(ARquNun^!xt z)=%^$n$wqz6FxH!Q)!BdK%Xv2e9PNl{yL-^RM|-Q{(8vG)dML2ux#GlyWOc10;m&W zV`B-4$ambnZ2xLveQdYaWM^d=AYs9)MJKG4zIqb!agOzS*B||Lu(#jCog-jo>=h!D zMtsB<7twVI$Uzu0?`AF+VI;GTjz3d6umqm)HvZ_%kMj2qS26GkD;Yy0a#J+0g~>Nr zFkt-nzLLF<@{%;ZCsmVc$g+>!Ry%CZP_oTHX6-BWbN)Ic$_O1Qj(pGDeYk7KLrJ<9CYN$T%iZXamO}i8Fl!Nh zy!MI{U^yh$3>xUNj#42y@*nV%YcL43(QwY3h&=lvs&EIW<+EMw@0OIFNw@BM)JUZyKXtJEXD34~7gjE+<@i^hW(!jSs9%bA zWj+#X`5Qf^Li9cyWzDavBs*H*F^|{I_CVEA$L+cnCieY0g$K5L3t{nr^gQUipge0XT+rOxgCR4~NiCnkkeWD)0S_F&n^z9Rrl#Y#_?#R#% zy}I?$Hcp7q(`H?7ls~L3tn4G64(;lD#M|_E*GynO8W>6HXJfRIz|rc{3h^3ZAresN zKbhWH)NdGFShqHxu6od^P!o&5%;0wl7k?$rz_7HJ_T9#*;7v^ucVEmK4m33%A0Mfv zYHm;P741d;k}0l6w=tB1_*%I+?GW`+*6kcc6GhC}YB%-*OIhd-TSIxC>||QNe7@t4 z&#J_*Mo#U2+CVO5_8co`KqKqq-r3pNMVyxO$;D}SAX`4na!6vSWT)+&00` zU(G`7deYzxrm3>6ufnq<;4ear%h)LlKrKaX`(QrZ5X@tG8U3O?+mWRb09SJJqi}{lV zef#!p^J19@=O5pxV(*o2y@8g~`%%b5I%n_Pdps{;Sxs@w^lkm9C_V^K=2EZ#k>gRG zLwCMY^p5Duljom5wdh&QDu#W__nU8f`k!s~@+;-~?!&_Likv>KrC8j~Qq!|OKhN0> z@S&OQM6)jxmTwx#U=pDthNy6A>+5%8$zHPUyQo3YoQVVO!{kSjPndV1HmWKU(@mIX zxqP!)yF}&VasP-8oSezn&l5B?gTDDSH&Gi&O(lk;c943Bm%(bezQ4YBk5U{kppN{Y zeV1Q=My>nx-D9w>@?%lST(!c{j=x4^2yrL6@?5}q_f_@?SjokA2ff~7 zWN%9%?v2R;CyC5oH7@gX!sUwf{xhNjMr(!~Jvxy1soU?Ssaq9|+`!dMTwKiaN31~o zh229`4OTBI$$r#XLcFZ}NB)#bVMp9{*sNi5RWiHz{v)8%sGpMrb`91|XNV4&TKrx| zHSFvdO8IgU{?UEjp~P4|&5=G5pKSmL(@A7vy3A17jbQ1 zy2AqHEfq{+ksbiV2ERDPjpY5J#cm@#N49EU%g=z$2?`!twig*x4Af>8#98ZzPgG6o z51E|rp0m@T=z8V;B#PDlwsh0Bj+VJgOx4GPW{Fec%~3VTqdnKJU!V18Gnk?OU}vt$ z+jMuizX_na^pyhcc149ebU@UHU&(at=)r^={%vVe$o)tCMZGEv%T9k5Mh=`GTE}~y zfu^M&OEN*JK7`l16bMi1-=>eC1O%gfX~?a)zI88ZsasswFf$^Eh#><;7kZ=+he{li z1Z}3LqJl}ks4YPP-fAe0nSvAo1%SIp_qK%ah<^;m&E$PfdT}E4(FM8w{ZBvsHa|h( zUw-o!QF7JUtAh{h`Zf6BnC7SzgWqzE-}@cQr&b<4I^%in8Pabls1XDjf{B;FeE~1r zn_82`qNc$MrL0XeKJCvnceA)yN@AYsl;yWOl5Q^L&fev5Xfqx7uHC!ktM*>JIR44m z#UZ>*VRiv`I(Pnj9wMG~n9$!l4Wih=rZk>P!9e|Lt?rR`C=ZG1*j2g81YEzwaEJf% zg`q8S^#O>{siP)VJ5dv_Xpf&6>8yY}BzbPe;JqH>j;4S6@x{FF-Z`CiP|F+`?KEoL zRWhxEuAfshDMBP9a-oWUPnkckfU;HV{>l#@K7`;P&0hJmx07E>%%Vxt7&ZnsB>Sl* zHv%H*?)4#YnORxCvg$^D&@}1K0X&BND#HGlw0=^_zb{Kpcp?TVDd`~Y5=kKkV^otw zu_~GjHsArzoTULWQs&q5VPS>E@p!3Z-<5K9+fg3I_U_}@QXAcT|53Bz2lYG0z(4+L z8En+8nl!umE;uRX+{<1%%Ix;y2~lH@tmLZpZYhpxxU>Sgfy2YLwuY2}T3P_g*<#<{ zf0r|!iH#>xacxJJp0Z%Ebs=0spb$QCVRr@;ba_a{!iBzvnz>0sN(92wXl*(%!iiBz z?}ZI>qVJD$_gxzm1H{N*z7WVI`bv?5JSS++yY){uC~qPCr%BRNIA~*=EGzcT(JGb3 zma?#SwEIOAi?^QtP79sfb9xk~bU!+wk?XFCn#uaxZ{5&r0P%Cgx#k?#ww#lRwqtJ9 z$>^9E^{1JC=TcFxfo2!|LIHOFI*Wl>(-5Xf`=T~%m{UwJvDPD`H^Ir{05Ddk62 z0(Ni$B;5^jkq}W=KYso+f6k<#<(m*|_6br5(5GfT_~Fh`3d-_hIw-lrKx&G7dwK{p zGBxa;-Me?&wvJYVUojS(kHUa3z2Fo{vMmW3F;^YcvlwI08286R#rqe1)xFi1L*1xe zRhVe5;&T*6F{^E&1o%RgOltmsg3D#Nz;TaxXN{&ypWSL`>FJsJ{LykIipu<2i&m{~ zl}z$NI$j}MT+Ope`dTJECUphZqxaMdXkA+?b+Y6Gh)+BIO=gCGXDFjYXbuZ?cH2mM zjtpD9$77501{Bzvu`br(2}Rp{&_|Bef8|`96+KV{7;!SY7dk8Wn;o&d&+!Eqz#%eT zxlJx4EKL6U7cB>--VCo8G_%yY#YAWG)?ljSwO5wzJSB!BG)cO6Y@q}an~?Hw_`0hU zB((^Drl`3n6g0ek{R&*md+OXHanXCEg<4(e#g3FvG>iF{U!v)JXMQ z0GEkgT`=cyk^6DBZIB-{yb@-y2F%YY<|5zOeebt1K-s5AMH*$5AumQ{$D(mjnHX@UQrRNvGlE_T`j*g0K=i zOIZio%6{vx`Mum+Ty}BT-9IsX2t|ind2o0(>_cCQRb%)A4^wA_hsH)mBFo9;()_Gt zLH6Hfx=Y24wiV6@F_^`t=Pl&_d8mRSV`D=>TwXUcV5X7gyU!pu?J=4=QJVc)#HUSZ zi@TPXV`(M16O!xHs^j**O{~Tyi#uboy7%g3?Cc!3 z=uySfrx{t7p5VC4WIEGqeHR{jPkz!R?0(OUUOy5CyoGw83^^S+HG#H31nYIiQDsbu z=SpeU@+wFlwcam&5NX4AoD4J7WoqsM0EEHQe9iO&hC*XmUb|&CZ(bnKlZe=-ZJEav zdN8PA9(5Sp0Tz+;7MC1GnO zb;=Rq}R6%*`SQsQ(H01f=($j{1lm6zgBw+DwV#0ml>wbj;xQx5t$J!Vs1} z?In;4_3Q4_<5D3zW_GjVr^`&ZwHOac<1Jez9~iYb7;TUXq2d~&MmcVhOajS! z)fm^;G}0vV^5uys7gr*BtK4h_6er2qlD(#8FvR`={fJa8ZEd@tT#qLjj>qxiGuB>Z z-Bm!!hmH0w3Ztlt>bd>Ar~j?VdquW%$@vv?DANo}+#5)-l!EcFX|G8SeZTaxkzV|n zGhx&_!v2<>u=XMSxftHid&soV@836os5_}{y`yvF17$ifLfMt8_{_z211eO%EARiS z-_t4@mpTx5@o#qVl>5i@`vXhNb)P*_A{jNUMW2rIWaEa1`<{2jO)v{g9xI?s>+u9DMUV2CW#8li{hAIJ(FDc6+xmer2(f{B0 z{g-}s+?tnzur9n&sp{XkYtNf>vJSu2AF!DYJorG`K;U;(Iuo4l#5TV6?I;9)}nk$2i0;~Yxoy{_Y$SUr5j%Xfl!rZv} z30{@fY^zgAN&n#G$T_~y-d?RGbvgRS7|*MKKL9EHz#<+Ws@<=wtPC6LuNUko-_Zkg?@IQju2N;k z_-ABf7~K0(nn)}sW75Q64zvn|oG`++DH?_`gLB*eZmZHs-rC-J#^B$a9C7fa7B=ou(LPJTJBZ3a_L4rDp;_&VuW!*4!Ix`B z#Kc)_zvmU-Q4B2|nasrsDrZj;g81(I_Zv~KVym)JTwq%ZeI=W1-&ovO=yE2x4> zt?1C9w77cKj*bCr@OTD1Fx#X$MK+(-o2hK z0qkg8UnXhA+goyN(@gyUdv<%O`PfVIC~W{P3JeDXM2^5yO5bd|X*&JwphV3KZa3lf zhC!KBGa+y7P7dkIAk$)}LCoGpH|S9qS%m$Xurv{J363NRJ377FPoG|TKG7$@c^c%~ z!Ae!bzFV9$QVBr%kB>J@r%vpKA@QV=p(bF$+E33XW!}pt$Uz#sOgkb)>nTF{Dj7N4 z(yIQ)|2}ov?u@P^X4(O+D>j>D#nE(KRHx5Ej?q^;`38lD-vPCK zd})o72)XeY6d-erOuxXU+b_>K8~T2GFk#9tTdS^JyK0qmb_Yw$R@Eo~R(?!ACz;I& z;BbQ6qxZ13wcSMw%u;j)IN^4eM_2B{%159)(t+}+ zAsb$PZk%+d+=jTm`*{`~1oOFl`(-9P@j=xDI3+i2JZdx2;980s)jt)@xvrZ`z8D$V zxlMIYWlxD%EYM0Uh*C~nBY~? z>yg(1aBZHISY_|LB(tv0<-&)J=NNSw<1XEK2V_TS_|5K+qrlCuTV2*wN5XSL ze>6`pZ|vP9*^us(dt5J2M{73=K9C~q7Wh2* z*Vzdgh%kphO-)3N(WKE+)g2gx^r5uMRUmwK9KS~XufJjOk1q__E}rTX{TR%CDOW9L z_ZdLwY}K`==%i>$=_9R|{@CnGXW$j;jtkg`f?LQM8KE?j7`Qcs7cw(<=H^ZZ76~Au1=b_<{MT2A z8NYn{);C|vwWq2*jg$$USxf}xEdi{tn*FGMlb2kd+@v78oG1=x_iWd-bzlR#e#MWP z%|3^2G1Bk4g07K9YaVo_=f>S$gdt_C7KtEwK66ixEg{$?rTC?9lzZjbLo!aSlDQ!Y zD2tXO%N2I$1$VE)<{=pG-&8f%20g;5DJlpSpD018@C2O2$jJW*rjOc|`g6T^ImWNk9*H3f;cdEp6+sL1kaL%A2}qsSI&R|@vKw%f8fJB z*>w8bDw&CR4^)cG&lZ3^m42v5J7|BsfYHDCoO~%}8rr|WECzzc%m4PQ>G^F9cjZZD zl!(;?1GXfkkjP6R`dRm`R2?CsGlbKR_h}hgizAaocRc>FjDaYOfMt(woV4Q5-*pu2 z&zZweTu4bKAjrXeQ~Ee~$XQxhIfUdCfabs=mQ$Y2(C>5_*KwCz-6nWR4^!)TC}<#y zXXppq2ZXlN-(`Um`$qmMQkhQL8oDdCd_`zfB}3%<4xZD+QniFAu@<K z8{^%R4Il#P;81R1o)HY=rp=qpFN|yZQk$o<=H-;x-@G^?=@acjmdNzX`^WsnL*9J| z_2`%VCf9j|$9%e^0L01uM|B!1|33|%mLNBwxjYJAJ^xQ4CC#Ip%Q5H$@TB$h@@a4| z0Kby(I9rOQ-E52C7Xi}3dH-d9pz|L~$*@e?Ia5^J9vn_NH2nV)A(&{4L=el*8<3&@ z6=cuOcSEx!h;xxtPtL~SHs_v0hy0%w-~tkdo`WXtEA!xMr>3j9Q{D?AL$MM{>%GO_gJ~o6`qG83*BfR4}~{ABrLm8fdAp-AzHc ziu5?JjJXZ<)K@sY1)dm@Qa*=^01H@7zj0Zt5DC^85h+>0=byu4R$-v(0jCkQ)5h9vdsDh6B!gxE#S zr)LzSZW=0m1p;eeXeiP(Xkuv}!hF z*m-ytn+$#PmIaQECRdN_{{(kg{^3eiT~m|QRnOPD2vgo~9KJ?JPfwkiGCR#W>CBSr z3tXo55h%p008?ZEs)%9Ka2$Nf#fEIzTrt-v602Smh+;IBXY&*Z-KO)Did@}27$Ua_TcS*^ixMbt}1~^gt zMWYVXZirvVMi)lhs?$YdEaYWmlv_W~Sh(=$wM}1l`OLZ?BQI~y&fq{5NXy8%u^FSgrGAkkN`6n z+*n^L?>xOo6nONy=1U9s)w&Ftkws?K`sM*;=BC_mK5i)J%h!45i$PIM7=by1(IZZ^ zzQ6?*l)9$+DhUdiVJ@~2nLxNE4B^N=I@X25{&r2x5YDrM?=}m886+tgzI)iHJv=qS z!oz!u8UXrY?X>A;go2BsnCV{0-?bcis)i@#4O zd{^r4vqy{WM@%F4>A8drn)1e=ZXxHSyXii2xXta6D zqUD^dc__jk!*rvH?M(ggpIB%WI;Y(LT-~>*uL$T|I{u81m_c1c@@yliOrrpumS23G za)A^eoA(DcL5kFU{yZl>KHk0Xzyd&slqJ`%*nXNNwhiW!l4-^PwdEK`f{<-hwr!(c zZaU<-0&QRU)>TCdcm)~Wc^U(8pIm>brG4Z{=N$C!BSMWPaXEUp~HD zsH`yGjQiW|jM$x34#T5qaI7=VZ$HR#D?8gy3&JG%X-A{TMyaY$j)wTP%NT2H$*=a9 zo3P*~^m@UQOK36wD+r?F(oyfEFS&k%e8zRZ+CIL0YbL1E@ejS{lNxGvb#j9brM_kJKY5QXoce$FX>TVwgvsJWjP5Ou>*ADs2 zG8u}~<5IS8lvRAXIyK=CP%(VxfCnG;YK>_9PB5OV zf`ZwN_D}jQG!JEy7A#yC4Wm>#?H`lP`Qw9wB$bVxca0J?B8;V>vvckCf3;+V%2Fgx z3ZIg(PsVs5mgD?E_d<2Vbc(JNc81i(n(B5pkd>41{0g;(g?_-^z1=uyx(a`n(r)Md zaak+#{(6F8K)x2Zsyx8L)`=#S%DN<6Wh{Di!2tl!XD681gL87>yh1o+zg{Sacv`U| zB^g_>6WDhs148+Sa>s;oiJ3b)=ox60Po6s`4Jsg_p^$n5{F3k51CB*v2Z$5(_R>|W zda)e{AQD5+XuU+7w_0OvuR#-~Q90Wb-%uytz?cXhVoXy8IrRhD6jdsHlhH8EX4eO*%Lc6_i;giR2YvQSp;{KG zJ~`#oB%Ro_CQM_$-!8(xsCGJ|?@&)%*2RUU_{hA5ghk`7zCi7RqwKUnNE)`M!_Z%w z(fan^Ycn4fWb{)9M>W|vT|}p^slGBxb#3XXuqTfkvcJlPpeaSkxOOw zcVJ^YmLhH{xhjX$13V<{j$Yq$m(%)XyXtm|Ida58bVMBkl+TJ$6+U`?*z~Q$orYx z&1X?tm;u&3ySz?S5uH8L!Oos@*g2J7IlOi16sXH88NIGGmPghv<_<1` zuymiBU{7|Pc1z-=Me^oT^@@v&i-2f~Xsm;H=S)yI{pU!rE#f9KA*tqXHQTi5vi$^Q z+KbIcpPHac97F&~+NFKA{(JWP=Y-18oLf*@8b!)y=~>4M2aX>fweFvrm|-6Cr56Dw zDdO*HS$U_?g#fC1?^BSQ2=|5>QyT5cKGdQ%2`B6EpjZS(i<=^NBj&tEwVZ27kz~){O$mY6=Vu#zLaPCq2Eb z=~c6d2|^xl(cxhDYD}Vm$2;-;bIl3-!1gY?b>}7_!hX$ek3H5m!TZM=@t)~&qiL%h ze%^tqP#t~dQ8p!@Scx7+^(WE^m;JlkO!I_*$Y0YYWY0O_EUCPQJY(~Z?G=QhsP2s| z`<}BRBlRFVAew`@szNh{5U@J9qUXIfc*Vh9j7G_V134e>2;{u<)N=H$CJ%cA)+}!T8wL&{_0hgzGK02+x$Kd)g+f_(_J9a!4*E3iF}+oq?;LT3NE?`O+lhdUcO?nk8v8m= zpp6h6A7r21+3qG~2(*J&$#uOsV6~>WR9Z%RFB0B>7{%G^pE}q$1+7! zbn0inqy`zIAf|}eE4q0$OEhxH+0$GZ4P8W09jALDO-4p0A6MhA54Sc78=!_ppihfu zEZa52Z}EQ&`7(QJ9MB#Wsq!r!f>Y%vh_amXx<(y6YT2ITy2pQYLgflUShuU5<8D{O z0-rH4R6?#_zu;r9+iL1>5JXW#5qcMkeh}C6ivDwuwCDm6ge~l{D{cj6yGuYV?WW?=lu&z4*M{oli6gBB!|L z&xiN!sR~QY&&^%Zv_xcdlhzY6E-RO<13$cbmnom5n8OE(VheuN?Q=Ty0G*XE>Slg^ zG|OR6iH3=!2w_%*CUdxP7{;xLLhWa0zO@gakXG(~G723wU&`SI;%<*dVIKvwu-;oo zKFBB$cw!m!yw}TaCMmc+@^AEKSI^OpH#w@evR}*Se;4)VwiK4AwEK%(k-p_uRK#q# zo~g1J#B{rR?(#$*k|rK*QI*PMtj_t(`5G*kgOyKL@ONV4(fUaJoQNd1JY%eZrb=X> z7-boDc4?w=tci}nv@;vj-bHM#%MbGUozay$WJ%0tf4-yng$RVm;@TE0NGV2d|Ghi8 zs-t)8knrZ$wKhc6mLoY*d-<+MpFXFR>=I~l?dJE~6HyA$1lgeS_CpmUlsU~y%2Rpm z(*9ysa8=HHq}}U?{&g2><1UAF&h*VD^>io;KDIx2`EtqQ=_B+Cge9iK@`P>( zst^1Emro8T1RN8!O}SfUXR8rh-+iA)wNY6vqagR3FydE@XtrKsILuC4SP$?n?dn>u zhQ(fWs6o^iTqn*45r}Q(eji%VzzwC^|8BDF51j8l2oo6Z+!q^ z+;`L(u-Oi)?TFfCWXw&5$`vk)QQb2`O%|Z&A{L^ZNdHc9(cX4NfOeDf=OiKEU)FIp z5wp94hy|?1uPAFaQ3ncKi7FYASz-q%A*eKPMoUtg3F`#3JPoVVilyTgvQj{Y<~P&} z_6~4v^Q?S(;XMV{?gkiF0oo(tM|qdK(Wu1q6k2-<5RNNdHFL%e zQ{J5Jz%y4Vam?=|jq) zefwt6KA6JKCZCwDU91U6v5cK7%HxA=_5>&X{Pt+N`orqv*-30yufg^^C#|d7Q~5NV zd?_}WC+BJa);hb~e(UBBn6P>ljfoK`qtVqZiY?0bkGq^`c=P5q@=fUXepg>0Gx&;b z*nq`wdRYVDS}y`##0a67`jxwQTY{+nOS#}*NP{A+q!h-5U}JV?%yU5^2Fj?W`aWka z7KlRm+4ZxG0WN2P3W@10RW_SkSFQ}&>F@5^?byQA}^|z40G%gFhBF4LN=pyT~00iQnFQ{STDY`_qI>8jbWUB2# zw+3q zBYHF83?(>&WRP%Ho`HmuzCthvwoq6w0y-WUQ?gU;G+bR046Vt0q`F6Rl)#P!^up5e z^8Ro~bfsVSFPaF*!(|Wv@tKt}waa@B>O!EHG~eG~1wyME5u~v3V083u8X$pu)4CQA z%}M)hh~w=sDiaja(Ll1-yXV^Ul%SC;0q7%gdiF;jw)Y*nJ25ESE^7`2jz8!@Ef;I- z{Z$3_t0nPX%n{?Q7d&hi<8yls7#CP*ADWE>w}fiYqH$`u z5jZ)lcmVNmL_z2NZs)p*JbE+)<*C+I^D(oQlSLt1KMIP9x6q2xDKfgr1ne4EI1-^D zjdYbx%K7ZZg>TOq6;^nPvqdqv^OO6FP$*Zou3Zu4R10(Gn&+gnQbAaMe%%1J_(^VlL7)Vux zdfICInNIY%OI=;$+Gfl_jX+&0< z=*-l6G(bUt`CF3gR3udfG1XJI{~1J=AcPQA7gyc0yvVg{2p!H|a|?5^a6^m75d}~) zSU%txrGpDQ7d@fNzx@?r1k#)tv31j?b6s_IGLdQqK2sqXy|Cn+xulTxi7>TZ^E;mR zP0<3GitSbL=)nVHn1Q4S?%6b7)imxWUdLCV*|90-gd4n#IZZ;Lx?sTq1d_JE79!G` zF|{QYLmt5&z*OA$^B9>Yk(H#(oZ%%-R>B#nOw?b^#=a)k3-o zlLZ`d7SoK0j|3o>d42R{dYfM05aKPtOkjAo`K6;bpMzXz{b@oHZo4vh;TAsR00N-x zIJKH1$Bp~VarZ{Edx9jcZe+6Ed5UIp=A2eCJwx(U9EzPow6D0D2=E)$2J>|(bHf!;NjGsB{&X8zFiZvQA6T}+S)bN+xBu;E|?`v>l+(lsE zIG`SQcoDrT@E;NqhH7i~fVKS5ye4?uG$7SFa0@eY^O>N$d}G0%`ae4HGm$f$tf#hwt=)8ZXb>8s5bKTsA zVIkZ_NfY>DHcNuPuwnV&<3c4$*(b(+33%{(y~h zS0vm{Vn9XZD#U$~z+;ee!4|Mkxw*L(8Q<09*Z~Su@NN%dgZMjza*cSE10Dw3nD7L* zNWI?e<;$01YU&3Y)!h(XByEW!Oy~)*I%Cui;fgSC@#TlbR6Ck8i5;8-+dYONNp*DF zaG?Qi393L$CF0cg<_YgQb7ob{`dUoS196bOo+O{}NqkQl#-zbaL>s_@i_iL@lviZ_ z@r$|i{~#uYLB@31QOTmQ;+D?%K83BWDq#j*8MAG~8-Xi2{=9Pa>ec^xPZ>p%cr-9U z*8?~)O2UhZ;;5HLFzA3aV*WeUCCLTh%dj5(Mhs=xwbT1?>gxWC6STPAN7R<=$z}@6 zEo-{y$tx;qmVE$4|6rVyr-+!ajIPSF0L~$~~?yW81 zK|yg!kv6I*8S(*O4z83Rl6ze-z%VAGuI+CZzkwAP8$s1fIh+bs&t0$}m=a&xPrg4# zl*CQ?SuEpe<*qs>giI8nOw6=?+}vR3Ut$BDmksxLy9@{VCX3r^%~=uLug2`i4;Qp{ zv&w6+0OnIopM$-|l=eRfNo5hj!10v$@+s8tIR>_n8MM&cL+@a^_S8?1;B;#8e_r4K z5;AmX4AeK7`1L6uqaXD6HChA*05p7IR2uA@yio6{>I`D)#=*+MImad!*vXporQaTa z+w|I?l_-&f?QL|Si^B508xj;GC}rw_vz@NIo|}}NEhmiOSO3i@&GKC>*LkQ@~KL_Sv!qDuuSaqkb3-GJUcZ z&1GF>gnGDiRNXq{CYexP981`r+rM`@7#< zX}t3C!L0MwK7ZKa1Td0aC>q^3cggvTiYf#rsQvu854OsSd6AY?RX%6E9{rydph}3R z_yOTC2R6*9YX8N47blEkEfSH7g|#nmdt z%cEh)qX88*%am1~*3}wXTEgOk1#$)$Ij5ti)dPKlakryf^iqzQNl%BpP{)aLITF_De=e@iSwuNw0K9f9S#mysCHbH{nQZYb zf0b>FjfILsIBax?38*?e8d7!lv15zFq;BAqbEbb0bJHjR)IXiRQTKI|FyM(}f%`OM zs$DvpeI&oI;7E6F-rW!|T%ppO^Ql?4W-Lws>YyAEL+nIeJ5YMHvW%{gjT{jA7bt`q zO{uA=Pp4BGj#Y)KJNp?kj^UjvRK8m|-J#CtOL}wrPxmgMju$Nnks0 zI_c9&lQ$&)egCFCZ8eucZw@u9pwrX|e;&Q<%gJ6?mCa(#tJ z3^gZ%t5gakmz!e0V)Gl(Jc3d8p{0EKcH2+8?JVL2O1FCQMZ56Q?+)O=9jirCXU(94Hw0170^7vZ4CRay6 z{#HP9v$v=?z=TYYl3*^%_c7CpPf6*HC%C85WI1jq*{8NV5+y1Og}>5kJUykjPza&^ zAN8BiS-_gTw_o=SOr3tPp4}L85D?-{lqIxvJ|$FQ=~M0n8KS1p=hgBs9I9|Y*1mrq zjJ%m!D6Y7HhMb_+eHJ~ZuVk_lyf}NH6C!ge;eNC=B3YrRQ93@D%13Ks(00zL3Bt&I zD=_fNTfPkCx*G#;XHct&J}#@fjPjiKjbXOPUyFa5ctpe!W4hPULuKh4=8V%sMY zo=fi)iAdeYOr@l>^tK3=q5S4%uxuD{4%!YC+))3wiOBRNFoqLYznj95?N4f9A9M~( zz@9-e5GFTo8-?TX8X%CW=!lt+a|@xD5ch$1&_OAW9N7!lF8z+eph2Oqq?Q*RLBFe# z62cW1bN)H4OeQS#<;$1t^>6Tobj=Uo@u3l`g)$MM0A8%t1b0s4)b!5tbfT@Tta`C_ z-7C*2W0(@`9+FbQ(BI!RG~Z?Ua({m9ja57tQ5S-vD1-+mTZ(x_!a4o1+QZ%bCSV0c z`~lCA_N4oWNmFc3^{c)5HlO^YULYaz90<5O`uKLc#ACud1u?62br(#&3Ukvl5%k&5P zfw8=UXu zCsR|U^z`)HJv|Lj4}gfh@;0vd*v=EDPVUM!*TU%T25RZ_fo)$l2R>0kMzo#(T zq@dh;-J^sTu#T}pfL7gLtR;4^X+$1X;{Hy`{|d`G>t52UPp@7LN6XbEZ8!vG#V@}M z8=MyFuaCF0_^F+TkVVo4mf+=0X|3lb@iT92CfVf`g^|8Ri~6 zzvB$_hbs?S_ubKbL*Ns4@7=ri{;x*v#0~FcDf7RA3+}wva2rDxoeLuX_C34YMLC?> z%NdxgQ#EH`4?wN1sw&0z?lrhP$;h5{Xw3gOFZ;Foi2EJ~;SV^w?x@k%= z(H*Z`uTu_QG*pu;X3kNo-aCkNuf1mFV_~A>x&8~=ej+_y>B$STH!%sgUoTF8q}0^n zUuSo`R%y57sL>c4G5lMjoUu(gWorttakh&_9fVX!q?0fOdL5%Xe&*xN!sJ;KL={l2 z{6wGCvRuDQ3XeMKt={Be!%kUDr?=VF=RBsw4dBia(=8Af>}LDiuNSEJ zvaAov%G*Wa5G#MIt|PdE3_f!B7e-YnA=wr-3FL9%Cw0 z%f_gP=pnDYLmi@ghd9{Sq&UVp85(wG&ECXumMFg<33A>AjkORxq^&w)Anlb}!3q~J z7u~gb^Y^FZG?c&HEt9)kjXo>p@^k}lkuHIDyXGQS((kW@Lzsl9&BD(A7e8Ef6Men_ zstik{TO3H#0XusP^2Izt$0cjr+yXF9a3FW)P=uE2v^URr#98PEo$>u{i6ri^laG@@ zeGxbn1JZ~E*^aJsy)3cZ94^rGRu0jvcZ+xz9^ za_GM+&d#;B7XzQp31N_@OE9IC6kX{Zipa?Q`_)?hbT?RZVVY)F32tL1jzyXka`xYT z@W85Wa5;p7WWk2Sy4=L%2eq83hy~73d{|_zbiqr)R zLd>fZXXu@J<@c1RS4u8Mf9`w#?!$*??b`n3Q*h+v-&icf#ns%@5gdDl+zO?SyToe< zak_xQIM;a6GU;_kZQ9V-7wbyj;9V0 z<^z&(j5+CqQJt~#GVmd$@R}3?&<!jJ2Is5>DAS+rt_ z{Yq6>L(;+ztQiKs#A?5JbIz3?`DTBQ$_x}4{%PvJ33`KF7>3O<2GQw8DMUX|D2ox*vUsWi++ z%oRGGm7SgLyA8>*9VF-edyNVag~HyM|0_K3_3htD;6Gl|J5pKgaVX@pr`JrThv6LZ z+G||yj7u!OL!bZWte?Lnj=xn-LJM(glXc?ls;UL?Pa9}TOw-qik3vCb!$J?ewr1kw z$+qZA(i`LhC;R)fnMX#s$TOG2QVfkOWvBmAds1GW$m2C0x%kR{DV#T=T}pF0Cp9?2 zlnyH7cOgz9EBXHYL!NUhw*PEhk48!HkK-N<3Qmn)y?Wh<=(9I3j-mFIy(5!{6A(P7 z{R#!ZK(3|tJ1}8#QDru7PC9#bw)mYQf3P~PJJN(ZTf74nRbDOnuzu{wkzoS}PAF*b z)?`(Bay^t&byfBbN#2bt->7{8Y91bHi@+oQ*&)@Z&kFth1F%WN{Lq`kEbMrlg!p(3 z&g?rc_32?#XfW+)^-8aQ3;eZbyyh^+I6taTv?9GJVMO6Ub<}0>Lg+Ah|C>*r3Jat# zHB&zC#mXtNt29QAlwaWnw4OlHf-_MywfG(To9 z`&MfN@1hb0vR9;vjNn=_sGc4Cv9F$h(_vAC^8E6)j>$xGk>$4&DXRE9vEh`id%?zYkdObE~E>-vt44&zbQW>GH9 zLUk)fxYk@0UN-*R{jNskmTH*HmpEPm3Sx&tK?Kjun?Ju6tkr9;Nx2M&N@C@Wj|Gel_>-$L$?<4~nx-)S z1+BqdX(;$C)_JJkA1JMvDBhu0_HvNlE-ULRG&D0!$`2WtXG-zJ1<6IJNaLer>z#i4 z|L@o6RypYOjI}5`Dv|VXKmfw z%&EAnaje0cZNG08^wG?k6R1r`KoeJh>`4^X0?>nECWB{(04i#OKGM&7?VWM%Ef+N< zg)AOyUiDB~9NxA+NWSnx_!z5trt^$wk2_UU#uhCd*HRX7qQtX(RwiKUD@rrj7mok0 zsMia>1^Oi599G}u=L3@Z<)fttp_t1nDUpHR!9j8x?&xOySA~$B$@+=SBsx?S5OWPb z5&&5g;xk`em^pfsd|Er?6ZjS;A-d;J#YF}WXrHx!@f@h4!!Xd4QBL+3$b*0 zd3dk8T%2=sd@rmrE5@kAPa`$cg!r(VKBEqXa3AU_$k-gc&Y&IPF~$l+-Oz=@pPUV4 zAP+?S%k-yY-c8-m$wuF|y%qGt=oCmk;$$vI*V^0uRES+6R6;;biyl1uVANE`8buu_ z=|$j?@(G*eAJO2%{LN?@TOc3{*r=e@X{S${9z7|)7lAuK&tBxUc~71=YWW4LvSYXx zjd)fisK9VKDf6GV@(%^I=U$0!k;rZ6d`Nkqjwq zqk~1MN{1ro0z|8|_?~#Sm@uVywdwgrMbi)8U*|>XNoyw72a>>`DGNWWUk9FvyxKbYLkCo-;{9p#eM;=1ZyRjiygGA|q3S)gmdCBIJu&%ekgnn!4J z{$>de4Q=f_-d84~b!yVi9O^<6xbnS;HBVjUc!7Ma=NxWTD6xbv!*{{OC@hgPB!0BO~#am+Zqq=@q zSr=t=ym3#HXP-{zBIh1E=UJfzTf3AFGJRN~mhYe6iZn9$^y|)R z*7SkpK)$>bO%Fce85=fa=ijplstSwr+=O*VWF%4wjiKxdYQOvFQ6URm%xz@qezNIG%IC{21SxI(;#UsO{63tl1iu~ zQ^tx!i6lbNpi)Fsq(Mn4Ly5lM+50{JyxX?kXD#=AU)Oma!+z}he(3jJX5*`3_3kn4 z_*=|1uL~)nP!neZ>Mi40Y}7XKl9?VpybCo_+*0-R4i4T>I0w_IQ7Wy}o*IMM^~oap zKY}q2}!#PN7dsJ)^M{sXSec@JU31G=s)h^3@cIn0|STect93dz(82JsTG*{M=iTkP2OyDVjHFNA{lcQs+v+d zbar|L(v?YdH}EO5`qE;yH7HQ#yt~Ici2)gzFChw|8lcIG)VW!XR+?)VFjqCXY>RR3 z!|hKA*{g{}-w5IPVJ13Z0QWJ}_$m6z@Aw_PYhMd-bV-lIM@ZC{C%0&$PVl#)j#J6l zNVJcPidqz2{Eo4|N&R)1Wss$+;Zz3&1}3y+sC;EFNM4uD4Y~FRJ6RVF`pY-(XEV!R3kAt*kzIiaiRk~04qmu)AbizNfo00y#BnBrJ z=te~n$7#=lmdXZ4rEQ6KXPl-eDR7jM>{?Uvz=1kX$nBJr{-YHZi(rLaxTBTZfM*bh z%e}uK(Ug5|rs`I;$aNS?1P&Y9Pk1&$Q&T8Sqn|W8JUssF#fw~UQGp(U$*p1`ZSvY) zr*ZeFq)HVYaBbLCFze|H-g<^84atRAj{)t<|)LQF8T%%0alq-4t~O zKzEtR5!eE8cxn1>8Gu}eBe@=(d#T=g{=Ktaqs2m@=eXmjp!pp3t0;Mddlw`d(c1$s zr(`R?lRlDt5UygTqgR-yQ9BNaozBU$sGTz5gbfwqZZPP(*ZZ8Bzo8;M_1^gOmld9% z>oVYJcOZ9j(0fp8GI&Vpw_q;6T?uPn?vx(=VDB-@y`lUGD=T|@dxs-43s;U(v(yC& zA4ZI zgYwP+(P?y}-5?`~`8DLuJ3L7t5>CmsIrV@3E?JHWl81y97E+&-?3KcXk%rBkBVQG; zV*ecqrk6cDkLzcdx(aL3pcyrN_osE&T-NJM*l+<9;q4|{4(QyC?V3(yXj*#mVSc}9 zul?Mt3r?jl zC|CcyA5HVWrcL+{ewogJH($6=J{4TGdkr_i|9Vth`?2;#(fwM#P95ay#dJDl^J`PF z{?eYQK7CV%V^Q((b9~@1L%TEc$-Oj>$&g-)6*Snyfy!n_De#_ToEK-WR=V@)Z@Z`3 zbHh)aInz%pCb{ME_k8VftA$IK&cT-1v*Gt(z`>(faxDaTZd`Coo?bH&eW3nER(vuY zbU31a&yULMSz9^KhIg&>=Lq$zcez+Le)fqV8^R1r#fM|RwQA)` z31&Hh7b6UH$yl!!4`Vt&eay0=E+Q5RBKWjT&f~YgpR=R3)PHjszX$Tk=tb*HJxUD= zpVoGXIB(TzF@L@U(+>exf#w;`m@(s4$dkCRFjsg++umP0#%&nL0baDBCdc?Jfd^V) zAr+i>_NT57R_}aP&B(qyzvJy78wmB@K7W~np(<18*RLNEZyy8ab5r-6sB1tjqKocE zXdL}){dRGQ7-1V28%qiP3aFheya_=Jz{|Yz=zw1q?m!%3(nN2G3a1j#0#4ptF-FF> zYOmIao0nxaKGDc#@|TDxNi>6k;K}2*FIoYMg*0%OgJz57MAV`XG{$_luAyfYl@I2O z;d8yjs5GJt1(7@PRxkpkdiLz|%aK?>q0|LC0tuRIzfJlU97i#W+`&<*YaPT$i7z8u zT}K+sEOMla8~@VEEjm~T$%&ujmiBa*7o2;>Y)0W%PwoW&i?MW?H0-Y6FAA@sO|JrL z=Ue5x-6A}-MD(V_5KJI;6_>4&8L5ST&&FXU8ZX}j$N@#!I{e{0pr`AEF`I1gNTwaR z@5<_`|HlO&>NKa&+_jewMle_}WZeGjOW&R|C}b`bfY`SU&`pK;pVK_iZB}viz#{c9 z*8w*pVJxy?gUmL)(I;)>A(CzZk94R= z?@QtU=-uG+o3EKob0azc2%bydX|)HzLLGF6lGyHcAv}a4RoIANz1#{jub|M?#fEe5seXyq_y9Tf}+FYzivjiPP&|s0B8i7pCye6Vpd(oL}ed zz*B@nA~S>ez%Hmu4mJhzKZ=nRF^+ZU(G?*NVtn6C!Rpc_3=m0CWF+u)X~@V`I?!d* z8#kiV#R686p7~&&)GRMyMFM;QMzSKgeB`GssY}JA1);S{U)43l3o)R6{h=M8B#&CU zPoE=#*(N>EY)R)4FJ`Uvdft;~PScivH$2GJ%N{eowz7lv#;&?Sf~?9q;goD8`wP4R z9#D8@`FYQ}q)7+rhW0nUn%*Ycn7X{gWP+aNAJvJg*Q^PJ28(=GbKpK0LHxA{o>eZ59Dk=IgiNqRUoI_5&$3v~R zd*!p$gIe_;(}a4~(uT>sQmuJj$VnR}q*jSG9RRu#w|_8Al(nHLB&(xRe?3puMw1bj z@Ji`=?&djMklCf^8aCX0$PoJ^N~10gRohZ`R{8`<9}61}n;;8XL+yO~?uZH~r8;r1 z<@ads(c4eY@CFJULw}{t8Zv%vR;{XEl4x)>7F&*P|3`L)oZ)?^&eik<2@L4+Lx!4o zRio<5Vf zDNs#lC3O4$b)hV!LrS1WOFZptZ)Ycs(b>SF2dxb0Ow&82(RkFkDXnm&1f_Q zlDlsX?gsi#iWFUSj(}B|U#$B5>(@fl_`(-2j0mzhU#n=QoQAD~c_!Sj7hc}*I^&k{ zQ0;r(2EQ6OCBhV(VZ1A@I%RK*_9UyTS>iq;N6Tz=d;hJ%iE``9u>Z+SVd4Zq#|BtK zbg%$~%9gLGIt1T>-(*-bXY6WZV)!}qDbc|uCQ)%_Sexu=xgk<|)Y7GI0evPpeo+Y8 zIBXp=wl|oE&=Pa>R&jP!4w^{If)@Q_R%@uJsXDu!ut9qr@G=lq7bdP-svmxi&yh8CspaJ__%ccAWf{*?7*mUG3Qt?6^$tfYK0Ggd&^4eShry!Y zPJz&!Pu$9+uY+J!>VlWi`Sh{g@pmR20KmgtatPxlC7-dKWf-3e*stB|bEqCPMDeou z`;4NVI|_!GJTY~Z9!Q-E-UV3=w#4xI7Y*2VM$$^V0WLvlEeMS%U;r^g1OZz|@jyhJ5LmAT`w$UD&jOVh^O} zEih2vl%49JYk7`V4SJX6dM&ggPeW?Le$mx>a@s7Icwmn-1w$0aF0Op{&W+>K)xia4 zb^&y}Z>MY@Ns7%#E43JGqTwp7$U}h$^zGgKUhuPf;5sZDylU9pXAHRouq*V&CMI{N zF2Pu4eBI}ITzQpAB5}`MIcB=#^T^ZB=Oj|FW9ku?zw?Aj4uis+U0H?{y&X2SD@MA` zX?xOm7P*4sNH>RcAT%+0ga0id7#4z(T445EF?r0VrHE;xyo%4k?t-{V8BIeGf(7as$>sVI!$DnKG5F!;DtiQKt!pP+{au zx7}^^;IH#^$)^=0?fJA8Wf>{-=5X9_9eS%gjnrLGy6XZ204 zvsbzAg=NEL`t%!`a5|hXNB}S@+iY%rq9jrJX&-s{4*T}CgB8*?pb9!(_Y=JSJSIm#NgxC!XH*#7owF4v)zx4wQYKMOD z-UNiN;N>>BW&-II!0at^08|nxNqP1&c)JucIG##^Y9A)chEY^9RgrFRO$f5tF?5O4 zeW@w`wbhnQM7}X=XFOwBNl8hIR8yF9h-;+dLI4P=;PW_@NsbDR7e0KYjl#s#Mk%6> z`0$Yf=3o7k3Cu98SggEb+hP{{{~0jmwe9GsQkNZ@gmwZ8@-EI=DhG&m;Y3FgsoyUv zOdUhZ>wnO*i1F0!splWqD%mTpQe19g?makSHZ2vgN}n%J0rCx1!yN$`CA<^+G7vN1 zwByPm8`?oPmEN6QJFLE_@I&$;?98$CDlvRc70+Se<)o`uWm{T4VeZzv{59j5(z;O+ zh4?lS>!lp#nV87L^_ZZvjGvK522&7;gl2GfEADmxrm z09&pTdo0xw3Wu0I=i2{w&v8*8FSN@)rBI(kEWhL;n7&VxPWDacd1ZB%ak}@8!JrcB zaNu`|*(Y!0FfB;gs{8UJK_P?(b;FqHU|ie?k|Xn!sksjrEf0IIN0s`VN&9*piJs=> zyj$M#%c;R0XUL#prvT3pR)Qz!$_1z&4o7nOfuLeHCC%hoc-aA<3T~sLm89;Q7V{}n zL{J92YBz8HY%R4$n8=6@7N#wKwbzC;UzMh>awPi>(*(mBTkr=g31mt}*Y@4!wuMqR z8`L3o8P11WgaY`mhsTs)x%L_JPV!`O$b*ZQ^?URAv*4VhEeQR>KRfuLupFIuTw zy?xSXu}jrzQB6b7%;OmMJ?eSfg+royFMxY#11D9K5A@I8)Z& z0DEo&Uj-i@;_=(m%G|q%R{^|=`Pz?-*g^YSC${pCek?mZ{OcSI-PyzT{rsr(z4Nh- z-TS?P$}CsXMv6?dQMOlQ#sPD*qoLWt%f(*hTy%V7lwzl9?xLz6h-0_2;9k)8a$>HO(_Sl{wUx(5c+? z1BgZpYB!&ao;_>?5LMUsjkDK!X?);F&??5Bmgdo4SPyfrtC`GE($6c@DdUZ22xTn2p zU7ydDeYUtpZ)E4y8@Pg84AH+Z&k`#nE}2>{a+tJ*11g|Wf}C=4mQ+}8>CtcFA`x3EvzuTCoSA_ibr?x_R8%xc3M0Y9#a^ceRHl~ zQ^2TxQS}Ezf4RAq=H^*|K8FX1Wf)hzPe^xux%ANBcY=sUd=m3a3KcQP18|>z$z(WK z>MH6WaZeC>2&yL#sFYr}ES)~bhd#clWOq?P0x$Bo+*WEunqeZ2Ua!#Uve$NO1LwOo zK1;Ix`*+vS&{fHwS+N2$QQgtS?W2HmfZU+|Mp&OEG_u2PZd=7-%eoUbnN7*hftZA& z&>F|yx%dmwrTd$_zFJ7CMMl(R!;AP;+Hw+^Vi?$~*MjPs3^T{}(FeEcR-iw3Xtha30Km_kj zQGYbW>XzrH!!?K;LUI|pWGySpyMp|20E;Y{)=DSqa~8(3QFSU?onY}F!Qvg+OQxfh z!~wXNRp{i{`?2xX%W($=*Qi4dC^KDq>|V!ZeS?|R_S>k_yKi4lILjFucig`bG7yJ{ z%}*`Jx=j9#Ps-r6hy^`yj?>gq#`Gr?DX_&@xCC6k#T>X9$epSAo2Uwx+1rUxJXyzdSi){Z1&SxIcY!%2}IFrXXCaZ8)QQhGPthUVOxqw!KO z!8Y!jyY?u?6jBbo8j{b34Hsg>&1G<&a;#nNKjy(-xO8b*^-Z`XMbYzIZ$s)YG(E@I zD&XwNO$`Hoo$h#nvQ#iu;EXM*+E4T`tJ2b@&Ghc=e?^=&iPyVwkLP|JG5$_n_fKT! zkp}IA-{rN2P7;Gb!ztrd4_6MGpiGe&eTxClo7!3*3Js2yKHB@l#AQpD3S4(ikmlh; zjn1yer(JbAF%ZShHBZ52Fn&eh*ES+yZc#P(fnU1~PLQNb!*1*+mP-otQgQihmlYq~ z&D?f#^MwD*!CKOwFzr_G_PC&*5!vbj>db`YslC{vYxT{8?)-;h2MAcH$;JuVy3*edjSN}c2`b4ly*=>JLVdeWC(6TM=w0&Y z%DrjRdE`u4A)yN{Tw+z_ItEHxcI=4%^NXh_mPpNdeYfepET>quQJ6mpOp3%5^GbuAJ`hYfBU>_z!br{i{W4mp=%6z78Bc{LU)Tz^u zDO1iosULy+_Sw3x15TvtnxF?yD^&KN4Hrn6J{@TH#mi?)@BH6V6DA&E0H)@3D;6y1 zMX@a+!LPzO(mRV7zS@=D=b*6)`Br8M1TD{u_RClklCtd7Tr(i3tvHipY6BT)aO_y|6X-DPg|Hm!hp;9niS zwPx6ESd;^+`p2#1jLF^_xAmmop#?oWXcz9LOH;QUKIq|5>aLO_74~&Ko~_mEx~<<> z3U$(h!coDJ8GRP^c~3GBP+0P3#2#P< zLmcPqfC`^LG{_GzF`qL9q~4QJN*9NTK@l(+;xI}YJ8qmC5=eyCBe(2gU!W#Ew=z97d>|G$>eA|iLE(4oB`D`ADpJnbe(oS6 z%9@&2uc`)&6N5uSW+3iKU{k}B_aDYNEsp}{KM9J~fZugP=fDNa)Ka;+^rEsCSwYBN zt{ILySU3J2_DGB8R?0l+b=u-XlhTpF9;LnnXF-p_%AdtGo(yY@Uj+c$0rae*Z_1J4 z{?5$G2$w2rJ$L+E!gEDHpxzr6WwN%n)U}4CkOz1}h5P9^zC4TaE&QE(Lad_^JJ@mc zrzUodj*hl(DBYjJs#JL1nq((+%lFkVHN_N5lzYr{-hB9Qgfg|T!ie(-|B%NXxAf5s z(P`M&*hHkbT~$^Se<0pEc@{K@tcr?^{Kbdx-_tlFM_~R7;`#9U+(9}6L|^;2tz`_1 z>JEc`u&7d4QZlWw8LO#Ralh(wZ(Zr9g7_t%Bp}`6cBk)9Hh2LGjNPm3Jy@zys%(ar z)7H}inZc0*zdT(xMs~|sOC<^kC%>>@?j7>h8$DzElE&?V*Ud}u`UER=e zGs19?i`i@X+@!+RE6_%~;Qm{Gh-2fR{& z_qD0KAEXhKi9$zP{r>)f#E~{&eQ&n?t=q&rIFHA46eP2qgy2*Ax1+wjzAdGMq5_n* zs$Y{@>_5Ms$%xW0-_cZ@$l;=L>s7J|UF8o2gD1S{KajKaeBq{ysjbs58bzSHRx4Ob z!)09uzY~tw7fpj_?VAG)R;U7h8g$i_-Z>AR&PHvJq$ODsOlTIeI8=r;M=zgOkO)mW z!0w-Vd=@e`y8Nj>VysbwDl_f)xpNQhS?x5TP(ju^C(VH+#<>Em-L5NRb22T$ssr>V zvE_l@ayRRHnKB5kh1~;4;1m&g%wr%S<~1qsZpAic(J+0x^;P_7`>Bp3rT?5lbjM?q z8}ndBufsk*bNxdK^7AE4Kl!V!vK3^U_ZlAT`dqA6{K}>Ew>;q7I@Q6hJF^~DVmhHI z*O{n%Ap5PLo8e|uSyq9Cz8IK7itG08UD8tw(aKp}0M9-pIIl_xOF_HoNGoKmYcA>@ z>Ugb|=To)s7W{I?j7sA=uD5%2Ljx+4o6Z=|SV}DAYej_=0HNXG+4BwDRNhTcYaGJR zO*jN1OUvcwC7`B5*9z}mSG7tFM$XHZeHj=E?IbPvh#gB-gG&M@EE>I&!dR6NgGKlx zby2~$`-v7Z*32c$o<2YMGkgU}`07a-RB`L0)SlHEu%bGWJ>s{Q42!ExJ=c;`aEpDovd_ zfPs&2_ad{Y?0t(O&ydTivF_0T*gs0p!?2#cLm~u)R<>Nh;6g|OpzmrRpQrtq{bjfM zpQ&0Kaai_Nv1%TgBbpE-4naPtjMqx%$}tZbl9V`fJpX#=k}14!|HLy6l4+E(u7jI} zPzzTa_30rDM#Qj^+0^REnl!)$YrQ;8E#ua>jSBTXcFc%GRI3rDlJjw`S1qoXb#+mw zH3ayHp8#eXeHD{Z1Q!}idd|KF#Ld`K%az~ta$aLMSr~a!pobBh{fnJBZ{UY$l5!YN z`r(pIC;`P08XWo|O2b3sW%P4`VubN{jLu@puWroW(5rW%yAk>tc*sK?r-N|fSAA)z z?RowR7^LSo|Ldy6E+Tf7{ff$-c&-Z)jB`5aJ+xr1%enEx#!MQ}1IR^C7e@I{Iv>Rn;CA^I}cHDEJ0v$FIc9WHvY{g>F@ z9+dK6+!6b$PT#Q%Xt!(Fu-RYV^)WOH#Cxi?n^BJ=L(F3a>ggO$b33@vbEWIpQKT;J&a z1}0ulM#-p3g>8y!HaDp8kdf3~>*=`}XADFEX%@GSVitQRKi{h{kUIGvB180jjSq;* zjsdz_IW+eVoA%;3<2UaDK4qRe1}IH~SC$3q-_92+M1B52phIeuNgDVXNOtTP277}= z1~$djmDj7PsT{|$E;#6`(Mv3?GKxcRS&6N}bWV}hKVlkWgbDFqrmCw$^AP*68Ur~Y z2ek{NcOJTVb4sn|7lr{-_x|)CKf4|uMnq~a5!x_Q$~C&h*nEx?(-7+5oInj*Y5cE} z_d`G%^D~g5;`=Qfj2D0k^)^4=oSg!HOxWDj)r;kwF_#%0#&yUwx@QD8i0o~&&s#(W zR<96D#~qJQXpc>iEA^Cw1p6-Mq&c>(gC_7+yXz-3Y4W=rb^A#U&7ro8oKhBPbKP`G zSo-3`jerAsQK@w$iTV*Vf{qx~9$@=otp-)D=G?WtB$aLhv&H?HEp5;`bflWm^(CvE z*RuDA?v9lfbM1%K82&m>66;Q}Y?0Q+C|}v<&XwKM%A0PGk(nOhgCZOZxCn+`2ag}) zH!5Ss&=?45Nh1Y@yU?~EFXB`%*4&-3kRC1JZJAi zMzKrVpHw4l`?ilf2<1^~JGXB2%in#qh%^yKU4J7*`=d>#$0Sh%d_FjcA zWR++{VxmVFPbeQO^(!DGWJT)YMPvYog1|$9_BtzafO|qjV8V$D8aH12j|-s(cF>;@`bt08B&3iS2)<$!T&g199*!@5Mwj+_cg{tO468#HK;gj?MO z6XSWSR)rS!T`5qX45!I-gip;sbg3;!B3_FmKx`AD1Dw#rM%>UPui4=CZB(Tdi^mDj zl^`?oElz#aj{#U%^PxbIAw|)RdqWM)lpo4c{9GBIwgW!j07xs5a~%bX09xhhvFffS zSvnT~WdcfVt*!E5r^9Mf&L_J z7TppMA$z_9o^Cdlc8gFqwZWR+tJ+v9Y(JzXLqtyq@4f+43ltchjgSt~qK@dbQvUDh zQ>Q$Nu5b?khz_8Z$i^E8)PTiYV1=X5-srbfjNmXWIu@D%Pd<|*L0Qv@Ye+)51HM(+ zR#a3Z2EdhIpx5#i%DpOn@+2nR^l1bzYhJAL`?$gRBNJ&}!r+60{_&UP)jEc~7gZ?i zi&9&mZzx{lBGm@?yNVHg4o&adFJHWYRfnJ=2J4FqUZshR&OxmLz}dH~_Jk*4Jk646 z=RuRiGfV=Epu-kcG3t*v|Lvr|kP4KQopp@z!_trr=E$6#mk&YWbj3z3ar2Nz-mE~I zAqa6mQ6FoWSm7z%gVIAZC!7f=GKUx+TGh)+D)k-Ydjz;aLb_$A>tLxTXo}v_vU~XY z=0SoM6frT*V=jvzBH;dRLM5L5l`TOj^u9vBMwP=8Y)_MP&Ecgi0~HlDOMS9|&}fME zU2KAb58)aW6?M0@q3-nxJM%B(2(jt${o{}odxE1)5*0rm0`p+<@qL;pi3rR%tQOnD z9$+Yv5)j$(yfNF(CkwE>Rs*;g4B2+LvR>?PS9#&t4W6bZVZpnMrX%e#^>kj988umy z#J5|JujbQ3`S0~cDdCidW)n-a(|22;K%1d*Zs!uBI0jhJ{EyG-G1I4q0D*?L2J?_h zdYn9d74gD+5$Qfn9yKpO(7g!9MVx>bP_OxX9f#Ncl4ipqFJ824D`%#gIIxFq(OyA# z^Yx7qgtzXBI_FSOMIgHSTo;!$nxkz&Q5*x#cewEejYfiVpMqPQ|5%`K|E8DpX)0eo zb48S=?HAvl&Q;CpciR9CpWG)$^k@FI%Tl))zS)Y$+qJFVcTbHKKP^vj@-~CY*Fn;c ziNS6fP&InuauUR4zg?;I|`iv(dKD8(SKo5OlUc8HSf|f8fsB**82+zzE-h@ffI>HHrT9v1Xd~5ao!N^z;GTATdo5qh|ui!rq0KYvB$EUp9mYp(R=>a^f zFnOCw#{O1y#}&+kTGW!o0#Z!8GRMisSOP0Cs-4jY`^7>Q@XNL~e(jRI!T|2eW?@S^@t2{rT;j)h%tHd$PsG z^wa0h3nYe*4w%i(_9MT+v)8nch}}%f-ffvw1Vb-EcAlMGbmmszzG!4UiNCw{=+S%6 znR<}G$nV+-kvDEweLQiKH^f^I!ZTRpDXg_8pJG?y6&@^e!O^Oq9afo>`d5!ui#ay;%noNt- zI?kg}PHwK(t4@3n5R~EGrNS?gRo}DTFZ}WEwabbR(p=CFRf4wV2A}Dy!h{o zk&pXOn%g6DDIz1j+<#9>ZxfyYvGni@+Z>@Gzht)lE(?G+1|DzMIlYe;{AIAPG@3#+ zsrC5%OH3SML5&xF)xu9nebOYWcRD1OG2g4uzl{8z2n+uLi?LJ^oN>2mXDi|}VJmLH zYiNgFc~vG=n}*nt>cu)jJ;m2FNJ#_Ll?KG9S)Nts+gDJTnA1o|h{2l(t`Nr!mtVD_ zGG*W>(4p~OhEU;Bri~tL`YvPbgFvNi-Q{tM`v~dj)%ElaxDT!g;~zx(im8g& zC#$BWctl%f#!a_c>=C3Qz6%1lK7aJV@nv>wkN zNF$cf6a>AyfrVJ1*X!6tU!-`2@7mdcF=*e{{zIs9iZ19LYOXLkw-TtMwhJLic zCDrlN<$<6Js?aW~ge7P}!Aa{7qDVH?*ZQs%&pLPQdWhd}x2o$|HP!r}ugu+vEX&?$ zP(=tvfQM6hdansfzVOnsIhSDi%`Y)C5cT(*GviD?KGJmC0)`NXDhDnzKwPflB*{Bd z_G?Wcu6%xQIayN43(z1F%Mc_if+tqeBkBd3BkDavPJaF0lC-+$=*wR>zj>`D_bI1~ zx_YClSI0vg9LzLS?;3>bgxMt+gg=Q+ygRi~!TXGz>=NbECE8XAyAoC!I*(kb6n5c& z*U$YG)18heN@>kW-Tt`Sm&exYMpai#-nV^S;MEVOXoMKbAKW#oqafpo-Hi1=U?>X! zlc&9KPs^2z-?P4*v+$8^Ve?y;T~Q8v%Ce)Ra+ z8<-#*FQ`^t9g)V}5+{}$5cP6aUDdLyBj&;Qu>uZw36|ZVwyAYz?zoLGOssR}nwXfq z-0ZKu(4s7EBE3;y)!Hx^oMBa*jVl%F1urG->k<+;4Z_4#_VT6##$h$rxt@GQ9 zqB?Hdh_LziE_=6+<+KSeC%$&dez$FMQ*rqBSAY^FJX+HY&)Zzk+s&G}*d)??0;KrJ zV)e1>DKC;jF)Z4vLo0=e;f) zD6is7KAE%@6OAxh}KL*}f7 z`WilcEn~Q9=cZoDvD91dKYp~Tc`b;Hd!lA~EC*E!LM89hvSGfVJ$$gy*XyFj`v(Qx zCt+RuX-xr_#1%H*tc?qs+1D$?;s6h4nq1klNyE=jRzNX())c9wz<|$zVk0ci=jhP| z8#;$W<@7v%c8cIZ@nwQ;-*!oCx0xXc-JV?C&)uP6fh9zuK)xW+#DNOH_1J#v)NPTL zpS|y|)=a@H-lyu;?XvqVb6=ioWbQPg)jj-LBjEMtz3aPKFAmS4rhBBF(}l+J+<1ub zEoWbNm7-2UV^#3+tb_TgXzQ~{oW=BKLkz}1n6~0Or`~>VQN&9s0Y6i>s5c0rr}6jA z!G@k@>zMxywxKtT`#m6hV;QQV#2ehecw+yo$05jetO6bl2EILvtF!LU8^kfo&uz~f zC!Ed@kDF3NbJ3z=*;Kg4A(ZXkP5p+cd(|h{JqgR|w2DzJ#yf#4o#|H2yeuuX|8sYP zy?s8&%_XyuYhG^|AuuSe0*SMhHsQ2(CH!uG=eA=|&xkx5BXJ0p4 z#vH@|=*62OXL~8TA+J_z)rPfg!{JO{pkgqV(ay-RV-p^Xv54_}2I1r-?be>@BX0nW z+mIF(?hRIv?R=K3QSjiwNXInV@=pzI_(zUztoTdhiU%y4_4tZKj4Vp$$dUlxI3bI^ zU%kz2;wt`x`3AQ*UKiS_u~SPr+)<-YmyC+cMwGJA`A~XJ1xBQ`5RJ z%hRPJ>WJP6Ko}eTNRA9B7;pU}9tN*^%aRPIJ&8YnF|2qrMUQ5xp6qNrcHHVUKE0Pf z8>*8lhyeZ4ZO$ZKMZ zMLvUu2S7M`rW@SAO=S%YhWeF-oH#G%Rl8u`+^U#JRWri70h~c|*R6BJR^Qoq&++ie zl}O<7KCx-T&)UA+g<7@Os9|f+C6-V$`kP))#%TFNHQLFdpy zIWY5uc&GoNP3Y5ELK#rwU-O?a1hntBj!W zd9<1Sr)0dAs)B7&e=uO&FupwS%c-}TD0hfnT@wb3ZQVu17ff3wZNK+52f+NqfQ~b>kX)ccH-3gMM-PtLSwl4iI5{``2{_3^T#wLV6Zg_AxU z_|HFE`uEQ{nRb;;B$F%DPIslYSi>#;(58Co%-)9xelKB^(vGYafG~Uo=Z$D|^ zJ|C2q-zw^~5z0gf_;2{Lo66*Z;@>8FAQ-D3v51SE(S9Wbm3QK<1x2)rMaPz~Y6I{m zqM^i<%n_b1D}(EXsI#hOu}HrvVQ5**8u47;?Vk3(^xk%%56}An4VV zTk#LP`)YN$lFd)~IcQ#a-+AC0FZPYDWAl&=)ypWYi9Eg2Cr&8u-EVZj%f@{$$!h19 z60=Y3ZxHGnvfMgVLnl(8M-G_}Yx2E=aN$}h~{gl*`mk0}@U)Z+M z^`|cwYOoRM8BystapdcjK1J6yBMguGwn7iq-Lgwrm-(>b7JI;p^fB@ifp4AP?qn|g zbVEFqck;Rwt$0H1eOT>`lyTL0EK%mKMFgxX8a4_m!g~VBFtm#m_oDi>4n@* zPlGIh1I#it^5zYxXlpO2nkYv+c>oYeapYZ&gWjhpOBr1}fS~bt*m5AY6ox%nZDREM zu~(Xbvf2w5!}p&*$NiiFw0$)8Pd^~3;Y+t4u>Q4PGss)G*feDymA)i4Xx1t_Gp1Aj zW1(u(%mcf)LrvN~w@h4){A=U%Py6}ZVPf}=9&~^JREc6Rojs5u_yJcu=yHD*8DeHJgzG$f>TNBZufJsGuqyPQTD6rbHIWWR#L zEZaS8wPsMoi~O66T}qn@zzTn!I3yhL`cK{#VV!!9+5(Qpt@jI#@y+K`{VYB{D*jS@ zd>}OL&lR_{H!#R;8dm?*+OT}Qq5Q%nZKUboU3)v?ROQ!x zlQKJdam9S+)`sHr1*#Di1Si>dx75dHZ=xo+n5AHg{kQF_V(Ipo6moZ3%oW^Zh8W4E zjb$V1wT+E=Xz|%+uK8hs7SDCVR#3Vb;bSba%7W=|Ma965 zb0sWL4_@u{hrT%VC(T>!2~zEb(Gf4Uy>e!tK89+4_im${juU!n(qE%($8ipI=eA# zW#iJRB|RXM-K8deG%7<$WBmA0jemIuod*p8O%kBa=cPc~&@yIz8z8X~UvE43UDhFg zg^3M_KdkK)V^S>)2)gmaUqRMFb(gIze@l!65l&`mcRy4_k08eUMYi4=f{F9a+q=DO zH}>R?$7JH3w!Cr}ea4PYPdhM;uMF;@q&{uWcY~^)r-g|oM2MfKWR%W`ZQu-sycwAB z>-_}GELID!psvp1#C zgIhS#QiG;5q!!UHpgfHkYt*fjo;mquIe@F= zBRA>VKfga;5o|Zg=-p9z2E7sQiD8Opz0_Y##o|xsAs|;rZ-q~>6+3!ZZXW%Lk9(TQ zF?S0t{lI-)P*_6ECd@V=M1;TirK+kbBx8cOU^mFk%ckQRvYgP$=(K#%s)+gc1((eq`Pu1YD#)zM63i=s?)>TH&5R z(A-?*JK|NJBp$3F0|F;qYzXLI`uokHo=kr)@91b^ z*wFU#KAr7gRaK*eHnzBM??Ydt3bHHzlH^fmNO#EO-;nj~gU9&KO5~CDQ@S|JLQZQe zM#XsmAc&DVT3lWlv}=m>dc;zAFGRnPF7$eoo7)k8s#(5QmZqBDb?8>czdDwso)zn* z+ryUWOv+3?lGaO^LSn!<9cSyfvbF2hRdIRStuyfW4d{Lhs7c*JM$kl%WX4E`cSuS~ zN@Nii?a@lkF^o=`@ojxpI31*I4abiqEO&fIhHwLm_O@^6N6a=<)ljUGJ7( zUDH+A#Pd_5cV-wU-{Dz^We2*ICr?BVUwZ-ij|_rgK;?kw6a8;e>IzRN=IFw_UhFIo z7Oa9KcWHjEfB75mgvTjsZSSr2URAa`te`kth$^vOeNQn_>@ zfwA`So9W@>dhoo9W32l3>!-eQf?IT*SkEzK$M9Q^@N8?W&4zihJ1TUMV3AOOd~0R# z6n#QiE+#`$sO&t2yAtS9QAuuP>!*_kwis&U1{xLgmeSaHLc^U=zCei>>X^4U5+-sm zb=h%j-l>yge^0shW|~v8v>-0>8g@s8X$8%3r{Tmku{&(P8BBTJR#v)V&Xfg|1zTV2 z=AhY(c=|W)<=WM&2Xh?-k@4QW8B{H+j{5q}#L0M zT{djkaO`%fz2g4n(5HL86IgROzJ`zPZ4tqN0j9vp>MT6|>xa7!fSx4zSzBID91u3n zt@vCC> zdtk2rq^kc(+NrKouA>{ZdiLz8`=Yltj>3?Jigf-GkfImHoqkb|G(rP`i%)US;A#6D zQ^%MdU47UW|S4l%E?9iYy$4L z6`KfxmHXh&6wkMNrFgWcTiU~OlTSA3`%C94*z($nf7K6ARD9f0JXqeC<3l~%yXnJL zd-pQ+MVB9(*Ky8o2~{U7b(_G?QT3l?c*~ui6Yui3%>ia0EFqt!HDmh`hlXB`WQ!8J zhYC~cK+B_@|Bh!fUz8OyrFbX~{ibgs4ee-~W)eC*wB6j0amrL*!eI-cfCQT;ZHC0; zha**C&Ef{9G=hh45(W|!b#KG*pl^(2F6_9|imD5ykmotSRYLxYD0@$oZnszLXavg{ z%?*bCIXt+9?<+UBmtOv9r0HWan%fce(ibRo90}2Y!Prn9?VhNxli-?oyA0%A#MwP_ zgS{tQvBiPG{OB%VpIvN3M=}1YT*nDQaeBtIRWoe^RfS_33EE3v4qhpA{~}!-n(Xn zotPCV%+&bWGKz}6L>+v=5AL$I78TZ0EQ4D%AGSU)8Dm!Bq%Jr=_sY+B-n^NRLprF< zk`5b%a6P!?LPNa=imH+WT$&%IhZOvTyoO7e{;bd#>MP$3J#2J+Kml=G{HKD+B$(?Y zNJ6bHB0!4uuBl5eZ@bH4_X|cFEd5UKLM&@GR3Z=EQN!uGWgi0268pSWf#pQhh{7eb zU-=`Lc9dzZK0ZFV8lH`Ip{?8AgDOff31{iaCMv?ODbFWpXy{U-eIxq||H0_RNvd_H zB-!$z4b9@z-Y?JQO*b?)_PE!#zW(9F*DJ9LNPjli$nH(%!4ftN){QgnfG3f!Frtjo zp_=JPU35@WIU)iE)djvJzu;|f7Y$7SHzR%th+~~aKAdZfZiA*;U3zj|GpHL~-%P%y z5IEBthLiSZJw~3WOC{s~RopL63Zd{b$u`lk7BS|PI@ch!gW|8GlwKIZ-?dE3Y_ZPXj)+B+-CI^R3-V_^%sne85IdGz zU7h8x{)KLCC4+2%NQzn(;>DgX;l4UHK(0>9rGEjb-a$)57s?9XkA6mz%$`_H_cJBRBT2?07_lnyc)$+c7|8Cd5 zzx|YC#3bMLE5u=0)}R zN2C{hcB;JF`O@!w^_7Q>LbRyC;?4dFSe4fCNpsySb^oi}nGFrvGRe_P?Lxe4jj9lW`S8c=We^Kc!c(~|>70zPovEfPB;TL04Aa}&$4)BFfx1ltV zuW2t@izGJw?A?ftWKUG|BH$t5gy5F`I`+Bc4Btw)(|e|$GwEZl_ZoY@CmK|oM1#X$3tEpo20+?+^_j6dIJ z|K~E~`=_Xo(|dzCrLhZLpl`qUSfBCFE^9wS&p1}NTe>XbyFRaJQnC*ZQ&1}>^%S+~ z@7%uKmhzr6AupdJif%{=MP9_GJ4g){zrC)GJ@FO)<>8dNSAmOpfe%rOr`~=UZmDBU z{3vL6-9=+5n+%@(-r8SA=8m?r`7zGqQINVVF8eR8{j(8iTDsz+p%|x==X_;TOG`=; z09dbryLXoTfEBgn-es#b`=;${x;%L6J#FmDhBsJ)%v;B`QTIIGe{QyltXPPc{M#97 zaNOdgi!r{{v;^V8=AI(Ng_qk{$I|yD>^6!hyT<7ZGDICEcKY|G4 zO&`|SSBIOZ$U4F{tmye!kJXUU_66=2V^ zV~VZS{q57^=g@&|cXYID*}DA7@@f2(-#=qsgx|$!T>Yc%so{qF4s$Tn?OV1C|Iza~ zn@yM9+Pg3Td1C6wNs|H_SS>0*u`wwyXTOm5U2`U=axl&JZsZZ|iHhn&JyzMK>TCVW zo*QWIl0DPoICV?J6WiydrDrbwk-cRN+q&t)YWwOq_JQ=f`|&hosTQ?-Ryr)!J7U!_ zKemH!vfUHq$YOnNE2DF%K}lgeIE%dxi)j_iRtV(hqeca3PqW4Q=uum0v%;FqG>yj? z+ppiZ-eavr?;N|?-d$?Ys4ci@>*Z>po&%pnNKx)o=Ksrnm5l8@jvW(p1$MSAshpat zAlL%Dk!&nU85^iypEi0k?OOcL5~d3U<;To8o_H0i5k)79T}7n3L%aG#wB!3`Bi$*Q zvPj*(k|xwWE-vnTlCu1BQu?RV<5FJHvTdq62 zbY*&H=d>iOQ~GR211EuM(wnTYywXR2IZzta9DaSTj%UiG{$o8zLf0-`Y$#O2-sLQC zaEQ-ewtZad+Pj4hgS7)%CY~?KUw88U>M!J;bj8YucI;MJ%_%Ya)qJX;+5rkA=vnnE zisl>B`z#m{atXZu)u3WR{953hU?Gdd<-UAuJ~`J?AfYV@!V{%6k?hz1+I zP`u_uXg4XV@1NK<{V!{E9?iOP5OAdKrzA?c(C065ilSr6ND{lJKQQUwfAVCu(D}j! zm_2p$l52Kmn~FVD+^Yx~>bF;i8_g|-s;2Qov6#+h4fVh(5}@3gF6)SzVE<>HZWxQK z^wx*@euBXDDfxAx?GbBdJPx#&EBOp6V+0e~$ah=vK45w587LbC4d320#OkTSe!mr6hnH{2h_%0_-HzTPa#!nu6su ziLek>QBJRE+$!4Iy?bK^KBG1P?H*CTJy$^}r2yQMThwELZH2D~;QY0l)1H)e@ifph z{bcq?UR(Jp$c(@LXMuvlp{-c2nOjjSy^Tc`jpY8=2dVT4{{UY+Mbj7b7B0gupn{&h z-iL=o`hudxlxJ}(ZtFB{o*d)<_XRcx6+L(9Ny$7XUf-DBWFDt53a9a=>}8v|PC3jf zPqlsJXIC?_*wyxDPh|0Hq78nG0Wb5A6p!CKXT;ex->Q74+qdpg_MesN9k1f8VQm*8 z$J9(IGLO(qBfV+t>m9}_vcSWg$lW}vVMsfd)$HMW3EC)gD_n+~&EtOO-m9n>MEslO zOKR`SHBw%yk`C-V)v5WohzFCLe~%NpN~3h8l){48pHnAi+(EdSR;Yax6fZ1QW>2$> zF&`Q^jwRe<#&z%H*;I{c*S&14n& zr>z@5B=vU{NPNaiC4=Dt5}=6q_rUk|QBb(2-NF1Ih?XtWCh^TFD*a%S)o9=XHs|bw zx$nHyFVoWvpXP`cA0(GqpXxcJu+-PfU9mI!!if=Px`g%dHTrj6yy#7Gbe*I6tfo;9 zf>su&`Hh^3J1pelW_-nJzh;Lvpqud5*EcHtjG+M;b*FzdotbVM=ZaXGjoY z!Dyk*!-o%3_g@&|{fHq2s85Mjf>%T0Rjv0-ImDY z10dGMU7HkpjCx?(vh%}ni=>3sBgEaPUilG=@h+=*dGNhQG-?3`pd0M~<@4NC*<6Y~ zA%yXtw|85rFi;Of>{C;GWsHsEO_nVqXa5-bvl$%bM$f$GM$~#x{&Kau+hp?k-01te zmad~L5SqcXHW|JAOQ6eAIwzc%O45g%&0lt>v)UN#@b)txo+lE5&3&0aS5kVjD6Y@s z5&OB-hs7(w4hjeYYVJ(*M>NWQ_+NS&`~zT!NHi>lRHSdee(ixWSho^0#ZW@he9M~{ zmL7WgSe^&4Xma;PXWEWj+PQj&l>)C~H$$QNx;N$KlWOMF6$Ma4K`ri2QK1WJb9nEA zj3bUADs=!Xw@Z2`$a=bw(7u_@p3mS#?6pPFtzeYUMtfS=G!Aj)eRXx@v)6~P)!g^vz0afD*Hgs&l+@KebIOj7 znWK8P*G-#uVpqkAC5#%j3Q*veO879s9O(WGh>V^cWaa6E5>So{3vzgw9jCX>S3UWb zs9~{pDmS#cV;A?2h@xlR{-k7pd2%}xQ?5;Edw#6F)1u-gtxw)x9(?iFsf$GF_c73Ef%va#Wc$!plbdu1^?+ zv3rE7XC+Xw^_8Z0`wvFBRx!_(J$w38_&57hQnRk2p#j1OxWBrYI0(f(YNz5&TpIHD zmRs5DDlvo4PJOp(Wb|;$gL2O8FzZ~CTJFjCQ*0fWxapM;X=BZO()@7&{}pjo6lP9M z<-3gM%#oYCprQMKN%O1g;U{>W@*GR~_&~dZjU;}4!q)0e3kERH_a<8CNolNR3ehM1 z@tE6x%&U6(N7#6Idyo6weWUO`5usw54nfcV>`MQMVi_b35nK3jQ}_Q-fcE`t@5^AO zp*HllcB~ztqz4v`c~ZMjo;|0;5w-fZuT(W~y`Bw!#1L&q#iYu#x{N~s0Rg7YA8mM- z?1LP0U{{Dw%y7$&kFrZVJV<4ICDyKAe_v23wFf?7PL>E^HjzIQH=xpf$f&#)^U5f>6cUq93O|6^WfjnJA=^F%X z{b#DV>d8bVvnhCke!bGU$UA)0^7O!_5Z4!+B`ac&f7>R2ly%?+>CfI>M%l*D_?RfV zqSlOt74oB@Vd0e8dS`T_4;Y)Q3E@$Uc09{0&!cUkFik%l6cpn-`^ss0(c3xSKM{Yg zFYP8Zm-~xN>;Q=br(fHIKjrobm45eE6X>!T2cuGPBTk*#yPT*(C*Kv9Q!&x#n5-2B z=6&G5_NNB1hgbd2gv`QVe9-xDfuam1dYZ?`akYA<*YSV5R1l!Q$@S|OTkqj<6&JHm z-~eo7Q^ct*2wc}5I>z$qkCCvcGXcErdUrc%pe)dv1bhL7kcph-&W+7&9h-{`$(4g% z#hc5fXSO)Mg8Bg9#jJWVwb7Wyzf3Oe&IOF7Q~{g1@Vr4fOK7(#pZc@}>8xFLY9je) zFlXctSG}9USPAZD`m?usqXVkqu4D++l~~un{2{gI2a}Pp=)=aLZ>IH?|I__qiUo&h zOZiKKUShbsV6c%gKULxzwijJ1nkcy>>rP7R?Vhszz4sgE-_6Yu67o47r7(Iu06;6& z+l?GLv;zc?@TXNA->^zOH4=p{Z?%em-VD!fu%Gd`uR{>|^S*_Pi5R;1e>gqamM>G3S_)eQTb*h0+jCe;Z#F1+W!fZ-R21fr<=%wzpP#R?}%XUXIGh#yb zmHJ)-(@~UOQq%u@JM?t36IZ~U@pc&01Tju(I{cQ2li12mH4S50Xwd@w9Cs|fcEVz$ zS6Cn+@~O=E(wmHK(L!{n6+iUpOtW~#l3XcqS|sbvo|;DyDD;nY25)7#RmbEANe!pp!wYMk!Q&9uCo=Ez^SC3xn+yo+ zzq6;6ca_Ltx^&b7gi5$|;LU4YDK0afq!Hg6z0^*DQ_F9hf5@gZf(JLe-fIdjazw6K z4w{89kXhwu@zcp$Mm|T~gF5U@cF*bWg57m7e#jE#67TCyaq*&0d%#GJ;1DkufAyQx z^wc)=nWZ11w}++#%F#ElSx<;^Y_$JR;-+J*`tHAC@RUAJPwK6%Vw(Qmef#v4{R#sX z8`z>7dmrp-1c(@W^&4>dQdU%W1o~~I%N0~saC7gfa({n^PZg}pd~ zCrwgdjy03=)c^c0*4N%hOS64<&yQmGut(pFf+y(rKGidUdqW&nUK-*4*#wmdVzy)oYgBfdrWTiIMtKKJ4(YoXMXT>mqEM(%f0Ey0h_7ohu9cZ zA4yBo=0v$a)@hj&SEiLKY?&EMN0zL+eAUZFLd+qbc-c^3-i6U`;JL7f8zZiIfOR1R zxtT&J`1rn+nVI+TCgHZh!Tz^UczimUAOQ?@oI{B;T}M*%vo87lMx`*y_tSjxil%aR z)C_O$u;HSNvAD8)Sz(6aw7n{JE^g9UM|FP^GvK)`AKnpS6V zK8Y)39Qo64@e*!{$X2X=iEXe;!$6azn<-n|T=-H2=Pur$lmOG1@wISf+ z=ZARpcVNUla;IA$zL)u*G9y%ecO7QnNe?2Vd(E}iU$5M}c}2}++?< z{oK#oYU^gTW6T3@zqEfh5oH ziTdZ>Ugxc@DgqP?-j8zZOoKm8ht%_GXB(}``0}M1C)a!1HYI2@Qr`Zerze`1y&3Nx zztiy72{D=LKp8-rD66>O>+nV%E^gHni#0fgIH0r?g77IQf3pKMeX$3ehLcfXzAOFc zg5yfb;av}43ys$?7qBIZtnY&~trZc1dFUvLs`j9UXelRP&!-tdmiE4!3RB@RC-8nt zg^DX1?};(}>!{(*0e~+(P_XWdbqox`Bx#dD9tM($CpBDRl(duu`%fIJwJ2SwEWr{* ztMku+#L(VPXelbulg{w}Gkqs+FX|g@-&)8E;z8`Z)4bmt)}P^Zbc#WDp1w_tCiFQy zxG%7BQNawSYp54ABvi!VA<%A-(0et_m~aZP^6($Qi$fY(6AHu_kVJl9RgSspY|%d# zR=a#0hUJ)caApKcyATrwmV*L)8B-b?!>|munwGN6hcP2^zzC$jdFHp?E zK23X*E0>hugv`H-tHZpZw^XD$YEy2QmM{Ty@ZAcac;pe1JAXfRP{iO!iw>^{vEp-i z#t;AJ=Z`zj!|@hFpeF6Ujfw1`TO=rT``S`KQZgM}@BvfC>$Wb#DUi zss^H2b;)*w@Rst=0`}w0J?Si7l{gV+4THNK?P1GSlgt5(N#vhn7YL(Dw!$_gh{9}c z&Bl3?_6)rHMS1=?H$XLLv`+7My%ISc(iXB%b0H z_6Tt7FQ6W|Rat4(t}YMa0sBu9UrlyiIm4B$W9@)$fPys;wYl40nC5mR;0%o;CJ*8D zrg?`^Tf2>*nm)qIYf5lXx75dM!Fyld`jQ z80Ohr$e-k>{KJJ7*BB*YrKb`&bt4H_x_i3$hWO%@=s;7nNLYg>R^^a4>hFSrDzx89 z!mzoH-_a+DcbC!XKrrT_Qgx&Iz`Tkt4!L_lcg&i)Xz^k)G`@+CMYc$}Z-L2;#aYxa z-(58QMpj@~gJXLjkw1#&89aAH#L#Dx&d=IW?9nCtRcY*lm($`f$1oA-XFMffwNIFp zjC}6c)#kWFRm$Y#%#e-`5GJzc1 zeP@y>f@Cv-iC##LEMa+8RC-Nz=4u za-t5d68r(+g#0`aDEJBPC$Yh2hx^>7hc+Vg$hPgN>yR-p#UB+jN%@s0cQ@L(udk1+ zfO6xohfxj0E7MatFGcJCoJHLruE@rd&tWTJ)HQU}mQIy7^|$fyBpu5SETxep;6qA8 zae_o`kifPGaD%w7DC<4%TZc@?C;$t41kl(TNh14@OEjw*eNf;$y}DH$Gg4d#Wn`^3 zwzi{#uWjPx+Na2q9n15AO-h`Il4x52E$$>~3E&Z>O`fh^kH<$sS~m)s3nPirmYk=+ z+3tBL_=1bGZ@w{c2<>?#3WJsT$7-Kra!dvir?hVfyaRarY+PvRf@997G4O&hKeKK6 zdMrbcvRDDPMB}$Tzhb1i2Z^tusw$`c6a9~8n=@&{ooUd&C#nMoRr`T|?E#K#dD^#A zv{^vhfQL}gV9AoHnR^DnaWKBG&Dw>eCYHLgrQt!@BN2Vre1xqhf`{g~`cDmPM#mMO zhI8?d)A`?L(@G{VjAH3Z8^aVara=KTAAdpqPs?*7K$Htn+3)W^4rdWyR$|0g-%Ht;xr-ax?lq0YU`#Q$v$k{{ z>`#pomu9j2u5RENPFGM+pf#MBT)u^=Cuj`TbcUd8L7uh23!wf0pswwQT{%!JVqw8$ z=R~xu>+xG@rUBxH@&hcNoV4vw>ALg9YI4fJsT z1u_i^AT_uZ=Y-Zo3jG=ld|vg_L4t-p{^CPVFG!CqKtk(>9XN90#D&1?570>34~2m6 zLo4szDFL6It9a$@>(?>Eqs|?|5;|1RMv~9KiWPB)4TDNBq&A8KFmM&5v3cY-tgluD zoG2=FdJCI2WE6A=rv9(O?-qgYvHA1QLhthE1t|aT>gL1js90YFjl38_r@36Npg6Kf z16NuZyTb7#$cIhLmWmFtAKUiKfUE_@u3E`jU?abyrv|gO(WrM;G6biU_&6~3@mTQ2 zz!CBi+M(XGrDIvo_0<3rD@+zkL8$-wui}#t@Fj+c$J5fLLq~&2cohR3=c8z$X|3LW zHJC_gbK&R=sU&b%G;;pn;K}I-xX#0(M35XUadXNZE>d?s(ZI*mudm?bK>-*!tsae_ zqWXk61KpUhR+(0tqPjj=RR}O0hQg~`-{yY4_z^)1El6@Gz`ki9*Y57&=k~BgUI+Tm z8uH{U@LYWSjAMH4+`fGi$pdrJ^`GH6M5A`$7vd8Uc%ii4!jjJLga=~<0A}DT6%dC6 zpE-b}=8X#LD?%wLwsIdxc*TItTFSRrD{~?`GF=T-cXhr7O~6T7N_d(~coZ%SMi4+K zbQ=er4u1LCoNq+rz}ErtJfuId0zgJV94wY>u-5nozlt=Nc9g^9?dNHK;811APn+ zy7AcX=r@CbZt>f+^z;gd?#`4bWlRZPbpl&JZ6P565dvfX`xO%w@oi_F)hwm3Zex^c zrFSF?1*k{aR8@OO?~=S)^X}~pSA<=IhN2xe zZ@kr9sW@`}#<*BKd^*soU+agCi7%hgA7wv71%6i?1A{wbEL0gGj4N{N$r&>gn!^11 zzI=%SK8o0P9vYPhAWyB}B7)&Kk63XCEhNM#__TB_aDPxh8V+`*h&z6&ca{R8hd{<=Okkue#hb$z?6>CXN8eDHzjG|6wYR9(s-*Y0UyeaaW#@tGWL*3KL^ zWNCU?gkb@mR@2?I8fjtl%$es^vMu8UkoCw}bZ1{5%V}Q=Y+llyo;>?ym6c4k4e1bf zZy$y=;N*n!`b_zzHsg}qj8RL!j(p&RS2>?qflc$Y9368bt>4E?$xGjH_I=$-lXk%~ z{lzO?j90c#{~~#mY*Jb8wJx>u&57_0Ud;Yt+_&3j+cYjoH8!V4x+B4LrL4JutyM zJS8QiX`vJaLjBf}2o`TR5i$bE75K6Lz(bI66Yv&4_RK-(8#Joaop?@c>9`N2XAm+G zdF+>U?H;i*b_z`*D;E+a{dKZ&?&}xEx&J0#d-U(MM|n5e3ZA|-82r9AuumWunB}e9 zv+}7}gS&^H8IyNt%Tx5gA^>19TB~|4`k<%aC(bsO>fgb1hK9A^Jpv`9Ytgy6x)^{+ zL}q_ya+0GaWyhtT8&Gq|Dp=UZe_|1wneUh9Fj$zct-oTLb(L#FHLpE^Wwv#u``zq< zsTC;+C36;Qd-X&f0ua(8(sdrSCM)&#|G``5iHs5_TGw7CGa>}b1eiu*|g~gO2KzienU7wyWLQnc?IBw z8pHb8Wxgk*!f-URX{mcqeW9b?E^N+DCrxxrz>8zGQdvA>OZ1pAP3UhYLe3f)IaQ)B z5~7iZY{jHABbBZ56KE~zq9Y?a(VjCbK=dN~+b^4>vL-mVs4gt+_;FH%J!q#>MA#@q|yG{C7dRP2h~(!2!hHWPbrlxLsvUPQ2jF+TGa za;&=nx~Bs(2=(A|?x0{b@vUbqDLcgeiI0}Q?%4NC6cwX_uN5BEXqHzLum^z~oShDs zpE#o zjrzYDzVEw41|lIeLu39v6bx61mX5j5O_bJv*O~{aprC9BN`tu=_LZBj9l@$}Dc-RW zi_ULUz0pc%o!39@wzadRGFn{a5{>WI&6an;s35}vdd<|YC>vT;{_i2{SLvsh%s~S5 zclZW)Mx?cNsYu|*Z~|y`5Z?|Z5%giB!}YT-kpxT2k$7wrmmaU;nVc#hgY{!USHcWD z^7m_rv9ZbvvYaVi9o(}ZN9=}M&M2AOzWqJL>hQIKTFgv3c<`7qc{*!ir&*_G7Q(%V zF?S%+D=xog)6%%gnIc!AW8!^`yL9Y^792FPS~(mSwv8-jo^?Vqnn3%teCwCCJueIH z;Z2yN`u6?%abQ5fePiwEyCo;Dv(1!J6-0C7rTRO{Rv4wyPFZmcJz?1|^1K**Dm)*% zj^CD&Dt3b%PfNMdS^d*RWxqDWyYLNDgz!QnU;rZB;4@ZCjtAi*q|b*sMKS1a*JzE& zkM(VqOn#)#Wx#m2J!8K&n=gS0MV!|2%F1jHRSl#d^&j63m2LZ#LhBF%5w?P`ho3__ zwq8%Vgh&|L_z6VIzwXV;QFq9WfanPH2o-0MB%B2Rqfoj}BB?tqp8$5r(>v0${GNB5 zP{cdP@zC)^BCmL2p~aUV565T$qA#%g>$M8c`f;}H1dp=atLxJMc#QV`&X0zB zvo^<+FTjMFJn?A&-1%`Iu&M+Mc@};w753=s&oXV89DBUIb+F+eMZC(yiL@6D0dPFJ z2W;6+>iC9xNQNp)3Jpa`LZ~cH%$FtLgrS5$J1cG1bipwefR&DNq^X z0)SX2g8EU{Rp1NWqY}c|P8IG8({eCp+DR^0ZrG61(LLGsNqz#pRjejG6Mm*1P8qgB zXLNKqY}mA^71c0!Rc#_k@Jem@%G z8cxI^kuNAyag~6bF>Y5*nq)<0U{H_(1KuN+^+gz=fCZ`~k2{uQt&WaABIX?0le{%o z&*gDpD}4|>5sv(t`XZj;8ny!n&NRD#crJwi1`4P?EkB6-LK7k6QN^gSI2$W| zs%4pA3}~ybK&Z@H%12-}F0FCGr~hE$$PIf0iT+N##;pL*P<4YL=J%p8 z;~N-FpMJ_e=oblA)l}TNB}b>J`X2?WK?XryjP3;{56?ioG5PbtenjyDj>gTdNmoo=_dYI4((G&EixpN3Xyz0}C&1@j?wN5Z#OP5MO6z!-3 zsHPADmZ&3lFIa6^TppcY6!`0~E;Y1k1Sn0Ui>i_xTq0V^6D0Z#*eiktVjU<7u{7B} z7X#i$fBdMWKYr9F0E=6*$}WxZt5@~=~;K{j-tjC zed_Oj^1SXotTFgDHc?&!4Uv*Wb{7__(f)CuX)@t>O=BkQJo@nrW9b=g$M>~<8QY7p z2gbSD=q#+`etaS907>1cr;Dz<6(SNSAJj_#Z2m<^_5OYb0EmB}`38}q{(498tFRHw z{f$}VT>CUm2sls*0cg_Eqk8tgA~6xRC$2)~4TIGHC*nMC$jZjXH3h-o7v|m7j%!W$ zhM;b`+1VMj8Nk7xy04+VgP}$Ct08a>g0%>^T#(MU1`}dqxwmnj-yNwgY?B~9qA#VD zmog!36@EX6g-qH9zE73UGJC$mDD(K^xfX* zTk|}MubiSEC4&LlCrl?8_+(-{0Cu;k1uHn%nNi^1vv>Vug9doncwn&#k{nyt3$LCM zJYB??GAcag~Y)jU4r!AoZKhCZGChXu(NCxxclTAuT6*^$)LnuW?r{~+ojSwHz}3X;nE7sV6n>gqxh(PIu^IFuJ- ziJ8z!q+9D}8$SZj_Kqj@RA{0u8&x!|5D3x?y#{KIR#^mT;%wSe$o+5Bu{<=4As+d6 zwmu@U8}^enTN}gMHy)`r`_w6mO97}NX_qfdp-QVc5iAKkTj5-&=@ZBy$yvi4DsY+? zD6LTzB_t>rtu3oHQz}J1A*m%Cr7-P_Cu<1tDNQM%?2;8<0)+i?;-k?>>@3^OGhME2 z_Y^ER^9{@;m$bUb1poUnPGEyYIE{oxTnd0HXI5jNBPaQ-I1O9NXpbzsHI4=u;tSOi z8Ay|pAne;RL|z(gOC_|mZ_}nulWLSeTnf$AsP-jv9EJWg8i~L7=j+?=3~H=5h1>;1 z#%DFFrI@D_f6&uaLuE#67Es>~C{elt0a^Im1Rw<*u>xn<3A|m?J0_K?jQI2VdVK>Y z`IyDJ;3nB`vlA0yy3|jNa|TY0A&z}W-(9n9uu>Lsic8mqBl`f3|Co=onhThy3RjSh z(XEEl_~zPWP*c)oZH&m6P_tk*)zaG5R%ibF$9W>*Kr7~ep-^pPM6;Z?|M)JK2?Rz? ze}y|Ay-aUn$q1Y_sGIt@=kqPMUe{cd6|~F%EgUg5zyl8k+2fJPV{6ual4L@KfFTrG z36EkZ_)P9qLUB-BTWxhWq6yHy$yk^sm*GURz#HNJK`&aW3@GOVR zNT`7=r0CZ%D~tj^eB*`cw%SYxIn0JTHv zDst5W=^H}jGcOClG3R@1TwEo33oPHhD&Hg63!eWibJlZc7Pv|z7By^M&p7rY@wZGVU$`4WCX$R0Ip@a z@s|ClK|cvpN;R|zz$r-9ykKEp>7C$ zP>HEz+KvLvxnmF{moF*o@&b`Y4?tE&Yne@Vb+XZr$bV?YAH`IMCHNacAzjb8+O>a! z7!Bw?1EL5KqQxznztMM0z=o)s*nEe}oD{v{IVud~#JW4E}Njg4im!P z(F6V6iG($#jHbXCg%Y1k+iVX``L_4NORj^CKqIoHZE*r~wC{0wl|N=xH#pmUxb%4; zvvJEGv)I;5JsVcF{pDxNpW58!9gXY`X)N|wKGY=K5f+w#*7E9z=}TkB>-qQY+_6J8 z-LYrS$(Ju*UcCk%Dcr}`y}dBpS{1T;x5wbY!fiW4L(|^{(yz9*wyWKYSGHn}n|g3~ zZXX^tU$J7v>WjaxHcEw;***BH1$8t&dv?di_%43=o7QfaXZP56^0UYNVcY%uK7IUn zsK39zFD$dH`?0Yj3gIi|<(UAV0zFgVMv(A<5$-tn{d?^n3+{dTv{NmP|K?K&sLf&1 z$A#O*9I(W%sLFNM^9Q@4^D;}y%HCjUIz%bQ9=u&WOAQ}l%ku@#pFeMQuWk##FTEpn z<0nigFPFQl40!_n)BasUx@YK~F8%k9zp + + + + + + + + + + diff --git a/tests/dummy/public/robots.txt b/tests/dummy/public/robots.txt new file mode 100644 index 0000000..f591645 --- /dev/null +++ b/tests/dummy/public/robots.txt @@ -0,0 +1,3 @@ +# http://www.robotstxt.org +User-agent: * +Disallow: diff --git a/tests/helpers/destroy-app.js b/tests/helpers/destroy-app.js new file mode 100644 index 0000000..c3d4d1a --- /dev/null +++ b/tests/helpers/destroy-app.js @@ -0,0 +1,5 @@ +import Ember from 'ember'; + +export default function destroyApp(application) { + Ember.run(application, 'destroy'); +} diff --git a/tests/helpers/module-for-acceptance.js b/tests/helpers/module-for-acceptance.js new file mode 100644 index 0000000..5a1a583 --- /dev/null +++ b/tests/helpers/module-for-acceptance.js @@ -0,0 +1,23 @@ +import { module } from 'qunit'; +import Ember from 'ember'; +import startApp from '../helpers/start-app'; +import destroyApp from '../helpers/destroy-app'; + +const { RSVP: { resolve } } = Ember; + +export default function(name, options = {}) { + module(name, { + beforeEach() { + this.application = startApp(); + + if (options.beforeEach) { + return options.beforeEach.apply(this, arguments); + } + }, + + afterEach() { + let afterEach = options.afterEach && options.afterEach.apply(this, arguments); + return resolve(afterEach).then(() => destroyApp(this.application)); + } + }); +} diff --git a/tests/helpers/resolver.js b/tests/helpers/resolver.js new file mode 100644 index 0000000..b208d38 --- /dev/null +++ b/tests/helpers/resolver.js @@ -0,0 +1,11 @@ +import Resolver from '../../resolver'; +import config from '../../config/environment'; + +const resolver = Resolver.create(); + +resolver.namespace = { + modulePrefix: config.modulePrefix, + podModulePrefix: config.podModulePrefix +}; + +export default resolver; diff --git a/tests/helpers/start-app.js b/tests/helpers/start-app.js new file mode 100644 index 0000000..9a605eb --- /dev/null +++ b/tests/helpers/start-app.js @@ -0,0 +1,15 @@ +import Ember from 'ember'; +import Application from '../../app'; +import config from '../../config/environment'; + +export default function startApp(attrs) { + let attributes = Ember.merge({}, config.APP); + attributes = Ember.merge(attributes, attrs); // use defaults, but you can override; + + return Ember.run(() => { + let application = Application.create(attributes); + application.setupForTesting(); + application.injectTestHelpers(); + return application; + }); +} diff --git a/tests/index.html b/tests/index.html new file mode 100644 index 0000000..5209b85 --- /dev/null +++ b/tests/index.html @@ -0,0 +1,33 @@ + + + + + + Dummy Tests + + + + {{content-for "head"}} + {{content-for "test-head"}} + + + + + + {{content-for "head-footer"}} + {{content-for "test-head-footer"}} + + + {{content-for "body"}} + {{content-for "test-body"}} + + + + + + + + {{content-for "body-footer"}} + {{content-for "test-body-footer"}} + + diff --git a/tests/integration/.gitkeep b/tests/integration/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/tests/integration/components/action-list/organization-filter-test.coffee b/tests/integration/components/action-list/organization-filter-test.coffee new file mode 100644 index 0000000..ec2c9ef --- /dev/null +++ b/tests/integration/components/action-list/organization-filter-test.coffee @@ -0,0 +1,25 @@ +import { test, moduleForComponent } from 'ember-qunit' +import hbs from 'htmlbars-inline-precompile' + +moduleForComponent 'action-list/organization-filter', 'Integration | Component | action list/organization filter', { + integration: true +} + +test 'it renders', (assert) -> + assert.expect 2 + + # Set any properties with @set 'myProperty', 'value' + # Handle any actions with @on 'myAction', (val) -> + + @render hbs """{{action-list/organization-filter}}""" + + assert.equal @$().text().trim(), '' + + # Template block usage: + @render hbs """ + {{#action-list/organization-filter}} + template block text + {{/action-list/organization-filter}} + """ + + assert.equal @$().text().trim(), 'template block text' diff --git a/tests/integration/components/action-list/project-filter-test.coffee b/tests/integration/components/action-list/project-filter-test.coffee new file mode 100644 index 0000000..cdc5dc9 --- /dev/null +++ b/tests/integration/components/action-list/project-filter-test.coffee @@ -0,0 +1,25 @@ +import { test, moduleForComponent } from 'ember-qunit' +import hbs from 'htmlbars-inline-precompile' + +moduleForComponent 'action-list/project-filter', 'Integration | Component | action list/project filter', { + integration: true +} + +test 'it renders', (assert) -> + assert.expect 2 + + # Set any properties with @set 'myProperty', 'value' + # Handle any actions with @on 'myAction', (val) -> + + @render hbs """{{action-list/project-filter}}""" + + assert.equal @$().text().trim(), '' + + # Template block usage: + @render hbs """ + {{#action-list/project-filter}} + template block text + {{/action-list/project-filter}} + """ + + assert.equal @$().text().trim(), 'template block text' diff --git a/tests/integration/components/panda-image-test.coffee b/tests/integration/components/panda-image-test.coffee new file mode 100644 index 0000000..bb91ade --- /dev/null +++ b/tests/integration/components/panda-image-test.coffee @@ -0,0 +1,25 @@ +import { test, moduleForComponent } from 'ember-qunit' +import hbs from 'htmlbars-inline-precompile' + +moduleForComponent 'panda-image', 'Integration | Component | panda image', { + integration: true +} + +test 'it renders', (assert) -> + assert.expect 2 + + # Set any properties with @set 'myProperty', 'value' + # Handle any actions with @on 'myAction', (val) -> + + @render hbs """{{panda-image}}""" + + assert.equal @$().text().trim(), '' + + # Template block usage: + @render hbs """ + {{#panda-image}} + template block text + {{/panda-image}} + """ + + assert.equal @$().text().trim(), 'template block text' diff --git a/tests/integration/components/project/edit-active-project-scenario-test.coffee b/tests/integration/components/project/edit-active-project-scenario-test.coffee new file mode 100644 index 0000000..059c741 --- /dev/null +++ b/tests/integration/components/project/edit-active-project-scenario-test.coffee @@ -0,0 +1,25 @@ +import { test, moduleForComponent } from 'ember-qunit' +import hbs from 'htmlbars-inline-precompile' + +moduleForComponent 'project/edit-active-project-scenario', 'Integration | Component | project/edit active project scenario', { + integration: true +} + +test 'it renders', (assert) -> + assert.expect 2 + + # Set any properties with @set 'myProperty', 'value' + # Handle any actions with @on 'myAction', (val) -> + + @render hbs """{{project/edit-active-project-scenario}}""" + + assert.equal @$().text().trim(), '' + + # Template block usage: + @render hbs """ + {{#project/edit-active-project-scenario}} + template block text + {{/project/edit-active-project-scenario}} + """ + + assert.equal @$().text().trim(), 'template block text' diff --git a/tests/integration/components/project/instance-action-value-display-test.coffee b/tests/integration/components/project/instance-action-value-display-test.coffee new file mode 100644 index 0000000..5052bdf --- /dev/null +++ b/tests/integration/components/project/instance-action-value-display-test.coffee @@ -0,0 +1,25 @@ +import { test, moduleForComponent } from 'ember-qunit' +import hbs from 'htmlbars-inline-precompile' + +moduleForComponent 'project/instance-action-value-display', 'Integration | Component | project/instance action value display', { + integration: true +} + +test 'it renders', (assert) -> + assert.expect 2 + + # Set any properties with @set 'myProperty', 'value' + # Handle any actions with @on 'myAction', (val) -> + + @render hbs """{{project/instance-action-value-display}}""" + + assert.equal @$().text().trim(), '' + + # Template block usage: + @render hbs """ + {{#project/instance-action-value-display}} + template block text + {{/project/instance-action-value-display}} + """ + + assert.equal @$().text().trim(), 'template block text' diff --git a/tests/integration/components/state-badge-test.coffee b/tests/integration/components/state-badge-test.coffee new file mode 100644 index 0000000..32661d5 --- /dev/null +++ b/tests/integration/components/state-badge-test.coffee @@ -0,0 +1,25 @@ +import { test, moduleForComponent } from 'ember-qunit' +import hbs from 'htmlbars-inline-precompile' + +moduleForComponent 'state-badge', 'Integration | Component | state badge', { + integration: true +} + +test 'it renders', (assert) -> + assert.expect 2 + + # Set any properties with @set 'myProperty', 'value' + # Handle any actions with @on 'myAction', (val) -> + + @render hbs """{{state-badge}}""" + + assert.equal @$().text().trim(), '' + + # Template block usage: + @render hbs """ + {{#state-badge}} + template block text + {{/state-badge}} + """ + + assert.equal @$().text().trim(), 'template block text' diff --git a/tests/integration/components/ui-multi-select-test.coffee b/tests/integration/components/ui-multi-select-test.coffee new file mode 100644 index 0000000..92a8932 --- /dev/null +++ b/tests/integration/components/ui-multi-select-test.coffee @@ -0,0 +1,25 @@ +import { test, moduleForComponent } from 'ember-qunit' +import hbs from 'htmlbars-inline-precompile' + +moduleForComponent 'ui-multi-select', 'Integration | Component | ui multi select', { + integration: true +} + +test 'it renders', (assert) -> + assert.expect 2 + + # Set any properties with @set 'myProperty', 'value' + # Handle any actions with @on 'myAction', (val) -> + + @render hbs """{{ui-multi-select}}""" + + assert.equal @$().text().trim(), '' + + # Template block usage: + @render hbs """ + {{#ui-multi-select}} + template block text + {{/ui-multi-select}} + """ + + assert.equal @$().text().trim(), 'template block text' diff --git a/tests/integration/components/value-type-display-test.coffee b/tests/integration/components/value-type-display-test.coffee new file mode 100644 index 0000000..c8f5997 --- /dev/null +++ b/tests/integration/components/value-type-display-test.coffee @@ -0,0 +1,25 @@ +import { test, moduleForComponent } from 'ember-qunit' +import hbs from 'htmlbars-inline-precompile' + +moduleForComponent 'value-type-display', 'Integration | Component | value type display', { + integration: true +} + +test 'it renders', (assert) -> + assert.expect 2 + + # Set any properties with @set 'myProperty', 'value' + # Handle any actions with @on 'myAction', (val) -> + + @render hbs """{{value-type-display}}""" + + assert.equal @$().text().trim(), '' + + # Template block usage: + @render hbs """ + {{#value-type-display}} + template block text + {{/value-type-display}} + """ + + assert.equal @$().text().trim(), 'template block text' diff --git a/tests/test-helper.js b/tests/test-helper.js new file mode 100644 index 0000000..f219659 --- /dev/null +++ b/tests/test-helper.js @@ -0,0 +1,8 @@ +import resolver from './helpers/resolver'; +import { + setResolver +} from 'ember-qunit'; +import { start } from 'ember-cli-qunit'; + +setResolver(resolver); +start(); diff --git a/tests/unit/.gitkeep b/tests/unit/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/tests/unit/controllers/action-list-test.coffee b/tests/unit/controllers/action-list-test.coffee new file mode 100644 index 0000000..afe2477 --- /dev/null +++ b/tests/unit/controllers/action-list-test.coffee @@ -0,0 +1,11 @@ +import { moduleFor, test } from 'ember-qunit' + +moduleFor 'controller:action-list', { + # Specify the other units that are required for this test. + # needs: ['controller:foo'] +} + +# Replace this with your real tests. +test 'it exists', (assert) -> + controller = @subject() + assert.ok controller diff --git a/tests/unit/models/panda/action-event-test.coffee b/tests/unit/models/panda/action-event-test.coffee new file mode 100644 index 0000000..de4f0e9 --- /dev/null +++ b/tests/unit/models/panda/action-event-test.coffee @@ -0,0 +1,11 @@ +import { moduleForModel, test } from 'ember-qunit' + +moduleForModel 'panda/action-event', 'Unit | Model | panda/action event', { + # Specify the other units that are required for this test. + needs: [] +} + +test 'it exists', (assert) -> + model = @subject() + # store = @store() + assert.ok !!model diff --git a/tests/unit/models/panda/adapter-test.coffee b/tests/unit/models/panda/adapter-test.coffee new file mode 100644 index 0000000..22d8b36 --- /dev/null +++ b/tests/unit/models/panda/adapter-test.coffee @@ -0,0 +1,11 @@ +import { moduleForModel, test } from 'ember-qunit' + +moduleForModel 'panda/adapter', 'Unit | Model | panda/adapter', { + # Specify the other units that are required for this test. + needs: [] +} + +test 'it exists', (assert) -> + model = @subject() + # store = @store() + assert.ok !!model diff --git a/tests/unit/models/panda/organization-test.coffee b/tests/unit/models/panda/organization-test.coffee new file mode 100644 index 0000000..a2289ce --- /dev/null +++ b/tests/unit/models/panda/organization-test.coffee @@ -0,0 +1,11 @@ +import { moduleForModel, test } from 'ember-qunit' + +moduleForModel 'panda/organization', 'Unit | Model | panda/organization', { + # Specify the other units that are required for this test. + needs: [] +} + +test 'it exists', (assert) -> + model = @subject() + # store = @store() + assert.ok !!model diff --git a/tests/unit/models/panda/tracker-subject-test.coffee b/tests/unit/models/panda/tracker-subject-test.coffee new file mode 100644 index 0000000..40b0d64 --- /dev/null +++ b/tests/unit/models/panda/tracker-subject-test.coffee @@ -0,0 +1,11 @@ +import { moduleForModel, test } from 'ember-qunit' + +moduleForModel 'panda/tracker-subject', 'Unit | Model | panda/tracker subject', { + # Specify the other units that are required for this test. + needs: [] +} + +test 'it exists', (assert) -> + model = @subject() + # store = @store() + assert.ok !!model diff --git a/tests/unit/models/panda/workflow-action-connection-test.coffee b/tests/unit/models/panda/workflow-action-connection-test.coffee new file mode 100644 index 0000000..246eb56 --- /dev/null +++ b/tests/unit/models/panda/workflow-action-connection-test.coffee @@ -0,0 +1,11 @@ +import { moduleForModel, test } from 'ember-qunit' + +moduleForModel 'panda/workflow-action-connection', 'Unit | Model | panda/workflow action connection', { + # Specify the other units that are required for this test. + needs: [] +} + +test 'it exists', (assert) -> + model = @subject() + # store = @store() + assert.ok !!model diff --git a/tests/unit/models/panda/workflow-instance-type-test.coffee b/tests/unit/models/panda/workflow-instance-type-test.coffee new file mode 100644 index 0000000..0ea4385 --- /dev/null +++ b/tests/unit/models/panda/workflow-instance-type-test.coffee @@ -0,0 +1,11 @@ +import { moduleForModel, test } from 'ember-qunit' + +moduleForModel 'panda/workflow-instance-type', 'Unit | Model | panda/workflow instance type', { + # Specify the other units that are required for this test. + needs: [] +} + +test 'it exists', (assert) -> + model = @subject() + # store = @store() + assert.ok !!model diff --git a/tests/unit/routes/action-list-test.coffee b/tests/unit/routes/action-list-test.coffee new file mode 100644 index 0000000..eb09182 --- /dev/null +++ b/tests/unit/routes/action-list-test.coffee @@ -0,0 +1,10 @@ +import { moduleFor, test } from 'ember-qunit' + +moduleFor 'route:action-list', 'Unit | Route | action list', { + # Specify the other units that are required for this test. + # needs: ['controller:foo'] +} + +test 'it exists', (assert) -> + route = @subject() + assert.ok route diff --git a/tests/unit/utils/panda-route-setup-test.coffee b/tests/unit/utils/panda-route-setup-test.coffee new file mode 100644 index 0000000..6972e62 --- /dev/null +++ b/tests/unit/utils/panda-route-setup-test.coffee @@ -0,0 +1,9 @@ +import pandaRouteSetup from '../../../utils/panda-route-setup' +import { module, test } from 'qunit' + +module 'Unit | Utility | panda route setup' + +# Replace this with your real tests. +test 'it works', (assert) -> + result = pandaRouteSetup() + assert.ok result diff --git a/vendor/.gitkeep b/vendor/.gitkeep new file mode 100644 index 0000000..e69de29